Files
2026-06-24 09:35:46 +08:00

11 KiB
Executable File
Raw Permalink Blame History

真值可视化流程分析文档

一、完整数据流程分析

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 归一化坐标 ↔ 像素坐标

# 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 相机坐标 ↔ 图像坐标

# 投影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坐标变换

# 原始图像 -> 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 深度归一化变换

# 训练时(原始 -> 归一化)
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 检查标签加载

labels = load_label_file(label_path)
print(f"标签数量: {len(labels)}")
print(f"类别分布: {np.unique(labels[:, 0])}")
print(f"第一个标签:\n{labels[0]}")

5.2 检查ROI变换

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 检查深度值

# 打印深度值范围
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 检查解码结果

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. 并行处理: 可以使用多进程批量处理独立的图像