Euro NCAP 2026 安全带错误佩戴检测技术实现

发布日期: 2026-07-02
标签: Euro NCAP, OMS, 安全带检测, 计算机视觉
分类: 法规解读


核心摘要

Euro NCAP 2026首次要求检测安全带错误佩戴方式(仅扣扣子、安全带在背后、仅腰带),而非简单的”是否扣好”。本文提供完整视觉检测算法实现,包括基于深度学习的安全带分割与定位、人体关键点检测、安全带路由分类网络,以及符合Euro NCAP评分标准的系统集成方案。


1. Euro NCAP 2026 安全带新规解读

1.1 从二元检测到行为分析

当前Euro NCAP: 仅检测安全带是否扣好(buckled/unbuckled二元状态)

Euro NCAP 2026新规: 检测安全带是否正确佩戴,需识别三种错误佩戴方式:

错误佩戴类型 分值 描述 检测难度
仅扣扣子(Buckle Only) 2分 扣子卡入但安全带未绕过身体 高(扣子状态与正常相同)
仅腰带(Lap Belt Only) 2分 斜跨部分在背后,仅腰部绑带 中(需检测肩部带子位置)
安全带在背后(Behind Back) 1分 整条安全带绕到背后 高(需检测带子轨迹)

1.2 检测时序要求

Euro NCAP Safe Driving Protocol v1.1 Section 3.2:

1
2
3
4
5
6
7
8
9
### Seatbelt Misuse Detection Requirements

1. **检测范围:** 驾驶员座椅(2026),2029扩展至所有座椅
2. **警告时限:** 检测到错误佩戴后≤30秒触发警告
3. **警告机制:**
- 视觉警告:仪表盘图标+文字,持续显示直到正确佩戴
- 听觉警告:≥90秒蜂鸣,≤10秒静音间隔
- 听觉警告可关闭一次,但视觉警告持续
4. **重新检测:** 安全带解开再错误佩戴后,完整警告序列重新启动

1.3 技术挑战

挑战 原因 解决方案
扣子状态相同 仅扣扣子时扣子传感器与正常佩戴相同 需视觉检测安全带轨迹
身体遮挡 安全带在背后时视觉不可见 需人体姿态推断+安全带分割
光照变化 夜间/强光影响视觉检测 红外摄像头+深度传感器
衣物遮挡 冬季厚衣物遮挡安全带 多传感器融合(压力传感器+视觉)

2. 安全带检测算法核心架构

2.1 多阶段检测流程

graph TD
    A[原始图像] --> B[人体关键点检测<br/>MediaPipe/HRNet]
    A --> C[安全带分割<br/>语义分割网络]
    
    B --> D[关键点提取<br/>肩部/胸部/腰部]
    C --> E[安全带分割掩码<br/>斜跨带+腰带]
    
    D --> F[人体姿态建模<br/>3D姿态推断]
    E --> G[安全带轨迹分析<br/>带子路由路径]
    
    F --> H[人体-安全带关系分析]
    G --> H
    
    H --> I[安全带路由分类<br/>正确/仅扣子/仅腰带/背后]
    
    I --> J{错误佩戴判定}
    J -->|正确| K[清除警告]
    J -->|错误| L[触发警告系统]

2.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
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
"""
Euro NCAP 2026 安全带检测人体关键点提取
基于MediaPipe Pose的实时人体姿态检测

关键点需求:
- 左肩/右肩(判断斜跨带位置)
- 左胸/右胸(判断带子是否绕过胸部)
- 左腰/右腰(判断腰带位置)
- 左肘/右肘(判断手臂遮挡)
"""

import cv2
import numpy as np
import mediapipe as mp
from typing import Dict, List, Tuple, Optional
from dataclasses import dataclass

@dataclass
class BodyKeypoints:
"""人体关键点(安全带检测所需)"""

# 肩部关键点(最重要的安全带路由判定依据)
left_shoulder: Tuple[float, float, float] # (x, y, z_confidence)
right_shoulder: Tuple[float, float, float]

# 胸部关键点(判断带子是否绕过胸部)
left_chest_estimate: Tuple[float, float] # (x, y) - 从肩部推断
right_chest_estimate: Tuple[float, float]

# 腰部关键点(判断腰带位置)
left_hip: Tuple[float, float, float]
right_hip: Tuple[float, float, float]

