KPGBeltNet:基于人体关键点引导与局部-全局注意力的车内安全带检测算法

论文信息

项目 内容
标题 KPGBeltNet: in-vehicle seatbelt detection algorithm based on human keypoint-guided sampling and local–global attention
发表期刊 The Visual Computer (Springer)
发表时间 2026年6月
DOI 10.1007/s00371-026-04572-1
关键词 安全带检测、关键点引导、局部-全局注意力、车内视觉

核心创新

KPGBeltNet 针对车内安全带检测的核心痛点:安全带细长、低对比度、与衣物/座椅背景视觉相似。传统目标检测框架(YOLO/Faster R-CNN)直接检测安全带时性能下降严重。KPGBeltNet 通过人体关键点引导采样缩小搜索空间,并用局部-全局注意力机制增强安全带特征。

与传统方法对比

方法 思路 问题
YOLO直接检测 安全带作为独立目标 细长目标检测差,背景干扰
Faster R-CNN 区域提议+分类 对低对比度安全带敏感
KPGBeltNet 关键点定位→引导采样→注意力增强 利用人体先验,缩小搜索空间

方法详解

整体架构

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

class KeypointGuidedSampling(nn.Module):
"""
关键点引导采样模块(KGS)

基于人体关键点(肩、胸、腰)定位安全带可能区域
安全带通常从肩部斜跨胸部到腰部
"""

# 人体关键点中与安全带相关的索引(COCO 17点)
SHOULDER_LEFT = 5 # 左肩
SHOULDER_RIGHT = 6 # 右肩
ELBOW_LEFT = 7
ELBOW_RIGHT = 8
HIP_LEFT = 11 # 左腰
HIP_RIGHT = 12 # 右腰

def __init__(self, img_size: int = 640, crop_size: int = 256):
super().__init__()
self.img_size = img_size
self.crop_size = crop_size

def forward(self, image: torch.Tensor,
keypoints: torch.Tensor) -> dict:
"""
Args:
image: (B, 3, H, W) 原始图像
keypoints: (B, 17, 2) COCO格式关键点坐标

Returns:
crops: dict containing local and global crops
"""
B = image.shape[0]
crops = {
'local_shoulder': [], # 肩部区域
'local_chest': [], # 胸部区域
'local_hip': [], # 腰部区域
'global': image # 整体图像
}

for b in range(B):
kp = keypoints[b] # (17, 2)

# 肩部区域采样
shoulder_center = (kp[self.SHOULDER_LEFT] +
kp[self.SHOULDER_RIGHT]) / 2
shoulder_crop = self._crop_region(
image[b], shoulder_center, self.crop_size
)
crops['local_shoulder'].append(shoulder_crop)

# 胸部区域采样(肩到腰的中点)
chest_center = (kp[self.SHOULDER_LEFT] + kp[self.SHOULDER_RIGHT] +
kp[self.HIP_LEFT] + kp[self.HIP_RIGHT]) / 4
chest_crop = self._crop_region(
image[b], chest_center, self.crop_size
)
crops['local_chest'].append(chest_crop)

# 腰部区域采样
hip_center = (kp[self.HIP_LEFT] + kp[self.HIP_RIGHT]) / 2
hip_crop = self._crop_region(
image[b], hip_center, self.crop_size
)
crops['local_hip'].append(hip_crop)

# 转为tensor
for key in ['local_shoulder', 'local_chest', 'local_hip']:
crops[key] = torch.stack(crops[key])

return crops

def _crop_region(self, img: torch.Tensor, center: torch.Tensor,
size: int) -> torch.Tensor:
"""以center为中心裁剪size×size区域"""
cx, cy = int(center[0].item()), int(center[1].item())
half = size // 2

# 边界检查
_, H, W = img.shape
x1 = max(0, cx - half)
y1 = max(0, cy - half)
x2 = min(W, x1 + size)
y2 = min(H, y1 + size)

crop = img[:, y1:y2, x1:x2]
# Resize到统一尺寸
if crop.shape[1] != size or crop.shape[2] != size:
crop = F.interpolate(
crop.unsqueeze(0), size=(size, size),
mode='bilinear', align_corners=False
).squeeze(0)

return crop


class LocalGlobalAttention(nn.Module):
"""
局部-全局注意力融合模块(LGA)

将局部裁剪区域的安全带细粒度特征
与全局图像的上下文特征融合
"""
def __init__(self, local_dim: int = 256, global_dim: int = 512,
fusion_dim: int = 256, nhead: int = 4):
super().__init__()

