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 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250
| """ 多模态驾驶员情绪识别系统
基于 Frontiers in AI 2024 + JICV 2025 论文方法 融合: 面部表情 + 车辆运动行为 + 生理信号(可选) 依赖: pip install torch numpy opencv-python """
import torch import torch.nn as nn import numpy as np from typing import Dict, Tuple, Optional
class FacialEmotionBranch(nn.Module): """面部表情分支 - 基于ResNet18""" EMOTION_CLASSES = ['neutral', 'happy', 'sad', 'surprise', 'anger', 'fear', 'disgust'] def __init__(self, n_classes: int = 7): super().__init__() self.features = nn.Sequential( nn.Conv2d(3, 32, 3, padding=1), nn.BatchNorm2d(32), nn.ReLU(), nn.MaxPool2d(2), nn.Conv2d(32, 64, 3, padding=1), nn.BatchNorm2d(64), nn.ReLU(), nn.MaxPool2d(2), nn.Conv2d(64, 128, 3, padding=1), nn.BatchNorm2d(128), nn.ReLU(), nn.MaxPool2d(2), nn.Conv2d(128, 256, 3, padding=1), nn.BatchNorm2d(256), nn.ReLU(), nn.AdaptiveAvgPool2d((1, 1)), ) self.classifier = nn.Sequential( nn.Linear(256, 128), nn.ReLU(), nn.Dropout(0.3), nn.Linear(128, n_classes) ) def forward(self, face_img): """ Args: face_img: 面部图像, shape=(B, 3, 64, 64) Returns: emotion_logits: shape=(B, n_classes) features: shape=(B, 256) 用于融合 """ feat = self.features(face_img).flatten(1) logits = self.classifier(feat) return logits, feat
class DrivingActivityBranch(nn.Module): """驾驶运动行为分支 - 转向/踏板/车速时序""" def __init__(self, input_dim: int = 6, hidden_dim: int = 64): """ Args: input_dim: [steering_angle, steering_velocity, gas_pedal, brake_pedal, vehicle_speed, lane_deviation] hidden_dim: LSTM隐藏维度 """ super().__init__() self.lstm = nn.LSTM( input_size=input_dim, hidden_size=hidden_dim, num_layers=2, batch_first=True, dropout=0.2 ) self.fc = nn.Sequential( nn.Linear(hidden_dim, 32), nn.ReLU(), nn.Linear(32, 7) ) def forward(self, driving_seq): """ Args: driving_seq: shape=(B, seq_len, input_dim), seq_len=60 (2秒@30Hz) Returns: emotion_logits: shape=(B, 7) features: shape=(B, 64) """ lstm_out, (h_n, c_n) = self.lstm(driving_seq) feat = h_n[-1] logits = self.fc(feat) return logits, feat
class PhysiologicalBranch(nn.Module): """生理信号分支 - 可选(心率/HRV/皮电)""" def __init__(self, input_dim: int = 3): """ Args: input_dim: [heart_rate, hrv, gsr] """ super().__init__() self.conv1d = nn.Sequential( nn.Conv1d(input_dim, 16, 5, padding=2), nn.ReLU(), nn.MaxPool1d(2), nn.Conv1d(16, 32, 5, padding=2), nn.ReLU(), nn.AdaptiveAvgPool1d(1), ) self.fc = nn.Linear(32, 7) def forward(self, physio_seq): """ Args: physio_seq: shape=(B, seq_len, input_dim) Returns: emotion_logits: shape=(B, 7) features: shape=(B, 32) """ x = physio_seq.transpose(1, 2) feat = self.conv1d(x).flatten(1) logits = self.fc(feat) return logits, feat
class MultimodalEmotionFusion(nn.Module): """ 多模态情绪融合模型 融合策略: 注意力加权 + 晚融合MLP 参考: - Frontiers in AI 2024: 面部+运动活动 - JICV 2025: 深度集成 - Springer 2025: 面部+生物信号 """ def __init__(self, use_physio: bool = False): super().__init__() self.face_branch = FacialEmotionBranch() self.drive_branch = DrivingActivityBranch() self.use_physio = use_physio if use_physio: self.physio_branch = PhysiologicalBranch() fusion_dim = 256 + 64 + 32 else: fusion_dim = 256 + 64 self.attention = nn.Sequential( nn.Linear(fusion_dim, 64), nn.ReLU(), nn.Linear(64, 3 if use_physio else 2), nn.Softmax(dim=1) ) self.fusion_classifier = nn.Sequential( nn.Linear(fusion_dim, 128), nn.ReLU(), nn.Dropout(0.3), nn.Linear(128, 64), nn.ReLU(), nn.Linear(64, 7) ) def forward(self, face_img, driving_seq, physio_seq=None): """ Returns: final_logits: shape=(B, 7) attention_weights: 各模态权重 """ face_logits, face_feat = self.face_branch(face_img) drive_logits, drive_feat = self.drive_branch(driving_seq) if self.use_physio and physio_seq is not None: physio_logits, physio_feat = self.physio_branch(physio_seq) concat_feat = torch.cat([face_feat, drive_feat, physio_feat], dim=1) else: concat_feat = torch.cat([face_feat, drive_feat], dim=1) attn_weights = self.attention(concat_feat) if self.use_physio and physio_seq is not None: weighted = torch.cat([ face_feat * attn_weights[:, 0:1], drive_feat * attn_weights[:, 1:2], physio_feat * attn_weights[:, 2:3] ], dim=1) else: weighted = torch.cat([ face_feat * attn_weights[:, 0:1], drive_feat * attn_weights[:, 1:2] ], dim=1) final_logits = self.fusion_classifier(weighted) return final_logits, attn_weights
if __name__ == "__main__": np.random.seed(42) torch.manual_seed(42) model = MultimodalEmotionFusion(use_physio=False) batch_size = 4 face_img = torch.randn(batch_size, 3, 64, 64) driving_seq = torch.randn(batch_size, 60, 6) logits, attn = model(face_img, driving_seq) emotions = FacialEmotionBranch.EMOTION_CLASSES print("=== 多模态情绪识别 ===") print(f"输入: 面部图像 {face_img.shape} + 驾驶行为 {driving_seq.shape}") print(f"输出: {logits.shape} (7类情绪)") print(f"注意力权重: 面部={attn[0,0]:.2f}, 驾驶行为={attn[0,1]:.2f}") for i in range(batch_size): pred = emotions[logits[i].argmax()] conf = torch.softmax(logits[i], dim=0).max().item() print(f" 样本{i}: {pred} ({conf:.1%})") n_params = sum(p.numel() for p in model.parameters()) print(f"\n模型参数: {n_params:,} ({n_params*4/1024:.1f} KB)") print(f"部署: 高通8255 Hexagon NPU, 预计延迟<15ms") model_full = MultimodalEmotionFusion(use_physio=True) physio_seq = torch.randn(batch_size, 60, 3) logits_full, attn_full = model_full(face_img, driving_seq, physio_seq) print(f"\n含生理信号版: {logits_full.shape}") print(f"注意力: 面部={attn_full[0,0]:.2f}, 驾驶={attn_full[0,1]:.2f}, 生理={attn_full[0,2]:.2f}")
|