MMDetection3D/3D目标检测实战:坐标系与边界框的代码级解析与转换指南
1. 3D目标检测中的坐标系基础
第一次接触3D目标检测时,我被各种坐标系搞得晕头转向。摄像头坐标系、激光雷达坐标系、世界坐标系...这些概念就像迷宫一样让人困惑。直到在实际项目中踩了几个坑,才真正理解它们的重要性。
在MMDetection3D框架中,最常见的三种坐标系是:
- 摄像头坐标系(Camera Coord):以摄像头光学中心为原点,Z轴指向拍摄方向
- 激光雷达坐标系(LiDAR Coord):以雷达传感器为中心,X轴向前,Y轴向左
- 世界坐标系(World Coord):整个场景的全局参考系
理解这些坐标系的关键在于抓住它们的右手定则。比如在摄像头坐标系中,X向右,Y向下,Z向前。这个规则在可视化时特别有用,我曾经因为忽略这个细节导致整个点云旋转了90度。
2. 边界框的表示方法
3D边界框比2D复杂得多,不仅要考虑长宽高,还要考虑朝向角。在MMDetection3D中,主要有两种表示方式:
2.1 LiDARInstance3DBoxes类
这是处理激光雷达数据的核心类,一个典型的边界框包含7个参数:
(x_center, y_center, z_center, length, width, height, yaw_angle)其中yaw_angle特别容易出错,它表示的是边界框相对于X轴的旋转角度,顺时针为正方向。
2.2 CameraInstance3DBoxes类
用于摄像头数据的边界框表示,参数看似相似但含义不同:
(x_center, y_center, z_center, width, height, length, yaw_angle)注意这里width和length的顺序与LiDAR表示相反,这是很多新手容易混淆的地方。
3. 坐标系转换实战
3.1 摄像头到激光雷达的转换
MMDetection3D提供了box_camera_to_lidar函数,但使用前需要确保输入格式正确:
from mmdet3d.core.bbox import box_camera_to_lidar # 假设我们有摄像头坐标系下的边界框 camera_boxes = CameraInstance3DBoxes(torch.tensor([[10, 5, 20, 3, 2, 4, 0.5]])) # 需要提供标定参数 calib = { 'rect': np.eye(4), # 矫正矩阵 'Trv2c': np.eye(4), # 雷达到摄像头的变换 'P2': np.eye(4) # 摄像头内参 } lidar_boxes = box_camera_to_lidar(camera_boxes, calib)3.2 点是否在框内的判断
points_in_bbox函数非常实用,但要注意输入点的坐标系必须与边界框一致:
from mmdet3d.core.bbox import points_in_bbox # 生成随机点云 (N,3) points = torch.rand(100, 3) * 10 # 创建LiDAR边界框 boxes = LiDARInstance3DBoxes(torch.tensor([[5,5,5,3,4,2,0]])) # 判断点是否在框内 inside = points_in_bbox(points, boxes)4. 常见问题与调试技巧
4.1 坐标系不一致导致的错误
这是最常见的bug来源。有一次我的检测结果总是偏移几十米,花了半天才发现是忘记转换坐标系。建议在可视化时立即检查:
# 可视化前确保坐标系一致 if isinstance(boxes, CameraInstance3DBoxes): boxes = box_camera_to_lidar(boxes, calib) # 然后才能和LiDAR点云一起显示4.2 边界框旋转方向错误
yaw角的正方向定义在不同框架中可能不同。MMDetection3D使用右手系,但有些数据集可能使用左手系。遇到旋转方向不对时,可以尝试:
# 如果发现旋转方向相反 boxes.tensor[:, 6] = -boxes.tensor[:, 6] # 反转yaw角4.3 单位不统一问题
摄像头数据常用米(m)为单位,而有些激光雷达数据可能使用厘米(cm)。转换时要注意单位一致性:
# 如果激光雷达数据是厘米级 boxes.tensor[:, :3] /= 100 # 转换中心点为米 boxes.tensor[:, 3:6] /= 100 # 转换长宽高为米5. 多传感器融合实践
当同时处理摄像头和激光雷达数据时,建议以激光雷达坐标系为基准。这里分享一个实际项目中的处理流程:
- 将摄像头检测结果转换到LiDAR坐标系
- 在LiDAR坐标系下进行NMS等后处理
- 最终结果可以根据需要转换回其他坐标系
# 多传感器融合示例 camera_results = [...] # 摄像头检测结果 lidar_results = [...] # 激光雷达检测结果 # 统一到LiDAR坐标系 all_boxes = LiDARInstance3DBoxes.cat([ lidar_results, box_camera_to_lidar(camera_results, calib) ]) # 执行NMS keep = nms(all_boxes, iou_threshold=0.5) final_boxes = all_boxes[keep]6. 性能优化建议
处理大规模点云时,坐标转换可能成为性能瓶颈。几个实测有效的优化方法:
- 批量处理:尽量使用框架提供的批量转换函数,避免循环处理单个边界框
- 张量运算:利用PyTorch的GPU加速能力,保持数据在张量格式
- 提前过滤:在转换坐标系前,先用简单规则过滤掉明显无效的检测框
# 优化后的处理流程 def process_boxes(boxes, calib): # 先过滤低分框 mask = boxes.scores > 0.3 boxes = boxes[mask] # 批量转换坐标系 if isinstance(boxes, CameraInstance3DBoxes): boxes = box_camera_to_lidar(boxes, calib) # GPU加速计算 boxes.tensor = boxes.tensor.cuda() return boxes7. 自定义数据集的适配
当使用非标准数据集时,可能需要自定义坐标系转换。这时需要理解变换矩阵的本质:
一个典型的雷达到摄像头的变换矩阵Trv2c包含旋转和平移:
[R | t] 0 0 0 1可以通过分解这个矩阵来验证坐标系关系是否正确:
# 分解变换矩阵 rotation = Trv2c[:3, :3] translation = Trv2c[:3, 3] print("旋转部分:", rotation) print("平移部分:", translation)如果发现转换结果不符合预期,可以先用单个点测试:
# 测试单个点转换 point_lidar = np.array([1, 0, 0, 1]) # 齐次坐标 point_camera = Trv2c @ point_lidar