18 KiB
18 KiB
3D目标检测标注详细指南
📋 目录
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标注任务:
-
新建任务
- Task name:
nuscenes_sample_001 - Project:
autonomous_driving - Label: 添加10类 (car, pedestrian, truck...)
- Task name:
-
上传数据
- 点云文件:
sample.pcd(velodyne格式) - 相机图像: 6张 (CAM_FRONT, CAM_FRONT_LEFT...)
- 关联相机内外参
- 点云文件:
-
3D标注模式
工具栏选择: "3D Cuboid" 快捷键: - N: 创建新box - Del: 删除box - R: 旋转box - F: 切换视角 (3D/2D) - Space: 播放/暂停 -
标注步骤 (详见下节)
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工作日 (单人)