车内乘员3D姿态估计新方法:深度+红外图像融合

论文信息

  • 标题: Three-Dimensional Posture Estimation of Vehicle Occupants Using Depth and Infrared Images
  • 期刊: Sensors 2024, 24(17):5530
  • 作者: Anuj Tambwekar, Byoung-Keon D. Park, Arpan Kusari, Wenbo Sun
  • 机构: University of Michigan Transportation Research Institute
  • DOI: 10.3390/s24175530

核心创新

首次提出车内3D姿态估计的深度+红外融合方案,解决了深度图像标注困难的问题,为Euro NCAP OOP(Out-of-Position)异常姿态检测提供了可落地的技术路径。

问题背景

OOP异常姿态的危害

Out-of-Position(OOP)指乘员在车内处于非正常坐姿,如:

OOP类型 危害 安全影响
身体前倾 安全带松弛 碰撞时约束失效
侧向倾斜 安全带位置偏离 腹部/颈部损伤
腿部异常 膝盖靠近仪表盘 腿部骨折风险
儿童异常坐姿 安全气囊危险 致命伤害

3D姿态估计的挑战

挑战 说明 传统方案缺陷
标注困难 深度图像需3D坐标 耗时、成本高
光照变化 车内光照不稳定 RGB方法失效
遮挡问题 座椅/方向盘遮挡 2D方法精度低
隐私问题 乘员面部曝光 欧美法规限制

方法详解

整体架构

graph TB
    A[深度相机] --> B[深度图像]
    C[红外相机] --> D[红外图像]
    
    B --> E[深度特征提取]
    D --> F[红外特征提取]
    
    E --> G[特征融合]
    F --> G
    
    G --> H[3D姿态回归网络]
    H --> I[关键点3D坐标]
    
    I --> J{OOP判断}
    J -->|异常| K[安全带预警]
    J -->|正常| L[持续监控]

核心技术要点

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
import numpy as np
import cv2

class DepthIRCameraSystem:
"""
深度+红外相机系统

适用于车内OOP检测
"""

def __init__(self,
depth_resolution=(640, 480),
ir_resolution=(640, 480),
fps=30):
self.depth_res = depth_resolution
self.ir_res = ir_resolution
self.fps = fps

# 深度相机参数(示例:Intel RealSense D435)
self.depth_scale = 0.001 # 米/单位
self.min_depth = 0.1 # 米
self.max_depth = 10.0 # 米

# 红外相机参数
self.ir_wavelength = 850 # nm(近红外)

def capture_sync(self):
"""
同步采集深度和红外图像

Returns:
depth_frame: 深度图,单位米
ir_frame: 红外图,8-bit灰度
"""
# 实际部署时连接真实相机
# depth_frame = self.depth_camera.get_depth_frame()
# ir_frame = self.ir_camera.get_ir_frame()

# 模拟数据
depth_frame = np.random.rand(*self.depth_res) * 2.0 # 0-2米
ir_frame = np.random.randint(0, 256, self.ir_res, dtype=np.uint8)

return depth_frame, ir_frame

def preprocess(self, depth_frame, ir_frame):
"""
预处理

Args:
depth_frame: 原始深度图
ir_frame: 原始红外图

Returns:
depth_normalized: 归一化深度图 [0, 1]
ir_normalized: 归一化红外图 [0, 1]
"""
# 深度归一化
depth_normalized = np.clip(
depth_frame / self.max_depth,
0, 1
).astype(np.float32)

# 红外归一化
ir_normalized = (ir_frame / 255.0).astype(np.float32)

return depth_normalized, ir_normalized

2. 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
import torch
import torch.nn as nn

class DepthIRPoseNet(nn.Module):
"""
深度+红外3D姿态估计网络

论文方法复现
"""

def __init__(self, num_keypoints=17, pretrained=True):
super().__init__()

self.num_keypoints = num_keypoints

# 深度分支(3D卷积)
self.depth_encoder = nn.Sequential(
nn.Conv2d(1, 32, kernel_size=7, stride=2, padding=3),
nn.BatchNorm2d(32),
nn.ReLU(inplace=True),

nn.Conv2d(32, 64, kernel_size=3, stride=2, padding=1),
nn.BatchNorm2d(64),
nn.ReLU(inplace=True),

nn.Conv2d(64, 128, kernel_size=3, stride=2, padding=1),
nn.BatchNorm2d(128),
nn.ReLU(inplace=True),

nn.Conv2d(128, 256, kernel_size=3, stride=2, padding=1),
nn.BatchNorm2d(256),
nn.ReLU(inplace=True),
)

