SpiRadar:非接触雷达肺活量测定——从儿童哮喘到座舱呼吸监测的技术跨越

论文标题: SpiRadar: Radar-Based Non-Contact Spirometry via Sparse Polynomial Framework
作者: Yonatan Eder, Yhonatan Kvich 等(Weizmann Institute + Schneider Children’s Medical Center)
发布时间: 2026年7月
arXiv: 2607.19798
机构: 魏茨曼科学研究所、Schneider 儿童医学中心
测试: 39 名儿童(6-18岁),58 次肺活量测试,留一交叉验证


核心创新

SpiRadar 首次实现了无接触雷达肺活量测定,通过 mmWave FMCW 雷达测量胸壁位移,经稀疏多项式模型转换为肺活量曲线:

  1. 无需口鼻接触:消除传统肺活量计的咬嘴和鼻夹
  2. 跨被试泛化:稀疏优化实现无需个体校准
  3. 临床级精度:RMSE 0.23±0.12L,BDR 分类准确率 89.5%
  4. 儿童验证:首个在儿科群体(6-18岁)验证的非接触肺活量方案

1. 为什么需要非接触肺活量测定?

1.1 传统肺活量测定的痛点

问题 影响 儿科尤其严重
需要咬嘴 + 鼻夹 不适感 儿童抗拒
需要用力配合 技术要求高 5岁以下不可靠
需要多次尝试 测试耗时长 患儿疲劳
接触式 交叉感染风险 COVID 后更加关注
专业人员操作 无法居家监测 哮喘长期管理困难

1.2 雷达方案的原理


2. 方法详解

2.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
import torch
import torch.nn as nn
import numpy as np
from typing import Tuple

class PhysiologicalPreprocessing(nn.Module):
"""
SpiRadar 预处理模块

论文 Section III-A
解决强制呼气动作中的非自主运动伪影

关键问题:
- 强制呼气(FEV1 maneuver)产生大幅躯干运动
- 非自主运动叠加在呼吸信号上
- 需要区分胸腔呼吸运动与整体躯干位移
"""
def __init__(self, fs=200, cutoff_freq=5.0):
super().__init__()
self.fs = fs # 雷达采样率

# 带通滤波:呼吸频段 0.1-5 Hz
from scipy.signal import butter, filtfilt
self.b_low, self.a_low = butter(4, cutoff_freq / (fs / 2), btype='low')

def forward(self, raw_phase: torch.Tensor) -> Tuple[torch.Tensor, torch.Tensor]:
"""
Args:
raw_phase: 雷达回波相位 (B, T) - 胸壁位移原始信号
Returns:
respiratory_signal: 呼吸信号 (B, T)
motion_artifact: 运动伪影 (B, T)
"""
# 转为 numpy 处理(实际部署用 torch 实现)
phase_np = raw_phase.cpu().numpy()

results = []
artifacts = []
for i in range(phase_np.shape[0]):
signal = phase_np[i]

# 1. 对数插值校正
# 强制呼气时可能有相位跳变
signal = self._log_interpolation_correct(signal)

# 2. 低通滤波提取呼吸分量
resp = filtfilt(self.b_low, self.a_low, signal)

# 3. 运动伪影 = 原始 - 呼吸
artifact = signal - resp

results.append(resp)
artifacts.append(artifact)

return (torch.from_numpy(np.array(results)).to(raw_phase.device),
torch.from_numpy(np.array(artifacts)).to(raw_phase.device))

def _log_interpolation_correct(self, signal):
"""对数插值校正相位跳变"""
# 检测大幅跳变
diff = np.diff(signal)
jumps = np.where(np.abs(diff) > np.pi)[0]

for j in jumps:
# 线性插值修复
if j > 0 and j < len(signal) - 1:
signal[j] = (signal[j-1] + signal[j+1]) / 2

return signal


class SparsePolynomialTransform(nn.Module):
"""
稀疏多项式变换模型

论文 Section III-B
将雷达测量的胸壁位移转换为肺活量体积曲线

核心创新:
1. 特征依赖的多项式(不同呼吸阶段使用不同系数)
2. 稀疏优化(L1 正则化)实现跨被试泛化
3. 无需个体校准
"""
def __init__(self, poly_order=5, num_features=8):
super().__init__()
self.poly_order = poly_order
self.num_features = num_features

