# 真值可视化流程分析文档 ## 一、完整数据流程分析 ### 1.1 从文件到内存的加载流程 ``` 原始文件 └─> load_label() [dataloaders3d.py:1625] │ ├─> 解析标签文本文件 │ └─> 支持3种格式: │ ├─ 2D only (6 dims) │ ├─ 2D+3D simple (18 dims) │ └─ 2D+3D+faces (50 dims) │ └─> 返回 (N, 47) numpy数组 └─> 列结构: [0]: class [1-4]: xywh (normalized) [5]: score [6-8]: xyz3d (camera coords) [9-11]: dimensions (l,h,w) [12]: rot_y [13-14]: center_2d_uv (normalized) [15]: alpha [16-23]: front_face [xyz, alpha, uv, score, visible] [24-31]: rear_face [32-39]: left_face [40-47]: right_face ``` ### 1.2 ROI变换流程 ``` 原始标签 (N, 47) └─> post_process_labels_to_roi() [dataloaders3d.py:1060] │ ├─> 步骤1: 计算ROI区域 │ └─> vanish_y = cy - fy * tan(pitch) │ └─> crop_center = (oriW/2, vanish_y) │ └─> roi_bounds = [x1, y1, x2, y2] │ ├─> 步骤2: 转换2D边界框 │ └─> xywhn -> xyxy (absolute) │ └─> shift to ROI coords │ └─> clip to ROI bounds │ └─> normalize to ROI size │ ├─> 步骤3: 转换UV坐标 │ └─> 对每个面的中心投影点 (5对UV) │ └─> u_roi = (u_orig * oriW - roi_x1) / roi_w │ └─> v_roi = (v_orig * oriH - roi_y1) / roi_h │ └─> 步骤4: 过滤无效框 └─> 移除完全在ROI外的框 └─> 标记部分可见的框(cut-in/cut-out) ``` ### 1.3 深度归一化流程 ``` ROI标签 (N, 47) └─> scale_z3d() [dataloaders3d.py:1518] │ ├─> 计算缩放因子 │ └─> scale = virtual_fx / fx_roi │ ├─> 缩放Z坐标 (仅Z,不包括X,Y) │ ├─> 列7: z3d_center *= scale │ ├─> 列17: z3d_front *= scale │ ├─> 列25: z3d_rear *= scale │ ├─> 列33: z3d_left *= scale │ └─> 列41: z3d_right *= scale │ └─> 原理说明: 投影公式: u = fx * X/Z + cx 同时缩放fx和Z -> 投影不变 X和Y从UV和Z恢复,无需缩放 ``` ## 二、可视化实现流程 ### 2.1 2D可视化流程 ``` 标签数组 (N, 47) └─> plot_2d_boxes() │ ├─> 图像预处理 │ └─> resize(scale_factor=2) -> 更清晰 │ └─> BGR -> RGB for Annotator │ ├─> 遍历每个标签 │ ├─> 提取: cls, xywh │ ├─> 转换: xywhn -> xyxy (pixel) │ ├─> 绘制: box + label │ └─> 颜色: colors(cls) │ └─> 后处理 └─> RGB -> BGR └─> 添加标签文字 (cv2.putText) ``` ### 2.2 3D可视化流程(车辆类) ``` 标签 (47,) + calib └─> decode_and_reconstruct_3d_box_from_target() [plots.py:908] │ ├─> 步骤1: 恢复原始深度 │ └─> z_original = z_normalized * depth_scale │ └─> depth_scale = fx_roi / virtual_fx │ ├─> 步骤2: 选择最佳可见面 │ └─> 遍历4个面 (front, rear, left, right) │ └─> 选择: visible==1 && score>0.3 && 最高score │ ├─> 步骤3: 从面重建3D中心 │ └─> _reconstruct_3d_box_from_face() │ ├─> uv -> 射线方向 │ │ └─> X = (u - cx) * Z / fx │ │ └─> Y = (v - cy) * Z / fy │ │ │ ├─> 根据面类型计算偏移 │ │ └─> front: center = face - [l/2, 0, 0] │ │ └─> rear: center = face + [l/2, 0, 0] │ │ └─> left: center = face - [0, 0, w/2] │ │ └─> right: center = face + [0, 0, w/2] │ │ │ └─> 计算8个角点 │ └─> compute_3d_box_corners_4face() │ └─> 返回: {corners_3d, face_center_2d, face_color, ...} ``` ### 2.3 3D可视化流程(行人类) ``` 标签 (47,) + calib └─> decode_and_reconstruct_3d_box_from_target() [plots.py:908] │ ├─> 步骤1: 恢复原始深度 │ └─> z_original = z_normalized * depth_scale │ ├─> 步骤2: 提取3D参数 │ └─> center = [x3d, y3d, z3d] │ └─> dims = [l, h, w] │ └─> rot_y │ ├─> 步骤3: 计算8个角点 │ └─> compute_3d_box_corners_4face() │ └─> 基于center, dims, rot_y │ └─> 注意: GT不需要corner reordering │ └─> 返回: {corners_3d, center_2d, object_3d, ...} ``` ### 2.4 投影和绘制流程 ``` decoded_results + image + calib └─> plot_3d_boxes_from_decoded_targets() [plots.py:1343] │ ├─> 图像预处理 │ └─> tensor -> numpy │ └─> normalize -> [0,255] │ └─> resize(scale_factor=2) │ ├─> 标定参数缩放 │ └─> fx, fy, cx, cy *= scale_factor │ ├─> 遍历每个decoded对象 │ └─> draw_3d_box_from_corners() [plots.py:1198] │ │ │ ├─> 步骤1: 3D角点 -> 2D投影 │ │ └─> u = fx * X/Z + cx │ │ └─> v = fy * Y/Z + cy │ │ └─> 应用畸变校正 (如果有) │ │ │ ├─> 步骤2: 绘制12条边 │ │ ├─> 前面4条边: 红色/face_color │ │ ├─> 后面4条边: 绿色 │ │ └─> 连接边4条: 蓝色 │ │ │ └─> 步骤3: 标记面中心 (车辆) │ └─> circle at face_center_2d │ └─> 后处理 └─> 添加标签文字 └─> BGR -> RGB ``` ### 2.5 BEV可视化流程 ``` object_3d = [x, y, z, l, h, w, rot_y, face_type] └─> drawbev() [plots.py:2404] │ ├─> 坐标系转换 │ └─> 相机坐标 -> BEV坐标 │ └─> bev_x = z (深度 -> 前后) │ └─> bev_y = -x (右 -> 左右) │ ├─> 计算4个角点 (俯视) │ └─> 基于 (bev_x, bev_y), l, w, rot_y │ ├─> 投影到图像坐标 │ └─> scale: 200m -> 800px │ └─> origin: bottom-center │ └─> 绘制 ├─> 填充矩形: 半透明 ├─> 边框: 实线 ├─> 方向指示: 箭头 └─> 颜色: GT=green, Pred=red ``` ## 三、关键坐标变换 ### 3.1 归一化坐标 ↔ 像素坐标 ```python # 2D边界框 xywh_norm -> xyxy_pixel: x1 = (x - w/2) * img_width y1 = (y - h/2) * img_height x2 = (x + w/2) * img_width y2 = (y + h/2) * img_height # UV坐标 uv_norm -> uv_pixel: u_pixel = u_norm * img_width v_pixel = v_norm * img_height ``` ### 3.2 相机坐标 ↔ 图像坐标 ```python # 投影(3D -> 2D) u = fx * X/Z + cx v = fy * Y/Z + cy # 反投影(2D + Z -> 3D) X = (u - cx) * Z / fx Y = (v - cy) * Z / fy # 注意: # - 需要已知深度Z # - 可能需要畸变校正 ``` ### 3.3 ROI坐标变换 ```python # 原始图像 -> ROI图像 u_roi = (u_orig - roi_x1) / roi_width v_roi = (v_orig - roi_y1) / roi_height # ROI图像 -> 原始图像 u_orig = u_roi * roi_width + roi_x1 v_orig = v_roi * roi_height + roi_y1 # 3D坐标: # - xyz3d保持不变(相机坐标系) # - 仅z3d需要深度归一化 ``` ### 3.4 深度归一化变换 ```python # 训练时(原始 -> 归一化) z_normalized = z_original * (virtual_fx / fx_original) # 推理/可视化时(归一化 -> 原始) z_original = z_normalized * (fx_original / virtual_fx) = z_normalized * depth_scale # 原理: # 投影: u = fx * X/Z + cx # 缩放: u' = (fx*s) * X/(Z*s) + cx = u # 即:同时缩放fx和Z保持投影不变 ``` ## 四、常见问题与注意事项 ### 4.1 深度值处理 **问题**: 为什么只缩放z3d,不缩放x3d和y3d? **答案**: - 投影公式: `u = fx * X/Z + cx` - X和Y是从UV和Z反推的: `X = (u-cx)*Z/fx` - 缩放Z后,如果缩放fx,投影保持不变 - X和Y会自动适配新的Z值,无需单独缩放 ### 4.2 角点顺序 **问题**: GT和预测的角点顺序一样吗? **答案**: - GT: 直接使用`compute_3d_box_corners_4face()`输出 - 预测: 需要根据`rot_y`的符号reorder角点 - 在可视化时需要注意这个差异 ### 4.3 坐标系统 **相机坐标系** (右手系): ``` Y (down) | |_____ X (right) / Z (forward, depth) ``` **图像坐标系**: ``` (0,0)_____ u (right) | | v (down) ``` **BEV坐标系** (俯视): ``` bev_x (depth) ↑ | | bev_y <———————· (left) ``` ### 4.4 畸变处理 当标定参数包含`distort_coeffs`时: - ROI模式: 保留畸变系数(需要在可视化时处理) - 虚拟相机模式: 已经去畸变(`distort_coeffs=[]`) ### 4.5 类别差异 不同类别使用不同的重建策略: - **车辆(0, 13)**: 基于最佳可见面 - **行人(1, 2, 3)**: 基于完整3D框 原因: - 车辆可能被部分遮挡或出画,面策略更鲁棒 - 行人尺寸小,完整框更简单准确 ## 五、调试技巧 ### 5.1 检查标签加载 ```python labels = load_label_file(label_path) print(f"标签数量: {len(labels)}") print(f"类别分布: {np.unique(labels[:, 0])}") print(f"第一个标签:\n{labels[0]}") ``` ### 5.2 检查ROI变换 ```python print(f"原始标签数量: {len(labels_orig)}") print(f"ROI后标签数量: {len(labels_roi)}") # 检查第一个框的变换 if len(labels_roi) > 0: print(f"原始xywh: {labels_orig[0, 1:5]}") print(f"ROI xywh: {labels_roi[0, 1:5]}") ``` ### 5.3 检查深度值 ```python # 打印深度值范围 z_values = labels[:, 7][~np.isnan(labels[:, 7])] print(f"深度范围: [{z_values.min():.2f}, {z_values.max():.2f}]") print(f"平均深度: {z_values.mean():.2f}") ``` ### 5.4 检查解码结果 ```python decoded = decode_and_reconstruct_3d_box_from_target(...) if decoded: print(f"类别: {decoded['cls']}") print(f"角点形状: {decoded['corners_3d'].shape if decoded['corners_3d'] is not None else None}") if decoded.get('object_3d'): print(f"3D位置: {decoded['object_3d'][:3]}") ``` ## 六、性能优化建议 1. **批量处理**: 使用`visualize_batch.py`而不是循环调用单帧脚本 2. **分辨率控制**: 调整`scale_factor`参数控制输出图像大小 3. **选择性可视化**: 根据需要选择2D、3D或BEV可视化 4. **并行处理**: 可以使用多进程批量处理独立的图像