# 局部特征编码器(3个区域共享权重)
self.local_encoder = nn.Sequential(
nn.Conv2d(3, 32, 3, stride=2, padding=1),
nn.BatchNorm2d(32),
nn.ReLU(),
nn.Conv2d(32, 64, 3, stride=2, padding=1),
nn.BatchNorm2d(64),
nn.ReLU(),
nn.Conv2d(64, 128, 3, stride=2, padding=1),
nn.BatchNorm2d(128),
nn.ReLU(),
nn.AdaptiveAvgPool2d(1)
)
self.local_proj = nn.Linear(128, local_dim)

# 全局特征编码器
self.global_encoder = nn.Sequential(
nn.Conv2d(3, 32, 3, stride=2, padding=1),
nn.BatchNorm2d(32),
nn.ReLU(),
nn.Conv2d(32, 64, 3, stride=2, padding=1),
nn.BatchNorm2d(64),
nn.ReLU(),
nn.Conv2d(64, 128, 3, stride=2, padding=1),
nn.BatchNorm2d(128),
nn.ReLU(),
nn.Conv2d(128, 256, 3, stride=2, padding=1),
nn.BatchNorm2d(256),
nn.ReLU(),
nn.AdaptiveAvgPool2d(1)
)
self.global_proj = nn.Linear(256, global_dim)

# 跨模态注意力
self.cross_attn = nn.MultiheadAttention(
embed_dim=fusion_dim,
num_heads=nhead,
batch_first=True
)

# 将local和global投影到统一维度
self.local_to_fusion = nn.Linear(local_dim, fusion_dim)
self.global_to_fusion = nn.Linear(global_dim, fusion_dim)

# 分类头
self.classifier = nn.Sequential(
nn.Linear(fusion_dim * 4, 256), # 3 local + 1 global
nn.ReLU(),
nn.Dropout(0.3),
nn.Linear(256, 3) # 正确/错误/未佩戴
)

def forward(self, crops: dict) -> torch.Tensor:
"""
Args:
crops: dict with local_shoulder, local_chest,
local_hip, global
Returns:
logits: (B, 3)
"""
features = []

# 局部特征
for key in ['local_shoulder', 'local_chest', 'local_hip']:
x = crops[key] # (B, 3, crop_size, crop_size)
feat = self.local_encoder(x) # (B, 128, 1, 1)
feat = feat.flatten(1) # (B, 128)
feat = self.local_proj(feat) # (B, local_dim)
feat = self.local_to_fusion(feat) # (B, fusion_dim)
features.append(feat)

# 全局特征
global_x = crops['global'] # (B, 3, H, W)
global_feat = self.global_encoder(global_x) # (B, 256, 1, 1)
global_feat = global_feat.flatten(1) # (B, 256)
global_feat = self.global_proj(global_feat) # (B, global_dim)
global_feat = self.global_to_fusion(global_feat) # (B, fusion_dim)
features.append(global_feat)

# 堆叠为序列: (B, 4, fusion_dim)
feat_seq = torch.stack(features, dim=1)

# 自注意力
attn_out, _ = self.cross_attn(feat_seq, feat_seq, feat_seq)

# 池化 + 分类
pooled = attn_out.mean(dim=1) # (B, fusion_dim)
# 拼接所有特征
all_feats = torch.cat([pooled] +
[f for f in features], dim=-1) # (B, fusion_dim*4)
logits = self.classifier(all_feats)

return logits


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

论文:The Visual Computer, 2026
DOI: 10.1007/s00371-026-04572-1

