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
| import numpy as np from scipy.signal import butter, filtfilt, find_peaks
class VitalSignsExtractor: """ 从雷达信号提取生命体征 原理: - 呼吸:胸部周期性运动,频率0.1-0.5Hz - 心跳:心脏跳动引起的微弱胸部振动,频率0.8-2.0Hz 60GHz雷达优势: - 波长5mm,可检测毫米级运动 - 高分辨率区分呼吸和心跳 """ def __init__(self, sample_rate_hz: float = 100.0): self.sample_rate = sample_rate_hz self.breath_band = (0.1, 0.5) self.heart_band = (0.8, 2.0) def extract(self, radar_signal: np.ndarray) -> dict: """ 提取生命体征 Args: radar_signal: 雷达相位信号 (N,) Returns: { 'breathing_rate': 呼吸频率 (次/分钟), 'heart_rate': 心跳频率 (次/分钟), 'signal_quality': 信号质量 (0-1) } """ breath_signal = self._bandpass_filter( radar_signal, self.breath_band[0], self.breath_band[1] ) heart_signal = self._bandpass_filter( radar_signal, self.heart_band[0], self.heart_band[1] ) breath_freq = self._find_dominant_freq(breath_signal) heart_freq = self._find_dominant_freq(heart_signal) breathing_rate = breath_freq * 60 heart_rate = heart_freq * 60 signal_quality = self._estimate_signal_quality(radar_signal, breath_signal, heart_signal) return { 'breathing_rate': breathing_rate, 'heart_rate': heart_rate, 'signal_quality': signal_quality } def _bandpass_filter(self, signal: np.ndarray, low_cut: float, high_cut: float) -> np.ndarray: """带通滤波""" nyq = 0.5 * self.sample_rate low = low_cut / nyq high = high_cut / nyq b, a = butter(4, [low, high], btype='band') return filtfilt(b, a, signal) def _find_dominant_freq(self, signal: np.ndarray) -> float: """寻找主频率""" n = len(signal) freq = np.fft.rfftfreq(n, 1/self.sample_rate) spectrum = np.abs(np.fft.rfft(signal)) peaks, _ = find_peaks(spectrum) if len(peaks) > 0: peak_idx = peaks[np.argmax(spectrum[peaks])] return freq[peak_idx] return 0.0 def _estimate_signal_quality(self, raw: np.ndarray, breath: np.ndarray, heart: np.ndarray) -> float: """估计信号质量""" breath_power = np.var(breath) heart_power = np.var(heart) noise = self._bandpass_filter(raw, 5.0, 20.0) noise_power = np.var(noise) signal_power = breath_power + heart_power snr = signal_power / (noise_power + 1e-8) quality = np.clip(np.log10(snr + 1) / 2, 0, 1) return quality
if __name__ == "__main__": extractor = VitalSignsExtractor() t = np.arange(0, 10, 0.01) breath_signal = 0.5 * np.sin(2 * np.pi * 0.25 * t) heart_signal = 0.05 * np.sin(2 * np.pi * 1.2 * t) noise = 0.01 * np.random.randn(len(t)) radar_signal = breath_signal + heart_signal + noise vitals = extractor.extract(radar_signal) print(f"呼吸频率: {vitals['breathing_rate']:.1f} 次/分钟") print(f"心跳频率: {vitals['heart_rate']:.1f} 次/分钟") print(f"信号质量: {vitals['signal_quality']:.2f}")
|