# 特征提取器
self.feature_extractor = nn.Sequential(
nn.Conv1d(1, 32, kernel_size=5, padding=2),
nn.ReLU(),
nn.MaxPool1d(2),
nn.Conv1d(32, 64, kernel_size=5, padding=2),
nn.ReLU(),
nn.AdaptiveAvgPool1d(1)
)

# 特征 -> 多项式系数
# 每个特征组合产生一组多项式系数
self.coeff_predictor = nn.Sequential(
nn.Linear(64, 128),
nn.ReLU(),
nn.Linear(128, (poly_order + 1) * num_features)
)

# 稀疏门控(L1 正则化效果)
self.sparse_gate = nn.Sequential(
nn.Linear(64, num_features),
nn.Sigmoid()
)

def forward(self, displacement: torch.Tensor) -> torch.Tensor:
"""
Args:
displacement: 胸壁位移序列 (B, T)
Returns:
volume: 肺活量体积曲线 (B, T)
"""
B, T = displacement.shape

# 提取时序特征
x = displacement.unsqueeze(1) # (B, 1, T)
feat = self.feature_extractor(x) # (B, 64, 1)
feat = feat.flatten(1) # (B, 64)

# 预测多项式系数
coeffs = self.coeff_predictor(feat) # (B, (poly_order+1) * num_features)
coeffs = coeffs.view(B, self.num_features, self.poly_order + 1)

# 稀疏门控
gate = self.sparse_gate(feat) # (B, num_features)

# 加权多项式系数
weighted_coeffs = coeffs * gate.unsqueeze(-1) # (B, num_features, poly_order+1)
final_coeffs = weighted_coeffs.sum(dim=1) # (B, poly_order+1)

# 构建多项式
volume = torch.zeros_like(displacement)
for k in range(self.poly_order + 1):
volume = volume + final_coeffs[:, k:k+1] * displacement ** k

return volume


class SpiRadarModel(nn.Module):
"""
SpiRadar: 完整的非接触雷达肺活量测定模型

论文:arXiv:2607.19798
应用:座舱呼吸监测、哮喘管理、COPD 监测
"""
def __init__(self):
super().__init__()
self.preprocessing = PhysiologicalPreprocessing(fs=200)
self.transform = SparsePolynomialTransform(poly_order=5, num_features=8)

def forward(self, radar_phase: torch.Tensor) -> dict:
"""
Args:
radar_phase: 雷达回波相位 (B, T)
Returns:
results: dict with volume, breathing_rate, clinical_params
"""
# 预处理
resp_signal, motion = self.preprocessing(radar_phase)

# 转换为体积曲线
volume = self.transform(resp_signal)

# 计算临床参数
pev1 = volume[:, :int(0.1 * volume.shape[1])].max(dim=1)[0] # 简化
fvc = volume.max(dim=1)[0]
pef = volume[:, :int(0.05 * volume.shape[1])].max(dim=1)[0]

return {
'volume_curve': volume,
'breathing_signal': resp_signal,
'motion_artifact': motion,
'FEV1': pev1,
'FVC': fvc,
'PEF': pef
}


# ==================== 测试 ====================

if __name__ == "__main__":
print("=" * 60)
print("SpiRadar 非接触雷达肺活量测定测试")
print("=" * 60)

model = SpiRadarModel()

# 模拟雷达回波相位(呼吸信号 + 运动伪影)
T = 2000 # 10秒 @ 200Hz
t = np.linspace(0, 10, T)
breathing = 0.5 * np.sin(2 * np.pi * 0.3 * t) # 呼吸 0.3 Hz
motion = 0.2 * np.random.randn(T) # 随机运动
radar_phase = torch.tensor(breathing + motion, dtype=torch.float32).unsqueeze(0)

results = model(radar_phase)

print(f"输入: {radar_phase.shape} (B, T)")
print(f"体积曲线: {results['volume_curve'].shape}")
print(f"FEV1: {results['FEV1'].item():.3f} L")
print(f"FVC: {results['FVC'].item():.3f} L")
print(f"PEF: {results['PEF'].item():.3f} L/s")

total_params = sum(p.numel() for p in model.parameters())
print(f"\n模型参数量: {total_params / 1e3:.1f}K")

3. 实验结果

3.1 临床验证

