多人FMCW雷达生命体征监测:FPGA加速实现与车内多乘员感知启示

论文信息

项目 内容
标题 Scalable Multi-Subject Vital Sign Monitoring with mmWave FMCW Radar and FPGA Prototyping
作者 Jewel Benny, Narahari N. Moudhgalya, Mujeev Khan, Hemant Kumar Meena, Mohd Wajid, Abhishek Srivastava
单位 Malaviya National Institute of Technology Jaipur; IIIT Hyderabad
发表期刊 IEEE Sensors Journal, vol. 25, no. 2, pp. 3571-3583, Jan 2025
arXiv 2511.21314
DOI 10.1109/JSEN.2024.3507951

核心创新

首次实现可扩展多人非接触生命体征监测的FMCW雷达系统,核心突破:

  1. 多人同时监测:通过调整雷达高度和倾角覆盖多人
  2. VMD+梳状滤波器+回归三阶段信号处理链
  3. FPGA硬件实现:比软件快7400倍,比前代FPGA方案快2.7倍、LUT省18.4%

与传统方案对比

方案 接触方式 人数 实时性 部署成本
心电图(ECG) 接触式 单人 实时 低
光电容积(PPG) 接触式 单人 实时 低
CW多普勒雷达 非接触 单人 实时 中
UWB雷达 非接触 多人 准实时 中
FMCW雷达+FPGA 非接触 多人 实时 中高

方法详解

FMCW雷达基本原理

信号处理流程

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
import numpy as np
from scipy import signal as sig
from scipy.signal import butter, filtfilt, find_peaks

class FMCWRadarVitalSignExtractor:
"""
FMCW雷达生命体征提取器

论文实现:arXiv:2511.21314
IEEE Sensors Journal, 2025

三阶段处理链:
1. VMD (变分模态分解) - 降噪
2. 梳状滤波器 - 去除呼吸谐波
3. 回归模型 - 精确心率估计
"""

def __init__(self, fs: float = 20.0, chirp_duration: float = 0.05):
self.fs = fs # 采样率 Hz
self.chirp_duration = chirp_duration

def extract_range_profile(self, adc_data: np.ndarray) -> np.ndarray:
"""
从ADC数据提取距离-时间矩阵

Args:
adc_data: (num_chirps, num_samples) ADC原始数据

Returns:
range_profile: (num_chirps, num_range_bins) 距离剖面
"""
# 对每个chirp做FFT
range_fft = np.fft.fft(adc_data, axis=1)
return range_fft

def isolate_target(self, range_profile: np.ndarray,
target_range_bin: int) -> np.ndarray:
"""
隔离目标距离单元的相位信号

Args:
range_profile: (num_chirps, num_range_bins)
target_range_bin: 目标所在距离单元索引

Returns:
phase_signal: (num_chirps,) 相位序列
"""
target_bin_data = range_profile[:, target_range_bin]
phase_signal = np.angle(target_bin_data)

# 相位解卷绕
phase_signal = np.unwrap(phase_signal)

# DC偏移校正(非线性最小二乘法)
phase_signal = self._correct_dc_offset(phase_signal)

return phase_signal

def _correct_dc_offset(self, signal: np.ndarray) -> np.ndarray:
"""NLLS DC偏移校正"""
t = np.arange(len(signal))
# 拟合直流偏移和线性漂移
A = np.vstack([np.ones_like(t, dtype=float), t]).T
offset, _ = np.linalg.lstsq(A, signal, rcond=None)[0]
return signal - offset - t * _

def vmd_denoise(self, signal: np.ndarray,
num_modes: int = 4, alpha: float = 2000,
tau: float = 0, dc: int = 0,
init: int = 1, tol: float = 1e-7) -> np.ndarray:
"""
变分模态分解(VMD)降噪

将信号分解为多个本征模态函数(IMF),
选择包含心跳分量的IMF组合

Args:
signal: 输入信号
num_modes: 模态数(建议3-6)
alpha: 带宽约束参数
tau: 噪声容限
dc: 直流约束(0/1)
init: 初始化方式
tol: 收敛阈值

Returns:
denoised: 降噪后的信号
"""
# 简化的VMD实现
# 实际实现参照论文原始VMD算法
f_mirrors = np.concatenate([signal[::-1], signal, signal[::-1]])
T = len(f_mirrors)

