IMS数据标注规范:从采集到验证的完整流程

引言:数据是AI的基石

标注质量决定模型性能

  • 垃圾进,垃圾出
  • 标注错误→模型学到错误模式
  • 不一致→无法收敛

一、标注规范

1.1 类别定义

DMS标注类别

类别 子类别 标注要求
疲劳 正常/疲劳/微睡眠 闭眼时长>3秒=疲劳
分心 正常/手机/中控屏/后视镜 视线偏移>2秒=分心
视线 前方/左/右/上/下 精度到5°
头位 正常/前倾/后倾/侧倾 角度偏差>10°=异常
安全带 系好/不系/错误佩戴 清晰可见=系好

1.2 标注指南

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
## IMS数据标注指南

### 疲劳标注

**判定标准**
1. **正常**:眼睑闭合<30%连续>5秒
2. **轻微疲劳**:眼睑闭合30-60%,连续3-5秒
3. **中度疲劳**:眼睑闭合60-80%,或打哈欠>2次/分
4. **重度疲劳**:眼睑闭合>80%,或微睡眠>1次

**特殊情况**
- 墨镜场景:依赖头位+眨眼频率
- 口罩场景:增加PERCLOS权重
- 夜间场景:降低视觉阈值

### 分心标注

**判定标准**
1. **正常**:视线在前方±15°
2. **手机分心**:视线向下>30°,持续>2秒
3. **中控屏分心**:视线向右/左>30°,持续>3秒
4. **后视镜分心**:视线向后>30°,持续>1秒
5. **侧窗分心**:视线向侧>45°,持续>1秒

**关键原则**
- 必须标注起始帧和结束帧
- 分心类型选择最符合的
- 短暂偏离不标注为分心

### 视线标注

**标注精度要求**

前方区域:[0°, ±15°]
左侧区域:[90°, 105°]
右侧区域:[255°, 285°]
上方区域:[270°, 315°]
下方区域:[45°, 135°]

精度要求:±5°以内

1
2
3
4
5

**标注工具**
- 眼动轨迹标注工具
- 可视化标线
- 批量标注支持
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54

---

## 二、标注工具链

### 2.1 主流工具

| 工具 | 类型 | 优势 | 劣势 |
|------|------|------|------|
| **Label Studio** | 视频标注 | 功能强大 | 商业许可 |
| **CVAT** | 开源 | 免费多模态 | 界面复杂 |
| **VGG Image Annotator** | 图像标注 | 轻量 | 不支持视频 |
| **LabelImg** | 边框标注 | 简单 | 功能有限 |

### 2.2 推荐工具链

