KPGBeltNet:基于人体关键点和注意力机制的安全带误用检测算法

论文信息

  • 标题: KPGBeltNet: in-vehicle seatbelt detection algorithm based on human keypoint-guided sampling and local–global attention
  • 期刊: The Visual Computer, 2026
  • 链接: https://link.springer.com/article/10.1007/s00371-026-04572-1
  • 核心创新: 首次将人体关键点先验与几何特征建模结合,实现复杂座舱环境下的安全带误用检测

Euro NCAP 2026 背景

**安全带误用检测(Belt Misuse)**是Euro NCAP 2026新增要求:

  • 检测安全带错误佩戴(腰带位置错误、斜带滑落等)
  • 纳入Safe Driving评分,占比提升至70%
  • 需在Dossier中声明检测阈值和警告策略

技术难点:

挑战 描述
光照变化 座舱内光照不均,红外补光可能影响可见光检测
姿态变化 驾驶员前后倾斜、侧身操作中控屏
部分遮挡 安全带细长、低对比度,易与衣物混淆
背景干扰 座椅、中控台等复杂背景

核心创新

1. 关键点引导的几何先验建模

传统方法缺陷:

  • 直接使用YOLO等通用检测模型
  • 依赖区域级外观特征
  • 安全带像素占比小、对比度低时性能下降

KPGBeltNet创新:

1
2
3
4
5
6
7
# 传统方法:直接检测
detections = yolo.detect(image) # 安全带可能被忽略

# KPGBeltNet:关键点引导
keypoints = yolo_pose.detect(image) # 肩部、髋部关键点
roi = get_upper_body_roi(keypoints) # 基于关键点生成ROI
seatbelt_path = get_shoulder_to_hip_path(keypoints) # 安全带几何路径

关键技术:

  1. 姿态自适应ROI生成

    • 使用YOLOv11-pose检测肩部、髋部关键点
    • 基于关键点生成上肢ROI,避免姿态变化影响
  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
import numpy as np

def diagonal_block_sampling(roi, keypoints, block_size=32, overlap=0.5):
"""
对角方向重叠块采样

Args:
roi: 上肢区域图像 (H, W, C)
keypoints: 肩部、髋部关键点坐标
block_size: 采样块大小
overlap: 重叠率

Returns:
patches: 采样块序列 [(block_size, block_size, C), ...]
positions: 采样块位置 [(x, y), ...]
"""
shoulder = keypoints['shoulder'] # (x1, y1)
hip = keypoints['hip'] # (x2, y2)

# 计算对角方向向量
direction = np.array([hip[0] - shoulder[0], hip[1] - shoulder[1]])
direction = direction / np.linalg.norm(direction)

# 计算采样点数量
distance = np.linalg.norm([hip[0] - shoulder[0], hip[1] - shoulder[1]])
step = int(block_size * (1 - overlap))
n_patches = int(distance / step)

patches = []
positions = []

for i in range(n_patches):
# 沿对角方向采样
center = shoulder + direction * (i * step)
x, y = int(center[0]), int(center[1])

# 提取图像块
patch = roi[y:y+block_size, x:x+block_size]
if patch.shape[0] == block_size and patch.shape[1] == block_size:
patches.append(patch)
positions.append((x, y))

return patches, positions


# 实际测试
if __name__ == "__main__":
# 模拟ROI和关键点
roi = np.random.rand(256, 256, 3)
keypoints = {
'shoulder': (50, 50),
'hip': (200, 200)
}

patches, positions = diagonal_block_sampling(roi, keypoints)
print(f"采样块数量: {len(patches)}")
print(f"采样块形状: {patches[0].shape}")
print(f"采样位置: {positions[:5]}")

优势:

  • 显式表示细长、低对比度安全带为空间连续序列
  • 保留局部结构连续性
  • 抑制背景区域干扰

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

class LocalGlobalAttention(nn.Module):
"""
局部-全局注意力机制

将躯干级上下文信息注入每个局部块特征
"""

def __init__(self, local_dim=256, global_dim=512):
super().__init__()
self.local_proj = nn.Linear(local_dim, local_dim)
self.global_proj = nn.Linear(global_dim, local_dim)
self.attention = nn.MultiheadAttention(local_dim, num_heads=8)

