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
| import numpy as np from typing import Tuple from scipy.signal import welch, find_peaks
def extract_csi_features( csi_data: np.ndarray, fs: float = 100.0, motion_band: Tuple[float, float] = (0.5, 3.0) ) -> dict: """ 从CSI数据中提取运动特征 论文核心算法复现:自适应PCA主成分选择 Args: csi_data: CSI矩阵, shape=(n_subcarriers, n_samples) fs: 采样率(Hz) motion_band: 人体运动频段(Hz) - 0.5-3 Hz: 呼吸/心跳微动 - 3-10 Hz: 肢体运动 Returns: 包含选择的主成分和检测结果的字典 参考: Naotunna et al., "Through-Wall Detection using SDR based on adaptive PCA", arXiv:2609.12443, 2026 """ n_subcarriers, n_samples = csi_data.shape from sklearn.decomposition import PCA pca = PCA(n_components=min(10, n_subcarriers)) components = pca.fit_transform(csi_data.T) scores = [] for i in range(components.shape[1]): freqs, psd = welch( components[:, i], fs=fs, nperseg=min(256, len(components[:, i])) ) motion_mask = (freqs >= motion_band[0]) & (freqs <= motion_band[1]) motion_energy = np.sum(psd[motion_mask]) total_energy = np.sum(psd) if total_energy > 0: spectral_score = motion_energy / total_energy else: spectral_score = 0 noise_band = (freqs >= 10) & (freqs <= 20) noise_power = np.mean(psd[noise_band]) if np.any(noise_band) else 1e-10 snr = motion_energy / noise_power scores.append({ 'component_idx': i, 'spectral_score': float(spectral_score), 'snr': float(snr), 'motion_energy': float(motion_energy), 'explained_variance': float(pca.explained_variance_ratio_[i]) }) scores.sort(key=lambda x: x['spectral_score'], reverse=True) best_component_idx = scores[0]['component_idx'] best_signal = components[:, best_component_idx] import pywt scales = np.arange(1, 64) wavelet = 'morl' coeffs, freqs_cwt = pywt.cwt(best_signal, scales, wavelet, 1.0/fs) energy = np.abs(coeffs) energy_sum = np.sum(energy, axis=0) threshold = np.mean(energy_sum) + 2 * np.std(energy_sum) peaks, _ = find_peaks(energy_sum, height=threshold, distance=int(fs*0.5)) return { 'motion_detected': len(peaks) > 0, 'n_events': len(peaks), 'event_times': peaks / fs, 'best_component': best_component_idx, 'spectral_scores': scores, 'energy_profile': energy_sum.tolist(), 'detection_threshold': float(threshold) }
def simulate_cabin_csi( n_subcarriers: int = 56, duration_s: float = 10.0, fs: float = 100.0, breathing_rate: float = 0.3, motion_events: list = None ) -> np.ndarray: """ 模拟座舱内CSI数据(含人体微动) Args: n_subcarriers: WiFi子载波数 duration_s: 持续时间(秒) fs: 采样率(Hz) breathing_rate: 呼吸频率(Hz), 婴儿~0.3-1.0 motion_events: 运动事件列表 [(start_s, duration_s, amplitude)] Returns: csi_data: shape=(n_subcarriers, n_samples) """ n_samples = int(duration_s * fs) t = np.linspace(0, duration_s, n_samples) if motion_events is None: motion_events = [(2.0, 3.0, 0.5)] static_csi = np.exp(1j * 2 * np.pi * 0.1 * np.random.randn(n_subcarriers)) csi = np.zeros((n_subcarriers, n_samples), dtype=complex) for k in range(n_subcarriers): csi[k, :] = static_csi[k] breathing = 0.01 * np.sin(2 * np.pi * breathing_rate * t) csi[k, :] += breathing * np.exp(-1j * 2 * np.pi * k * 0.01) for start, dur, amp in motion_events: mask = (t >= start) & (t <= start + dur) motion = amp * np.random.randn(np.sum(mask)) csi[k, mask] += motion * np.exp(-1j * 2 * np.pi * k * 0.05) noise = 0.01 * (np.random.randn(*csi.shape) + 1j * np.random.randn(*csi.shape)) csi += noise return csi
if __name__ == "__main__": print("=== WiFi CSI 穿墙/座舱感知检测仿真 ===\n") np.random.seed(42) print("[场景1] 后排有儿童(呼吸频率0.4Hz)") csi_with_person = simulate_cabin_csi( n_subcarriers=56, duration_s=10.0, fs=100.0, breathing_rate=0.4, motion_events=[(3.0, 1.0, 0.3), (6.5, 0.5, 0.2)] ) result1 = extract_csi_features(csi_with_person, fs=100.0, motion_band=(0.3, 3.0)) print(f" 运动检测: {result1['motion_detected']}") print(f" 检测到 {result1['n_events']} 个运动事件") print(f" 最佳主成分: PC{result1['best_component']}") print(f" 频谱评分: {result1['spectral_scores'][0]['spectral_score']:.4f}") print(f" 事件时刻: {[f'{t:.2f}s' for t in result1['event_times']]}") print("\n[场景2] 空座(无人体微动)") csi_empty = simulate_cabin_csi( n_subcarriers=56, duration_s=10.0, fs=100.0, breathing_rate=0.0, motion_events=[] ) result2 = extract_csi_features(csi_empty, fs=100.0, motion_band=(0.3, 3.0)) print(f" 运动检测: {result2['motion_detected']}") print(f" 检测到 {result2['n_events']} 个运动事件") print(f" 最佳主成分: PC{result2['best_component']}") print(f" 频谱评分: {result2['spectral_scores'][0]['spectral_score']:.4f}")
|