# 肘部关键点(判断手臂遮挡)
left_elbow: Tuple[float, float, float]
right_elbow: Tuple[float, float, float]

# 人体姿态置信度
pose_confidence: float


class BodyKeypointDetector:
"""
人体关键点检测器

使用MediaPipe Pose提取人体关键点,用于安全带路由分析

模型选择:
- MediaPipe Pose(轻量级,实时,适合车载部署)
- HRNet-W32(高精度,适合离线训练)
"""

def __init__(self,
model_complexity: int = 1,
min_detection_confidence: float = 0.5,
min_tracking_confidence: float = 0.5):
"""
Args:
model_complexity: 模型复杂度(0=轻量,1=默认,2=高精度)
min_detection_confidence: 最小检测置信度
min_tracking_confidence: 最小跟踪置信度
"""
self.mp_pose = mp.solutions.pose
self.pose = self.mp_pose.Pose(
static_image_mode=False,
model_complexity=model_complexity,
smooth_landmarks=True,
enable_segmentation=False,
min_detection_confidence=min_detection_confidence,
min_tracking_confidence=min_tracking_confidence
)

# MediaPipe关键点索引
self.KEYPOINT_IDX = {
'left_shoulder': 11,
'right_shoulder': 12,
'left_hip': 23,
'right_hip': 24,
'left_elbow': 13,
'right_elbow': 14,
'left_wrist': 15,
'right_wrist': 16
}

def detect_keypoints(self,
image: np.ndarray) -> Optional[BodyKeypoints]:
"""
检测人体关键点

Args:
image: 输入图像(RGB格式,H×W×3)

Returns:
BodyKeypoints: 人体关键点数据结构,若检测失败返回None
"""
# MediaPipe处理
results = self.pose.process(image)

if not results.pose_landmarks:
return None

landmarks = results.pose_landmarks.landmark

# 提取关键点
left_shoulder = landmarks[self.KEYPOINT_IDX['left_shoulder']]
right_shoulder = landmarks[self.KEYPOINT_IDX['right_shoulder']]
left_hip = landmarks[self.KEYPOINT_IDX['left_hip']]
right_hip = landmarks[self.KEYPOINT_IDX['right_hip']]
left_elbow = landmarks[self.KEYPOINT_IDX['left_elbow']]
right_elbow = landmarks[self.KEYPOINT_IDX['right_elbow']]

# 推断胸部关键点(MediaPipe无直接胸部关键点,从肩部推断)
# 胸部位置:肩部向下约15%距离
left_chest_x = left_shoulder.x
left_chest_y = left_shoulder.y + 0.15
right_chest_x = right_shoulder.x
right_chest_y = right_shoulder.y + 0.15

# 计算整体置信度
confidence_scores = [
left_shoulder.visibility,
right_shoulder.visibility,
left_hip.visibility,
right_hip.visibility
]
pose_confidence = np.mean(confidence_scores)

return BodyKeypoints(
left_shoulder=(left_shoulder.x, left_shoulder.y, left_shoulder.visibility),
right_shoulder=(right_shoulder.x, right_shoulder.y, right_shoulder.visibility),
left_chest_estimate=(left_chest_x, left_chest_y),
right_chest_estimate=(right_chest_x, right_chest_y),
left_hip=(left_hip.x, left_hip.y, left_hip.visibility),
right_hip=(right_hip.y, right_hip.y, right_hip.visibility),
left_elbow=(left_elbow.x, left_elbow.y, left_elbow.visibility),
right_elbow=(right_elbow.x, right_elbow.y, right_elbow.visibility),
pose_confidence=pose_confidence
)

def draw_keypoints(self,
image: np.ndarray,
keypoints: BodyKeypoints) -> np.ndarray:
"""
在图像上绘制关键点(用于可视化调试)

Args:
image: 输入图像
keypoints: 关键点数据

Returns:
image_with_keypoints: 绘制关键点的图像
"""
h, w = image.shape[:2]

# 绘制肩部关键点(绿色,最重要)
cv2.circle(image,
(int(keypoints.left_shoulder[0] * w),
int(keypoints.left_shoulder[1] * h)),
8, (0, 255, 0), -1)
cv2.circle(image,
(int(keypoints.right_shoulder[0] * w),
int(keypoints.right_shoulder[1] * h)),
8, (0, 255, 0), -1)

