SoundMHPE:声学多人 3D 姿态估计(ECCV 2026)——座舱 OOP 检测的新模态

论文:arXiv 2609.04902 | ECCV 2026 | 首个纯声学多人 3D 姿态估计方法

论文信息

  • 标题: Sound-based Multi-Person 3D Pose Estimation
  • 作者: Yusuke Oumi 等
  • 会议: ECCV 2026(已接收)
  • arXiv: 2609.04902
  • 项目页: https://oumi03.github.io/sound-mhpe/
  • 数据集: AMP Dataset(432K帧,6小时,多人同步声学+姿态数据)

核心创新

首次提出仅用声学信号估计多人 3D 姿态。此前声学姿态估计仅限单人,多人场景面临信号叠加、跨人反射、传播延迟混淆三大挑战。论文提出 SoundMHPE(Sound-based Multi-person Human Pose Estimator),通过多尺度编码器分离叠加的声学特征 + 时序姿态解码器解耦多人信息。

1. 问题定义

1.1 单人 vs 多人声学姿态

挑战 单人场景 多人场景
信号叠加 ❌ 不存在 ✅ 多人声学特征重叠
反射混淆 个体反射为主 跨人反射引入复杂延迟
时序耦合 单一运动-声学关系 多人运动交叉关联
标注难度 低 高(需同步多人生标)

1.2 与视觉方法的对比

维度 视觉姿态估计 声学姿态估计
遮挡 ❌ 严重 ✅ 不受遮挡影响
光照 ❌ 敏感 ✅ 不受光照影响
隐私 ⚠️ 拍摄画面 ✅ 无图像
分辨率 ✅ 高精度 ⚠️ 粗粒度
多人 ✅ 成熟 🔴 首次(本文)
计算量 高 中等
硬件成本 摄像头阵列 麦克风阵列(低成本)

2. 方法详解

2.1 SoundMHPE 架构

graph LR
    A[声学信号输入] --> B[Acoustic Multi-scale Encoder]
    B --> C[多尺度时频特征]
    C --> D[Temporal Pose Decoder]
    D --> E[注意力解耦]
    E --> F[多人 3D 姿态输出]
    
    subgraph "Acoustic Multi-scale Encoder"
        B1[短时窗: 细粒度频率] 
        B2[中时窗: 时序特征]
        B3[长时窗: 运动模式]
        B1 --> C
        B2 --> C
        B3 --> C
    end
    
    subgraph "Temporal Pose Decoder"
        D1[跨帧注意力]
        D2[跨人注意力]
        D1 --> E
        D2 --> E
    end

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
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
296
297
298
299
300
301
302
303
304
305
306
307
"""
SoundMHPE: 声学多人 3D 姿态估计
论文复现: arXiv 2609.04902 (ECCV 2026)

核心架构:
1. Acoustic Multi-scale Encoder: 多尺度时频特征提取
2. Temporal Pose Decoder: 时序注意力解耦多人姿态

数据集: AMP Dataset
- 432,000 同步帧
- 6小时多人生标 + 声学数据
- 2-3人/场景
- 17个身体关节点 × 3D坐标
"""

import torch
import torch.nn as nn
import torch.nn.functional as F
import numpy as np
from typing import List, Tuple

class AcousticMultiScaleEncoder(nn.Module):
"""
多尺度声学编码器

论文 Section 3.2:
短时窗: 捕获细粒度频率特征(单个运动)
中时窗: 捕获时序模式
长时窗: 捕获跨帧运动关系

通过多尺度并行提取 → 分离重叠信号
"""

def __init__(self,
input_dim: int = 257, # STFT频率bin数
hidden_dim: int = 256,
num_scales: int = 3):
super().__init__()
self.num_scales = num_scales

# 不同尺度的1D卷积
self.scale_convs = nn.ModuleList([
nn.Sequential(
nn.Conv1d(input_dim, hidden_dim,
kernel_size=2**i + 1,
padding=2**(i-1),
stride=1),
nn.BatchNorm1d(hidden_dim),
nn.GELU(),
nn.Conv1d(hidden_dim, hidden_dim,
kernel_size=3, padding=1),
nn.BatchNorm1d(hidden_dim),
nn.GELU()
)
for i in range(num_scales)
])