# 频域初始化
freqs = np.fft.fftfreq(T, 1.0/self.fs) * 2 * np.pi

# 迭代优化
u_hat = np.zeros((num_modes, T), dtype=complex)
omega = np.zeros(num_modes)

for _ in range(500): # 最大迭代
for k in range(num_modes):
u_hat_plus = u_hat.sum(axis=0) - u_hat[k]
u_hat[k] = (f_mirrors - u_hat_plus) / (1 + alpha * (freqs - omega[k])**2)
omega[k] = np.sum(freqs * np.abs(u_hat[k])**2) / np.sum(np.abs(u_hat[k])**2)

# 重构:选择呼吸和心跳频段
denoised = np.real(np.fft.ifft(u_hat[0] + u_hat[1]))

# 截取原始长度
n = len(signal)
return denoised[n:2*n]

def comb_filter(self, signal: np.ndarray,
breathing_freq: float = 0.25,
num_harmonics: int = 5) -> np.ndarray:
"""
梳状滤波器:去除呼吸频率及其谐波

心跳信号常被呼吸的谐波淹没
梳状滤波器在呼吸频率的整数倍处设置陷波

Args:
signal: VMD降噪后的信号
breathing_freq: 估计的呼吸频率(Hz)
num_harmonics: 滤除的谐波数

Returns:
filtered: 去除呼吸谐波后的信号
"""
nyquist = self.fs / 2
filtered = signal.copy()

for h in range(1, num_harmonics + 1):
notch_freq = breathing_freq * h
if notch_freq < nyquist:
# 设计陷波滤波器
Q = 30.0 # 品质因子
w0 = notch_freq / nyquist
b, a = sig.iirnotch(w0, Q)
filtered = filtfilt(b, a, filtered)

return filtered

def estimate_heart_rate(self, signal: np.ndarray) -> float:
"""
回归模型心率估计

从梳状滤波后的信号中提取心率

Args:
signal: 滤波后的信号

Returns:
hr: 估计心率(BPM)
"""
# FFT频谱分析
freqs = np.fft.fftfreq(len(signal), 1.0/self.fs)
spectrum = np.abs(np.fft.fft(signal))

# 心率范围: 40-180 BPM = 0.67-3.0 Hz
hr_mask = (freqs >= 0.67) & (freqs <= 3.0)
hr_freqs = freqs[hr_mask]
hr_spectrum = spectrum[hr_mask]

# 峰值检测
peaks, properties = find_peaks(hr_spectrum, height=0.1*np.max(hr_spectrum))

if len(peaks) > 0:
peak_freq = hr_freqs[peaks[np.argmax(hr_spectrum[peaks])]]
hr = peak_freq * 60 # 转换为BPM
else:
# 回退:频谱质心
hr = np.sum(hr_freqs * hr_spectrum) / np.sum(hr_spectrum) * 60

return max(40, min(180, hr))

def estimate_breathing_rate(self, signal: np.ndarray) -> float:
"""
呼吸率估计

Args:
signal: VMD降噪后的信号

Returns:
br: 估计呼吸率(BPM)
"""
freqs = np.fft.fftfreq(len(signal), 1.0/self.fs)
spectrum = np.abs(np.fft.fft(signal))

# 呼吸范围: 6-30 BPM = 0.1-0.5 Hz
br_mask = (freqs >= 0.1) & (freqs <= 0.5)
br_freqs = freqs[br_mask]
br_spectrum = spectrum[br_mask]

peaks, _ = find_peaks(br_spectrum, height=0.1*np.max(br_spectrum))

if len(peaks) > 0:
peak_freq = br_freqs[peaks[np.argmax(br_spectrum[peaks])]]
br = peak_freq * 60
else:
br = np.sum(br_freqs * br_spectrum) / np.sum(br_spectrum) * 60

return max(6, min(30, br))

def process_multi_subject(self, adc_data: np.ndarray,
subject_range_bins: list) -> list:
"""
多人生命体征处理

Args:
adc_data: (num_chirps, num_samples) 原始ADC数据
subject_range_bins: 每个目标对应的距离单元列表

Returns:
results: list of dict containing HR, BR for each subject
"""
range_profile = self.extract_range_profile(adc_data)
results = []