# 绘制推断的胸部关键点(黄色)
cv2.circle(image,
(int(keypoints.left_chest_estimate[0] * w),
int(keypoints.left_chest_estimate[1] * h)),
5, (255, 255, 0), -1)

# 绘制腰部关键点(蓝色)
cv2.circle(image,
(int(keypoints.left_hip[0] * w),
int(keypoints.left_hip[1] * h)),
6, (255, 0, 0), -1)

# 绘制肘部关键点(紫色)
cv2.circle(image,
(int(keypoints.left_elbow[0] * w),
int(keypoints.left_elbow[1] * h)),
4, (255, 0, 255), -1)

return image


# 测试示例
if __name__ == "__main__":
detector = BodyKeypointDetector(model_complexity=1)

# 模拟图像(实际应从摄像头读取)
test_image = np.random.randint(0, 255, (480, 640, 3), dtype=np.uint8)

keypoints = detector.detect_keypoints(test_image)

if keypoints:
print(f"=== 人体关键点检测结果 ===")
print(f"左肩:({keypoints.left_shoulder[0]:.3f}, {keypoints.left_shoulder[1]:.3f})")
print(f"右肩:({keypoints.right_shoulder[0]:.3f}, {keypoints.right_shoulder[1]:.3f})")
print(f"左腰:({keypoints.left_hip[0]:.3f}, {keypoints.left_hip[1]:.3f})")
print(f"右腰:({keypoints.right_hip[0]:.3f}, {keypoints.right_hip[1]:.3f})")
print(f"姿态置信度:{keypoints.pose_confidence:.3f}")
else:
print("人体关键点检测失败")

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
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
"""
Euro NCAP 2026 安全带语义分割
基于深度学习的安全带区域分割与轨迹检测

分割目标:
- 斜跨带(Shoulder Belt):从肩部斜跨至腰部的带子
- 腰带(Lap Belt):腰部水平的带子
- 扣子区域(Buckle):安全带扣合位置

算法选择:
- U-Net(轻量级,适合车载实时部署)
- DeepLabV3+(高精度,适合离线训练)
"""

import torch
import torch.nn as nn
import numpy as np
from typing import Dict, Tuple
from dataclasses import dataclass

@dataclass
class BeltSegmentationResult:
"""安全带分割结果"""

shoulder_belt_mask: np.ndarray # 斜跨带分割掩码(H×W)
lap_belt_mask: np.ndarray # 腰带分割掩码(H×W)
buckle_mask: np.ndarray # 扣子分割掩码(H×W)

shoulder_belt_confidence: float # 斜跨带置信度
lap_belt_confidence: float # 腰带置信度
buckle_confidence: float # 扣子置信度

# 分割掩码格式:0=背景,1=安全带区域


class BeltSegmentationNet(nn.Module):
"""
安全带分割网络(轻量级U-Net)

输入:RGB图像(H×W×3)
输出:3类分割掩码(斜跨带、腰带、扣子)

网络架构:
- Encoder: 4层卷积下采样(64→128→256→512通道)
- Decoder: 4层转置卷积上采样(512→256→128→64通道)
- 输出层:3类分割掩码(softmax激活)

模型参数:
- 输入尺寸:320×240(可调)
- 参数量:约7.5M(轻量级)
- 推理速度:约25fps on QCS8255
"""

def __init__(self, in_channels: int = 3, out_channels: int = 3):
super(BeltSegmentationNet, self).__init__()

# Encoder
self.enc1 = self._conv_block(in_channels, 64)
self.enc2 = self._conv_block(64, 128)
self.enc3 = self._conv_block(128, 256)
self.enc4 = self._conv_block(256, 512)

# Decoder
self.dec4 = self._upconv_block(512, 256)
self.dec3 = self._upconv_block(256, 128)
self.dec2 = self._upconv_block(128, 64)
self.dec1 = self._upconv_block(64, out_channels)

# 最大池化
self.pool = nn.MaxPool2d(kernel_size=2, stride=2)

def _conv_block(self, in_ch: int, out_ch: int) -> nn.Sequential:
"""卷积块(Conv+ReLU+Conv+ReLU)"""
return nn.Sequential(
nn.Conv2d(in_ch, out_ch, kernel_size=3, padding=1),
nn.ReLU(inplace=True),
nn.Conv2d(out_ch, out_ch, kernel_size=3, padding=1),
nn.ReLU(inplace=True)
)

