车内乘员分类与姿态检测:Euro NCAP 2026 OOP 检测技术

车内乘员分类与姿态检测:Euro NCAP 2026 OOP 检测技术

Euro NCAP 2026 乘员监控要求

核心要求概览

要求 描述 评估位置
安全带路由 检测误用(仅扣扣子/背后/仅腰带) 驾驶员位
Out-of-Position 检测异常姿态(靠近气囊/脚放仪表台) 前排乘客位
乘员体型分类 识别 5th/50th/95th 百分位 驾驶员 + 前排乘客
气囊状态管理 根据乘员状态控制气囊启用/禁用 前排乘客位

OOP 检测定义

1
2
3
4
5
6
7
8
9
Out-of-Position(OOP)定义:

1. 靠近气囊:
- 乘员头部距离仪表台 <20cm
- 触发条件:任何体型,任何座椅位置

2. 脚放仪表台:
- 三种位置:内侧/中线/外侧
- 需要检测不同体型和坐姿

乘员体型分类

身高百分位定义

1
2
3
4
5
6
7
8
9
基于 CDC 2000 Growth Charts:

5th 百分位:小体型成年人(身高约 152cm
50th 百分位:平均体型成年人(身高约 170cm
95th 百分位:大体型成年人(身高约 185cm

Euro NCAP 评分:
- 驾驶员位分类: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
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
import numpy as np
from typing import Tuple, List
from dataclasses import dataclass
from enum import Enum

class BodySize(Enum):
"""体型分类"""
UNKNOWN = "unknown"
P5 = "5th_percentile" # 小体型
P50 = "50th_percentile" # 平均体型
P95 = "95th_percentile" # 大体型
CHILD = "child" # 儿童


@dataclass
class OccupantMetrics:
"""乘员测量数据"""
shoulder_height: float # 肩高(相对座椅)
head_top_height: float # 头顶高度
shoulder_width: float # 肩宽
torso_length: float # 躯干长度
thigh_length: float # 大腿长度


class OccupantClassifier:
"""
乘员体型分类器

基于 3D 关键点测量乘员体型
"""

def __init__(self):
# CDC Growth Charts 参考数据
self.size_reference = {
'female': {
BodySize.P5: {
'height': 152.0, # cm
'shoulder_height': 125.0,
'shoulder_width': 36.0,
'torso_length': 50.0
},
BodySize.P50: {
'height': 163.0,
'shoulder_height': 135.0,
'shoulder_width': 40.0,
'torso_length': 55.0
},
BodySize.P95: {
'height': 175.0,
'shoulder_height': 145.0,
'shoulder_width': 45.0,
'torso_length': 60.0
}
},
'male': {
BodySize.P5: {
'height': 163.0,
'shoulder_height': 135.0,
'shoulder_width': 40.0,
'torso_length': 55.0
},
BodySize.P50: {
'height': 175.0,
'shoulder_height': 145.0,
'shoulder_width': 45.0,
'torso_length': 60.0
},
BodySize.P95: {
'height': 188.0,
'shoulder_height': 155.0,
'shoulder_width': 50.0,
'torso_length': 65.0
}
}
}

# 分类阈值(相对误差)
self.classification_tolerance = 0.10 # 10%

def classify(self,
keypoints_3d: np.ndarray,
seat_position: float = 0.5) -> Tuple[BodySize, float]:
"""
分类乘员体型

Args:
keypoints_3d: 3D 关键点,shape=(N, 3),单位 cm
seat_position: 座椅位置 (0=最后, 1=最前)

Returns:
body_size: 体型分类
confidence: 置信度
"""
# 提取测量数据
metrics = self._extract_metrics(keypoints_3d)

if metrics is None:
return BodySize.UNKNOWN, 0.0

# 与参考数据对比
best_match = BodySize.UNKNOWN
best_score = 0.0

for gender in ['male', 'female']:
for size, reference in self.size_reference[gender].items():
# 计算相似度
score = self._compute_similarity(metrics, reference)

if score > best_score:
best_score = score
best_match = size

# 儿童检测
if metrics.head_top_height < 100: # 头顶高度 < 100cm
return BodySize.CHILD, 0.9

return best_match, best_score

def _extract_metrics(self, keypoints_3d: np.ndarray) -> OccupantMetrics:
"""从关键点提取测量数据"""
# 假设关键点顺序:
# 0: 鼻尖, 1-2: 眼睛, 3-4: 耳朵, 5-6: 肩膀, 7-8: 肘部
# 9-10: 手腕, 11-12: 髋部, 13-14: 膝盖, 15-16: 脚踝

if len(keypoints_3d) < 13:
return None

# 肩高(取平均值)
shoulder_height = (keypoints_3d[5, 1] + keypoints_3d[6, 1]) / 2

# 头顶高度(眼睛 + 估计头顶偏移)
eye_height = (keypoints_3d[1, 1] + keypoints_3d[2, 1]) / 2
head_top_height = eye_height + 10 # 估计头顶在眼睛上方 10cm

# 肩宽
shoulder_width = np.linalg.norm(keypoints_3d[5] - keypoints_3d[6])

# 躯干长度(肩膀到髋部)
shoulder_center = (keypoints_3d[5] + keypoints_3d[6]) / 2
hip_center = (keypoints_3d[11] + keypoints_3d[12]) / 2
torso_length = np.linalg.norm(shoulder_center - hip_center)

# 大腿长度
thigh_length = np.linalg.norm(
(keypoints_3d[11] + keypoints_3d[12]) / 2 -
(keypoints_3d[13] + keypoints_3d[14]) / 2
)

return OccupantMetrics(
shoulder_height=shoulder_height,
head_top_height=head_top_height,
shoulder_width=shoulder_width,
torso_length=torso_length,
thigh_length=thigh_length
)

def _compute_similarity(self,
metrics: OccupantMetrics,
reference: dict) -> float:
"""计算与参考数据的相似度"""
# 归一化误差
errors = []

if 'shoulder_height' in reference:
error = abs(metrics.shoulder_height - reference['shoulder_height']) / reference['shoulder_height']
errors.append(1 - error)

if 'shoulder_width' in reference:
error = abs(metrics.shoulder_width - reference['shoulder_width']) / reference['shoulder_width']
errors.append(1 - error)

if 'torso_length' in reference:
error = abs(metrics.torso_length - reference['torso_length']) / reference['torso_length']
errors.append(1 - error)

# 平均相似度
return np.mean(errors) if errors else 0.0

Out-of-Position 检测

靠近气囊检测

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
class AirbagProximityDetector:
"""
气囊近距离检测器

检测乘员头部是否距离仪表台 <20cm
"""

def __init__(self,
warning_distance: float = 20.0, # cm
critical_distance: float = 15.0):
self.warning_distance = warning_distance
self.critical_distance = critical_distance

# 仪表台位置(车辆坐标系)
# 假设仪表台在前方约 60cm 处
self.dash_position_z = 60.0 # cm

def detect(self,
head_position_3d: np.ndarray,
seat_position: float = 0.5) -> dict:
"""
检测气囊近距离风险

Args:
head_position_3d: 头部 3D 位置, shape=(3,)
seat_position: 座椅位置

Returns:
result: 检测结果
"""
# 计算头部到仪表台距离
distance_to_dash = self.dash_position_z - head_position_3d[2]

# 考虑座椅位置调整
# 座椅越靠前,仪表台相对距离越小
adjusted_distance = distance_to_dash - (seat_position - 0.5) * 20

# 判断风险等级
if adjusted_distance < self.critical_distance:
risk_level = 'critical'
warning_required = True
elif adjusted_distance < self.warning_distance:
risk_level = 'warning'
warning_required = True
else:
risk_level = 'normal'
warning_required = False

return {
'distance_to_dash': adjusted_distance,
'risk_level': risk_level,
'warning_required': warning_required,
'airbag_should_disable': adjusted_distance < self.critical_distance
}

脚放仪表台检测

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
class FeetOnDashDetector:
"""
脚放仪表台检测器

Euro NCAP 要求检测三种位置:内侧/中线/外侧
"""

def __init__(self):
# 仪表台区域定义(相对车辆坐标系)
self.dash_area = {
'z_min': 50.0, # cm, 仪表台前沿
'z_max': 70.0, # cm, 仪表台后沿
'y_range': 40.0 # cm, 横向范围
}

# 脚部检测阈值
self.foot_height_threshold = 30.0 # cm, 脚部高度阈值

def detect(self,
keypoints_3d: np.ndarray,
is_front_passenger: bool = True) -> dict:
"""
检测脚放仪表台

Args:
keypoints_3d: 3D 关键点
is_front_passenger: 是否为前排乘客

Returns:
result: 检测结果
"""
if not is_front_passenger:
return {'detected': False, 'reason': 'Not applicable'}

# 提取脚踝关键点
left_ankle = keypoints_3d[15] if len(keypoints_3d) > 15 else None
right_ankle = keypoints_3d[16] if len(keypoints_3d) > 16 else None

if left_ankle is None and right_ankle is None:
return {'detected': False, 'reason': 'Feet not detected'}

# 检测逻辑
feet_on_dash = []

for ankle, side in [(left_ankle, 'left'), (right_ankle, 'right')]:
if ankle is None:
continue

# 检查高度(脚是否抬起)
if ankle[1] > self.foot_height_threshold: # y 轴向上为正
# 检查是否在仪表台区域
if (self.dash_area['z_min'] < ankle[2] < self.dash_area['z_max']):
# 确定位置(内侧/中线/外侧)
position = self._classify_foot_position(ankle[0])

feet_on_dash.append({
'side': side,
'position': position,
'height': ankle[1]
})

return {
'detected': len(feet_on_dash) > 0,
'feet': feet_on_dash,
'warning_required': len(feet_on_dash) > 0
}

def _classify_foot_position(self, y_position: float) -> str:
"""分类脚的位置(内侧/中线/外侧)"""
y_normalized = y_position + self.dash_area['y_range'] / 2

if y_normalized < self.dash_area['y_range'] / 3:
return 'inboard'
elif y_normalized < 2 * self.dash_area['y_range'] / 3:
return 'centerline'
else:
return 'outboard'

气囊状态管理

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
class AirbagStateManager:
"""
气囊状态管理器

根据乘员状态控制气囊启用/禁用
"""

def __init__(self):
self.current_state = 'enabled' # enabled/disabled

# 状态转换条件
self.disable_conditions = [
'child_seat_detected',
'occupant_too_small',
'oop_critical'
]

def update(self,
occupant_size: BodySize,
oop_status: dict,
child_seat_detected: bool) -> dict:
"""
更新气囊状态

Args:
occupant_size: 乘员体型
oop_status: OOP 检测状态
child_seat_detected: 是否检测到儿童座椅

Returns:
state: 气囊状态
"""
# 判断是否应禁用
should_disable = False
reason = None

# 儿童座椅检测
if child_seat_detected:
should_disable = True
reason = 'child_seat_detected'

# 乘员体型过小(< 5th 百分位)
elif occupant_size == BodySize.P5 or occupant_size == BodySize.CHILD:
should_disable = True
reason = 'occupant_too_small'

# OOP 严重风险
elif oop_status.get('airbag_should_disable', False):
should_disable = True
reason = 'oop_critical'

# 更新状态
new_state = 'disabled' if should_disable else 'enabled'
state_changed = new_state != self.current_state

if state_changed:
self.current_state = new_state
self._send_airbag_command(new_state)

return {
'state': self.current_state,
'state_changed': state_changed,
'reason': reason
}

def _send_airbag_command(self, state: str):
"""发送气囊控制命令"""
# 实际实现通过 CAN 总线发送
pass

Euro NCAP 测试场景

场景 测试内容 通过条件
OC-01 5th 百分位乘员 正确分类,气囊禁用
OC-02 50th 百分位乘员 正确分类,气囊启用
OC-03 95th 百分位乘员 正确分类,气囊启用
OOP-01 头部距离仪表台 15cm 检测 OOP,发出警告
OOP-02 脚放仪表台内侧 正确检测位置
OOP-03 脚放仪表台外侧 正确检测位置

IMS 开发启示

1. 传感器需求

传感器 用途 安装位置
深度摄像头 3D 关键点检测 A 柱/仪表台
ToF 传感器 距离测量 仪表台
座椅传感器 座椅位置 座椅滑轨

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
# 完整的乘员监控管道
class OccupantMonitoringPipeline:
def __init__(self):
self.keypoint_detector = KeypointDetector3D()
self.classifier = OccupantClassifier()
self.oop_detector = FeetOnDashDetector()
self.airbag_manager = AirbagStateManager()

def process(self, image, depth, seat_position):
# 1. 3D 关键点检测
keypoints_3d = self.keypoint_detector.detect(image, depth)

# 2. 体型分类
body_size, confidence = self.classifier.classify(keypoints_3d, seat_position)

# 3. OOP 检测
oop_status = self.oop_detector.detect(keypoints_3d, is_front_passenger=True)

# 4. 气囊状态管理
airbag_state = self.airbag_manager.update(body_size, oop_status, False)

return {
'body_size': body_size.value,
'confidence': confidence,
'oop_status': oop_status,
'airbag_state': airbag_state
}

参考来源:


车内乘员分类与姿态检测:Euro NCAP 2026 OOP 检测技术
https://dapalm.com/2026/06/13/2026-06-13-Euro-NCAP-2026-Occupant-Classification-OOP/
作者
Mars
发布于
2026年6月13日
许可协议