# 红外分支(类似结构)
self.ir_encoder = nn.Sequential(
nn.Conv2d(1, 32, kernel_size=7, stride=2, padding=3),
nn.BatchNorm2d(32),
nn.ReLU(inplace=True),

nn.Conv2d(32, 64, kernel_size=3, stride=2, padding=1),
nn.BatchNorm2d(64),
nn.ReLU(inplace=True),

nn.Conv2d(64, 128, kernel_size=3, stride=2, padding=1),
nn.BatchNorm2d(128),
nn.ReLU(inplace=True),

nn.Conv2d(128, 256, kernel_size=3, stride=2, padding=1),
nn.BatchNorm2d(256),
nn.ReLU(inplace=True),
)

# 融合层
self.fusion = nn.Sequential(
nn.Conv2d(512, 512, kernel_size=3, padding=1),
nn.BatchNorm2d(512),
nn.ReLU(inplace=True),
)

# 3D关键点回归
self.keypoint_head = nn.Sequential(
nn.AdaptiveAvgPool2d((1, 1)),
nn.Flatten(),
nn.Linear(512, 512),
nn.ReLU(inplace=True),
nn.Linear(512, num_keypoints * 3), # x, y, z per keypoint
)

def forward(self, depth_img, ir_img):
"""
前向传播

Args:
depth_img: 深度图 (B, 1, H, W)
ir_img: 红外图 (B, 1, H, W)

Returns:
keypoints_3d: 3D关键点坐标 (B, num_keypoints, 3)
"""
# 特征提取
depth_feat = self.depth_encoder(depth_img)
ir_feat = self.ir_encoder(ir_img)

# 特征融合
fused_feat = torch.cat([depth_feat, ir_feat], dim=1)
fused_feat = self.fusion(fused_feat)

# 3D关键点回归
keypoints_flat = self.keypoint_head(fused_feat)
keypoints_3d = keypoints_flat.view(-1, self.num_keypoints, 3)

return keypoints_3d


# 实际测试
if __name__ == "__main__":
# 初始化模型
model = DepthIRPoseNet(num_keypoints=17)
model.eval()

# 模拟输入
depth_input = torch.randn(1, 1, 480, 640)
ir_input = torch.randn(1, 1, 480, 640)

# 推理
with torch.no_grad():
keypoints_3d = model(depth_input, ir_input)

print(f"输出关键点形状: {keypoints_3d.shape}")
# 输出: torch.Size([1, 17, 3])

3. 关键点定义(人体17关键点)

关键点ID 名称 应用场景
0 头部 判断头部位置
1-2 左右肩 判断上躯干姿态
3-4 左右肘 手臂姿态
5-6 左右手 手部位置(手机检测)
7 脊柱 身体倾斜度
8 骨盆 坐姿基线
9-10 左右膝 腿部姿态
11-12 左右脚踝 脚部位置
13-16 其他关键点 辅助判断

OOP判断逻辑

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
class OOPDetector:
"""
OOP异常姿态检测器
"""

def __init__(self,
forward_threshold=0.15, # 前倾阈值(米)
lateral_threshold=0.20, # 侧倾阈值(米)
knee_threshold=0.30): # 膝盖距离阈值
self.forward_thresh = forward_threshold
self.lateral_thresh = lateral_threshold
self.knee_thresh = knee_threshold

# 标准坐姿基线(需标定)
self.baseline_keypoints = None

def calibrate_baseline(self, keypoints_sequence):
"""
标定标准坐姿

Args:
keypoints_sequence: 正常坐姿关键点序列
"""
self.baseline_keypoints = np.mean(keypoints_sequence, axis=0)

def detect_oop(self, current_keypoints):
"""
检测OOP异常姿态

Args:
current_keypoints: 当前3D关键点 (17, 3)

Returns:
is_oop: 是否异常
oop_type: 异常类型
"""
if self.baseline_keypoints is None:
raise ValueError("需先标定基线坐姿")

oop_flags = {
'forward': False,
'lateral': False,
'knee': False
}

# 1. 前倾检测(头部相对脊柱)
head_z = current_keypoints[0, 2] # 头部深度
spine_z = current_keypoints[7, 2] # 脊柱深度
forward_distance = head_z - spine_z
baseline_forward = self.baseline_keypoints[0, 2] - self.baseline_keypoints[7, 2]

if abs(forward_distance - baseline_forward) > self.forward_thresh:
oop_flags['forward'] = True

# 2. 侧倾检测(肩部水平)
shoulder_diff = abs(current_keypoints[1, 1] - current_keypoints[2, 1])
baseline_diff = abs(self.baseline_keypoints[1, 1] - self.baseline_keypoints[2, 1])

if abs(shoulder_diff - baseline_diff) > self.lateral_thresh:
oop_flags['lateral'] = True

