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
| import numpy as np from typing import Tuple
class VitalSignExtractor: """ mmWave雷达生命体征提取器 基于微多普勒效应提取呼吸和心跳信号 """ def __init__(self, sample_rate: float = 1000.0, breath_freq_range: Tuple[float, float] = (0.1, 0.5), heart_freq_range: Tuple[float, float] = (0.8, 2.0)): """ Args: sample_rate: 雷达采样率(Hz) breath_freq_range: 呼吸频率范围(Hz) heart_freq_range: 心跳频率范围(Hz) """ self.sample_rate = sample_rate self.breath_freq_range = breath_freq_range self.heart_freq_range = heart_freq_range def extract_vital_signs(self, range_doppler_map: np.ndarray, range_bin: int, window_sec: float = 10.0) -> dict: """ 提取生命体征 Args: range_doppler_map: 距离-多普勒图(时间×距离×多普勒) range_bin: 目标距离单元 window_sec: 分析窗口(秒) Returns: vital_signs: 呼吸率、心率、置信度 """ window_samples = int(window_sec * self.sample_rate) phase_series = np.angle(range_doppler_map[-window_samples:, range_bin, :]) phase_unwrapped = np.unwrap(phase_series, axis=0) doppler_var = np.var(phase_unwrapped, axis=0) best_doppler = np.argmax(doppler_var) phase_signal = phase_unwrapped[:, best_doppler] fft_result = np.fft.fft(phase_signal) freqs = np.fft.fftfreq(len(phase_signal), 1/self.sample_rate) breath_mask = (np.abs(freqs) >= self.breath_freq_range[0]) & \ (np.abs(freqs) <= self.breath_freq_range[1]) breath_power = np.sum(np.abs(fft_result[breath_mask])**2) positive_freqs = freqs[:len(freqs)//2] positive_power = np.abs(fft_result[:len(freqs)//2]) breath_band_power = positive_power.copy() breath_band_power[~breath_mask[:len(freqs)//2]] = 0 breath_freq = positive_freqs[np.argmax(breath_band_power)] breath_rate = breath_freq * 60 heart_mask = (np.abs(freqs) >= self.heart_freq_range[0]) & \ (np.abs(freqs) <= self.heart_freq_range[1]) heart_power = np.sum(np.abs(fft_result[heart_mask])**2) heart_band_power = positive_power.copy() heart_band_power[~heart_mask[:len(freqs)//2]] = 0 heart_freq = positive_freqs[np.argmax(heart_band_power)] heart_rate = heart_freq * 60 total_power = np.sum(np.abs(fft_result)**2) breath_confidence = breath_power / total_power heart_confidence = heart_power / total_power return { "breath_rate": float(breath_rate), "heart_rate": float(heart_rate), "breath_confidence": float(breath_confidence), "heart_confidence": float(heart_confidence), "phase_signal": phase_signal, "freq_spectrum": { "freqs": freqs, "power": np.abs(fft_result) } } def detect_child_presence(self, vital_signs: dict, threshold: dict = None) -> dict: """ 检测儿童存在 Args: vital_signs: 生命体征数据 threshold: 检测阈值 Returns: detection_result: 检测结果 """ if threshold is None: threshold = { "breath_confidence": 0.1, "heart_confidence": 0.05 } has_breath = vital_signs["breath_confidence"] > threshold["breath_confidence"] has_heart = vital_signs["heart_confidence"] > threshold["heart_confidence"] breath_valid = 10 <= vital_signs["breath_rate"] <= 40 heart_valid = 80 <= vital_signs["heart_rate"] <= 160 is_child_present = has_breath and has_heart and breath_valid and heart_valid return { "is_child_present": is_child_present, "confidence": min(vital_signs["breath_confidence"], vital_signs["heart_confidence"]), "breath_rate": vital_signs["breath_rate"], "heart_rate": vital_signs["heart_rate"], "breath_valid": breath_valid, "heart_valid": heart_valid }
if __name__ == "__main__": np.random.seed(42) sample_rate = 1000 duration = 30 n_range_bins = 100 n_doppler_bins = 64 t = np.linspace(0, duration, sample_rate * duration) breath_signal = 0.5 * np.sin(2 * np.pi * 0.25 * t) heart_signal = 0.2 * np.sin(2 * np.pi * 1.2 * t) phase_signal = breath_signal + heart_signal + np.random.normal(0, 0.05, len(t)) range_doppler_map = np.zeros((len(t), n_range_bins, n_doppler_bins), dtype=complex) range_doppler_map[:, 50, 32] = np.exp(1j * phase_signal) extractor = VitalSignExtractor(sample_rate=sample_rate) vital_signs = extractor.extract_vital_signs(range_doppler_map, range_bin=50) print(f"呼吸率: {vital_signs['breath_rate']:.1f} breaths/min") print(f"心率: {vital_signs['heart_rate']:.1f} beats/min") print(f"呼吸置信度: {vital_signs['breath_confidence']:.3f}") print(f"心率置信度: {vital_signs['heart_confidence']:.3f}") result = extractor.detect_child_presence(vital_signs) print(f"\n儿童存在检测: {result['is_child_present']}") print(f"综合置信度: {result['confidence']:.3f}")
|