def _upconv_block(self, in_ch: int, out_ch: int) -> nn.Sequential:
"""转置卷积上采样块"""
return nn.Sequential(
nn.ConvTranspose2d(in_ch, out_ch, kernel_size=2, stride=2),
nn.ReLU(inplace=True)
)

def forward(self, x: torch.Tensor) -> torch.Tensor:
"""
前向传播

Args:
x: 输入图像(B×C×H×W)

Returns:
output: 3类分割掩码(B×3×H×W)
"""
# Encoder
e1 = self.enc1(x) # 64 channels
e2 = self.enc2(self.pool(e1)) # 128 channels
e3 = self.enc3(self.pool(e2)) # 256 channels
e4 = self.enc4(self.pool(e3)) # 512 channels

# Decoder with skip connections
d4 = self.dec4(e4) + e3 # 256 channels
d3 = self.dec3(d4) + e2 # 128 channels
d2 = self.dec2(d3) + e1 # 64 channels

# Output layer(softmax激活)
d1 = self.dec1(d2) # 3 channels

# Softmax归一化(多类分割)
output = torch.softmax(d1, dim=1)

return output

def segment_belt(self,
image: np.ndarray,
device: str = 'cpu') -> BeltSegmentationResult:
"""
安全带分割推理

Args:
image: 输入图像(H×W×3,RGB格式)
device: 计算设备('cpu' or 'cuda')

Returns:
BeltSegmentationResult: 分割结果数据结构
"""
self.eval()

# 图像预处理
# 1. 归一化(0-255 → 0-1)
# 2. 调整尺寸(H×W → 模型输入尺寸)
# 3. 转换为tensor(H×W×C → C×H×W)

input_h, input_w = 240, 320 # 模型输入尺寸

# resize
image_resized = cv2.resize(image, (input_w, input_h))

# 归一化
image_normalized = image_resized.astype(np.float32) / 255.0

# 转tensor(添加batch维度)
image_tensor = torch.from_numpy(image_normalized).permute(2, 0, 1).unsqueeze(0)
image_tensor = image_tensor.to(device)

# 推理
with torch.no_grad():
output = self.forward(image_tensor)

# 提取分割掩码(去除batch维度)
output_masks = output.squeeze(0).cpu().numpy() # (3, H, W)

# 分离三类掩码
shoulder_belt_mask = output_masks[0] # 斜跨带
lap_belt_mask = output_masks[1] # 腰带
buckle_mask = output_masks[2] # 扣子

# resize回原始尺寸
orig_h, orig_w = image.shape[:2]
shoulder_belt_mask = cv2.resize(shoulder_belt_mask, (orig_w, orig_h))
lap_belt_mask = cv2.resize(lap_belt_mask, (orig_w, orig_h))
buckle_mask = cv2.resize(buckle_mask, (orig_w, orig_h))

# 计算置信度(mask区域平均值)
shoulder_conf = np.mean(shoulder_belt_mask[shoulder_belt_mask > 0.5])
lap_conf = np.mean(lap_belt_mask[lap_belt_mask > 0.5])
buckle_conf = np.mean(buckle_mask[buckle_mask > 0.5])

# 二值化(阈值0.5)
shoulder_belt_binary = (shoulder_belt_mask > 0.5).astype(np.uint8)
lap_belt_binary = (lap_belt_mask > 0.5).astype(np.uint8)
buckle_binary = (buckle_mask > 0.5).astype(np.uint8)

return BeltSegmentationResult(
shoulder_belt_mask=shoulder_belt_binary,
lap_belt_mask=lap_belt_binary,
buckle_mask=buckle_binary,
shoulder_belt_confidence=shoulder_conf if not np.isnan(shoulder_conf) else 0.0,
lap_belt_confidence=lap_conf if not np.isnan(lap_conf) else 0.0,
buckle_confidence=buckle_conf if not np.isnan(buckle_conf) else 0.0
)


# 模型加载与测试
if __name__ == "__main__":
# 初始化模型
model = BeltSegmentationNet(in_channels=3, out_channels=3)

# 模型参数统计
total_params = sum(p.numel() for p in model.parameters())
print(f"模型参数量:{total_params / 1e6:.2f}M")

# 模拟输入
test_image = np.random.randint(0, 255, (480, 640, 3), dtype=np.uint8)

# 分割推理
result = model.segment_belt(test_image)

