
简介本资源是一套面向深度学习初学者与课程实践者的面部表情识别完整项目方案基于PyTorch框架构建轻量级卷积神经网络解决人脸图像中七类基本情绪如愤怒、高兴、悲伤等的分类识别问题适用于AI课程设计、毕业设计及Kaggle风格入门实战。压缩包为446.18MB的ZIP文件包含可直接运行的训练/测试源码、预处理后的面部表情数据集含FER2013等主流子集、完整学术论文含模型设计、实验对比与结果分析以及README说明文档。目前已有194人学习下载所有代码均经本地环境编译验证评审得分达95分以上项目难度适中、结构清晰——主干模块涵盖数据加载、CNN架构定义、训练日志可视化、模型保存与推理部署全流程助教审定内容确保教学可用性与工程规范性。1. 这不是“调个模型跑个acc”的玩具项目而是一套可落地的表情识别闭环系统你手头那张刚拍的自拍照或者监控里截取的一帧人脸能不能在 0.3 秒内判断出是“惊讶”还是“厌恶”而不是只输出一个模糊的“负面情绪”这个 PyTorch 表情识别项目恰恰卡在工业级轻量部署和教学级可解释性之间的黄金交点上——它不依赖云端 API不调用黑盒 SDK所有模块数据加载、预处理、CNN 架构、训练调度、推理封装全部用原生 PyTorch 实现且已通过本地 CUDA 环境实测Python 3.8 / PyTorch 1.12 / CUDA 11.3训练完的模型.pth文件可直接torch.jit.script导出为 TorchScript嵌入到 OpenCV Python 的边缘设备流程中。项目覆盖 FER2013、JAFFE、CK 三类主流公开数据集的适配逻辑论文部分明确标注了各数据集划分比例如 FER2013 的 64% 训练 / 16% 验证 / 20% 测试并给出混淆矩阵热力图生成脚本。适合两类人想把课程设计做成答辩亮点的本科生以及需要快速验证表情识别 baseline 的算法工程师——前者能直接复现 95 分评审结果后者可基于models/resnet18_fer.py中的注意力门控模块做迁移改造。2. 从原始图像到分类 logitsPyTorch 数据流水线与 CNN 架构解耦设计2.1 数据集结构标准化与动态路径解析机制项目未采用硬编码路径而是通过config.py中的DATASET_ROOT和DATASET_NAME双参数驱动数据加载。以 FER2013 为例目录结构必须严格遵循datasets/ └── fer2013/ ├── train/ │ ├── angry/ # 每类子目录存放对应表情图像 │ ├── disgust/ │ ├── fear/ │ ├── happy/ │ ├── sad/ │ ├── surprise/ │ └── neutral/ ├── val/ └── test/关键代码位于data/dataset_loader.py的get_dataloader()函数def get_dataloader(dataset_name: str, batch_size: int 32, num_workers: int 4, pin_memory: bool True) - Dict[str, DataLoader]: transform_train transforms.Compose([ transforms.Grayscale(num_output_channels1), # 强制转灰度FER2013 原生为灰度 transforms.Resize((48, 48)), # 统一尺寸避免后续 resize 失真 transforms.RandomHorizontalFlip(p0.5), # 水平翻转增强仅训练集 transforms.ToTensor(), transforms.Normalize(mean[0.5], std[0.5]) # 归一化至 [-1, 1]适配 tanh 激活 ]) dataset_train ImageFolder( rootos.path.join(DATASET_ROOT, dataset_name, train), transformtransform_train ) # 注意ImageFolder 自动按子目录名映射 label顺序为 [angry,disgust,...,neutral] # 对应索引 0~6与 FER2013 官方 label 编码完全一致提示若使用 CK 数据集RGB 图像需修改transforms.Grayscale()为transforms.Lambda(lambda x: x.convert(L))并保留num_output_channels1否则ToTensor()会报维度错误。项目已内置data/ckplus_preprocess.py脚本可批量将 CK 的 64x64 RGB 图转换为灰度并重命名。2.2 卷积神经网络主干ResNet18 改造与局部特征强化模型定义在models/resnet18_fer.py核心改造点有三处首层卷积通道适配原始 ResNet18 输入为 3 通道 RGB此处改为单通道灰度输入self.conv1 nn.Conv2d(1, 64, kernel_size7, stride2, padding3, biasFalse)全局平均池化前插入通道注意力SE Block在layer4后添加提升对微表情区域如眼周、嘴角的权重self.se SELayer(512, reduction16) # reduction 控制压缩比16 是经验值分类头精简原始 ResNet18 输出 1000 类此处替换为 7 类全连接层self.fc nn.Sequential( nn.Dropout(0.5), # 防止过拟合FER2013 小样本场景必需 nn.Linear(512, 128), nn.ReLU(inplaceTrue), nn.Dropout(0.3), nn.Linear(128, 7) # 输出 7 类 logits )2.2.1 SE Block 实现细节与梯度流验证SELayer类位于models/attention.py其 forward 过程如下def forward(self, x): b, c, _, _ x.size() y self.avg_pool(x).view(b, c) # 全局平均池化 → (b, c) y self.fc(y) # 两层全连接压缩-恢复通道权重 y self.sigmoid(y).view(b, c, 1, 1) # Sigmoid 归一化为 [0,1] return x * y # 逐通道缩放特征图该设计使模型在训练后期自动聚焦于fear类别中眉毛上扬、surprise类别中眼睛睁大等判别性区域。可通过torchvision.utils.make_grid()可视化y.view(b,c,1,1)的权重分布验证注意力是否合理激活。2.3 训练策略带标签平滑的交叉熵与余弦退火调度损失函数采用LabelSmoothingCrossEntropy定义在utils/loss.py缓解 FER2013 中disgust类样本极少仅占 4.2%导致的类别不平衡criterion LabelSmoothingCrossEntropy(smoothing0.1) # 0.1 表示将 10% 置信度分配给其他类优化器使用torch.optim.AdamW而非 Adam配合余弦退火scheduler torch.optim.lr_scheduler.CosineAnnealingLR( optimizer, T_max50, eta_min1e-6 # 50 个 epoch 后学习率降至 1e-6 )注意AdamW的 weight_decay 参数设为 1e-4比传统 Adam 的 L2 正则更稳定eta_min必须显式设置否则默认为 0可能导致末期 loss 震荡。3. 从训练脚本到端到端推理命令行参数驱动与模型导出实战3.1 一键启动训练参数化配置与 checkpoint 管理项目提供train.py主入口支持命令行覆盖配置python train.py \ --dataset fer2013 \ --model resnet18_fer \ --batch_size 64 \ --epochs 100 \ --lr 1e-3 \ --save_dir ./checkpoints/fer2013_res18_v1 \ --resume ./checkpoints/fer2013_res18_v0/best.pth关键逻辑在train.py的main()函数中# 动态导入模型 model_module importlib.import_module(fmodels.{args.model}) model model_module.build_model(num_classes7) # 自动创建保存目录并记录超参 os.makedirs(args.save_dir, exist_okTrue) with open(os.path.join(args.save_dir, config.json), w) as f: json.dump(vars(args), f, indent2) # 保存本次运行全部参数checkpoint 保存策略为双模式每 epoch 保存last.pth同时当val_acc刷新时保存best.pth。best.pth包含model_state_dict、optimizer_state_dict、scheduler_state_dict和best_acc四个 key确保断点续训可靠。3.2 推理脚本infer.py支持单图/批量/摄像头三种模式infer.py的核心是InferenceEngine类其predict()方法统一处理不同输入源def predict(self, input_source: Union[str, np.ndarray, List[str]]): if isinstance(input_source, str) and os.path.isfile(input_source): # 单图路径 img cv2.imread(input_source, cv2.IMREAD_GRAYSCALE) elif isinstance(input_source, np.ndarray): # OpenCV 读取的 ndarray img input_source elif isinstance(input_source, list): # 批量路径列表 return [self._predict_single(p) for p in input_source] # 标准化预处理与训练时 transform 一致 img_tensor self.transform(Image.fromarray(img)).unsqueeze(0) # 添加 batch 维度 with torch.no_grad(): logits self.model(img_tensor.to(self.device)) probs torch.nn.functional.softmax(logits, dim1) pred_class torch.argmax(probs, dim1).item() confidence probs[0][pred_class].item() return {class: self.class_names[pred_class], confidence: confidence}3.2.1 摄像头实时推理的帧率优化技巧在infer.py的--mode webcam下关键优化点有二预热模型首次推理前执行model(torch.randn(1,1,48,48).to(device))避免 CUDA 初始化延迟异步读帧使用cv2.VideoCapture的set(cv2.CAP_PROP_BUFFERSIZE, 1)降低缓冲区配合threading.Thread独立读帧线程主循环只做推理# 在推理循环中 ret, frame cap.read() if not ret: break # 裁剪人脸区域需提前加载 face detector face_roi detector.detect_and_crop(frame) # 返回 48x48 灰度图 result engine.predict(face_roi)3.3 TorchScript 模型导出脱离训练环境的部署包生成项目提供export.py脚本将训练好的best.pth转为.pt# export.py model resnet18_fer.build_model(num_classes7) model.load_state_dict(torch.load(checkpoints/xxx/best.pth)[model_state_dict]) model.eval() # 构造示例输入必须与训练时 shape 一致 example_input torch.randn(1, 1, 48, 48) # 导出为 TorchScript traced_model torch.jit.trace(model, example_input) traced_model.save(models/fer_res18_traced.pt)导出后模型可被 C 加载无需 Python 环境或在 Android/iOS 的 PyTorch Mobile 中运行。验证导出正确性loaded_model torch.jit.load(models/fer_res18_traced.pt) loaded_model.eval() output loaded_model(torch.randn(1,1,48,48)) # 应返回 shape [1,7] 的 logits4. 混淆矩阵分析与跨数据集泛化能力验证4.1 混淆矩阵生成定位具体误判类别与数据缺陷项目eval.py提供generate_confusion_matrix()函数输出.npy和可视化热力图from sklearn.metrics import confusion_matrix import seaborn as sns # 获取所有预测结果和真实标签 y_true, y_pred [], [] for images, labels in test_loader: outputs model(images.to(device)) _, preds torch.max(outputs, 1) y_true.extend(labels.cpu().numpy()) y_pred.extend(preds.cpu().numpy()) cm confusion_matrix(y_true, y_pred, normalizetrue) # 行归一化看各类别召回率 sns.heatmap(cm, annotTrue, fmt.2f, xticklabels[Angry,Disgust,Fear,Happy,Sad,Surprise,Neutral], yticklabels[Angry,Disgust,Fear,Happy,Sad,Surprise,Neutral]) plt.savefig(results/confusion_matrix.png)典型问题定位若disgust行中anger列值高达 0.6说明模型难以区分二者需检查disgust类样本是否多为低质量截图模糊/光照不均此时应在data/augmentation.py中增加transforms.RandomAdjustSharpness(sharpness_factor2)增强边缘。4.2 跨数据集迁移测试评估模型鲁棒性边界项目设计cross_eval.py脚本验证在 A 数据集训练、B 数据集测试的效果# 在 FER2013 上训练在 JAFFE 上测试 model.load_state_dict(torch.load(checkpoints/fer2013_best.pth)[model_state_dict]) test_loader get_dataloader(jaffe, batch_size32, is_trainFalse) acc_jaffe evaluate(model, test_loader, device) # 得到 72.3% acc实测结果表项目附带训练集 \ 测试集FER2013JAFFECKFER201371.2%68.5%73.1%JAFFE52.4%89.7%85.2%CK58.6%76.3%91.4%提示FER2013 训练模型在 CK 上表现最好因其样本量最大35887 张且包含更多自然表情变体而 JAFFE213 张因样本少、姿态单一作为训练集时泛化性最差。项目论文第 4.2 节据此建议工业场景优先用 FER2013 预训练再用目标场景小样本微调。4.3 关键参数调优对照表不同超参组合的精度/速度权衡以下为项目实测的 5 组关键参数对比硬件RTX 3060CUDA 11.3Batch SizeLearning RateDropout RateVal Acc (%)Avg Inference Time (ms)GPU Memory (MB)321e-30.569.812.42150641e-30.571.214.82890645e-40.370.513.224201281e-30.370.115.63420641e-30.5 SE Block71.214.82890结论batch_size64lr1e-3dropout0.5是精度与显存占用的最优平衡点SE Block 带来 0.3% 的 acc 提升但未增加推理耗时值得保留。5. 模型轻量化实践知识蒸馏压缩与 ONNX 跨平台部署5.1 知识蒸馏用 ResNet34 教师模型指导 ResNet18 学生模型项目distill.py实现 KDKnowledge Distillation核心是 KL 散度损失def kd_loss(student_logits, teacher_logits, temperature3.0): soft_student F.log_softmax(student_logits / temperature, dim1) soft_teacher F.softmax(teacher_logits / temperature, dim1) return F.kl_div(soft_student, soft_teacher, reductionbatchmean) * (temperature ** 2) # 总损失 0.7 * CE_loss 0.3 * KD_loss loss 0.7 * criterion_ce(stu_out, labels) 0.3 * kd_loss(stu_out, tea_out)教师模型用 ResNet34 在 FER2013 上训至 73.5% acc学生 ResNet18 经蒸馏后达 72.1% acc原 71.2%参数量减少 38%推理速度提升 22%。5.2 ONNX 导出与跨框架验证确保部署一致性onnx_export.py将 TorchScript 模型转 ONNXmodel torch.jit.load(models/fer_res18_traced.pt) dummy_input torch.randn(1, 1, 48, 48) torch.onnx.export( model, dummy_input, models/fer_res18.onnx, input_names[input], output_names[output], dynamic_axes{input: {0: batch_size}, output: {0: batch_size}}, opset_version12 )验证 ONNX 输出与 PyTorch 一致import onnxruntime as ort ort_session ort.InferenceSession(models/fer_res18.onnx) ort_inputs {ort_session.get_inputs()[0].name: dummy_input.numpy()} ort_outs ort_session.run(None, ort_inputs) # assert np.allclose(torch_out.detach().numpy(), ort_outs[0], atol1e-5)注意ONNX opset_version 必须 ≥12否则torch.nn.functional.interpolate用于某些上采样操作会报错dynamic_axes启用 batch 维度动态便于后续 TensorRT 优化。5.3 TensorRT 加速在 Jetson Nano 上实现 23 FPS 实时推理在 Jetson NanoJetPack 4.6上部署步骤# 1. 安装 TensorRT 8.0 sudo apt-get install tensorrt # 2. 使用 trtexec 工具优化 ONNX 模型 trtexec --onnxmodels/fer_res18.onnx \ --saveEnginemodels/fer_res18.trt \ --fp16 \ --workspace512 \ --minShapesinput:1x1x48x48 \ --optShapesinput:4x1x48x48 \ --maxShapesinput:16x1x48x48 # 3. Python 加载 TRT 引擎需安装 python-onnx-tensorrt import tensorrt as trt engine trt.Runtime(trt.Logger()).deserialize_cuda_engine(open(models/fer_res18.trt, rb).read())实测FP16 模式下Jetson Nano 达到 23 FPS41.7ms/frame满足边缘设备实时性要求内存占用仅 480MB低于 Nano 的 4GB 总内存上限。最终模型可在树莓派 4B配 Coral USB Accelerator或 Jetson Orin Nano 上直接运行无需重新训练——这才是“下载即用”背后真正的工程价值。本文还有配套的精品资源点击获取