bev-project/project/docs/3D标注详细指南.md

18 KiB
Raw Blame History

3D目标检测标注详细指南

📋 目录

  1. 3D Box标注原理
  2. 标注工具使用
  3. 标注流程步骤
  4. 质量控制
  5. 自动化辅助

1. 3D Box标注原理

1.1 3D Bounding Box定义

一个3D box由9个参数完整描述:

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个顶点

# 计算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标注 (推荐)

安装:

# 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格式

配置示例:

# 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

# 轻量级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. 数据检查

# 检查数据完整性
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. 可视化初始化

# 在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: 自动检测 + 人工修正

# 使用预训练模型生成初始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 基础属性

{
  "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)

{
  "attribute_tokens": [
    "vehicle.moving",           // 车辆运动状态
    "cycle.with_rider",         // 自行车有骑手
    "pedestrian.standing",      // 行人站立
    ...
  ]
}

Step 5: 多视图验证

验证清单:

□ 3D点云视图:
  - box紧密包裹点云
  - 底面贴地
  - 朝向正确

□ CAM_FRONT投影:
  - box边缘对齐车辆轮廓
  - 顶部和底部正确
  - 无明显溢出

□ CAM_FRONT_LEFT/RIGHT:
  - 侧面投影准确
  - 宽度和高度正确

□ CAM_BACK投影:
  - 后视角对齐
  - 遮挡处理合理

□ 俯视图 (BEV):
  - 中心位置准确
  - 占据车道合理
  - 与周围物体无碰撞

常见错误检查:

# 自动化检查脚本
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 质量评估指标

# 评估脚本
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)

# 使用现有模型生成初始标注
# 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 跨帧跟踪

# 自动跟踪同一实例
# 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 质量检查自动化

# 自动检查异常
# 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工作日 (单人)