print(f"\n=== 安全带分割结果 ===")
print(f"斜跨带掩码尺寸:{result.shoulder_belt_mask.shape}")
print(f"腰带掩码尺寸:{result.lap_belt_mask.shape}")
print(f"扣子掩码尺寸:{result.buckle_mask.shape}")
print(f"斜跨带置信度:{result.shoulder_belt_confidence:.3f}")
print(f"腰带置信度:{result.lap_belt_confidence:.3f}")
print(f"扣子置信度:{result.buckle_confidence:.3f}")

2.4 安全带路由分类网络

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
"""
Euro NCAP 2026 安全带路由分类
基于人体关键点+安全带分割的佩戴方式判定

分类类别:
1. Correct Routing: 正确佩戴(斜跨带绕过肩部+腰部)
2. Buckle Only: 仅扣扣子(带子未绕过身体)
3. Lap Belt Only: 仅腰带(斜跨带在背后)
4. Behind Back: 安全带在背后(整条带子绕到背后)
"""

import torch
import torch.nn as nn
import numpy as np
from typing import Dict, Tuple
from dataclasses import dataclass
from enum import Enum

class BeltRoutingClass(Enum):
"""安全带路由分类"""
CORRECT_ROUTING = 0 # 正确佩戴
BUCKLE_ONLY = 1 # 仅扣扣子
LAP_BELT_ONLY = 2 # 仅腰带(斜跨带在背后)
BEHIND_BACK = 3 # 安全带在背后


@dataclass
class BeltRoutingResult:
"""安全带路由判定结果"""

routing_class: BeltRoutingClass # 路由分类
confidence: float # 置信度(0-1)

# 详细判定依据
shoulder_belt_detected: bool # 是否检测到斜跨带
lap_belt_detected: bool # 是否检测到腰带
shoulder_belt_crossing_body: bool # 斜跨带是否绕过身体
lap_belt_on_waist: bool # 腰带是否在腰部

# 分值(Euro NCAP评分)
score: int # 0-5分


class BeltRoutingClassifier(nn.Module):
"""
安全带路由分类网络

输入:
- 人体关键点向量(18维:肩部/胸部/腰部/肘部坐标)
- 安全带分割掩码特征(12维:斜跨带/腰带/扣子区域统计)

输出:
- 4类路由分类(softmax概率)

网络架构:
- 两分支特征提取(关键点+分割特征)
- 全连接融合层
- 4类分类输出
"""

def __init__(self,
keypoints_dim: int = 18,
segmentation_feat_dim: int = 12,
hidden_dim: int = 64):
super(BeltRoutingClassifier, self).__init__()

# 关键点特征提取分支
self.keypoints_encoder = nn.Sequential(
nn.Linear(keypoints_dim, hidden_dim),
nn.ReLU(inplace=True),
nn.Linear(hidden_dim, hidden_dim),
nn.ReLU(inplace=True)
)

# 分割特征提取分支
self.segmentation_encoder = nn.Sequential(
nn.Linear(segmentation_feat_dim, hidden_dim),
nn.ReLU(inplace=True),
nn.Linear(hidden_dim, hidden_dim),
nn.ReLU(inplace=True)
)

# 融合分类层
self.classifier = nn.Sequential(
nn.Linear(hidden_dim * 2, hidden_dim),
nn.ReLU(inplace=True),
nn.Linear(hidden_dim, 4), # 4类输出
nn.Softmax(dim=1)
)

def forward(self,
keypoints_feat: torch.Tensor,
segmentation_feat: torch.Tensor) -> torch.Tensor:
"""
前向传播

Args:
keypoints_feat: 人体关键点特征(B×18)
segmentation_feat: 安全带分割特征(B×12)

Returns:
output: 4类分类概率(B×4)
"""
# 关键点编码
kp_encoded = self.keypoints_encoder(keypoints_feat)

# 分割特征编码
seg_encoded = self.segmentation_encoder(segmentation_feat)

# 融合
fused = torch.cat([kp_encoded, seg_encoded], dim=1)

# 分类
output = self.classifier(fused)

return output

def classify_routing(self,
keypoints: BodyKeypoints,
segmentation: BeltSegmentationResult,
device: str = 'cpu') -> BeltRoutingResult:
"""
安全带路由分类推理

Args:
keypoints: 人体关键点
segmentation: 安全带分割结果
device: 计算设备

Returns:
BeltRoutingResult: 路由判定结果
"""
self.eval()

