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

830 lines
18 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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工作日 (单人)