ToF深度相机车内3D姿态感知:10类危险驾驶行为实时检测系统

论文信息

项目 内容
标题 In-vehicle 3D vision for perceiving dangerous driving behaviors
发表期刊 Scientific Reports (Nature)
发表时间 2026年5月
DOI 10.1038/s41598-026-52381-2
关键词 3D-ToF、驾驶员姿态估计、危险行为检测、ST-GCN、实时监控

核心创新

首次提出基于3D-ToF深度相机的车内驾驶员3D姿态完整感知方案,解决RGB/NIR相机的三大痛点:光照敏感、隐私泄露、2D姿态信息不足。

ToF vs RGB/NIR 对比

维度 RGB摄像头 NIR近红外 3D-ToF
强光抗性 ❌ 过曝 ⚠️ 部分 ✅ 抗强光
弱光性能 ❌ 噪声大 ✅ 好 ✅ 一致
隐私保护 ❌ 面部可识别 ⚠️ 部分 ✅ 无纹理
3D信息 ❌ 2D投影 ❌ 2D投影 ✅ 完整3D
算力需求 低 低 中
成本 低 低-中 中

系统设计

整体架构

双视角设计解决遮挡

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
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
import torch
import torch.nn as nn
import torch.nn.functional as F

class DualViewToFSystem:
"""
双视角ToF深度相机系统

主视角:仪表台上方,正对驾驶员
侧视角:A柱内侧,侧对驾驶员

解决单视角遮挡问题:
- 手被方向盘遮挡 → 侧视角可见
- 身体前倾被遮挡 → 侧视角补充
"""

def __init__(self):
# 主从双视角(论文使用 Azure Kinect ToF)
self.main_cam = {
'position': 'dashboard_top_center',
'resolution': (640, 480),
'fps': 30,
'fov': (120, 120), # H, V degrees
'sync_error': '<10ms', # 主从同步误差
}

# 侧视角参数
self.side_cam = {
'position': 'a_pillar_left',
'resolution': (640, 480),
'fps': 30,
'fov': (120, 120),
'sync_error': '<10ms',
}

def get_coverage(self):
"""双视角覆盖区域分析"""
return {
'main': ['面部', '上身正面', '双手前方', '大腿正面'],
'side': ['手臂侧面', '上身侧面', '后背上部', '肩部'],
'overlap': ['头部', '肩部', '胸部'], # 双视角均可见
'blind_spot': ['腰部以下后方', '脚部']
}


class Lightweight3DPoseEstimator(nn.Module):
"""
轻量级3D姿态估计器

论文方案:模型大小约为基准的27%(46.25MB),精度96.02%(基准95.65%)
输入:ToF深度图像 (1, H, W)
输出:16个3D关键关节点
"""

# 16个关键关节点定义
JOINT_NAMES = [
'head_top', 'head', 'neck', # 头部
'left_shoulder', 'right_shoulder', # 肩部
'left_elbow', 'right_elbow', # 肘部
'left_wrist', 'right_wrist', # 手腕
'spine', 'pelvis', # 躯干
'left_hip', 'right_hip', # 髋部
'left_knee', 'right_knee', # 膝盖
]

def __init__(self, num_joints: int = 16):
super().__init__()

# 深度图像特征提取(比RGB更简单,单通道)
self.backbone = nn.Sequential(
# Block 1
nn.Conv2d(1, 32, 3, stride=2, padding=1),
nn.BatchNorm2d(32),
nn.ReLU(),
# Block 2
nn.Conv2d(32, 64, 3, stride=2, padding=1),
nn.BatchNorm2d(64),
nn.ReLU(),
# Block 3
nn.Conv2d(64, 128, 3, stride=2, padding=1),
nn.BatchNorm2d(128),
nn.ReLU(),
# Block 4
nn.Conv2d(128, 256, 3, stride=1, padding=1),
nn.BatchNorm2d(256),
nn.ReLU(),
# Block 5
nn.Conv2d(256, 512, 3, stride=1, padding=1),
nn.BatchNorm2d(512),
nn.ReLU(),
)

# 2D热力图预测头
self.heatmap_head = nn.Sequential(
nn.Conv2d(512, 256, 1),
nn.ReLU(),
nn.Conv2d(256, num_joints, 1) # 每个关节一个通道
)