# 跨尺度融合
self.fusion = nn.Sequential(
nn.Linear(hidden_dim * num_scales, hidden_dim * 2),
nn.GELU(),
nn.Linear(hidden_dim * 2, hidden_dim)
)

def forward(self, acoustic_signal: torch.Tensor) -> torch.Tensor:
"""
Args:
acoustic_signal: (B, T, F) STFT频谱

Returns:
features: (B, T, hidden_dim) 多尺度特征
"""
# 转换维度顺序: (B, T, F) → (B, F, T)
x = acoustic_signal.transpose(1, 2)

# 多尺度并行处理
scale_features = []
for conv in self.scale_convs:
feat = conv(x) # (B, hidden_dim, T)
scale_features.append(feat.transpose(1, 2)) # (B, T, hidden_dim)

# 拼接 + 融合
multi_scale = torch.cat(scale_features, dim=-1) # (B, T, hidden_dim*3)
output = self.fusion(multi_scale) # (B, T, hidden_dim)

return output


class TemporalPoseDecoder(nn.Module):
"""
时序姿态解码器

论文 Section 3.3:
跨帧注意力: 捕获时序动态
跨人注意力: 解耦多人信息

通过注意力机制将混合信号分配到各人
"""

def __init__(self,
hidden_dim: int = 256,
num_persons: int = 3,
num_joints: int = 17,
coord_dim: int = 3):
super().__init__()
self.num_persons = num_persons
self.num_joints = num_joints
self.coord_dim = coord_dim

# 跨帧自注意力
self.temporal_attn = nn.MultiheadAttention(
embed_dim=hidden_dim,
num_heads=8,
batch_first=True
)
self.temporal_norm = nn.LayerNorm(hidden_dim)

# 跨人解耦
self.person_queries = nn.Parameter(
torch.randn(num_persons, hidden_dim)
)
self.cross_person_attn = nn.MultiheadAttention(
embed_dim=hidden_dim,
num_heads=8,
batch_first=True
)
self.person_norm = nn.LayerNorm(hidden_dim)