# 提取关键点特征(18维)
keypoints_feat = np.array([
keypoints.left_shoulder[0], keypoints.left_shoulder[1],
keypoints.right_shoulder[0], keypoints.right_shoulder[1],
keypoints.left_chest_estimate[0], keypoints.left_chest_estimate[1],
keypoints.right_chest_estimate[0], keypoints.right_chest_estimate[1],
keypoints.left_hip[0], keypoints.left_hip[1],
keypoints.right_hip[0], keypoints.right_hip[1],
keypoints.left_elbow[0], keypoints.left_elbow[1],
keypoints.right_elbow[0], keypoints.right_elbow[1],
keypoints.left_shoulder[2], keypoints.right_shoulder[2] # 置信度
])

# 提取分割特征(12维)
# 统计各分割区域的覆盖比例、位置分布
h, w = segmentation.shoulder_belt_mask.shape

shoulder_area_ratio = np.sum(segmentation.shoulder_belt_mask > 0.5) / (h * w)
lap_area_ratio = np.sum(segmentation.lap_belt_mask > 0.5) / (h * w)
buckle_area_ratio = np.sum(segmentation.buckle_mask > 0.5) / (h * w)

# 计算各区域中心位置
def get_mask_center(mask):
if np.sum(mask) == 0:
return 0.5, 0.5
y_coords, x_coords = np.where(mask > 0.5)
return np.mean(x_coords) / w, np.mean(y_coords) / h

shoulder_center_x, shoulder_center_y = get_mask_center(segmentation.shoulder_belt_mask)
lap_center_x, lap_center_y = get_mask_center(segmentation.lap_belt_mask)
buckle_center_x, buckle_center_y = get_mask_center(segmentation.buckle_mask)

segmentation_feat = np.array([
shoulder_area_ratio, lap_area_ratio, buckle_area_ratio,
shoulder_center_x, shoulder_center_y,
lap_center_x, lap_center_y,
buckle_center_x, buckle_center_y,
segmentation.shoulder_belt_confidence,
segmentation.lap_belt_confidence,
segmentation.buckle_confidence
])

# 转tensor
kp_tensor = torch.from_numpy(keypoints_feat.astype(np.float32)).unsqueeze(0)
seg_tensor = torch.from_numpy(segmentation_feat.astype(np.float32)).unsqueeze(0)

kp_tensor = kp_tensor.to(device)
seg_tensor = seg_tensor.to(device)

# 推理
with torch.no_grad():
output = self.forward(kp_tensor, seg_tensor)

# 提取分类概率
probs = output.squeeze(0).cpu().numpy()

# 最高概率类别
pred_class_idx = np.argmax(probs)
confidence = probs[pred_class_idx]

routing_class = BeltRoutingClass(pred_class_idx)

# Euro NCAP分值判定
score_mapping = {
BeltRoutingClass.CORRECT_ROUTING: 5, # 正确佩戴得满分
BeltRoutingClass.BUCKLE_ONLY: 0, # 仅扣扣子扣2分(检测得分)
BeltRoutingClass.LAP_BELT_ONLY: 0, # 仅腰带扣2分
BeltRoutingClass.BEHIND_BACK: 0 # 在背后扣1分
}

score = score_mapping[routing_class]

# 详细判定依据
shoulder_detected = shoulder_area_ratio > 0.02
lap_detected = lap_area_ratio > 0.02

# 判断斜跨带是否绕过身体
# 条件:斜跨带中心在肩部与腰部之间
shoulder_belt_crossing = (shoulder_center_y > keypoints.left_shoulder[1]) and \
(shoulder_center_y < keypoints.left_hip[1])

# 判断腰带是否在腰部
lap_belt_on_waist = abs(lap_center_y - keypoints.left_hip[1]) < 0.1

return BeltRoutingResult(
routing_class=routing_class,
confidence=confidence,
shoulder_belt_detected=shoulder_detected,
lap_belt_detected=lap_detected,
shoulder_belt_crossing_body=shoulder_belt_crossing,
lap_belt_on_waist=lap_belt_on_waist,
score=score
)


# 测试示例
if __name__ == "__main__":
classifier = BeltRoutingClassifier()

