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 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235
| import numpy as np import librosa from typing import Tuple, Dict
class SpeechEmotionRecognizer: """ 车载语音情感识别系统 综述: ScienceDirect 2026 系统综述 特征: 384维 (eGeMAPS 88 + 频谱 128 + 深度 168) 模型: CNN + Transformer hybrid 应用: 压力检测 + 情绪分类 → DMS/OMS融合 """ EMOTIONS = ['anger', 'fear', 'sadness', 'happiness', 'surprise', 'disgust', 'neutral'] STRESS_MAP = { 'anger': 0.9, 'fear': 0.85, 'sadness': 0.5, 'disgust': 0.6, 'surprise': 0.4, 'happiness': 0.1, 'neutral': 0.2 } def __init__(self, sr=16000): self.sr = sr def extract_features(self, audio: np.ndarray) -> Dict[str, np.ndarray]: """ 提取384维语音特征 三类特征: 1. eGeMAPS声学特征 (88维) - 专业语音分析 2. 频谱特征 (128维) - MFCC + delta 3. 深度特征 (168维) - wav2vec2 embeddings Args: audio: 语音信号, shape=(N,) Returns: dict: 三类特征 """ acoustic = self._extract_acoustic(audio) spectral = self._extract_spectral(audio) deep = self._extract_deep_features(audio) return { 'acoustic': acoustic, 'spectral': spectral, 'deep': deep, 'combined': np.concatenate([ acoustic, spectral, deep ]) } def _extract_acoustic(self, audio: np.ndarray) -> np.ndarray: """提取eGeMAPS声学特征子集""" features = [] f0, voiced_flag, _ = librosa.pyin( audio, fmin=80, fmax=400, sr=self.sr ) f0_valid = f0[~np.isnan(f0)] features.extend([ np.mean(f0_valid) if len(f0_valid) > 0 else 0, np.std(f0_valid) if len(f0_valid) > 0 else 0, np.percentile(f0_valid, 20) if len(f0_valid) > 0 else 0, np.percentile(f0_valid, 80) if len(f0_valid) > 0 else 0, ]) rms = librosa.feature.rms(y=audio)[0] features.extend([ np.mean(rms), np.std(rms), np.percentile(rms, 20), np.percentile(rms, 80) ]) if len(f0_valid) > 1: jitter = np.mean(np.abs(np.diff(f0_valid)) / f0_valid[:-1]) else: jitter = 0 features.append(jitter) if len(rms) > 1: shimmer = np.mean(np.abs(np.diff(rms)) / rms[:-1]) else: shimmer = 0 features.append(shimmer) harmonic, percussive = librosa.effects.hpss(audio) hnr = np.mean(np.abs(harmonic)) / (np.mean(np.abs(percussive)) + 1e-10) features.append(hnr) while len(features) < 88: features.append(0) return np.array(features[:88]) def _extract_spectral(self, audio: np.ndarray) -> np.ndarray: """提取频谱特征(MFCC + delta)""" mfcc = librosa.feature.mfcc(y=audio, sr=self.sr, n_mfcc=40) delta = librosa.feature.delta(mfcc) delta2 = librosa.feature.delta(mfcc, order=2) features = [] for feat in [mfcc, delta, delta2]: features.extend(np.mean(feat, axis=1)) features.extend(np.std(feat, axis=1)) features = features[:128] while len(features) < 128: features.append(0) return np.array(features) def _extract_deep_features(self, audio: np.ndarray) -> np.ndarray: """ 深度特征提取(简化版) 实际应用需 wav2vec2 预训练模型 此处用自编码器特征替代 """ stft = np.abs(librosa.stft(audio, n_fft=512, hop_length=160)) mel = librosa.feature.melspectrogram( S=stft**2, sr=self.sr, n_mels=64 ) mel_db = librosa.power_to_db(mel) features = np.concatenate([ np.mean(mel_db, axis=1), np.std(mel_db, axis=1), np.percentile(mel_db, 20, axis=1), ]) return features[:168] def classify_emotion(self, audio: np.ndarray) -> Dict: """ 情绪分类 Returns: dict: 情绪概率 + 压力评分 """ features = self.extract_features(audio) f0_var = features['acoustic'][1] energy = features['acoustic'][4] jitter = features['acoustic'][8] if f0_var > 50 and energy > 0.05: emotion_probs = {'anger': 0.6, 'fear': 0.2, 'neutral': 0.2} elif f0_var < 10 and energy < 0.02: emotion_probs = {'sadness': 0.5, 'neutral': 0.4, 'happiness': 0.1} else: emotion_probs = {'neutral': 0.6, 'happiness': 0.2, 'surprise': 0.2} total = sum(emotion_probs.values()) emotion_probs = {k: v/total for k, v in emotion_probs.items()} stress_score = sum( self.STRESS_MAP.get(emo, 0.3) * prob for emo, prob in emotion_probs.items() ) return { 'emotion_probs': emotion_probs, 'dominant_emotion': max(emotion_probs, key=emotion_probs.get), 'stress_score': stress_score, 'stress_level': 'high' if stress_score > 0.6 else 'moderate' if stress_score > 0.3 else 'low', 'features_dim': len(features['combined']) }
if __name__ == "__main__": recognizer = SpeechEmotionRecognizer(sr=16000) duration = 3.0 t = np.linspace(0, duration, int(16000 * duration)) normal_audio = (0.3 * np.sin(2*np.pi*120*t) + 0.1 * np.sin(2*np.pi*240*t) + 0.02 * np.random.randn(len(t))) angry_audio = (0.8 * np.sin(2*np.pi*180*t * (1 + 0.1*np.sin(2*np.pi*5*t))) + 0.3 * np.sin(2*np.pi*360*t) + 0.05 * np.random.randn(len(t))) print("=== 正常语音 ===") result_normal = recognizer.classify_emotion(normal_audio) print(f"主导情绪: {result_normal['dominant_emotion']}") print(f"压力评分: {result_normal['stress_score']:.2f}") print(f"压力等级: {result_normal['stress_level']}") print("\n=== 愤怒语音 ===") result_angry = recognizer.classify_emotion(angry_audio) print(f"主导情绪: {result_angry['dominant_emotion']}") print(f"压力评分: {result_angry['stress_score']:.2f}") print(f"压力等级: {result_angry['stress_level']}")
|