# 3D深度偏移预测头
self.depth_head = nn.Sequential(
nn.Conv2d(512, 256, 1),
nn.ReLU(),
nn.Conv2d(256, num_joints, 1) # 每个关节的深度偏移
)

def forward(self, depth_img: torch.Tensor) -> dict:
"""
Args:
depth_img: (B, 1, H, W) 深度图像, 单位毫米

Returns:
joints_2d: (B, num_joints, 2) 像素坐标
joints_3d: (B, num_joints, 3) 3D坐标(相机坐标系)
confidences: (B, num_joints) 置信度
"""
B = depth_img.shape[0]

# 特征提取
feats = self.backbone(depth_img) # (B, 512, H/8, W/8)

# 2D热力图
heatmaps = self.heatmap_head(feats) # (B, num_joints, H/8, W/8)
heatmaps = F.interpolate(
heatmaps, scale_factor=8, mode='bilinear', align_corners=False
)

# 深度偏移
depth_offsets = self.depth_head(feats)
depth_offsets = F.interpolate(
depth_offsets, scale_factor=8, mode='bilinear', align_corners=False
)

# 提取2D关节点
joints_2d = self._extract_joints_2d(heatmaps)

# 提取3D关节点
joints_3d = self._extract_joints_3d(
joints_2d, depth_img, depth_offsets
)

# 置信度
confidences = self._get_confidences(heatmaps)

return {
'joints_2d': joints_2d,
'joints_3d': joints_3d,
'confidences': confidences
}

