# 3D目标检测标注详细指南 ## 📋 目录 1. [3D Box标注原理](#1-3d-box标注原理) 2. [标注工具使用](#2-标注工具使用) 3. [标注流程步骤](#3-标注流程步骤) 4. [质量控制](#4-质量控制) 5. [自动化辅助](#5-自动化辅助) --- ## 1. 3D Box标注原理 ### 1.1 3D Bounding Box定义 一个3D box由**9个参数**完整描述: ```python 3D Box = [x, y, z, w, l, h, yaw, vx, vy] 其中: - (x, y, z): 中心点3D坐标 (米) - nuScenes: 全局坐标系 (Global) - BEVFusion训练: LiDAR坐标系 - (w, l, h): 宽度、长度、高度 (米) - w (width): 车辆宽度 (左右) - l (length): 车辆长度 (前后) - h (height): 车辆高度 (上下) - yaw: 偏航角 (弧度) - 车辆朝向,相对于x轴 - 范围: [-π, π] - (vx, vy): 速度 (m/s) - x方向速度 - y方向速度 ``` ### 1.2 坐标系说明 ``` Z (up) | | o-----> X (forward) / / Y (left) nuScenes坐标系: - X轴: 车辆前进方向 - Y轴: 车辆左侧 - Z轴: 垂直向上 - 原点: LiDAR中心 (距地面约1.8米) 角度定义: - yaw = 0°: 正X方向 (车头朝前) - yaw = 90°: 正Y方向 (车头朝左) - yaw = 180°: 负X方向 (车头朝后) - yaw = -90°: 负Y方向 (车头朝右) ``` ### 1.3 Box的8个顶点 ```python # 计算3D box的8个角点 def get_box_corners(center, size, yaw): """ center: (x, y, z) size: (w, l, h) yaw: 偏航角 """ w, l, h = size # 局部坐标系下的8个角点 (中心为原点) corners = np.array([ [-w/2, -l/2, -h/2], # 0: 左后下 [ w/2, -l/2, -h/2], # 1: 右后下 [ w/2, l/2, -h/2], # 2: 右前下 [-w/2, l/2, -h/2], # 3: 左前下 [-w/2, -l/2, h/2], # 4: 左后上 [ w/2, -l/2, h/2], # 5: 右后上 [ w/2, l/2, h/2], # 6: 右前上 [-w/2, l/2, h/2], # 7: 左前上 ]) # 旋转矩阵 (绕Z轴) R = np.array([ [np.cos(yaw), -np.sin(yaw), 0], [np.sin(yaw), np.cos(yaw), 0], [0, 0, 1] ]) # 旋转 + 平移到全局坐标 corners_rotated = corners @ R.T corners_global = corners_rotated + center return corners_global # (8, 3) ``` --- ## 2. 标注工具使用 ### 2.1 CVAT 3D标注 (推荐) **安装**: ```bash # Docker方式 (推荐) git clone https://github.com/opencv/cvat cd cvat docker-compose up -d # 访问: http://localhost:8080 # 默认账号: admin / 12qwaszx ``` **创建3D标注任务**: 1. **新建任务** - Task name: `nuscenes_sample_001` - Project: `autonomous_driving` - Label: 添加10类 (car, pedestrian, truck...) 2. **上传数据** - 点云文件: `sample.pcd` (velodyne格式) - 相机图像: 6张 (CAM_FRONT, CAM_FRONT_LEFT...) - 关联相机内外参 3. **3D标注模式** ``` 工具栏选择: "3D Cuboid" 快捷键: - N: 创建新box - Del: 删除box - R: 旋转box - F: 切换视角 (3D/2D) - Space: 播放/暂停 ``` 4. **标注步骤** (详见下节) ### 2.2 Scalabel标注 **在线使用**: https://scalabel.ai/ **特点**: - ✅ 支持3D + 2D联合标注 - ✅ 自动多视角一致性检查 - ✅ 支持导出nuScenes格式 **配置示例**: ```yaml # scalabel_config.yaml project: name: bevfusion_annotation items: - name: sample_001 videoName: scene_001 labels: - category: car box3d: position: {x: 10.5, y: 2.3, z: 0.5} dimension: {x: 1.8, y: 4.5, z: 1.5} orientation: {x: 0, y: 0, z: 0.1} ``` ### 2.3 开源替代: 3D BAT ```bash # 轻量级3D标注工具 git clone https://github.com/walzimmer/3d-bat cd 3d-bat pip install -e . # 启动标注 python app.py --data_root /path/to/samples ``` --- ## 3. 标注流程步骤 ### 3.1 标注前准备 **1. 数据检查** ```python # 检查数据完整性 import open3d as o3d import cv2 # 加载点云 pcd = o3d.io.read_point_cloud("sample.pcd") print(f"点云数量: {len(pcd.points)}") # 加载6张相机图像 for cam in ['CAM_FRONT', 'CAM_FRONT_LEFT', ...]: img = cv2.imread(f'{cam}.jpg') print(f"{cam}: {img.shape}") # 加载标定参数 calib = load_calibration('calib.json') print(f"相机内参: {calib['CAM_FRONT']['intrinsic']}") ``` **2. 可视化初始化** ```bash # 在CVAT中加载数据后,检查: - ✅ 点云显示正常 (颜色、密度) - ✅ 相机视图对齐 (无明显偏移) - ✅ 坐标轴方向正确 (X前Y左Z上) ``` ### 3.2 详细标注步骤 #### Step 1: 选择标注对象 ``` 在3D点云视图中: 1. 识别目标物体 (车辆、行人、障碍物...) 2. 评估可见性: - Level 1: 0-40% 可见 (大部分遮挡) - Level 2: 40-60% 可见 - Level 3: 60-80% 可见 - Level 4: 80-100% 可见 (完全可见) 3. 判断是否标注: - 完全遮挡 (0%): 不标注 - 部分可见 (>40%): 标注 - 距离过远 (>60m): 可选标注 ``` #### Step 2: 放置初始Box **方法A: 点云视图手动绘制** ``` 1. 切换到顶视图 (Top View) 2. 点击工具栏 "3D Cuboid" 3. 在点云上框选目标: 操作: a) 点击目标的4个底面角点 - 顺序: 左后 → 右后 → 右前 → 左前 b) 拖动调整高度 c) 系统自动生成box 注意: - 尽量贴合点云边缘 - 底面应该在地面上 (z ≈ -1.8m for nuScenes) - 朝向箭头指向车头方向 ``` **方法B: 2D图像辅助 (推荐)** ``` 1. 在CAM_FRONT视图中绘制2D box 2. 标注高度和深度信息 3. 系统自动反投影到3D空间 4. 切换到3D视图微调 优点: - 更精确的边界 - 利用图像纹理信息 - 多视图交叉验证 ``` **方法C: 自动检测 + 人工修正** ```python # 使用预训练模型生成初始box python tools/auto_label.py \ --model pretrained/pointpillars.pth \ --input sample.pcd \ --output initial_boxes.json \ --confidence 0.3 # 然后在CVAT中导入,人工修正 ``` #### Step 3: 精细调整Box **3.1 调整位置** (平移) ``` 快捷键: 鼠标拖动 精度要求: - 车辆: ±5cm - 行人: ±3cm - 小物体: ±2cm 检查方法: 1. 切换到侧视图 (Side View) - 检查box底面是否贴地 - 检查z坐标是否正确 2. 切换到俯视图 (Top View) - 检查xy中心是否在物体中心 - 使用点云密度判断 3. 在多个相机视图中投影验证 - box边缘应该包裹物体 - 不应有大量溢出或缺失 ``` **3.2 调整尺寸** (缩放) ``` 操作: 拖动box的6个面 标准尺寸参考 (nuScenes): 目标类型 长(l) 宽(w) 高(h) ───────────────────────────────────── car 4.5m 1.8m 1.5m truck 6.0m 2.5m 2.5m bus 11.0m 2.8m 3.2m pedestrian 0.6m 0.6m 1.7m bicycle 1.7m 0.6m 1.3m motorcycle 2.0m 0.8m 1.4m traffic_cone 0.4m 0.4m 0.7m barrier 2.5m 0.3m 1.0m 调整技巧: 1. 先调整长度 (最容易判断) 2. 再调整宽度 (参考车道宽度) 3. 最后调整高度 (使用侧视图) 精度要求: - 尺寸误差 < 5% - 特别注意不要遗漏突出部分 (如后视镜、天线) ``` **3.3 调整朝向** (旋转) ``` 操作: R键 或 旋转手柄 朝向判断: 1. 观察车辆形状: - 车头较窄、车尾较宽 (轿车) - 查看车窗、车灯位置 2. 利用运动方向: - 如果有速度信息 - yaw应该与速度方向一致 3. 多视角验证: - 在CAM_FRONT中看车头 - 在CAM_BACK中看车尾 - 确保投影一致 精度要求: - 角度误差 < 3° (0.05 rad) - 对于静止物体,朝向应对齐车道方向 ``` #### Step 4: 标注属性 **4.1 基础属性** ```json { "category_name": "car", // 10类之一 "instance_token": "inst_001", // 实例ID (跨帧跟踪) "visibility": 4, // 可见性 1-4 "num_lidar_pts": 120 // box内点云数 (自动计算) } ``` **4.2 速度标注** (重要!) ``` 方法1: 跨帧跟踪计算 - 在t和t+1帧标注同一目标 - 系统自动计算: v = Δposition / Δt 方法2: 手动估计 - 静止: (0, 0) - 慢速: (1-5 m/s) - 正常: (5-15 m/s) - 快速: (15-30 m/s) 方向判断: - vx > 0: 向前 - vx < 0: 向后 - vy > 0: 向左 - vy < 0: 向右 精度要求: - 误差 < 0.5 m/s - 静止车辆必须为 (0, 0) ``` **4.3 特殊属性** (nuScenes) ```json { "attribute_tokens": [ "vehicle.moving", // 车辆运动状态 "cycle.with_rider", // 自行车有骑手 "pedestrian.standing", // 行人站立 ... ] } ``` #### Step 5: 多视图验证 **验证清单**: ``` □ 3D点云视图: - box紧密包裹点云 - 底面贴地 - 朝向正确 □ CAM_FRONT投影: - box边缘对齐车辆轮廓 - 顶部和底部正确 - 无明显溢出 □ CAM_FRONT_LEFT/RIGHT: - 侧面投影准确 - 宽度和高度正确 □ CAM_BACK投影: - 后视角对齐 - 遮挡处理合理 □ 俯视图 (BEV): - 中心位置准确 - 占据车道合理 - 与周围物体无碰撞 ``` **常见错误检查**: ```python # 自动化检查脚本 def validate_annotation(box): errors = [] # 1. 尺寸合理性 if box['size'][0] > 15: # 宽度超过15m errors.append("宽度异常") if box['size'][2] > 5: # 高度超过5m errors.append("高度异常") # 2. 位置合理性 if box['center'][2] > 2: # z坐标过高 errors.append("悬浮在空中") if box['center'][2] < -5: # z坐标过低 errors.append("埋在地下") # 3. 点云数量 if box['num_lidar_pts'] < 5: errors.append("点云过少,可能误标") # 4. 速度合理性 v_norm = np.linalg.norm(box['velocity']) if v_norm > 40: # 超过40 m/s ≈ 144 km/h errors.append("速度异常") return errors ``` --- ## 4. 质量控制 ### 4.1 标注规范 **必须遵守**: ``` 1. 完整性: - 标注所有可见目标 (visibility > 1) - 不遗漏小物体 (traffic_cone, barrier) - 遮挡物体也要标注 (至少40%可见) 2. 准确性: - 位置误差 < 10cm - 尺寸误差 < 5% - 角度误差 < 3° 3. 一致性: - 同一实例跨帧一致 - 多视角投影一致 - 标注者之间一致 4. 边界情况: - 部分遮挡: 标注可见部分的完整box - 截断 (出画面): 标注完整box,标记truncated - 远距离 (>60m): 可选标注 ``` ### 4.2 质量评估指标 ```python # 评估脚本 def evaluate_annotation_quality(gt_boxes, pred_boxes): """ gt_boxes: 金标准 (专家标注) pred_boxes: 待评估标注 """ # 1. IoU (3D) iou_3d = compute_iou_3d(gt_boxes, pred_boxes) print(f"平均IoU: {iou_3d.mean():.3f}") # 要求: IoU > 0.7 # 2. 中心距离 center_dist = np.linalg.norm( gt_boxes[:, :3] - pred_boxes[:, :3], axis=1 ) print(f"中心误差: {center_dist.mean():.3f}m") # 要求: < 0.1m # 3. 角度差 angle_diff = np.abs(gt_boxes[:, 6] - pred_boxes[:, 6]) angle_diff = np.minimum(angle_diff, 2*np.pi - angle_diff) print(f"角度误差: {np.degrees(angle_diff.mean()):.2f}°") # 要求: < 3° # 4. 召回率 recall = len(pred_boxes) / len(gt_boxes) print(f"召回率: {recall:.2%}") # 要求: > 95% return { 'iou': iou_3d.mean(), 'center_error': center_dist.mean(), 'angle_error': np.degrees(angle_diff.mean()), 'recall': recall } ``` ### 4.3 标注审核流程 ``` 第一轮: 初标 (标注员A) ↓ 第二轮: 自动检查 - 运行validation脚本 - 标记异常box ↓ 第三轮: 交叉审核 (标注员B) - 检查标记的异常 - 随机抽查10%样本 ↓ 第四轮: 专家审核 - 复杂场景 (遮挡、极端天气) - 边界case ↓ 第五轮: 最终确认 - 修正所有问题 - 导出最终标注 ``` --- ## 5. 自动化辅助 ### 5.1 预标注 (Pre-labeling) ```python # 使用现有模型生成初始标注 # tools/pre_label.py import torch from mmdet3d.apis import init_model, inference_detector # 加载预训练模型 config = 'configs/pointpillars/hv_pointpillars_secfpn_8xb6-160e_kitti-3d-car.py' checkpoint = 'pretrained/pointpillars.pth' model = init_model(config, checkpoint) # 推理 result = inference_detector(model, 'sample.pcd') # 转换为标注格式 boxes_3d = result['boxes_3d'] scores_3d = result['scores_3d'] labels_3d = result['labels_3d'] # 过滤低置信度 mask = scores_3d > 0.3 initial_annotations = [] for box, score, label in zip(boxes_3d[mask], scores_3d[mask], labels_3d[mask]): anno = { 'translation': box[:3].tolist(), 'size': box[3:6].tolist(), 'rotation': yaw_to_quaternion(box[6]), 'category_name': CLASSES[label], 'score': float(score), # 标记为自动生成 } initial_annotations.append(anno) # 导出到CVAT export_to_cvat_format(initial_annotations, 'pre_labels.xml') ``` **优点**: - 节省80%标注时间 - 减少遗漏 - 提高一致性 **注意**: - 必须人工检查所有box - 低置信度区域重点检查 - 复杂场景重新标注 ### 5.2 跨帧跟踪 ```python # 自动跟踪同一实例 # tools/track_objects.py from mot import MultiObjectTracker tracker = MultiObjectTracker() for frame_id in range(num_frames): # 当前帧检测结果 detections = load_annotations(frame_id) # 更新跟踪器 tracks = tracker.update(detections) # 分配instance_token for track in tracks: track['instance_token'] = f'inst_{track.id:04d}' # 保存 save_annotations(frame_id, tracks) ``` ### 5.3 质量检查自动化 ```python # 自动检查异常 # tools/check_annotation_quality.py def auto_check_annotations(anno_file): annos = load_annotations(anno_file) issues = [] for idx, anno in enumerate(annos): # 检查1: 尺寸异常 w, l, h = anno['size'] if w > 3 or l > 15 or h > 5: issues.append({ 'id': idx, 'type': 'size_error', 'value': anno['size'] }) # 检查2: 位置异常 x, y, z = anno['translation'] if abs(z) > 3: # 距地面过高/过低 issues.append({ 'id': idx, 'type': 'height_error', 'value': z }) # 检查3: 点云数量 if anno['num_lidar_pts'] < 5: issues.append({ 'id': idx, 'type': 'low_points', 'value': anno['num_lidar_pts'] }) # 检查4: 重叠 for other_idx, other in enumerate(annos[idx+1:], idx+1): iou = compute_iou_3d(anno, other) if iou > 0.5: # 严重重叠 issues.append({ 'id': (idx, other_idx), 'type': 'overlap', 'value': iou }) # 生成报告 print(f"发现 {len(issues)} 个问题:") for issue in issues: print(f" {issue['type']}: {issue}") return issues # 使用 issues = auto_check_annotations('annotations.json') export_issues_for_review(issues, 'review_list.json') ``` --- ## 6. 实战案例 ### 案例1: 标注一个复杂路口场景 ``` 场景描述: - 10辆车 - 5个行人 - 3个红绿灯 - 2个交通标志 - 部分遮挡 步骤: 1. 俯视图整体浏览,标记所有目标位置 2. 先标注完全可见的车辆 (4辆) 3. 标注部分遮挡的车辆 (6辆) - 估计完整尺寸 - visibility设为2或3 4. 标注行人 (优先级高,安全关键) 5. 标注静态物体 (红绿灯、标志) 6. 跨6个相机视图验证 7. 标注速度信息 (通过下一帧计算) 8. 最终检查 时间: 约15分钟/帧 ``` ### 案例2: 夜间场景标注 ``` 挑战: - 点云稀疏 - 图像黑暗 - 反光干扰 技巧: 1. 增强图像对比度辅助判断 2. 主要依靠点云 3. 利用车灯位置确定朝向 4. 减小box,避免包含噪声点 5. visibility降级 (通常为2) 质量要求可适当放宽: - 位置误差 < 20cm (vs 10cm白天) - 角度误差 < 5° (vs 3°) ``` --- ## 7. 常见问题FAQ **Q1: 遮挡物体如何标注?** ``` A: 标注完整的3D box,即使部分不可见 - 估计完整尺寸 - visibility设为1-3 - 在多视角中寻找线索 ``` **Q2: 速度如何标注?** ``` A: 优先使用跨帧跟踪自动计算 - 间隔0.5秒: v = Δposition / 0.5 - 静止车辆: 手动设为(0, 0) - 无法跟踪: 根据场景估计 ``` **Q3: box底面不贴地怎么办?** ``` A: 调整z坐标 - nuScenes: z ≈ -1.8m (LiDAR在1.8m高) - 地面点云z值 ≈ -1.8m - 车辆底部应该在地面上 ``` **Q4: 角度标注不准确?** ``` A: 使用多种方法验证 1. 观察车辆形状 (车头窄车尾宽) 2. 查看车灯位置 3. 运动方向 (与速度一致) 4. 在2D图像中验证 ``` **Q5: 标注效率如何提升?** ``` A: 综合使用自动化 1. 预标注: 节省80%时间 2. 快捷键: 熟练使用 3. 模板: 常见尺寸预设 4. 跟踪: 自动跨帧 ``` --- ## 8. 总结 **标注质量标准**: ``` 优秀 (A级): - IoU > 0.8 - 中心误差 < 5cm - 角度误差 < 2° - 召回率 > 98% 良好 (B级): - IoU > 0.7 - 中心误差 < 10cm - 角度误差 < 3° - 召回率 > 95% 合格 (C级): - IoU > 0.5 - 中心误差 < 20cm - 角度误差 < 5° - 召回率 > 90% ``` **训练所需标注量**: - 最小: 5000帧 (基础性能) - 推荐: 10000帧 (良好性能) - 理想: 28000帧 (nuScenes规模) **标注成本估算**: - 简单场景: 5分钟/帧 - 复杂场景: 15分钟/帧 - 平均: 10分钟/帧 - 10000帧 = 1667小时 ≈ 208工作日 (单人)