# 3. 膝盖异常(膝盖距仪表盘距离)
knee_z = min(current_keypoints[9, 2], current_keypoints[10, 2])
if knee_z < self.knee_thresh: # 距离仪表盘过近
oop_flags['knee'] = True

is_oop = any(oop_flags.values())
oop_type = [k for k, v in oop_flags.items() if v]

return is_oop, oop_type, oop_flags


# 实际测试
if __name__ == "__main__":
detector = OOPDetector()

# 模拟正常坐姿标定
normal_keypoints = np.random.randn(10, 17, 3) * 0.1 + np.array([
[0, 0, 0.5], # 头部
[-0.2, 0, 0.3], # 左肩
[0.2, 0, 0.3], # 右肩
# ... 其他关键点
])
detector.calibrate_baseline(normal_keypoints)

# 测试当前姿态
current_keypoints = np.random.randn(17, 3) * 0.1 + np.array([
[0, 0, 0.6], # 头部前倾
[-0.2, 0.05, 0.3], # 左肩
[0.2, 0.05, 0.3], # 右肩
# ... 其他关键点
])

is_oop, oop_type, flags = detector.detect_oop(current_keypoints)
print(f"OOP异常: {is_oop}")
print(f"异常类型: {oop_type}")

实验设置

数据采集

设备 参数 用途
深度相机 Intel RealSense D435 采集深度图
红外相机 850nm近红外 采集红外图
采样率 30fps 实时性要求
分辨率 640×480 平衡精度与速度

评估指标

指标 说明 公式
MPJPE 平均关键点位置误差 $\frac{1}{N}\sum_{i=1}^{N}|p_i - \hat{p}_i|$
PCK 关键点检测准确率 $\frac{#|p_i - \hat{p}_i| < \theta}{N}$
3DPCK 3D关键点准确率 同上(3D空间)

IMS开发启示

1. 传感器选型建议

传感器 推荐型号 特点 成本
深度相机 Intel RealSense D435 结构光,0.1-10m $150
Azure Kinect DK ToF,更高精度 $400
红外相机 850nm IR Camera 隐私保护 $50
集成方案 STURDeCAM57 RGB-IR,车规级 $200

2. 部署架构

graph LR
    A[深度+红外相机] --> B[预处理模块]
    B --> C[DepthIRPoseNet]
    C --> D[关键点3D坐标]
    D --> E[OOP检测器]
    E --> F{异常?}
    F -->|是| G[安全带预警]
    F -->|否| H[持续监控]

3. 与Euro NCAP对接

Euro NCAP要求 本文方案 覆盖程度
OOP检测 ✅ 3D姿态估计 满足
实时性 30fps 满足
隐私保护 红外替代RGB 满足
多乘员 单相机限制 需扩展

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
# 模型量化(适用于QCS8255)
import torch.quantization as quant

def quantize_for_edge(model):
"""
模型量化

减小模型大小,提升推理速度
"""
model.eval()

# 静态量化
model.qconfig = quant.get_default_qconfig('fbgemm')
quant.prepare(model, inplace=True)

# 校准(使用真实数据)
# ... calibration_data

quant.convert(model, inplace=True)

return model

# 量化后模型大小:约5MB(原始约20MB)
# 推理时延:<50ms(QCS8255 NPU)

6. 潜在改进方向

  1. 时序建模: 使用LSTM/Transformer建模姿态序列
  2. 自监督学习: 减少标注依赖
  3. 多任务学习: 同时估计姿态+安全带位置
  4. 域自适应: 不同车型/座椅自适应

论文局限性

局限 影响 改进建议
单相机 后排覆盖不足 多相机方案
静态场景 动态驾驶噪声未知 实车验证
标注成本 训练数据有限 合成数据补充
个体差异 未考虑身材差异 个性化阈值

结论

本文提出的深度+红外融合3D姿态估计方法,为Euro NCAP OOP检测提供了可行的技术路径:

  1. 隐私保护: 红外替代RGB,符合欧美法规
  2. 3D精度: 克服2D方法遮挡问题
  3. 实时性: 30fps满足车内实时监控
  4. 可扩展性: 可融合安全带/气囊检测

IMS落地建议: 优先部署于驾驶员监控,后续扩展至全车乘员,结合安全带提醒系统形成完整OMS方案。


参考资料:

  1. Tambwekar et al. (2024): Sensors 24(17):5530, DOI 10.3390/s24175530
  2. Euro NCAP 2026 OOP Assessment Protocol
  3. Intel RealSense D400 Series Product Datasheet
  4. Human Pose Estimation Survey (2024)

车内乘员3D姿态估计新方法:深度+红外图像融合
https://dapalm.com/2026/08/13/2026-08-13-vehicle-occupant-3d-posture-estimation/
作者
Mars
发布于
2026年8月13日
许可协议