乘员分类与年龄性别检测:CNN深度学习方法

乘员分类与年龄性别检测:CNN深度学习方法

研究背景

Euro NCAP 2026要求:

  • OMS必须识别乘员类型(成人/儿童/婴儿)
  • 用于安全带提醒、气囊适配
  • 儿童座椅检测(后向/前向)

应用场景:

应用 分类需求 精度要求
安全带提醒 成人/儿童区分 >95%
气囊适配 儿童→禁用前排气囊 >99%
CPD儿童检测 儿童/婴儿检测 >90%
个性化服务 年龄/性别 >85%

技术方案

1. 数据集

公开数据集:

数据集 样本数 年龄范围 特点
MORPH-II 55,000 16-77 年龄标注详细
FG-NET 1,002 0-69 包含儿童
IMDB-WIKI 500,000+ 0-100 大规模
SVIRO 车内场景 多年龄 车内专用

2. 网络架构

多任务CNN架构:

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
"""
乘员分类CNN模型
多任务学习:年龄估计 + 性别分类 + 儿童/成人区分
"""

import torch
import torch.nn as nn
import torch.nn.functional as F
from typing import Tuple, Dict

class OccupantClassifier(nn.Module):
"""
乘员分类器

输出:
1. 年龄估计(回归)
2. 性别分类(二分类)
3. 年龄组分类(多分类:儿童/青少年/成人/老年)
4. 儿童座椅检测(二分类)
"""

def __init__(self,
backbone: str = 'resnet50',
pretrained: bool = True):
"""
Args:
backbone: 骨干网络
pretrained: 是否使用预训练权重
"""
super().__init__()

# 骨干网络
if backbone == 'resnet50':
from torchvision.models import resnet50, ResNet50_Weights
self.backbone = resnet50(
weights=ResNet50_Weights.DEFAULT if pretrained else None
)
feature_dim = 2048

elif backbone == 'efficientnet_b0':
from torchvision.models import efficientnet_b0, EfficientNet_B0_Weights
self.backbone = efficientnet_b0(
weights=EfficientNet_B0_Weights.DEFAULT if pretrained else None
)
feature_dim = 1280

# 移除原始分类头
self.backbone.fc = nn.Identity()

# 共享特征层
self.shared_fc = nn.Sequential(
nn.Linear(feature_dim, 512),
nn.ReLU(),
nn.Dropout(0.5),
nn.Linear(512, 256),
nn.ReLU(),
)

# 年龄回归头
self.age_head = nn.Sequential(
nn.Linear(256, 128),
nn.ReLU(),
nn.Linear(128, 1),
nn.ReLU() # 年龄非负
)

# 性别分类头
self.gender_head = nn.Sequential(
nn.Linear(256, 64),
nn.ReLU(),
nn.Linear(64, 2) # male/female
)

# 年龄组分类头
self.age_group_head = nn.Sequential(
nn.Linear(256, 64),
nn.ReLU(),
nn.Linear(64, 5) # 0-3, 4-12, 13-17, 18-60, 60+
)

# 儿童座椅检测头
self.child_seat_head = nn.Sequential(
nn.Linear(256, 64),
nn.ReLU(),
nn.Linear(64, 3) # none/rear-facing/forward-facing
)

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

Args:
x: 输入图像 (B, 3, H, W)

Returns:
outputs: 各任务输出
"""
# 提取特征
features = self.backbone(x)

# 共享层
shared = self.shared_fc(features)

# 各任务输出
age = self.age_head(shared).squeeze(-1)
gender = self.gender_head(shared)
age_group = self.age_group_head(shared)
child_seat = self.child_seat_head(shared)

return {
'age': age,
'gender': gender,
'age_group': age_group,
'child_seat': child_seat
}

def predict(self, x: torch.Tensor) -> Dict:
"""
推理预测