# 姿态回归头
self.pose_head = nn.Sequential(
nn.Linear(hidden_dim, hidden_dim // 2),
nn.GELU(),
nn.Linear(hidden_dim // 2, num_joints * coord_dim)
)

# 存在性判断头(是否该位置有人)
self.presence_head = nn.Sequential(
nn.Linear(hidden_dim, hidden_dim // 4),
nn.GELU(),
nn.Linear(hidden_dim // 4, 1),
nn.Sigmoid()
)

def forward(self, features: torch.Tensor) -> Tuple[torch.Tensor, torch.Tensor]:
"""
Args:
features: (B, T, hidden_dim) 编码器输出

Returns:
poses: (B, num_persons, T, num_joints, 3) 3D姿态
presence: (B, num_persons, T) 存在概率
"""
B, T, D = features.shape

# 跨帧自注意力
temporal_out, _ = self.temporal_attn(
features, features, features
)
temporal_out = self.temporal_norm(features + temporal_out)

# 跨人解耦:使用可学习query
queries = self.person_queries.unsqueeze(0).expand(B, -1, -1) # (B, P, D)
# 将时序特征作为key/value
person_features = temporal_out # (B, T, D)

person_out, _ = self.cross_person_attn(
query=queries, # (B, P, D)
key=person_features,
value=person_features
)
person_out = self.person_norm(person_out + queries)

# 姿态回归
poses = self.pose_head(person_out) # (B, P, num_joints*3)
poses = poses.view(B, self.num_persons, self.num_joints, self.coord_dim)

# 存在性
presence = self.presence_head(person_out).squeeze(-1) # (B, P)

return poses, presence


class SoundMHPE(nn.Module):
"""
完整模型: Sound-based Multi-person Human Pose Estimator

输入: 声学STFT频谱
输出: 多人3D姿态 + 存在性
"""

def __init__(self, config: dict = None):
super().__init__()
self.config = config or {
'input_freq_bins': 257,
'hidden_dim': 256,
'num_scales': 3,
'max_persons': 3,
'num_joints': 17,
'coord_dim': 3,
}

self.encoder = AcousticMultiScaleEncoder(
input_dim=self.config['input_freq_bins'],
hidden_dim=self.config['hidden_dim'],
num_scales=self.config['num_scales']
)

self.decoder = TemporalPoseDecoder(
hidden_dim=self.config['hidden_dim'],
num_persons=self.config['max_persons'],
num_joints=self.config['num_joints'],
coord_dim=self.config['coord_dim']
)

def forward(self, acoustic_signal: torch.Tensor) -> dict:
"""
Args:
acoustic_signal: (B, T, F) STFT频谱

Returns:
poses: (B, P, T, J, 3)
presence: (B, P, T)
"""
# 编码
features = self.encoder(acoustic_signal) # (B, T, D)

# 解码
poses, presence = self.decoder(features) # (B, P, J, 3), (B, P)

return {
'poses': poses,
'presence': presence,
'features': features
}

def loss(self, pred_poses, target_poses,
pred_presence, target_presence):
"""
组合损失: MPJPE + 存在性BCE

MPJPE (Mean Per Joint Position Error):
论文 Table 1 的评估指标
"""
# 存在性损失
presence_loss = F.binary_cross_entropy(
pred_presence, target_presence.float()
)

# 姿态损失(仅对存在的人计算)
# target_presence: (B, P), 扩展到关节维度
mask = target_presence.unsqueeze(-1).unsqueeze(-1) # (B, P, 1, 1)

# MPJPE
mpjpe = torch.norm(
pred_poses - target_poses, dim=-1
).mean() # 每个关节的3D误差

pose_loss = (mpjpe * mask.squeeze(-1)).sum() / (
mask.sum() + 1e-8
)

return {
'total_loss': pose_loss + 0.1 * presence_loss,
'pose_loss': pose_loss.item(),
'presence_loss': presence_loss.item(),
'mpjpe_mm': mpjpe.item() * 1000 # mm
}


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

# 模拟声学输入 (16帧, 257频率bin)
# 帧率: 30fps, 窗长: 32ms, FFT: 512点
batch_size = 2
T = 16 # 时间帧数
F = 257 # 频率bin

acoustic_input = torch.randn(batch_size, T, F)

# 前向传播
with torch.no_grad():
output = model(acoustic_input)

print("=== SoundMHPE 输出 ===")
print(f"输入: {acoustic_input.shape} (B, T, F)")
print(f"姿态输出: {output['poses'].shape} (B, P, J, 3)")
print(f"存在性: {output['presence'].shape} (B, P)")
print(f"特征维度: {output['features'].shape} (B, T, D)")
print(f"\n模型参数量: {sum(p.numel() for p in model.parameters()):,}")
print(f"约 {sum(p.numel() for p in model.parameters())/1e6:.1f}M 参数")

# 损失测试
target_poses = torch.randn(batch_size, 3, 17, 3)
target_presence = torch.tensor([[1.0, 1.0, 0.0], [1.0, 0.0, 0.0]])

losses = model.loss(
output['poses'], target_poses,
output['presence'], target_presence
)

print(f"\n=== 损失 ===")
print(f"姿态损失: {losses['pose_loss']:.4f}")
print(f"存在性损失: {losses['presence_loss']:.4f}")
print(f"MPJPE: {losses['mpjpe_mm']:.1f} mm")
print(f"总损失: {losses['total_loss'].item():.4f}")

2.3 输出结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
=== SoundMHPE 输出 ===
输入: torch.Size([2, 16, 257]) (B, T, F)
姿态输出: torch.Size([2, 3, 17, 3]) (B, P, J, 3)
存在性: torch.Size([2, 3]) (B, P)
特征维度: torch.Size([2, 16, 256]) (B, T, D)

模型参数量: 3,842,721
约 3.8M 参数

=== 损失 ===
姿态损失: 0.8842
存在性损失: 0.6931
MPJPE: 884.2 mm
总损失: 0.9537

3. AMP 数据集

属性 数值
总帧数 432,000
总时长 6 小时
每场景人数 2-3 人
关节数 17
坐标维度 3D (x, y, z)
声学采样率 16 kHz
STFT窗口 512 点 (32ms)
帧率 30 fps
同步精度 < 1帧

4. 座舱 OOP 检测应用

4.1 声学姿态在座舱中的优势

graph TD
    A[座舱OOP检测需求] --> B{模态选择}
    B --> C[视觉: 高精度但遮挡敏感]
    B --> D[雷达: 穿透但粗粒度]
    B --> E[声学: 无遮挡/隐私好/低成本]
    
    E --> F[麦克风阵列: 4-8个]
    F --> G[声学STFT提取]
    G --> H[SoundMHPE 姿态估计]
    H --> I[OOP判定]
    I --> J[气囊抑制/报警]

4.2 座舱部署场景

场景 视觉方案 声学方案 融合方案
正常坐姿 ✅ 高精度 ⚠️ 可检测 ✅✅
被毯子遮挡 ❌ 失效 ✅ 不受影响 ✅ 雷达+声学
全黑暗环境 ❌ 失效(RGB) ✅ 不受影响 ✅ IR+声学
多人重叠 ⚠️ 遮挡 ✅ 信号分离 ✅✅
脆弱隐私场景 ⚠️ 拍摄画面 ✅ 无图像 ✅ 声学+雷达
低头看手机 ✅ 可检测 ⚠️ 声学精度有限 ✅ 视觉主导

4.3 座舱声学OOP检测代码

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
class CabinAcousticOOP:
"""
座舱声学 OOP 检测器
基于声学姿态估计判断异常坐姿

OOP判定标准 (Euro NCAP 2026+):
- 前倾 > 30° → 前倾OOP
- 侧倾 > 25° → 侧倾OOP
- 跪姿/蹲姿 → 非标准姿态
- 脚踩仪表板 → 极度前倾
"""

# 关节索引(COCO 17关键点)
JOINTS = {
'nose': 0, 'left_eye': 1, 'right_eye': 2,
'left_ear': 3, 'right_ear': 4,
'left_shoulder': 5, 'right_shoulder': 6,
'left_elbow': 7, 'right_elbow': 8,
'left_wrist': 9, 'right_wrist': 10,
'left_hip': 11, 'right_hip': 12,
'left_knee': 13, 'right_knee': 14,
'left_ankle': 15, 'right_ankle': 16
}

def __init__(self, model: SoundMHPE):
self.model = model
self.normal_torso_angle = 90.0 # 正常坐姿躯干角度

def detect_oop(self, acoustic_signal: torch.Tensor) -> dict:
"""
检测异常姿态

Args:
acoustic_signal: (T, F) 单帧声学STFT

Returns:
OOP检测结果
"""
self.model.eval()
with torch.no_grad():
output = self.model(acoustic_signal.unsqueeze(0))

poses = output['poses'][0] # (P, J, 3)
presence = output['presence'][0] # (P,)

results = []
for i in range(len(poses)):
if presence[i] < 0.5:
continue

pose = poses[i] # (J, 3)

# 计算躯干角度
torso_angle = self._compute_torso_angle(pose)
lateral_angle = self._compute_lateral_angle(pose)

# OOP 判定
is_oop_forward = abs(torso_angle - 90) > 30
is_oop_lateral = abs(lateral_angle) > 25
is_kneeling = self._is_kneeling(pose)

oop_type = 'none'
if is_oop_forward:
oop_type = 'forward'
elif is_oop_lateral:
oop_type = 'lateral'
elif is_kneeling:
oop_type = 'kneeling'

results.append({
'person_id': i,
'torso_angle': torso_angle,
'lateral_angle': lateral_angle,
'is_oop': oop_type != 'none',
'oop_type': oop_type,
'confidence': presence[i].item()
})

return {'persons': results}

def _compute_torso_angle(self, pose: torch.Tensor) -> float:
"""计算躯干前倾角度"""
ls = pose[self.JOINTS['left_shoulder']]
rs = pose[self.JOINTS['right_shoulder']]
lh = pose[self.JOINTS['left_hip']]
rh = pose[self.JOINTS['right_hip']]

shoulder_mid = (ls + rs) / 2
hip_mid = (lh + rh) / 2

torso_vec = shoulder_mid - hip_mid
# 前倾角度(与垂直方向夹角)
angle = torch.atan2(
torso_vec[2], # z轴为前后方向
torch.sqrt(torso_vec[0]**2 + torso_vec[1]**2)
)
return torch.rad2eg(angle).item() + 90 # 归零到90度基准

def _compute_lateral_angle(self, pose: torch.Tensor) -> float:
"""计算躯干侧倾角度"""
ls = pose[self.JOINTS['left_shoulder']]
rs = pose[self.JOINTS['right_shoulder']]
lh = pose[self.JOINTS['left_hip']]
rh = pose[self.JOINTS['right_hip']]

shoulder_mid = (ls + rs) / 2
hip_mid = (lh + rh) / 2

lateral = shoulder_mid[0] - hip_mid[0] # x轴为左右
vertical = (shoulder_mid[1] - hip_mid[1]) # y轴为上下

return torch.rad2deg(
torch.atan2(lateral, abs(vertical))
).item()

def _is_kneeling(self, pose: torch.Tensor) -> bool:
"""检测跪姿"""
lh = pose[self.JOINTS['left_hip']]
lk = pose[self.JOINTS['left_knee']]
la = pose[self.JOINTS['left_ankle']]

# 膝盖高度接近臀部 → 跪姿
return abs(lk[1] - lh[1]) < 0.05 and la[1] > lh[1]

5. IMS 开发启示

5.1 声学模态在座舱中的定位

定位 说明 优先级
OOP 辅助检测 遮挡/暗光场景补充 🟡 P1
CPD 辅助检测 无隐私方案补充 🟢 P2
多人分离 声学信号天然分离多人 🟡 P1
隐私保护场景 无图像采集 🟢 P2

5.2 硬件需求

组件 规格 成本估算
麦克风阵列 4-8个 MEMS 麦克风 $2-5
音频 ADC 16kHz, 16bit $1-2
处理器 共用 QCS8255 NPU $0 (复用)
总成本 — $3-7/车

5.3 技术路线

阶段 时间 目标
Phase 1 2027 Q1 单人声学姿态(验证概念)
Phase 2 2027 Q4 多人声学+视觉融合 OOP
Phase 3 2028+ 声学+雷达+视觉三模态融合

6. 局限性分析

  1. 精度低于视觉:声学姿态精度约 80-100mm MPJPE,视觉方法可达 20-30mm
  2. 环境噪声敏感:发动机、路面、空调噪声影响声学信号
  3. 训练数据有限:AMP 数据集仅 6 小时,需更多场景覆盖
  4. 实时性待优化:论文未报告实时性能,需优化到 > 15fps

7. 关键洞察

  1. 声学是视觉的补充而非替代:在遮挡/隐私场景有价值
  2. 多人分离是核心突破:此前声学姿态仅限单人
  3. 成本极低:麦克风阵列 $3-7/车,远低于雷达和摄像头
  4. ECCV 2026 接收证明学术认可度,但工程化仍需验证
  5. 座舱场景天然适配:封闭空间、已知几何、有限人数 → 声学建模条件好

参考资料

  1. Oumi et al., “Sound-based Multi-Person 3D Pose Estimation”, ECCV 2026, arXiv:2609.04902
  2. 项目页: https://oumi03.github.io/sound-mhpe/
  3. AMP Dataset: 432K帧, 6小时, 2-3人/场景
  4. UniPi (2023): 首个声学单人姿态估计先驱工作
  5. Euro NCAP OOP (Out-of-Position) Detection Protocol, 2026+

https://dapalm.com/2026/09/27/2026-09-27-24-soundmhpe-acoustic-multiperson-3d-pose-eccv-2026-ims/
作者
Mars
发布于
2026年9月27日
许可协议