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
| import numpy as np
class RadarHeartRate: """ 77GHz mmWave雷达心率检测 原理: 雷达波穿透衣物,被胸壁反射, 通过相位变化检测胸壁微小位移(<0.5mm), 从位移信号中提取心跳和呼吸 优势: - 隐私友好(无图像) - 全天候(光照无关) - 穿透毛毯/衣物 77GHz vs 60GHz: - 波长更短(3.9mm vs 5mm)→灵敏度更高 - 分辨率更好→心跳/呼吸分离更清晰 """ def __init__(self, fs: int = 20): self.fs = fs def extract_vital_signs( self, radar_phase: np.ndarray, range_bin: int = None ) -> dict: """ 从雷达相位提取生命体征 Args: radar_phase: (N,) 相位序列(弧度) range_bin: 目标距离bin Returns: vitals: {'hr': float, 'rr': float, 'hrv': float} """ phase_unwrapped = np.unwrap(radar_phase) phase_detrend = phase_unwrapped - np.polyval( np.polyfit(np.arange(len(phase_unwrapped)), phase_unwrapped, 3), np.arange(len(phase_unwrapped)) ) from scipy.signal import butter, filtfilt b_r, a_r = butter(3, [0.1, 0.5], btype='band', fs=self.fs) resp = filtfilt(b_r, a_r, phase_detrend) b_h, a_h = butter(3, [0.7, 3.0], btype='band', fs=self.fs) cardiac = filtfilt(b_h, a_h, phase_detrend) from scipy.signal import welch freqs, psd = welch(cardiac, fs=self.fs, nperseg=self.fs*10) hr_idx = np.argmax(psd[(freqs >= 0.7) & (freqs <= 3.0)]) hr_hz = freqs[(freqs >= 0.7) & (freqs <= 3.0)][hr_idx] hr_bpm = hr_hz * 60 freqs_r, psd_r = welch(resp, fs=self.fs, nperseg=self.fs*10) rr_idx = np.argmax(psd_r[(freqs_r >= 0.1) & (freqs_r <= 0.5)]) rr_hz = freqs_r[(freqs_r >= 0.1) & (freqs_r <= 0.5)][rr_idx] rr_per_min = rr_hz * 60 hrv = np.std(np.diff(np.where(cardiac > np.mean(cardiac))[0])) / self.fs * 1000 return { 'hr_bpm': float(hr_bpm), 'rr_per_min': float(rr_per_min), 'hrv_ms': float(hrv), 'cardiac_signal': cardiac, 'resp_signal': resp }
if __name__ == "__main__": rng = np.random.default_rng(42) detector = RadarHeartRate(fs=20) t = np.arange(0, 60, 1/20) hr_signal = 0.1 * np.sin(2*np.pi*1.2*t) resp_signal = 1.0 * np.sin(2*np.pi*0.3*t) noise = 0.05 * rng.normal(size=len(t)) radar_phase = hr_signal + resp_signal + noise vitals = detector.extract_vital_signs(radar_phase) print("=== 77GHz雷达生命体征检测 ===") print(f"心率: {vitals['hr_bpm']:.1f} bpm (真值: 72.0)") print(f"呼吸率: {vitals['rr_per_min']:.1f} /min (真值: 18.0)") print(f"HRV: {vitals['hrv_ms']:.1f} ms")
|