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
| import numpy as np
class MultimodalHeartRateFusion: """ 多模态心率融合器 策略: 基于信号质量指数(SQI)的加权融合 劣质信号自动降权,优质信号自动升权 """ def __init__(self, fs: int = 30): self.fs = fs self.modalities = ['ecg_sw', 'ppg_sw', 'rppg_rgb', 'rppg_ir', 'rppg_thermal'] @staticmethod def compute_sqi(signal: np.ndarray, fs: int = 30) -> float: """ 信号质量指数 综合: 1. 信号方差(运动过大→低质量) 2. 频谱集中度(心率频段能量占比) 3. 自相关峰值(周期性强度) """ var = np.var(signal) var_score = np.exp(-var / (np.max(signal)**2)) from scipy.signal import welch freqs, psd = welch(signal, fs=fs, nperseg=fs*4) hr_band = (freqs >= 0.7) & (freqs <= 3.0) hr_power = np.sum(psd[hr_band]) total_power = np.sum(psd) + 1e-8 spectral_score = hr_power / total_power autocorr = np.correlate(signal, signal, mode='full') autocorr = autocorr[len(autocorr)//2:] autocorr /= autocorr[0] peaks = [] for i in range(1, len(autocorr)-1): if autocorr[i] > autocorr[i-1] and autocorr[i] > autocorr[i+1]: peaks.append((i, autocorr[i])) if peaks: peak_score = max(p[1] for p in peaks) else: peak_score = 0 sqi = 0.3 * var_score + 0.4 * spectral_score + 0.3 * peak_score return float(np.clip(sqi, 0, 1)) def estimate_hr(self, signal: np.ndarray, fs: int = 30) -> float: """单模态心率估计""" freqs, psd = welch(signal, fs=fs, nperseg=fs*4) hr_band = (freqs >= 0.7) & (freqs <= 3.0) if np.any(hr_band): peak_idx = np.argmax(psd[hr_band]) hr_hz = freqs[hr_band][peak_idx] return hr_hz * 60 return 0.0 def fuse( self, signals: dict, sample_rates: dict ) -> dict: """ 多模态心率融合 Args: signals: {'ecg_sw': array, 'ppg_sw': array, ...} sample_rates: {'ecg_sw': 500, 'ppg_sw': 100, ...} Returns: result: {'hr': float, 'quality': float, 'source': str} """ estimates = {} qualities = {} for mod, sig in signals.items(): if sig is None or len(sig) == 0: continue fs = sample_rates.get(mod, 30) hr = self.estimate_hr(sig, fs) sqi = self.compute_sqi(sig, fs) estimates[mod] = hr qualities[mod] = sqi if not estimates: return {'hr': 0, 'quality': 0, 'source': 'none'} total_q = sum(qualities.values()) if total_q == 0: return {'hr': 0, 'quality': 0, 'source': 'none'} fused_hr = sum( estimates[m] * qualities[m] for m in estimates ) / total_q best_mod = max(qualities, key=qualities.get) return { 'hr': float(fused_hr), 'quality': float(total_q / len(estimates)), 'best_source': best_mod, 'all_estimates': estimates, 'all_qualities': qualities }
if __name__ == "__main__": rng = np.random.default_rng(42) fusion = MultimodalHeartRateFusion() t = np.arange(0, 10, 1/500) hr_freq = 1.2 ecg = np.sin(2*np.pi*hr_freq*t) + 0.1*rng.normal(size=len(t)) t_ppg = np.arange(0, 10, 1/100) ppg = np.sin(2*np.pi*hr_freq*t_ppg) + 0.2*rng.normal(size=len(t_ppg)) t_rppg = np.arange(0, 10, 1/30) rppg_rgb = 0.5*np.sin(2*np.pi*hr_freq*t_rppg) + 0.5*rng.normal(size=len(t_rppg)) rppg_ir = 0.7*np.sin(2*np.pi*hr_freq*t_rppg) + 0.3*rng.normal(size=len(t_rppg)) rppg_thermal = 0.3*np.sin(2*np.pi*hr_freq*t_rppg) + 0.7*rng.normal(size=len(t_rppg)) signals = { 'ecg_sw': ecg, 'ppg_sw': ppg, 'rppg_rgb': rppg_rgb, 'rppg_ir': rppg_ir, 'rppg_thermal': rppg_thermal } sample_rates = {'ecg_sw': 500, 'ppg_sw': 100, 'rppg_rgb': 30, 'rppg_ir': 30, 'rppg_thermal': 30} result = fusion.fuse(signals, sample_rates) print("=== 多模态心率融合结果 ===") print(f"融合心率: {result['hr']:.1f} bpm (真值: 72.0)") print(f"最佳模态: {result['best_source']}") print(f"总体质量: {result['quality']:.3f}") print("\n各模态估计:") for mod, hr in result['all_estimates'].items(): q = result['all_qualities'][mod] print(f" {mod:15s}: {hr:6.1f} bpm (SQI: {q:.3f})")
|