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
| """ UWB CPD 信号处理算法
核心: 从 Channel Impulse Response (CIR) 中提取微动呼吸信号 CIR 可视为车内3D地形图的时间切片
数据流: 原始 UWB 回波 → CIR 序列 → 静态背景消除 → 微动信号提取 → 频域分析 → 生命体征判定 """
import numpy as np from scipy.signal import butter, filtfilt, fft
class UWBCPDProcessor: """ UWB CPD 信号处理器 按 Euro NCAP 要求: - 响应时间 < 13 秒 - 检测率 > 99% - 误报率 < 1% """ INFANT_BREATHING_RANGE = (20, 40) ADULT_BREATHING_RANGE = (12, 20) def __init__(self, fs=100, n_samples=512): """ Args: fs: CIR 采样率 (Hz), 每秒采集 100 个 CIR 快照 n_samples: 每个 CIR 快照的采样点数 """ self.fs = fs self.n_samples = n_samples self.background = None self.background_count = 0 def update_background(self, cir_snapshot): """更新静态背景模型""" if self.background is None: self.background = cir_snapshot.copy() self.background_count = 1 else: alpha = 0.95 self.background = alpha * self.background + (1 - alpha) * cir_snapshot self.background_count += 1 def extract_micro_motion(self, cir_sequence): """ 从 CIR 序列中提取微动信号 Args: cir_sequence: (n_frames, n_samples) CIR 时间序列 Returns: motion_signal: (n_frames,) 微动强度序列 """ if self.background is not None: diff = cir_sequence - self.background else: diff = cir_sequence - np.mean(cir_sequence, axis=0) motion_signal = np.sum(np.abs(diff), axis=1) motion_signal = self._bandpass(motion_signal, 0.1, 1.5) return motion_signal def detect_breathing(self, motion_signal, duration_sec=10): """ 检测呼吸信号 Args: motion_signal: 微动信号序列 duration_sec: 分析窗口长度 Returns: result: dict with detection info """ window_samples = int(duration_sec * self.fs) if len(motion_signal) < window_samples: return {'detected': False, 'reason': 'insufficient_data'} signal = motion_signal[-window_samples:] freqs, power = self._compute_spectrum(signal) infant_mask = ( (freqs >= self.INFANT_BREATHING_RANGE[0] / 60) & (freqs <= self.INFANT_BREATHING_RANGE[1] / 60) ) adult_mask = ( (freqs >= self.ADULT_BREATHING_RANGE[0] / 60) & (freqs <= self.ADULT_BREATHING_RANGE[1] / 60) ) infant_power = np.sum(power[infant_mask]) adult_power = np.sum(power[adult_mask]) total_power = np.sum(power) + 1e-10 snr = (infant_power + adult_power) / total_power if np.any(infant_mask) and infant_power > 0: peak_freq = freqs[infant_mask][np.argmax(power[infant_mask])] peak_bpm = peak_freq * 60 else: peak_bpm = 0 result = { 'detected': snr > 0.3, 'snr': snr, 'peak_bpm': peak_bpm, 'infant_power': infant_power, 'adult_power': adult_power, 'duration_analyzed': duration_sec } return result def _bandpass(self, signal, low, high): """带通滤波""" nyq = self.fs / 2 b, a = butter(4, [low / nyq, high / nyq], btype='band') return filtfilt(b, a, signal) def _compute_spectrum(self, signal): """计算功率谱""" n = len(signal) freqs = np.fft.rfftfreq(n, 1 / self.fs) fft_vals = np.fft.rfft(signal * np.hanning(n)) power = np.abs(fft_vals) ** 2 return freqs, power
def simulate_cpd_scenario(): """模拟 CPD 检测场景""" fs = 100 duration = 15 n_samples = 512 processor = UWBCPDProcessor(fs=fs, n_samples=n_samples) print("=== 场景1: 空车 ===") noise = np.random.normal(0, 0.01, (duration * fs, n_samples)) motion = processor.extract_micro_motion(noise) result = processor.detect_breathing(motion, duration_sec=10) print(f"检测到生命: {result['detected']}") print(f"SNR: {result['snr']:.4f}") print("\n=== 场景2: 婴儿呼吸 ===") breathing_freq = 30 / 60 t = np.arange(duration * fs) / fs breathing_signal = 0.05 * np.sin(2 * np.pi * breathing_freq * t) cir_with_breathing = noise.copy() target_bin = 200 cir_with_breathing[:, target_bin] += breathing_signal motion = processor.extract_micro_motion(cir_with_breathing) result = processor.detect_breathing(motion, duration_sec=10) print(f"检测到生命: {result['detected']}") print(f"SNR: {result['snr']:.4f}") print(f"主频: {result['peak_bpm']:.1f} BPM") print("\n=== 场景3: 毯子覆盖婴儿 ===") attenuated_signal = breathing_signal * 0.3 cir_covered = noise.copy() cir_covered[:, target_bin] += attenuated_signal motion = processor.extract_micro_motion(cir_covered) result = processor.detect_breathing(motion, duration_sec=10) print(f"检测到生命: {result['detected']}") print(f"SNR: {result['snr']:.4f}") print(f"主频: {result['peak_bpm']:.1f} BPM")
if __name__ == "__main__": simulate_cpd_scenario()
|