管道:
1. 人体关键点检测(SPPE)
2. 关键点引导采样(KGS)
3. 局部-全局注意力融合(LGA)
4. 三分类:正确佩戴/错误佩戴/未佩戴
"""
def __init__(self, img_size: int = 640, crop_size: int = 128):
super().__init__()

# 关键点检测器(使用预训练SPPE)
self.keypoint_detector = self._load_sppe()

# 关键点引导采样
self.kgs = KeypointGuidedSampling(
img_size=img_size,
crop_size=crop_size
)

# 局部-全局注意力
self.lga = LocalGlobalAttention(
local_dim=256,
global_dim=512,
fusion_dim=256,
nhead=4
)

def forward(self, image: torch.Tensor) -> torch.Tensor:
"""
Args:
image: (B, 3, H, W)
Returns:
logits: (B, 3) [正确, 错误, 未佩戴]
"""
# 1. 关键点检测
with torch.no_grad():
keypoints = self.keypoint_detector(image)

# 2. 引导采样
crops = self.kgs(image, keypoints)

# 3. 局部-全局注意力 + 分类
logits = self.lga(crops)

return logits

def _load_sppe(self):
"""加载预训练单人姿态估计模型"""
# 简化实现:实际使用FastPose或HRNet
model = nn.Sequential(
nn.Conv2d(3, 64, 3, stride=2, padding=1),
nn.BatchNorm2d(64),
nn.ReLU(),
nn.Conv2d(64, 128, 3, stride=2, padding=1),
nn.BatchNorm2d(128),
nn.ReLU(),
nn.Conv2d(128, 256, 3, stride=2, padding=1),
nn.BatchNorm2d(256),
nn.ReLU(),
nn.AdaptiveAvgPool2d((4, 4)),
nn.Flatten(),
nn.Linear(256 * 16, 17 * 2) # 17个关键点, xy
)
return model


# ==================== 测试代码 ====================
if __name__ == "__main__":
model = KPGBeltNet(img_size=640, crop_size=128)

# 模拟输入
batch_size = 4
image = torch.randn(batch_size, 3, 640, 640)

with torch.no_grad():
logits = model(image)
probs = torch.softmax(logits, dim=1)

classes = ['正确佩戴', '错误佩戴', '未佩戴']
print(f"输出形状: {logits.shape}")
for i in range(batch_size):
pred = classes[probs[i].argmax()]
print(f" 样本{i}: {pred} (置信度={probs[i].max():.2%})")
print(f"参数量: {sum(p.numel() for p in model.parameters()) / 1e6:.1f}M")

2. 局部-全局注意力机制

KPGBeltNet的核心设计理念:

特征层次 来源 作用
局部细粒度 肩部/胸部/腰部裁剪区域 捕获安全带纹理、边缘、方向
全局上下文 整体图像 人体姿态、座椅背景、光照条件
注意力融合 跨区域注意力 局部特征互相验证 + 全局约束

关键技术细节

安全带检测的三大难点

难点 描述 KPGBeltNet解决方案
细长形状 安全带宽度仅20-50px,长宽比>10:1 关键点引导采样,聚焦肩-胸-腰区域
低对比度 黑色安全带与深色衣物/座椅难区分 局部注意力增强微弱特征
形变复杂 安全带随体型弯曲,非刚性 全局上下文约束形变范围

与Euro NCAP安全带检测要求的关联

Euro NCAP 2026 OMS协议要求检测安全带错误佩戴(belt misuse),包括:

  • 安全带在手臂下方(under-arm)
  • 安全带在背后(behind-back)
  • 安全带扭曲(twisted)
  • 安全带松弛(loose)
场景 KPGBeltNet检测能力 关键依据
正确佩戴 ✅ 高精度 肩-胸-腰三角特征匹配
手臂下方 ✅ 可检测 肩部关键点位置偏移
背后 ⚠️ 需侧视角 正面摄像头受限
扭曲 ⚠️ 需高分辨率 局部纹理分析
未佩戴 ✅ 高精度 关键点区域无带状特征

性能对比

方法 mAP@0.5 FPS 模型大小 车内适用性
YOLOv5s 68.2 85 14MB ⚠️ 细长目标差
YOLOv8m 72.5 45 52MB ⚠️ 模型大
Faster R-CNN 70.1 15 108MB ❌ 速度不足
KPGBeltNet 78.3 38 42MB ✅ 均衡

IMS开发启示

1. 算法集成路线

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
IMS安全带检测集成路线(基于KPGBeltNet):

Step 1: 关键点检测复用
├── IMS已有人体姿态估计模块
├── 提取肩/胸/腰关键点
└── 无需额外模型

Step 2: 裁剪区域采样
├── 基于关键点动态裁剪
├── 裁剪大小128×128
└── 3个局部区域 + 1个全局

Step 3: 注意力融合
├── 局部特征: 安全带纹理/方向
├── 全局特征: 人体姿态/背景
└── 跨区域注意力互相验证

Step 4: 三分类输出
├── 正确佩戴 → 正常
├── 错误佩戴 → 一级警告
└── 未佩戴 → 二级警告

2. 硬件部署评估

硬件 推理速度(预估) 内存占用 功耗
QCS8255 (Hexagon NPU) 25-35 fps <200MB <2W
TI TDA4VM 30-40 fps <150MB <3W
NVIDIA Orin Nano 45-60 fps <300MB <5W

3. 与现有IMS模块的协同

关键优势:KPGBeltNet复用IMS已有的人体关键点检测模块,不增加额外检测器开销,仅增加注意力融合层,适合在现有DMS管线上即插即用。

4. 数据需求

数据类型 数量需求 来源
正确佩戴 5000+ 现有IMS数据
错误佩戴(各子类) 2000+/子类 需定向采集
未佩戴 3000+ 现有数据
多体型/多衣着 覆盖性采集 补充采集

5. 开发优先级

功能 优先级 预计工时 依赖
关键点复用接口 P0 1周 已有关键点模块
裁剪采样实现 P0 1周 无
局部-全局注意力 P1 2周 裁剪采样
模型训练+调优 P1 3周 数据集
量化部署 P2 2周 模型训练完成
Euro NCAP合规 P2 2周 部署完成

论文DOI: 10.1007/s00371-026-04572-1