def _extract_joints_2d(self, heatmaps: torch.Tensor) -> torch.Tensor:
"""从热力图提取2D坐标"""
B, J, H, W = heatmaps.shape
# 每个通道的最大值位置
heatmaps_flat = heatmaps.view(B, J, -1)
max_idx = heatmaps_flat.argmax(dim=2) # (B, J)
x = (max_idx % W).float()
y = (max_idx // W).float()
return torch.stack([x, y], dim=2) # (B, J, 2)

def _extract_joints_3d(self, joints_2d: torch.Tensor,
depth_img: torch.Tensor,
depth_offsets: torch.Tensor) -> torch.Tensor:
"""从2D坐标+深度图提取3D坐标"""
B, J, _ = joints_2d.shape
_, _, H, W = depth_img.shape

joints_3d = torch.zeros(B, J, 3, device=joints_2d.device)

for b in range(B):
for j in range(J):
x, y = joints_2d[b, j].long()
x = torch.clamp(x, 0, W-1)
y = torch.clamp(y, 0, H-1)

# 深度值 + 偏移
z = depth_img[b, 0, y, x] + depth_offsets[b, j, y, x]

# 转换到相机坐标系
# fx, fy, cx, cy 为相机内参(ToF典型值)
fx, fy = 320, 320 # 焦距
cx, cy = 160, 120 # 光心

X = (x - cx) * z / fx
Y = (y - cy) * z / fy
Z = z

joints_3d[b, j] = torch.tensor([X, Y, Z], device=joints_2d.device)

return joints_3d

def _get_confidences(self, heatmaps: torch.Tensor) -> torch.Tensor:
"""提取置信度"""
B, J, H, W = heatmaps.shape
return heatmaps.view(B, J, -1).max(dim=2)[0]


class STGCNBehaviorRecognition(nn.Module):
"""
ST-GCN++ 驾驶行为识别

输入:3D关节点序列
输出:10类行为分类

10类行为定义:
0: 正常驾驶
1: 低头看手机
2: 接打电话
3: 抽烟
4: 喝水
5: 左顾右盼
6: 调节中控
7. 身体前倾
8: 打哈欠/疲劳
9: 双手离开方向盘
"""

BEHAVIOR_CLASSES = [
'normal_driving',
'looking_down_phone',
'calling',
'smoking',
'drinking',
'looking_around',
'adjusting_console',
'leaning_forward',
'yawning_fatigue',
'hands_off_wheel'
]

def __init__(self, num_joints: int = 16,
num_classes: int = 10,
hidden_dim: int = 64,
num_layers: int = 3):
super().__init__()

# 关节空间图卷积
self.spatial_gcn = SpatialGraphConv(num_joints, hidden_dim)

# 时序卷积
self.temporal_conv = nn.Sequential(
nn.Conv1d(hidden_dim, hidden_dim * 2, kernel_size=3, padding=1),
nn.BatchNorm1d(hidden_dim * 2),
nn.ReLU(),
nn.Conv1d(hidden_dim * 2, hidden_dim * 2, kernel_size=3, padding=1),
nn.BatchNorm1d(hidden_dim * 2),
nn.ReLU(),
)

# 多层ST块
self.st_blocks = nn.ModuleList([
STBlock(hidden_dim * 2, hidden_dim * 2)
for _ in range(num_layers)
])

# 分类头
self.classifier = nn.Sequential(
nn.AdaptiveAvgPool1d(1),
nn.Flatten(),
nn.Linear(hidden_dim * 2, 128),
nn.ReLU(),
nn.Dropout(0.5),
nn.Linear(128, num_classes)
)

def forward(self, joint_sequence: torch.Tensor) -> torch.Tensor:
"""
Args:
joint_sequence: (B, T, num_joints, 3) 3D关节点时序
T=30 (1秒@30fps)
Returns:
logits: (B, num_classes)
"""
B, T, J, C = joint_sequence.shape

# 空间图卷积
x = self.spatial_gcn(joint_sequence) # (B, T, J, hidden_dim*2)

# 重排维度: (B, J*hidden, T)
x = x.view(B, T, -1).permute(0, 2, 1) # (B, J*hidden_dim*2, T)

# 时序卷积
x = self.temporal_conv(x)

# ST块
for block in self.st_blocks:
x = block(x)

# 分类
logits = self.classifier(x)
return logits


class SpatialGraphConv(nn.Module):
"""空间图卷积:关节点间空间关系建模"""

def __init__(self, num_joints: int, out_dim: int):
super().__init__()
# 预定义关节连接关系(16关节)
# [关节i, 关节j] 表示连接
self.edges = [
(0, 1), (1, 2), # head
(2, 3), (2, 4), # neck-shoulder
(3, 5), (5, 7), (7, 9), # left arm
(4, 6), (6, 8), (8, 10), # right arm
(2, 11), (11, 12), # spine
(12, 13), (12, 14), # pelvis-hip
(13, 15), (14, 16), # hip-knee
]

self.num_joints = num_joints
self.out_dim = out_dim
self.linear = nn.Linear(3, out_dim)

# 邻接矩阵
adj = torch.zeros(num_joints, num_joints)
for i, j in self.edges:
adj[i, j] = 1
adj[j, i] = 1
adj += torch.eye(num_joints) # 自环
# 归一化
D = adj.sum(dim=1).diag()
D_inv = D.inverse()
self.register_buffer('adj', D_inv @ adj)

def forward(self, x: torch.Tensor) -> torch.Tensor:
"""
Args:
x: (B, T, J, 3)
Returns:
(B, T, J, out_dim)
"""
x = self.linear(x) # (B, T, J, out_dim)
# 图卷积
x = torch.einsum('ij,btjd->btid', self.adj, x)
return F.relu(x)


class STBlock(nn.Module):
"""ST-GCN 时空块"""
def __init__(self, in_dim: int, out_dim: int):
super().__init__()
self.spatial = nn.Conv1d(in_dim, out_dim, 1)
self.temporal = nn.Conv1d(out_dim, out_dim, 3, padding=1)
self.bn = nn.BatchNorm1d(out_dim)
self.relu = nn.ReLU()

def forward(self, x):
identity = x
x = self.spatial(x)
x = self.temporal(x)
x = self.bn(x)
x = self.relu(x + identity)
return x


# ==================== 测试代码 ====================
if __name__ == "__main__":
# 测试3D姿态估计
pose_model = Lightweight3DPoseEstimator(num_joints=16)
depth_img = torch.randn(2, 1, 240, 320) # ToF深度图
with torch.no_grad():
pose_out = pose_model(depth_img)
print(f"2D关节点: {pose_out['joints_2d'].shape}")
print(f"3D关节点: {pose_out['joints_3d'].shape}")
print(f"置信度: {pose_out['confidences'].shape}")
print(f"姿态模型参数量: {sum(p.numel() for p in pose_model.parameters())/1e6:.1f}M")

# 测试行为识别
behav_model = STGCNBehaviorRecognition(num_joints=16, num_classes=10)
joint_seq = torch.randn(2, 30, 16, 3) # 30帧
with torch.no_grad():
logits = behav_model(joint_seq)
print(f"\n行为识别输出: {logits.shape}")
print(f"行为模型参数量: {sum(p.numel() for p in behav_model.parameters())/1e6:.1f}M")
print(f"姿态+行为总参数量: {(sum(p.numel() for p in pose_model.parameters()) + sum(p.numel() for p in behav_model.parameters()))/1e6:.1f}M")

数据集

3D-ToF驾驶姿态数据集

维度 规格
行为类别 10类(含正常驾驶)
原始视频 731段
图像帧数 100,000帧
标注 3D骨架(16关节点)
光照条件 多种(日间/夜间/逆光)
服装变化 多种(厚衣/薄衣/深浅色)
驾驶员人数 25人(15男10女,22-45岁)
拍摄设备 Azure Kinect 3D-ToF 双主从视角,640×480,120°×120° FOV,30FPS,同步误差<10ms

注:论文内部对数据规模的表述不一致——引言称 731 段原始视频、10 万帧图像,数据集章节则称”900+视频片段、约30万对深度/红外图像序列”,引用时请注意。

性能指标

3D姿态估计

指标 本文方案 基准方案
精度 96.02% 95.65%
模型大小 46.25MB(约为基准的27%) —
计算量 1.49G FLOPs —

论文未提供逐项的参数量(0.8M/2.7M)与基线推理速度(120ms)数据,此前的对比列已删除。

行为识别(10类)

论文未提供分行为类别的精度/召回/F1明细表(此前表格中的逐类数字无法溯源,已删除)。原文报告的总体结果为:

  • 10类行为识别总体准确率 98.0%;
  • 对比基线:GCN 74.5%、1D-CNN 83.4%、DNN 92.6%、BLS 93.5%;
  • 单样本推理延迟 0.0375s,ST-GCN++ 模型参数仅 507KB。

三级控制策略

等级 触发行为 系统响应
一级 低头、喝水、调节中控 声光提醒
二级 打电话、抽烟、左顾右盼 强烈警告 + ADAS灵敏度提升
三级 双手离盘、疲劳、身体前倾 紧急停车提示 + 气囊预紧

IMS开发启示

1. ToF方案的可行性评估

维度 评估
硬件成本 ToF模组价格高于RGB-IR摄像头(原文未提供具体价格,需按项目询价)
算力需求 单通道深度图 < 三通道RGB,特征提取更快(论文实测1.49G FLOPs、27-28FPS实时)
隐私优势 深度图无面部纹理,有利于隐私保护与GDPR合规(论文的核心动机之一),但”零风险”表述过于绝对
3D信息 直接输出3D坐标,无需多视角三角化
OOP应用 3D姿态直接支持异常姿态检测(OOP)

2. 与现有IMS RGB-IR方案的对比

功能 RGB-IR方案 ToF方案 建议
疲劳检测 ✅ PERCLOS ⚠️ 无眼球细节 保留RGB-IR
分心检测 ✅ 视线方向 ⚠️ 无注视点 保留RGB-IR
OOP姿态 ⚠️ 2D限制 ✅ 完整3D ToF更优
行为识别 ⚠️ 2D动作 ✅ 3D空间动作 ToF更优
手机使用 ✅ 可检测 ⚠️ 无纹理难识别 RGB-IR更优
隐私合规 ⚠️ 面部数据 ✅ 无面部 ToF更优

3. 融合方案建议

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
IMS多模态融合架构(推荐):

主DMS摄像头 (RGB-IR)
├── 疲劳检测(PERCLOS/眨眼)
├── 分心检测(视线方向/注视点)
├── 手机使用检测
└── 面部表情分析

辅助ToF深度相机
├── 3D全身姿态估计(OOP核心)
├── 危险行为识别(ST-GCN 10类)
├── 乘员分类(体型/姿态3D特征)
└── 安全带状态(3D空间关系)

融合决策层
├── DMS + ToF → 综合驾驶员状态
├── 冲突仲裁(如DMS说正常但ToF发现前倾)
└── 三级警告输出

4. 部署评估

论文未提供分硬件平台(QCS8255/TDA4VM/Orin 等)的逐项延迟数据(此前表格数字无法溯源,已删除)。原文仅报告:在车规级嵌入式平台上达到 27–28 FPS 的实时性能(单样本推理延迟 0.0375s,总计算量 1.49G FLOPs)。


论文DOI: 10.1038/s41598-026-52381-2