def forward(self, local_features, global_feature):
"""
Args:
local_features: (N, local_dim) N个局部块特征
global_feature: (global_dim,) 全局躯干特征

Returns:
fused_features: (N, local_dim) 融合后的特征
"""
# 投影
local_proj = self.local_proj(local_features) # (N, local_dim)
global_proj = self.global_proj(global_feature) # (local_dim,)

# 扩展全局特征
global_expanded = global_proj.unsqueeze(0).expand(local_features.size(0), -1) # (N, local_dim)

# 注意力融合
fused, _ = self.attention(
local_proj.unsqueeze(1), # (N, 1, local_dim)
global_expanded.unsqueeze(1), # (N, 1, local_dim)
global_expanded.unsqueeze(1) # (N, 1, local_dim)
)

return fused.squeeze(1) # (N, local_dim)


class BiGRUSequence(nn.Module):
"""
双向GRU序列建模

建模对角块序列的结构连续性
"""

def __init__(self, input_dim=256, hidden_dim=128):
super().__init__()
self.gru = nn.GRU(input_dim, hidden_dim, bidirectional=True, batch_first=True)
self.classifier = nn.Linear(hidden_dim * 2, 2) # worn/not_worn

def forward(self, patch_features):
"""
Args:
patch_features: (N, input_dim) 采样块特征序列

Returns:
logits: (2,) 安全带佩戴状态
"""
# 添加batch维度
x = patch_features.unsqueeze(0) # (1, N, input_dim)

# 双向GRU
output, _ = self.gru(x) # (1, N, hidden_dim * 2)

# 取最后时刻输出
final_output = output[:, -1, :] # (1, hidden_dim * 2)

# 分类
logits = self.classifier(final_output) # (1, 2)

return logits.squeeze(0) # (2,)


# 完整模型
class KPGBeltNet(nn.Module):
"""
KPGBeltNet完整模型
"""

def __init__(self):
super().__init__()
# 特征提取器(假设使用ResNet18)
self.feature_extractor = torch.hub.load('pytorch/vision:v0.10.0', 'resnet18', pretrained=True)
self.feature_extractor.fc = nn.Identity() # 移除分类层

# 局部-全局注意力
self.attention = LocalGlobalAttention(local_dim=512, global_dim=512)

# 双向GRU
self.gru = BiGRUSequence(input_dim=512, hidden_dim=128)

def forward(self, patches, global_roi):
"""
Args:
patches: [(H, W, C), ...] 采样块列表
global_roi: (H, W, C) 全局ROI图像

Returns:
logits: (2,) 安全带佩戴状态
"""
# 提取局部特征
local_features = []
for patch in patches:
patch_tensor = torch.from_numpy(patch).permute(2, 0, 1).float() / 255.0
patch_tensor = patch_tensor.unsqueeze(0) # (1, C, H, W)
feature = self.feature_extractor(patch_tensor) # (1, 512)
local_features.append(feature)

local_features = torch.cat(local_features, dim=0) # (N, 512)

# 提取全局特征
global_tensor = torch.from_numpy(global_roi).permute(2, 0, 1).float() / 255.0
global_tensor = global_tensor.unsqueeze(0) # (1, C, H, W)
global_feature = self.feature_extractor(global_tensor).squeeze(0) # (512,)

# 局部-全局注意力融合
fused_features = self.attention(local_features, global_feature) # (N, 512)

# 双向GRU序列建模
logits = self.gru(fused_features) # (2,)

return logits


# 实际测试
if __name__ == "__main__":
model = KPGBeltNet()

# 模拟输入
patches = [np.random.rand(32, 32, 3) for _ in range(10)]
global_roi = np.random.rand(256, 256, 3)

# 前向传播
logits = model(patches, global_roi)
print(f"输出形状: {logits.shape}")
print(f"预测结果: {'佩戴' if logits[0] > logits[1] else '未佩戴'}")

性能对比

方法 精度 召回率 F1 推理速度
YOLOv5s 82.3% 78.5% 80.3% 45fps
YOLOv8s 84.1% 81.2% 82.6% 40fps
NADSNet 86.7% 83.4% 85.0% 35fps
KPGBeltNet 91.2% 89.5% 90.3% 30fps

关键改进:

  • 精度提升7%:关键点引导的几何先验建模
  • 召回率提升11%:对角方向采样策略
  • F1提升5%:局部-全局注意力融合

IMS应用启示

1. 安全带误用检测模块设计