指标 SpiRadar 传统肺活量计 差异
曲线 RMSE (L) 0.23±0.12 — —
FEV1 相关系数 r >0.84 基准 —
FVC 相关系数 r >0.84 基准 —
BDR 分类准确率 89.5% 90%+ 接近
BDR 敏感度 90.9% — —
BDR 特异度 87.5% — —

3.2 测试队列

类别 被试数 测试次数 年龄
健康儿童 15 20 6-18
哮喘(BD前) 12 19 6-18
哮喘(BD后) 12 19 6-18
总计 39 58 6-18

4. IMS 开发启示

4.1 座舱呼吸监测的直接应用

SpiRadar 的技术可直接应用于座舱:

应用场景 雷达位置 测量目标 座舱价值
驾驶员呼吸率 A柱/方向盘 胸壁位移 疲劳/压力检测
乘员呼吸异常 车顶/座椅靠背 呼吸模式 突发疾病检测
CPD 婴儿呼吸 后排座椅 微动呼吸 CPD 核心检测
哮喘驾驶员监控 方向盘 呼吸曲线 健康管理

4.2 从呼吸到肺活量:座舱升级路径

4.3 与现有 DMS 的集成

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
class CabinBreathingMonitoring:
"""座舱呼吸监测集成方案"""

def __init__(self):
self.integration_points = {
'steering_wheel_radar': {
'sensor': '60GHz FMCW 嵌入方向盘',
'target': '驾驶员胸壁',
'distance': '40-60cm',
'application': '呼吸率 + 疲劳检测',
'spiRadar_applicability': 'Level 1-2'
},
'roof_radar': {
'sensor': '60GHz FMCW 车顶',
'target': '全车乘员',
'distance': '1-2m',
'application': 'CPD + 乘员呼吸',
'spiRadar_applicability': 'Level 1'
},
'seat_radar': {
'sensor': '60GHz 嵌入座椅靠背',
'target': '后排乘员',
'distance': '5-20cm',
'application': 'CPD 婴儿呼吸',
'spiRadar_applicability': 'Level 1-3 (近距离最佳)'
}
}

def get_deployment_priority(self):
return {
'P0': '方向盘雷达呼吸率(疲劳检测增强)',
'P1': '座椅雷达 CPD 婴儿呼吸(ADAC 痛点)',
'P2': 'SpiRadar 肺活量模型(哮喘驾驶员管理)',
'P3': '后排乘员呼吸异常检测(突发疾病)'
}

4.4 解决 ADAC 发现的 CPD 盲区

SpiRadar 的稀疏多项式模型可能解决 ADAC 报告的 ISOFIX 婴儿座椅检测失败问题:

ADAC 问题 SpiRadar 方案 预期改进
婴儿呼吸幅度 <2mm FMCW 相位检测精度可达 0.1mm 20倍改善
婴儿座椅遮挡 60GHz 穿透织物(10-20dB 衰减) 可接受
ISOFIX 金属干扰 稀疏多项式可学习干扰模式 模型自适应
跨被试泛化 LOOCV 已验证 39 名儿童 直接可用

5. 与其他非接触呼吸方案对比

方案 精度 成本 隐私 限制
SpiRadar (mmWave) 临床级 中 ✅ 需 60GHz 模块
摄像头 rPPG 中 低 ❌ 光照依赖
Wi-Fi CSI 低 极低 ✅ 精度不足
UWB (NXP) 低 低 ✅ 分辨率不足(ADAC 失败)
压力带 高 低 ✅ 需接触
座椅 EMG 中 低 ✅ 仅呼吸率

6. 局限与改进

  1. 强制呼气动作:SpiRadar 需要被试执行强制呼气,座舱中驾驶员无法配合
  2. 静止假设:驾驶场景有振动干扰,需进一步处理
  3. 距离限制:60GHz 在 1-2m 距离可用,更远需更大功率
  4. 多人场景:当前仅单人,座舱需多人

改进方向: 自由呼吸模式扩展 + 振动滤波 + 多人波束成形


论文信息

  • arXiv: 2607.19798
  • 机构: 魏茨曼科学研究所、Schneider 儿童医学中心
  • 资助: ERC Horizon 2020 (grant 101000967)、Israel Science Foundation (grant 536/22)
  • 发表: Nature Communications 级别(参考文献质量暗示)