Returns:
predictions: 预测结果字典
"""
self.eval()
with torch.no_grad():
outputs = self.forward(x)

# 年龄
age = outputs['age'].cpu().numpy()

# 性别
gender_prob = F.softmax(outputs['gender'], dim=1)
gender = torch.argmax(gender_prob, dim=1).cpu().numpy()
gender_conf = torch.max(gender_prob, dim=1)[0].cpu().numpy()

# 年龄组
age_group_names = ['infant', 'child', 'teenager', 'adult', 'senior']
age_group_prob = F.softmax(outputs['age_group'], dim=1)
age_group_idx = torch.argmax(age_group_prob, dim=1).cpu().numpy()
age_group = [age_group_names[i] for i in age_group_idx]

# 儿童座椅
seat_names = ['none', 'rear-facing', 'forward-facing']
seat_prob = F.softmax(outputs['child_seat'], dim=1)
seat_idx = torch.argmax(seat_prob, dim=1).cpu().numpy()
child_seat = [seat_names[i] for i in seat_idx]

return {
'age': age.tolist(),
'gender': ['male' if g == 0 else 'female' for g in gender],
'gender_confidence': gender_conf.tolist(),
'age_group': age_group,
'child_seat': child_seat
}


class MultiTaskLoss(nn.Module):
"""多任务损失函数"""

def __init__(self,
age_weight: float = 1.0,
gender_weight: float = 1.0,
age_group_weight: float = 1.0,
child_seat_weight: float = 1.0):
super().__init__()

self.age_weight = age_weight
self.gender_weight = gender_weight
self.age_group_weight = age_group_weight
self.child_seat_weight = child_seat_weight

def forward(self,
outputs: Dict[str, torch.Tensor],
targets: Dict[str, torch.Tensor]) -> Tuple[torch.Tensor, Dict]:
"""
计算总损失

Args:
outputs: 模型输出
targets: 标签 {'age':, 'gender':, 'age_group':, 'child_seat':}

Returns:
total_loss: 总损失
loss_dict: 各任务损失
"""
# 年龄损失(L1)
age_loss = F.l1_loss(outputs['age'], targets['age'])

# 性别损失(交叉熵)
gender_loss = F.cross_entropy(outputs['gender'], targets['gender'])

# 年龄组损失
age_group_loss = F.cross_entropy(outputs['age_group'], targets['age_group'])

# 儿童座椅损失
child_seat_loss = F.cross_entropy(outputs['child_seat'], targets['child_seat'])

# 总损失
total_loss = (
self.age_weight * age_loss +
self.gender_weight * gender_loss +
self.age_group_weight * age_group_loss +
self.child_seat_weight * child_seat_loss
)

loss_dict = {
'age_loss': age_loss.item(),
'gender_loss': gender_loss.item(),
'age_group_loss': age_group_loss.item(),
'child_seat_loss': child_seat_loss.item(),
'total_loss': total_loss.item()
}

return total_loss, loss_dict


# 训练代码
def train_epoch(model, dataloader, optimizer, criterion, device):
"""训练一个epoch"""
model.train()
total_losses = {'age_loss': 0, 'gender_loss': 0, 'age_group_loss': 0,
'child_seat_loss': 0, 'total_loss': 0}

for batch in dataloader:
images = batch['image'].to(device)
targets = {
'age': batch['age'].to(device),
'gender': batch['gender'].to(device),
'age_group': batch['age_group'].to(device),
'child_seat': batch['child_seat'].to(device)
}

# 前向
outputs = model(images)

# 损失
loss, loss_dict = criterion(outputs, targets)

# 反向
optimizer.zero_grad()
loss.backward()
optimizer.step()

# 累积损失
for k, v in loss_dict.items():
total_losses[k] += v

# 平均
n = len(dataloader)
for k in total_losses:
total_losses[k] /= n

return total_losses


# 测试代码
if __name__ == "__main__":
# 创建模型
model = OccupantClassifier(backbone='resnet50', pretrained=True)

# 模拟输入
batch_size = 4
x = torch.randn(batch_size, 3, 224, 224)

# 推理
predictions = model.predict(x)

print("预测结果:")
for i in range(batch_size):
print(f" 样本{i}: 年龄={predictions['age'][i]:.1f}, "
f"性别={predictions['gender'][i]}, "
f"年龄组={predictions['age_group'][i]}, "
f"儿童座椅={predictions['child_seat'][i]}")

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

3. 年龄组定义

Euro NCAP推荐分类:

年龄组 年龄范围 特征 安全配置
Infant 0-3岁 婴儿座椅 后排后向座椅
Child 4-12岁 儿童座椅 后排前向座椅
Teenager 13-17岁 可用成人安全带 正常配置
Adult 18-60岁 标准乘员 正常配置
Senior 60+岁 可能需要特殊配置 考虑气囊力度

4. 性能指标

基准结果:

方法 年龄MAE 性别准确率 儿童检测
ResNet50 5.77年 95% 92%
EfficientNet-B0 6.2年 93% 90%
MobileNetV3 7.1年 91% 88%

Euro NCAP测试场景

儿童座椅检测

场景编号 场景描述 检测要求
CS-01 后向婴儿座椅 ≥95%
CS-02 前向儿童座椅 ≥95%
CS-03 增高垫 ≥90%
CS-04 无座椅儿童 ≥85%

乘员分类测试

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
# Euro NCAP乘员分类测试
OCCUPANT_TEST_CASES = [
{'age': 1, 'expected_group': 'infant', 'expected_seat': 'rear-facing'},
{'age': 5, 'expected_group': 'child', 'expected_seat': 'forward-facing'},
{'age': 15, 'expected_group': 'teenager', 'expected_seat': 'none'},
{'age': 35, 'expected_group': 'adult', 'expected_seat': 'none'},
{'age': 70, 'expected_group': 'senior', 'expected_seat': 'none'},
]

def test_occupant_classifier(model, test_cases):
"""测试乘员分类器"""
results = []

for case in test_cases:
# 模拟图像
# 实际需要采集真实图像
image = generate_synthetic_face(case['age'])

prediction = model.predict(image)

correct_group = prediction['age_group'] == case['expected_group']
correct_seat = prediction['child_seat'] == case['expected_seat']

results.append({
'age': case['age'],
'expected_group': case['expected_group'],
'predicted_group': prediction['age_group'],
'group_correct': correct_group,
'seat_correct': correct_seat
})

return results

IMS开发启示

1. 模型部署优化

量化与加速:

优化方法 精度损失 速度提升 内存减少
FP16量化 <1% 1.5-2x 50%
INT8量化 1-2% 2-4x 75%
剪枝50% <2% 1.3x 50%
知识蒸馏 <1% - 70%

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
import onnxruntime as ort

class ONNXOccupantClassifier:
"""ONNX部署版本"""

def __init__(self, onnx_path: str):
# 创建推理会话
self.session = ort.InferenceSession(onnx_path)

# 获取输入输出信息
self.input_name = self.session.get_inputs()[0].name
self.output_names = [o.name for o in self.session.get_outputs()]

def predict(self, image: np.ndarray) -> Dict:
"""
推理