```python
class AnnotationPipeline:
"""
标注流水线
"""
def __init__(self, tool_type='cvat'):
# 选择工具
if tool_type == 'cvat':
self.tool = CVATClient()
elif tool_type == 'label_studio':
self.tool = LabelStudioClient()

# 项目配置
self.config = self.load_annotation_config()

def setup_project(self):
"""
设置项目
"""
# 1. 创建项目
project_id = self.tool.create_project(
name=self.config['project_name'],
task_type=self.config['task_type'] # 'image' / 'video'
)

# 2. 上传数据
self.tool.upload_data(project_id, self.config['data_path'])

# 3. 配置标注规范
self.tool.setup_attributes(project_id, self.config['labels'])

# 4. 分配标注员
self.tool.assign_annotators(
project_id,
annotators=self.config['annotators']
)

return project_id

2.3 自定义标注工具

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
import cv2
import numpy as np

class CustomAnnotationTool:
"""
自定义标注工具
"""
def __init__(self, video_path):
self.video_path = video_path
self.cap = cv2.VideoCapture(video_path)

# 当前帧
self.current_frame = 0
self.annotations = []

# 标注状态
self.current_annotation = None
self.playing = False

def load_video(self):
"""
加载视频
"""
self.cap.open(self.video_path)
self.total_frames = int(self.cap.get(cv2.CAP_PROP_FRAME_COUNT))

def navigate_frame(self, frame_idx):
"""
跳转到指定帧
"""
self.cap.set(cv2.CAP_PROP_POS_FRAMES, frame_idx)
ret, frame = self.cap.read()
return ret, frame

def mark_start(self, annotation_type):
"""
标记开始
"""
self.current_annotation = {
'type': annotation_type,
'start_frame': self.current_frame,
'start_timestamp': self.current_frame / 30 # 30fps
'keypoints': [],
'bboxes': []
}
self.playing = True

def mark_end(self):
"""
标记结束
"""
if self.current_annotation:
self.current_annotation['end_frame'] = self.current_frame
self.current_annotation['end_timestamp'] = self.current_frame / 30
self.annotations.append(self.current_annotation)
self.current_annotation = None
self.playing = False

# 保存
self.save_annotations()

def save_annotations(self):
"""
保存标注
"""
output_path = self.video_path.replace('.mp4', '_annotations.json')

with open(output_path, 'w') as f:
json.dump({
'video': self.video_path,
'total_frames': self.total_frames,
'annotations': self.annotations
}, f, indent=2)

print(f"✅ 标注已保存:{output_path}")

三、质量控制流程

3.1 质量检查

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
class QualityController:
"""
质量控制器
"""
def __init__(self):
self.checks = {
'completeness': 0,
'consistency': 0,
'accuracy': 0
}

def check_completeness(self, annotations):
"""
检查完整性
"""
issues = []

for ann in annotations:
# 1. 必须有开始和结束帧
if 'start_frame' not in ann:
issues.append({
'type': 'missing_start_frame',
'annotation_id': ann['id']
})

if 'end_frame' not in ann:
issues.append({
'type': 'missing_end_frame',
'annotation_id': ann['id']
})

# 2. 结束帧必须>开始帧
if ann['end_frame'] <= ann['start_frame']:
issues.append({
'type': 'invalid_frame_range',
'annotation_id': ann['id']
})

self.checks['completeness'] = len(issues)
return issues

def check_consistency(self, annotations):
"""
检查一致性
"""
issues = []

# 1. 检查重叠标注
for i in range(len(annotations)):
for j in range(i+1, len(annotations)):
ann1 = annotations[i]
ann2 = annotations[j]

# 检查时间重叠
if ann1['end_frame'] > ann2['start_frame'] and \
ann1['end_frame'] < ann2['end_frame']:

# 检查类别冲突
if ann1['type'] == 'fatigue' and ann2['type'] == 'distraction':
issues.append({
'type': 'category_conflict',
'annotation_ids': [ann1['id'], ann2['id']]
})

self.checks['consistency'] = len(issues)
return issues

3.2 双重验证

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
class DoubleValidator:
"""
双重验证系统
"""
def __init__(self):
self.annotators = []
self.agreements = []

def assign_validation(self, annotation_id, annotator1, annotator2):
"""
分配双重验证
"""
self.agreements.append({
'annotation_id': annotation_id,
'annotator1': annotator1,
'annotator2': annotator2,
'status': 'pending' # pending / accepted / rejected
})

def calculate_agreement(self):
"""
计算一致性
"""
total = len(self.agreements)
agreed = 0

for agreement in self.agreements:
if agreement['status'] == 'accepted':
agreed += 1

agreement_rate = agreed / total

return agreement_rate

def get_disagreements(self):
"""
获取分歧标注
"""
disagreements = []

for agreement in self.agreements:
if agreement['status'] == 'rejected':
disagreements.append({
'annotation_id': agreement['annotation_id'],
'annotator1': agreement['annotator1'],
'annotator2': agreement['annotator2']
})

return disagreements

四、标注员培训

4.1 培训内容

培训计划

阶段 内容 时长
第一阶段 标注规范解读 2小时
第二阶段 工具使用培训 4小时
第三阶段 实战标注练习 8小时
第四阶段 质量检查培训 2小时

4.2 培训评估

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
class AnnotatorEvaluator:
"""
标注员评估器
"""
def __init__(self, ground_truth_path):
self.ground_truth = load_json(ground_truth_path)
self.scores = {}

def evaluate(self, annotator, predictions):
"""
评估标注员
"""
# 1. IoU计算(用于边界框)
iou_scores = []
for pred, gt in zip(predictions, self.ground_truth):
iou = self.calculate_iou(pred['bbox'], gt['bbox'])
iou_scores.append(iou)

# 2. 分类准确率
class_accuracy = self.calculate_class_accuracy(predictions, self.ground_truth)

# 3. 时序准确率(用于事件)
temporal_accuracy = self.calculate_temporal_accuracy(predictions, self.ground_truth)

score = {
'mean_iou': np.mean(iou_scores),
'class_accuracy': class_accuracy,
'temporal_accuracy': temporal_accuracy
}

self.scores[annotator] = score
return score

def calculate_iou(self, bbox1, bbox2):
"""
计算IoU
"""
x1, y1, w1, h1 = bbox1
x2, y2, w2, h2 = bbox2

# 交集
xi1 = max(x1, x2)
yi1 = max(y1, y2)
xi2 = min(x1+w1, x2+w2)
yi2 = min(y1+h1, y2+h2)

inter_w = max(0, xi2 - xi1)
inter_h = max(0, yi2 - yi1)
intersection = inter_w * inter_h

# 并集
union_w = (w1 + w2) - inter_w
union_h = (h1 + h2) - inter_h
union = union_w * union_h

iou = intersection / union

return iou

五、迭代优化机制

5.1 数据闭环

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
┌─────────────────────────────────┐
│ 数据采集 │
│ ├── 视频录制 │
│ ├── 多传感器数据 │
│ └── 场景多样化 │
└─────────────────────────────────┘

┌─────────────────────────────────┐
│ 数据标注 │
│ ├── 人工标注 │
│ ├── 质量验证 │
│ └── 审核流程 │
└─────────────────────────────────┘

┌─────────────────────────────────┐
│ 模型训练 │
│ ├── 模型架构 │
│ ├── 超参数优化 │
│ └── 性能评估 │
└─────────────────────────────────┘

┌─────────────────────────────────┐
│ 模型推理 │
│ ├── 自动标注 │
│ ├── 人工修正 │
│ └── 边界案例收集 │
└─────────────────────────────────┘

┌─────────────────────────────────┐
│ 数据增强 │
│ ├── 自动标注数据加回训练集 │
│ ├── 难例挖掘 │
│ └── 重新训练 │
└─────────────────────────────────┘

5.2 主动学习

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
class ActiveLearning:
"""
主动学习系统
"""
def __init__(self, model, uncertainty_threshold=0.3):
self.model = model
self.uncertainty_threshold = uncertainty_threshold
self.annotator_queue = []

def select_samples(self, predictions):
"""
选择待标注样本
"""
uncertain_samples = []

for pred in predictions:
# 计算不确定性
uncertainty = self.model.predict_uncertainty(pred['features'])

if uncertainty > self.uncertainty_threshold:
uncertain_samples.append({
'sample_id': pred['id'],
'features': pred['features'],
'prediction': pred,
'uncertainty': uncertainty
})

return uncertain_samples

def prioritize_samples(self, samples, strategy='uncertainty'):
"""
优先级排序
"""
if strategy == 'uncertainty':
# 按不确定性排序
samples.sort(key=lambda x: x['uncertainty'], reverse=True)
elif strategy == 'diversity':
# 按多样性排序
features = [s['features'] for s in samples]
samples = self.diversity_sort(samples, features)

return samples

def update_model(self, annotated_samples):
"""
更新模型
"""
# 1. 添加标注数据
self.model.add_data(annotated_samples)

# 2. 增量训练
self.model.incremental_train()

# 3. 更新不确定性估计
self.model.update_uncertainty_estimator()

六、总结

6.1 最佳实践

实践 说明
标准化 使用统一标注规范
工具化 使用标注工具平台
质量控制 双重验证+质量检查
主动学习 优先标注困难样本
持续迭代 数据闭环

6.2 常见问题

问题 解决方案
标注不一致 建立标注规范+培训
质量低 双重验证+审核
效率低 使用快捷键+批量工具
边界案例 主动学习+数据挖掘

参考文献

  1. CVAT. “Computer Vision Annotation Tool.” 2025.
  2. Label Studio. “Data Labeling Platform.” 2025.
  3. Euro NCAP. “Test Protocols.” 2026.

本文是IMS工程实践系列文章之一,上一篇:OEM DMS系统对比


IMS数据标注规范:从采集到验证的完整流程
https://dapalm.com/2026/03/13/2026-03-13-IMS数据标注规范-从采集到验证的完整流程/
作者
Mars
发布于
2026年3月13日
许可协议