# 模拟人体关键点
keypoints = BodyKeypoints(
left_shoulder=(0.3, 0.4, 0.9),
right_shoulder=(0.7, 0.4, 0.9),
left_chest_estimate=(0.3, 0.55),
right_chest_estimate=(0.7, 0.55),
left_hip=(0.3, 0.8, 0.8),
right_hip=(0.7, 0.8, 0.8),
left_elbow=(0.2, 0.6, 0.7),
right_elbow=(0.8, 0.6, 0.7),
pose_confidence=0.85
)

# 模拟安全带分割
segmentation = BeltSegmentationResult(
shoulder_belt_mask=np.zeros((480, 640), dtype=np.uint8),
lap_belt_mask=np.zeros((480, 640), dtype=np.uint8),
buckle_mask=np.zeros((480, 640), dtype=np.uint8),
shoulder_belt_confidence=0.0,
lap_belt_confidence=0.0,
buckle_confidence=0.0
)

# 分类推理
result = classifier.classify_routing(keypoints, segmentation)

print(f"=== 安全带路由分类结果 ===")
print(f"分类:{result.routing_class.name}")
print(f"置信度:{result.confidence:.3f}")
print(f"斜跨带检测:{result.shoulder_belt_detected}")
print(f"腰带检测:{result.lap_belt_detected}")
print(f"Euro NCAP分值:{result.score}")

3. 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
"""
Euro NCAP 2026 安全带错误佩戴警告系统
符合Safe Driving Protocol v1.1 Section 3.3

警告机制:
1. 视觉警告:仪表盘图标+文字,持续显示直到正确佩戴
2. 听觉警告:≥90秒蜂鸣,≤10秒静音间隔
3. 听觉警告可关闭一次,但视觉警告持续
"""

from enum import Enum
from typing import Optional
import time

class BeltWarningLevel(Enum):
"""安全带警告等级"""
NONE = 0 # 正确佩戴
UNFASTENED = 1 # 未扣好
MISUSE = 2 # 错误佩戴


class BeltMisuseWarningSystem:
"""
安全带错误佩戴警告系统

Euro NCAP要求:
- 错误佩戴检测后≤30秒触发警告
- 听觉警告≥90秒,≤10秒静音间隔
- 听觉警告可关闭一次,视觉警告持续
- 解开再错误佩戴,完整警告序列重新启动
"""

def __init__(self):
self.current_level = BeltWarningLevel.NONE
self.warning_start_time: Optional[float] = None
self.audible_warning_played = False
self.audible_warning_disabled_once = False

# Euro NCAP时间参数
self.warning_trigger_delay_sec = 30 # ≤30秒触发
self.audible_warning_duration_sec = 90 # ≥90秒听觉警告
self.silent_interval_max_sec = 10 # ≤10秒静音间隔

def trigger_warning(self,
routing_result: BeltRoutingResult,
current_time: float) -> dict:
"""
触发安全带警告

Args:
routing_result: 安全带路由判定结果
current_time: 当前时间戳

Returns:
warning_command: 警告指令
"""
# 判定警告等级
if routing_result.routing_class == BeltRoutingClass.CORRECT_ROUTING:
# 正确佩戴:清除警告
self._clear_warning()
return {'action': 'clear_warning'}

elif routing_result.routing_class in [
BeltRoutingClass.BUCKLE_ONLY,
BeltRoutingClass.LAP_BELT_ONLY,
BeltRoutingClass.BEHIND_BACK
]:
# 错误佩戴:触发警告
return self._trigger_misuse_warning(current_time)

return {'action': 'no_action'}

def _trigger_misuse_warning(self, current_time: float) -> dict:
"""触发错误佩戴警告"""

if self.current_level != BeltWarningLevel.MISUSE:
# 首次触发:初始化警告
self.current_level = BeltWarningLevel.MISUSE
self.warning_start_time = current_time
self.audible_warning_played = False

elapsed = current_time - self.warning_start_time

# Euro NCAP要求:≤30秒内触发
if elapsed < self.warning_trigger_delay_sec:
# 延迟触发(等待30秒)
return {
'action': 'pending',
'delay_remaining_sec': self.warning_trigger_delay_sec - elapsed
}

# 生成警告指令
command = {
'level': self.current_level.value,
'elapsed_sec': elapsed,
'visual': {
'icon': 'belt_misuse_red',
'text': 'SEATBELT MISUSE DETECTED - Fasten correctly',
'duration': 'persistent',
'location': 'instrument_cluster'
},
'audible': None,
'audible_available': not self.audible_warning_disabled_once
}