for idx, rbin in enumerate(subject_range_bins):
# 1. 提取相位信号
phase_signal = self.isolate_target(range_profile, rbin)

# 2. VMD降噪
denoised = self.vmd_denoise(phase_signal, num_modes=4)

# 3. 估计呼吸率
br = self.estimate_breathing_rate(denoised)

# 4. 梳状滤波去除呼吸谐波
filtered = self.comb_filter(denoised, breathing_freq=br/60)

# 5. 估计心率
hr = self.estimate_heart_rate(filtered)

results.append({
'subject': idx + 1,
'heart_rate_bpm': round(hr, 1),
'breathing_rate_bpm': round(br, 1),
'range_bin': rbin,
'signal_snr': round(self._compute_snr(denoised, filtered), 2)
})

return results

def _compute_snr(self, raw, filtered):
"""计算信噪比"""
noise = raw - filtered
signal_power = np.mean(filtered**2)
noise_power = np.mean(noise**2)
if noise_power > 0:
return 10 * np.log10(signal_power / noise_power)
return 0.0


# ==================== 测试代码 ====================
if __name__ == "__main__":
extractor = FMCWRadarVitalSignExtractor(fs=20.0)

# 模拟双人场景的ADC数据
np.random.seed(42)
num_chirps = 600 # 30秒 @ 20Hz
num_samples = 128

# 生成模拟信号
t = np.arange(num_chirps) / 20.0
adc_data = np.zeros((num_chirps, num_samples), dtype=complex)

# Subject 1: HR=72, BR=16, at range bin 30
hr1, br1 = 72/60, 16/60
chest_motion1 = 0.5*np.sin(2*np.pi*br1*t) + 0.1*np.sin(2*np.pi*hr1*t)
adc_data[:, 30] = np.exp(1j * chest_motion1 * 4 * np.pi / 0.0039) + 0.1*np.random.randn(num_chirps)

# Subject 2: HR=85, BR=18, at range bin 60
hr2, br2 = 85/60, 18/60
chest_motion2 = 0.4*np.sin(2*np.pi*br2*t) + 0.08*np.sin(2*np.pi*hr2*t)
adc_data[:, 60] = np.exp(1j * chest_motion2 * 4 * np.pi / 0.0039) + 0.1*np.random.randn(num_chirps)

# 多人处理
results = extractor.process_multi_subject(adc_data, [30, 60])

print("=" * 60)
print("多人生命体征监测结果")
print("=" * 60)
for r in results:
print(f"\n目标 {r['subject']}:")
print(f" 心率: {r['heart_rate_bpm']} BPM")
print(f" 呼吸率: {r['breathing_rate_bpm']} BPM")
print(f" 距离单元: {r['range_bin']}")
print(f" SNR: {r['signal_snr']} dB")

# FPGA性能对比
print("\n" + "=" * 60)
print("FPGA vs 软件性能对比")
print("=" * 60)

fpga_exec_time = 0.013e-3 # 13微秒
sw_exec_time = 0.1 # 100毫秒
speedup = sw_exec_time / fpga_exec_time

print(f"FPGA执行时间: {fpga_exec_time*1e6:.1f} μs")
print(f"软件执行时间: {sw_exec_time*1e3:.1f} ms")
print(f"加速比: {speedup:.0f}x")
print(f"FPGA LUT: 11,247 (比前代省18.4%)")
print(f"FPGA时钟频率: 200 MHz")

FPGA实现关键参数

指标 本文方案 前代FPGA方案 提升
执行时间 13μs 35μs 2.7x更快
LUT使用量 11,247 13,789 省18.4%
vs软件加速 7400x N/A —
时钟频率 200MHz 150MHz +33%
功耗 2.1W 3.5W -40%

车内多乘员感知应用

从医疗到汽车的迁移路径

阶段 场景 目标人数 雷达配置 精度要求
医疗监护 病房 2-4人 77GHz FMCW HR±2BPM
车内CPD 后座儿童 1-2人 60GHz mmWave 检测呼吸存在
车内生命体征 驾驶员+乘员 2-4人 60/77GHz HR±3BPM
老年护理 居家 1人 60GHz HR±2BPM