输入:

  • 座舱红外摄像头(推荐OV2311,2MP,全局快门)
  • 帧率:≥25fps
  • 分辨率:1600×1200

处理流程:

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
# IMS安全带检测模块伪代码
class SeatbeltModule:
def __init__(self):
self.keypoint_detector = YOLOv11Pose()
self.belt_classifier = KPGBeltNet()

def process(self, frame):
# 1. 检测关键点
keypoints = self.keypoint_detector.detect(frame)

# 2. 生成ROI
roi = self.get_roi(frame, keypoints)

# 3. 对角采样
patches = self.diagonal_sampling(roi, keypoints)

# 4. 分类
logits = self.belt_classifier(patches, roi)

# 5. 输出
is_worn = logits[0] > logits[1]

return {
'status': 'worn' if is_worn else 'not_worn',
'confidence': torch.softmax(logits, dim=0)[0].item()
}

2. Euro NCAP测试场景映射

Euro NCAP场景 KPGBeltNet检测能力 通过条件
BM-01:正确佩戴 ✅ 支持 置信度≥90%,≤2s检测
BM-02:未佩戴 ✅ 支持 置信度≥90%,≤2s检测
BM-03:腰带错误 ✅ 支持(关键点检测) 识别腰带位置偏差
BM-04:斜带滑落 ✅ 支持(对角采样) 识别斜带不完整
BM-05:遮挡场景 ⚠️ 部分支持 需配合红外补光

3. 边缘部署优化

模型量化:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# INT8量化示例
import torch.quantization as quant

model = KPGBeltNet()
model.eval()

# 动态量化
quantized_model = quant.quantize_dynamic(
model,
{nn.GRU, nn.Linear},
dtype=torch.qint8
)

# 导出ONNX
torch.onnx.export(
quantized_model,
(patches, global_roi),
"kpgbeltnet_int8.onnx",
opset_version=11
)

性能预估(Snapdragon 8255):

量化方式 模型大小 推理延迟 功耗
FP32 45MB 120ms 1.8W
FP16 23MB 65ms 1.2W
INT8 12MB 35ms 0.8W

4. 开发优先级排序

功能模块 优先级 工作量 Euro NCAP得分
基础佩戴检测 🔴 高 2周 3分
误用检测(腰带/斜带) 🔴 高 3周 5分
遮挡场景优化 🟡 中 2周 2分
多乘客检测 🟡 中 3周 2分
夜间红外模式 🟢 低 1周 1分

后续研究方向

1. 多模态融合

  • 结合座椅压力传感器数据
  • 融合安全带张紧器状态
  • 提升遮挡场景鲁棒性

2. 时序建模

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 时序平滑示例
class TemporalSmoother:
def __init__(self, window_size=5):
self.window_size = window_size
self.history = []

def smooth(self, current_status, confidence):
self.history.append((current_status, confidence))

if len(self.history) < self.window_size:
return current_status, confidence

# 滑动窗口平均
window = self.history[-self.window_size:]
avg_conf = np.mean([c for _, c in window])

# 多数投票
statuses = [s for s, _ in window]
final_status = max(set(statuses), key=statuses.count)

return final_status, avg_conf

3. 数据合成

使用NVIDIA Omniverse生成合成数据:

  • 不同光照条件(白天/夜晚/隧道)
  • 不同姿态变化(前倾/后仰/侧身)
  • 不同遮挡情况(手臂遮挡/衣物遮挡)

总结

KPGBeltNet通过关键点引导的几何先验建模对角方向采样策略局部-全局注意力机制,在复杂座舱环境下实现了91.2%的安全带检测精度,为Euro NCAP 2026安全带误用检测要求提供了有效解决方案。

IMS开发启示:

  1. 优先部署基础佩戴检测,满足Euro NCAP 3分要求
  2. 集成关键点检测,为误用检测奠定基础
  3. 采用INT8量化,在边缘设备实现实时推理
  4. 预留时序平滑接口,降低误报率

参考论文: KPGBeltNet: in-vehicle seatbelt detection algorithm based on human keypoint-guided sampling and local–global attention, The Visual Computer, 2026


KPGBeltNet:基于人体关键点和注意力机制的安全带误用检测算法
https://dapalm.com/2026/07/18/2026-07-18-01-KPGBeltNet-Seatbelt-Misuse-Detection-Keypoint-Attention/
作者
Mars
发布于
2026年7月18日
许可协议