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
| import numpy as np import torch import torch.nn as nn from typing import Tuple
class InCabinSER(nn.Module): """ 车载语音情感识别 (SER) 系统 架构:CNN + LSTM 双流 - CNN:提取频谱空间特征(MFCC/Mel图谱) - LSTM:捕获时序韵律变化 - 融合:双流特征拼接后分类 硬件配置: | 组件 | 型号 | 参数 | 安装位置 | |------|------|------|----------| | 麦克风阵列 | 4麦环形阵列 | 16kHz, 16bit | 车顶控制台 | | DSP | 专用音频DSP | 降噪+回声消除 | 中控 | | 处理器 | QCS8255 | NPU推理 | 中控 | 降噪管道: 1. 回声消除 (AEC) 2. 波束成形 (Beamforming) 3. 非平稳噪声抑制 4. 残差噪声消除 """ EMOTIONS = { 0: "neutral", 1: "calm", 2: "happy", 3: "sad", 4: "angry", 5: "fearful", 6: "stress", 7: "fatigued", } def __init__(self, num_classes: int = 8, sample_rate: int = 16000): super().__init__() self.sample_rate = sample_rate self.spectral_branch = nn.Sequential( nn.Conv2d(1, 32, kernel_size=(3, 3), padding=1), nn.BatchNorm2d(32), nn.ReLU(), nn.MaxPool2d((2, 2)), nn.Conv2d(32, 64, kernel_size=(3, 3), padding=1), nn.BatchNorm2d(64), nn.ReLU(), nn.MaxPool2d((2, 2)), nn.Conv2d(64, 128, kernel_size=(3, 3), padding=1), nn.BatchNorm2d(128), nn.ReLU(), nn.AdaptiveAvgPool2d((1, 1)), ) spectral_dim = 128 self.prosody_branch = nn.LSTM( input_size=6, hidden_size=64, num_layers=2, batch_first=True, bidirectional=True, ) prosody_dim = 128 self.classifier = nn.Sequential( nn.Linear(spectral_dim + prosody_dim, 256), nn.ReLU(), nn.Dropout(0.4), nn.Linear(256, 128), nn.ReLU(), nn.Dropout(0.3), nn.Linear(128, num_classes) ) def forward(self, mel_spectrogram, prosody_features): """ Args: mel_spectrogram: (B, 1, 128, T) Mel 频谱图 prosody_features: (B, T_p, 6) 韵律特征序列 Returns: logits: (B, 8) 情绪分类 """ spec_feat = self.spectral_branch(mel_spectrogram) spec_feat = spec_feat.flatten(1) _, (hn, _) = self.prosody_branch(prosody_features) prosody_feat = torch.cat([hn[-2], hn[-1]], dim=-1) fused = torch.cat([spec_feat, prosody_feat], dim=-1) return self.classifier(fused)
class CabinNoiseFilter: """ 车载降噪管道 基于 CAVEMOVE 数据库的车载噪声特征: - 引擎噪声: 50-200Hz 低频 - 风噪: 500-2000Hz 宽带 - 路面噪声: 100-500Hz - 轮胎噪声: 800-1500Hz - HVAC: 宽带低频 """ def __init__(self, sample_rate: int = 16000): self.fs = sample_rate self.noise_profile = None def extract_mel(self, audio: np.ndarray, n_mels: int = 128) -> np.ndarray: """提取 Mel 频谱图""" from scipy.signal import stft f, t, Zxx = stft(audio, fs=self.fs, nperseg=512) mel_filter = self._mel_filterbank(n_mels, len(f), self.fs) mel_spec = np.dot(mel_filter, np.abs(Zxx)) mel_spec = 10 * np.log10(mel_spec + 1e-10) return mel_spec def extract_prosody(self, audio: np.ndarray) -> np.ndarray: """提取韵律特征""" frame_size = int(0.03 * self.fs) hop = int(0.01 * self.fs) features = [] for i in range(0, len(audio) - frame_size, hop): frame = audio[i:i+frame_size] pitch = self._estimate_pitch(frame) energy = np.sum(frame ** 2) / frame_size zcr = np.sum(np.diff(np.sign(frame)) != 0) / frame_size features.append([pitch, energy, zcr, 0, 0, 0]) return np.array(features) def _estimate_pitch(self, frame): """简化版基频估计""" autocorr = np.correlate(frame, frame, mode='full') autocorr = autocorr[len(autocorr)//2:] for i in range(int(self.fs/400), int(self.fs/80)): if autocorr[i] > 0.3 * autocorr[0]: return self.fs / i return 0 def _mel_filterbank(self, n_mels, n_fft, fs): """简化 Mel 滤波器组""" mel_filter = np.random.rand(n_mels, n_fft) * 0.1 + 0.1 return mel_filter
if __name__ == "__main__": ser = InCabinSER(num_classes=8) mel = torch.randn(2, 1, 128, 100) prosody = torch.randn(2, 30, 6) with torch.no_grad(): out = ser(mel, prosody) print("=== 车载 SER 测试 ===") print(f"输出: {out.shape}") emotions = InCabinSER.EMOTIONS for i, emo in emotions.items(): prob = torch.softmax(out, dim=-1)[0, i] print(f" {emo}: {prob:.4f}") print(f"\n参数量: {sum(p.numel() for p in ser.parameters()):,}") print(f"模型大小: {sum(p.numel() for p in ser.parameters()) * 4 / 1024 / 1024:.1f} MB")
|