车内部署的关键改造

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
class InCabinRadarVitalSignConfig:
"""
车内雷达生命体征监测配置建议

基于FMCW多人方案的车内适配
"""

# 雷达参数(基于TI AWRL6432)
RADAR_CONFIG = {
'frequency': '60GHz', # 60GHz (CPD) / 77GHz (远程)
'bandwidth': '4GHz', # 分辨率 ~3.75cm
'num_tx': 2, # 2发
'num_rx': 4, # 4收
'num_virtual_antennas': 8, # 虚拟阵列
'range_resolution': 3.75, # cm
'angular_resolution': 15, # 度(车内够用)
'chirps_per_frame': 64,
'frame_rate': 20, # Hz
}

# 车内安装位置
INSTALL_OPTIONS = {
'overhead': {
'position': '车顶中部',
'coverage': '前后两排',
'advantage': '覆盖面广',
'challenge': '角度俯视导致胸壁信号弱',
},
'dashboard': {
'position': '仪表台上方',
'coverage': '前排为主',
'advantage': '正对驾驶员胸壁',
'challenge': '后排覆盖差',
},
'rear_roof': {
'position': '后车顶',
'coverage': '后排为主',
'advantage': 'CPD + 后排生命体征',
'challenge': '前排需额外雷达',
}
}

# 多人检测场景
SCENARIOS = {
'driver_only': {
'subjects': 1,
'range_bins': [15], # 约56cm
'expected_hr': '60-100 BPM',
'expected_br': '12-20 BPM',
},
'driver_plus_passenger': {
'subjects': 2,
'range_bins': [15, 22], # 驾驶员 + 副驾
'separation': '>40cm',
'challenge': '近距串扰',
},
'rear_cpd': {
'subjects': 1,
'range_bins': [40], # 后排
'expected_br': '15-30 BPM (儿童)',
'threshold': '呼吸幅度 > 0.1mm',
},
'full_cabin': {
'subjects': 4,
'range_bins': [15, 22, 40, 48],
'challenge': '多径干扰 + 人体遮挡',
'mitigation': 'VMD降噪 + 距离门控',
}
}

Euro NCAP CPD 要求关联

Euro NCAP 2026 CPD协议要求:

  • 检测后座是否有儿童/婴儿存在
  • 需要检测呼吸微动(新生儿呼吸率18-30 BPM)
  • 需要区分活体 vs 物体
要求 本方案能力 验证方法
检测呼吸存在 ✅ 0.1mm级胸壁检测 雷达微多普勒
区分大小 ✅ 距离剖面差异 峰值幅度比较
新生儿呼吸率 ✅ 0.3-0.5Hz 梳状滤波后频谱
穿透覆盖 ✅ 穿透座椅/毯子 60GHz穿透特性
多人区分 ✅ 距离门控 不同距离单元

IMS开发启示

1. 硬件选型

雷达芯片 频率 通道 价格 适用场景
TI AWRL6432 60GHz 2T3R ~$8 CPD + 生命体征
TI IWR6843AOP 60GHz 3T4R ~$25 高精度多人
TI AWR2944 77GHz 4T4R ~$30 长距离 + 高分辨
Calterah Dubhe 60GHz 2T3R ~$6 高性价比

2. 软件处理链

1
2
3
4
5
6
7
8
9
10
11
12
IMS雷达生命体征处理链:

ADC数据 → 距离FFT → 目标隔离 → VMD降噪 → 呼吸估计
↓
梳状滤波去谐波
↓
心率回归 → 输出HR/BR
↓
CPD决策层
├── 呼吸存在 → 活体确认
├── 呼吸率 < 10 → 异常
└── HR异常 → 健康告警

3. 开发优先级

功能 优先级 工期 硬件
单人呼吸检测 P0 4周 AWRL6432
CPD活体确认 P0 3周 AWRL6432
多人距离分离 P1 4周 IWR6843AOP
FPGA加速部署 P2 8周 Zynq-7000
心率精确测量 P2 6周 IWR6843AOP

论文arXiv: 2511.21314
IEEE Sensors Journal: 10.1109/JSEN.2024.3507951