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
| """ CPD 儿童遗留检测:120GHz FMCW 雷达信号处理流程 基于相位变化检测呼吸与心跳
硬件参数(参考 indie.inc 120GHz 模块): - 中心频率: 120 GHz - 带宽: 10 GHz - 距离分辨率: c/(2*B) = 3e8/(2*10e9) = 0.015m = 1.5cm - 帧率: 20 Hz - 功耗: < 10 mW(待机模式) """
import numpy as np from scipy.signal import butter, filtfilt, find_peaks
class CPD_Radar_Processor: """ 120GHz FMCW 雷达 CPD 检测处理器 数据流: ADC采样 → 距离FFT → 相位提取 → 呼吸带通滤波 → 心跳带通滤波 → 活体判定 → CPD报警 """ def __init__(self, config: dict = None): self.config = config or { 'sample_rate': 20, 'range_bins': 64, 'breath_low': 0.15, 'breath_high': 0.5, 'heart_low': 0.8, 'heart_high': 2.0, 'detection_window': 10, 'min_breath_amp': 0.3, 'min_heart_amp': 0.05, } self.breath_b, self.breath_a = butter( 2, [self.config['breath_low'], self.config['breath_high']], btype='band', fs=self.config['sample_rate'] ) self.heart_b, self.heart_a = butter( 2, [self.config['heart_low'], self.config['heart_high']], btype='band', fs=self.config['sample_rate'] ) def extract_phase(self, range_fft: np.ndarray) -> np.ndarray: """ 从距离FFT结果提取相位 Args: range_fft: shape=(N_frames, N_bins), complex Returns: phase: shape=(N_frames,) 目标距离门的相位序列 """ energy = np.abs(range_fft).mean(axis=0) target_bin = np.argmax(energy) phase = np.unwrap(np.angle(range_fft[:, target_bin])) wavelength = 2.5e-3 displacement = phase * wavelength / (4 * np.pi) return displacement def detect_vital_signs(self, displacement: np.ndarray) -> dict: """ 检测呼吸和心跳 Args: displacement: 位移序列 (米), shape=(N,) Returns: result: 包含呼吸率、心率、是否活体 """ breath_signal = filtfilt( self.breath_b, self.breath_a, displacement ) residual = displacement - breath_signal heart_signal = filtfilt( self.heart_b, self.heart_a, residual ) breath_freq = self._dominant_freq( breath_signal, self.config['sample_rate'] ) heart_freq = self._dominant_freq( heart_signal, self.config['sample_rate'] ) breath_amp = np.std(breath_signal) * 1e3 heart_amp = np.std(heart_signal) * 1e3 is_alive = ( breath_amp > self.config['min_breath_amp'] and heart_amp > self.config['min_heart_amp'] and self.config['breath_low'] <= breath_freq <= self.config['breath_high'] and self.config['heart_low'] <= heart_freq <= self.config['heart_high'] ) return { 'breath_rate_bpm': breath_freq * 60, 'heart_rate_bpm': heart_freq * 60, 'breath_amp_mm': breath_amp, 'heart_amp_mm': heart_amp, 'is_alive': is_alive, 'target_bin': np.argmax(np.abs( np.fft.fft(displacement) )) } def _dominant_freq(self, signal: np.ndarray, fs: float) -> float: """计算主频率""" if len(signal) < 4: return 0.0 fft = np.fft.rfft(signal) freqs = np.fft.rfftfreq(len(signal), 1/fs) return freqs[np.argmax(np.abs(fft))] def classify_occupant(self, vital_signs: dict, radar_cross_section: float) -> str: """ 基于生命体征 + RCS 分类乘员 Args: vital_signs: detect_vital_signs 输出 radar_cross_section: 目标RCS (dBsm) Returns: classification: 'adult' | 'child' | 'pet' | 'empty' | 'object' """ if not vital_signs['is_alive']: if radar_cross_section > -20: return 'object' return 'empty' hr = vital_signs['heart_rate_bpm'] if radar_cross_section > -10: return 'adult' elif radar_cross_section > -25: if hr > 80: return 'child' else: return 'pet' else: return 'child'
if __name__ == "__main__": processor = CPD_Radar_Processor() np.random.seed(42) n_frames = 200 n_bins = 64 t = np.linspace(0, 10, n_frames) breath = 0.001 * np.sin(2 * np.pi * 0.3 * t) heartbeat = 0.0002 * np.sin(2 * np.pi * 1.2 * t) noise = np.random.normal(0, 0.0001, n_frames) displacement = breath + heartbeat + noise phase = displacement * 4 * np.pi / 2.5e-3 range_data = np.zeros((n_frames, n_bins), dtype=complex) range_data[:, 32] = np.exp(1j * phase) disp = processor.extract_phase(range_data) result = processor.detect_vital_signs(disp) classification = processor.classify_occupant(result, -15) print("=== 120GHz CPD 检测结果 ===") print(f"呼吸率: {result['breath_rate_bpm']:.1f} BPM") print(f"心率: {result['heart_rate_bpm']:.1f} BPM") print(f"呼吸幅度: {result['breath_amp_mm']:.3f} mm") print(f"心跳幅度: {result['heart_amp_mm']:.3f} mm") print(f"活体检测: {'是' if result['is_alive'] else '否'}") print(f"分类结果: {classification}") print(f"\n检测窗口: {processor.config['detection_window']}s") print(f"距离分辨率: 1.5 cm (120GHz, 10GHz带宽)")
|