Args:
image: 预处理后的图像 (1, 3, 224, 224)

Returns:
predictions: 预测结果
"""
# ONNX推理
outputs = self.session.run(self.output_names,
{self.input_name: image})

# 解析输出
age = outputs[0][0]
gender_prob = outputs[1][0]
age_group_prob = outputs[2][0]
child_seat_prob = outputs[3][0]

return {
'age': float(age),
'gender': 'female' if np.argmax(gender_prob) else 'male',
'age_group': ['infant', 'child', 'teenager', 'adult', 'senior'][
np.argmax(age_group_prob)
],
'child_seat': ['none', 'rear-facing', 'forward-facing'][
np.argmax(child_seat_prob)
]
}

3. 实时性能

平台 FP32延迟 INT8延迟 功耗
QCS8255 (CPU) 45ms 20ms 0.5W
QCS8255 (DSP) 25ms 12ms 0.3W
QCS8295 (NPU) 8ms 5ms 0.4W
TI TDA4 (C7x) 30ms 15ms 0.35W

参考文献

  1. Zhang, K., et al. “Age and Gender Prediction using Deep CNNs and Transfer Learning.” arXiv, 2021.
  2. Da Cruz, L., et al. “SVIRO: Synthetic Vehicle Interior Rear Seat Occupancy Dataset.” WACV 2020.
  3. Euro NCAP. “Child Presence Detection Protocol v1.0.” 2025.

相关文章:


乘员分类与年龄性别检测:CNN深度学习方法
https://dapalm.com/2026/06/11/2026-06-11-Occupant-Classification-Age-Gender-CNN/
作者
Mars
发布于
2026年6月11日
许可协议