# 听觉警告(可关闭一次)
if not self.audible_warning_disabled_once:
if elapsed < self.audible_warning_duration_sec:
command['audible'] = {
'type': 'continuous_beep',
'duration_sec': min(self.audible_warning_duration_sec, elapsed),
'max_volume_db': 90,
'pause_interval_sec': self.silent_interval_max_sec
}
self.audible_warning_played = True

return command

def disable_audible_warning_once(self):
"""关闭听觉警告一次(Euro NCAP允许)"""
if self.audible_warning_played:
self.audible_warning_disabled_once = True

def _clear_warning(self):
"""清除警告(正确佩戴)"""
self.current_level = BeltWarningLevel.NONE
self.warning_start_time = None
self.audible_warning_played = False
self.audible_warning_disabled_once = False

3.2 多传感器融合方案

传感器 检测能力 局限性 融合权重
红外摄像头(940nm) 安全带分割+人体关键点 夜间可视,但厚衣物遮挡 50%
压力传感器(座椅) 压力分布推断带子位置 无法区分正确/错误佩戴 20%
扣子传感器 扣子是否卡入 无法检测仅扣扣子 10%
深度摄像头 人体姿态3D建模 成本较高 20%

4. IMS开发落地指导

4.1 硬件选型建议

组件 推荐型号 参数要求 原因
红外摄像头 OV2311 2MP, 940nm IR, 全局快门 夜间安全带分割必需
深度摄像头 Intel RealSense D435 Depth+RGB, 1280×720 人体3D姿态建模
座椅压力传感器 Bosch BMA456 压力分布阵列 辅助安全带位置推断
处理器 Qualcomm QCS8255 Hexagon NPU 26TOPS 实时分割+分类推理

4.2 测试场景清单

场景编号 检测项 测试条件 通过标准
SB-01 正确佩戴检测 安全带正确佩戴 分类为CORRECT_ROUTING,置信度≥90%
SB-02 仅扣扣子检测 扣子卡入但带子在背后 分类为BUCKLE_ONLY,≤30秒警告
SB-03 仅腰带检测 斜跨带在背后 分类为LAP_BELT_ONLY
SB-04 安全带在背后 整条带子绕到背后 分类为BEHIND_BACK
SB-05 夜间检测 光照<50 lux,红外补光 置信度≥80%
SB-06 厚衣物遮挡 冬季厚外套 压力传感器辅助判定
SB-07 警告时序 检测到错误佩戴 ≤30秒触发警告
SB-08 听觉警告 错误佩戴持续 ≥90秒蜂鸣,可关闭一次

5. 参考文献

  1. Euro NCAP Safe Driving Protocol v1.1 (2025-10-01), Section 3.2 Seatbelt Misuse Detection
  2. Smart Eye (2025-08-04), How Euro NCAP Tightens the Rules on Seatbelt Use in 2026
  3. MediaPipe Pose Estimation (Google Research), Real-time Human Pose Detection
  4. U-Net: Convolutional Networks for Biomedical Image Segmentation (Ronneberger et al., 2015)

IMS开发启示

优先级排序

优先级 任务 原因
P0(最高) 安全带分割网络 视觉检测核心,Euro NCAP必需
P1(高) 人体关键点检测 安全带路由判定依据
P2(中) 警告系统集成 Euro NCAP评分项
P3(低) 多传感器融合 提升鲁棒性,可选

技术路线判断

  1. 算法路线: 视觉分割+关键点融合优于单一传感器
  2. 传感器路线: 红外摄像头必需,深度摄像头可选
  3. 部署路线: U-Net轻量化后可在QCS8255实时推理(约25fps)
  4. 测试路线: 需构建错误佩戴场景数据集(合成数据+真实场景)

下一步行动:

  1. 训练安全带分割网络(数据集采集+标注)
  2. 集成MediaPipe Pose人体关键点检测
  3. 实现路由分类网络(多特征融合)
  4. 构建错误佩戴测试场景数据集

Euro NCAP 2026 安全带错误佩戴检测技术实现
https://dapalm.com/2026/07/02/2026-07-02-euro-ncap-2026-seatbelt-misuse-detection-zh/
作者
Mars
发布于
2026年7月2日
许可协议