ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

人脸表情识别课程设计实战:从CNN训练到实时推理系统

人脸表情识别课程设计实战:从CNN训练到实时推理系统 简介面向Python与深度学习初学者的课程设计项目实现基于卷积神经网络的人脸表情识别系统涵盖数据预处理、模型训练、表情分类及摄像头实时识别等完整流程。整套代码在本地编译运行通过评审得分95分以上难度适中适合高校学生用于课程设计、毕业设计或动手实践深度学习项目。压缩包共19个文件约10.81MB其中包含10个Python源码文件如模型定义、训练与GUI界面脚本、1份表情识别系统PPT说明文档、模型结构图、人脸样本图片、字体与配置文件等目录划分清晰方便对照学习。已有204人学习下载具有一定参考价值。从中可快速获得可运行的系统原型并借助说明文档梳理人脸识别与情绪分类的技术要点节省从零搭建环境与调试参数的时间。也可以基于现有代码替换数据集或调整网络结构进行功能扩展与实验对比。1. 为什么课程设计都选它人脸表情识别是深度学习的完整练兵场在课程设计选题这件事上人脸表情识别几乎是深度学习方向里性价比最高的一个。数据公开且量大模型从零搭建可控性高演示效果一眼能看懂——你对着摄像头笑一下界面上的分类标签跟着变这种反馈对答辩打分的影响比任何流程图都直接。这个系统的核心链路并不复杂输入人脸图像经过一个卷积神经网络输出七个基本表情类别的概率分布——愤怒、厌恶、恐惧、高兴、悲伤、惊讶、中性。难点和价值都藏在细节里数据怎么对齐、网络怎么设计、训练怎么不翻车、实时推理怎么保持帧率。我按自己做过的课程设计版本把从数据准备到系统集成的完整路径拆开说清楚适合正在做毕设或者课程设计的学生也适合想用一套源码快速验证CNN能力的入门开发者。2. 把表情数据喂进模型之前数据集选型、人脸对齐与数据增强2.1 FER2013与CK怎么选课程设计里公开数据集的天平先回答一个常见问题这份系统设计里该用哪份数据集。做课设最怕两件事——数据太大训练不动数据太小效果难看。FER2013是表情识别领域被引用最多的公开数据集总共35887张48x48像素的灰度人脸图像七类表情原始文件是csv格式。对课设来说优势很明显单张图尺寸小普通CPU十几分钟就能跑完一个epochcsv格式加载方便不用处理一堆图片文件的路径问题七分类的设定和教材里的softmax分类完全对齐。另一个常见选择是CK样本质量高但数量少而且本身是带时序的序列数据做课设要额外处理帧间关联复杂度不划算。我一般建议只用单张静态图判断表情首选FER2013想在论文里强调“时序特征”或“连续帧表情识别”再考虑CK。选定数据集后第一个要解决的问题是数据怎么导入。常见做法是用pandas直接读csv把pixels列按空格切分成整数数组再reshape成48x48。下面这段代码作为课设数据加载的基础模块完整保留在数据预处理脚本里import pandas as pd import numpy as np def load_fer2013(csv_path): df pd.read_csv(csv_path) x_data [] y_data [] for _, row in df.iterrows(): pixels row[pixels].split( ) img np.array(pixels, dtypefloat32) img img.reshape(48, 48, 1) x_data.append(img) y_data.append(row[emotion]) x_data np.array(x_data) / 255.0 y_data np.array(y_data, dtypeint32) return x_data, y_data这段代码的逻辑很直白逐行迭代csv的DataFrame把pixels列中空格分隔的灰度值字符串切分出来转成float32并reshape成48x48的单通道矩阵。归一化统一除以255让像素值落到0到1之间这一步直接决定梯度下降的收敛速度。有一个细节值得注意np.array(pixels, dtypefloat32)比逐像素append快得多因为iterrows本身已经够慢不要在更内层再逐像素操作。如果机器内存紧张可以用数据生成器批量读入而不是一次性加载全部图像不过FER2013这种规模一次性加载问题不大。返回的y_data是整数标签后面用ImageDataGenerator的flow会自动转成one-hot这一条在第四章训练时会再次遇到。2.2 用OpenCV做人脸检测与对齐别把背景喂给模型如果项目只需要在测试集上跑静态图准确率数据准备到这里就够用了。但课设答辩几乎都要现场演示摄像头场景绕不开一个关键环节从摄像头画面里找到人脸裁剪后送进模型。常见做法是OpenCV的Haar级联另一个更稳健的是OpenCV DNN模块里的SSD人脸检测器两者的差别放在实时推理章节展开。这里先给出基于Haar级联的检测和裁剪逻辑import cv2 face_cascade cv2.CascadeClassifier( cv2.data.haarcascades haarcascade_frontalface_default.xml) def extract_face(frame, target_size(48, 48), use_equalizeTrue): gray cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) faces face_cascade.detectMultiScale( gray, scaleFactor1.1, minNeighbors5, minSize(48, 48)) if len(faces) 0: return None x, y, w, h faces[0] face_roi gray[y:yh, x:xw] if use_equalize: face_roi cv2.equalizeHist(face_roi) face_resized cv2.resize( face_roi, target_size, interpolationcv2.INTER_AREA) return face_resized / 255.0这段代码做了四件事把BGR彩色帧转成灰度图检测人脸位置取第一个检测框裁剪并缩放成48x48。scaleFactor和minNeighbors是Haar级联里最常被调的两个参数——scaleFactor越小检测越细致但耗时越长一般取1.1minNeighbors越大误检越少但漏检越多取3到5比较稳妥。如果画面里出现多个人用面积排序取最大的人脸框逻辑放在第6章的类里。新增的equalizeHist是直方图均衡化很多教程会漏掉这一步。它把灰度分布拉伸到整个动态范围能显著减轻侧光造成的一侧过暗、一侧过亮的影响。FER2013样本整体居中对齐但摄像头场景光照变化很大这个处理在课设演示中经常是决定性的同一张侧光人脸不做均衡化时模型可能输出fear做了之后能稳定输出neutral。注意它只能作用在单通道灰度图上想对彩色图做要先分离通道否则颜色会失真。训练和推理阶段的预处理必须完全一致很多人训练时归一化到0-1、推理时忘了这步预测概率全部偏低且分布奇怪这个是后面避坑章节的重点。2.3 数据增强让模型对光照和角度不那么敏感表情识别模型最容易翻车的场景不是表情本身而是光照和轻微旋转带来的像素变化。FER2013的图片相对居中但摄像头画面里人脸会有角度偏移和远近变化。数据增强是绕不开的。在Keras里这是内置能力关键是参数怎么设。我习惯把rotation_range控制在10度以内因为过大的旋转会改变五官的空间位置关系水平翻转对人脸表情是安全增强放心用。宽度和高度的平移设为0.1配合zoom_range0.1模拟人脸远近变化。from tensorflow.keras.preprocessing.image import ImageDataGenerator datagen ImageDataGenerator( rotation_range10, width_shift_range0.1, height_shift_range0.1, shear_range0.1, zoom_range0.1, horizontal_flipTrue, fill_modenearest, validation_split0.2) train_generator datagen.flow( x_train, y_train, batch_size64, subsettraining, shuffleTrue) val_generator datagen.flow( x_train, y_train, batch_size64, subsetvalidation, shuffleFalse)这里有一个容易被忽略的细节我没有手动把x_train拆成训练和验证两份而是直接通过validation_split和subset参数完成划分。这样做的好处是数据增强只在训练子集上生效验证子集始终使用原始图片得到的验证准确率才真实反映模型泛化能力不会被增强后的“假样本”带偏。fill_modenearest表示平移或旋转后的空缺区域用最邻近像素填充对灰度人脸图来说这个策略比填黑色更合理避免在脸边缘引入不自然的黑色边框。这一点与很多人的直觉相反——边框填黑色看起来更“干净”但会让卷积核学到不该学的边缘特征。3. 搭一个能在FER2013上冲到65%的CNN网络结构设计与参数选型3.1 从零搭建CNN为什么比直接用迁移学习更适合课设模型层面的第一个选择是自己搭CNN还是用预训练模型。很多教程一上来就推荐ResNet50或EfficientNet做迁移学习理由是准确率高。这个建议在真实工业项目是对的但在课设场景下未必合适。原因有三个预训练模型一般要求224x224的三通道输入FER2013是48x48单通道光是对齐输入就要多写一堆转换代码还丢了灰度信息课程设计评分通常会看“网络结构是否合理、参数设置是否有效”自己搭的网络每一层的输出尺寸和参数数量都能在答辩时讲清楚而迁移学习把模型变成了黑匣子评委追问“为什么这里用残差结构”很难答到位从训练时间看ResNet50在CPU上跑一个epoch比轻量CNN慢不少课设阶段不是所有人都拿得到GPU。我一般建议课设阶段从零搭一个小型CNN跑通全流程。如果课题明确要求对比预训练模型再在说明文档里补一组EfficientNet的对比实验作为加分项而不是主干。这套思路和传统机器学习方案也不同。传统机器学习要手动提取LBP、HOG特征再喂给SVM特征工程的工作量大而且表情在不同光照下局部纹理变化剧烈特征鲁棒性很难保证。CNN把特征提取和分类一起学省去特征工程这一步这也是深度学习在这个任务上把传统机器学习模型比下去的核心原因。课程设计的说明文档里这一段对比值得用一两页写清楚评委会认为你理解了自己在做什么而不只是跑通了一个现成demo。3.2 基线模型结构三个卷积块加两个全连接层这里给出课设里使用的基线网络结构。设计原则不复杂输入48x48灰度图经过三个卷积块每个块由卷积层、批归一化、激活函数和池化层组成最后接全连接层和softmax输出七类概率。通道数从32增长到64再到128符合特征图尺寸减半、通道数加倍的经典做法。特征图尺寸变化可以用一张表说明。层级输出尺寸通道数说明Input48 x 481灰度图Conv1 BN Pool24 x 2432Dropout 0.25Conv2 BN Pool12 x 1264Dropout 0.25Conv3 BN Pool6 x 6128Dropout 0.25Flatten Dense512-Dropout 0.5Dense Softmax7-分类输出from tensorflow.keras.models import Sequential from tensorflow.keras.layers import (Conv2D, MaxPooling2D, Flatten, Dense, Dropout, BatchNormalization) from tensorflow.keras.optimizers import Adam model Sequential([ Conv2D(32, (3, 3), paddingsame, activationrelu, input_shape(48, 48, 1)), BatchNormalization(), MaxPooling2D(pool_size(2, 2)), Dropout(0.25), Conv2D(64, (3, 3), paddingsame, activationrelu), BatchNormalization(), MaxPooling2D(pool_size(2, 2)), Dropout(0.25), Conv2D(128, (3, 3), paddingsame, activationrelu), BatchNormalization(), MaxPooling2D(pool_size(2, 2)), Dropout(0.25), Flatten(), Dense(512, activationrelu), Dropout(0.5), Dense(7, activationsoftmax) ]) model.compile( optimizerAdam(learning_rate1e-3), losscategorical_crossentropy, metrics[accuracy]) model.summary()这个结构里每一层都有它的理由。3x3卷积核是VGG系列开始被反复验证过的经验值两个3x3叠加的感受野等同于一个5x5参数量更少。BatchNormalization放在卷积层与池化层之间把每层输出拉回到标准分布显著缓解梯度消失课设浅层网络里可能感觉不明显但它能让你不用花太多心思初始化权重属于性价比很高的后悔药。Dropout是防过拟合的核心手段训练时按比例随机丢弃神经元迫使网络不依赖特定节点。注意卷积块后Dropout取0.25全连接层后取0.5因为全连接层参数量大最容易过拟合。激活函数选择ReLU而不是sigmoid原因很简单ReLU的梯度在正区间恒为1不会像sigmoid一样在两端饱和深层网络训练更稳定。3.3 类别权重配置让少数类不被多数类淹没网络结构定了还有一个容易被忽略的配置类别权重。FER2013的类别分布很不均衡disgust类样本量只有happy类的十分之一左右。如果不做处理模型只需要把所有样本预测成多数类就能获得不错的准确率但实际效果是disgust几乎永远识别不出来。解决路径有两条过采样少数类或者在损失函数层面调整权重。Keras里更简单的做法是提前按样本数量计算类别权重传给fit方法。from sklearn.utils.class_weight import compute_class_weight class_weights compute_class_weight( class_weightbalanced, classesnp.unique(y_train), yy_train) class_weight_dict dict(enumerate(class_weights)) print(class_weight_dict)compute_class_weight的balanced模式按公式n_samples / (n_classes * np.bincount(y))自动计算类别权重样本少的类别权重高样本多的权重低。训练时Keras在损失函数前对每个样本的loss乘上对应类别的权重相当于在梯度更新时给了少数类更大的牵引力。注意这里y_train是整数标签数组compute_class_weight要求传入整数标签而不是one-hot。加类别权重后训练的整体准确率数字会略微下降因为模型不再盲目偏重大类这是正常现象。判断模型真实能力不要只看准确率要逐个类看recall和f1-score这个习惯从课设开始就应该养成。4. 把训练跑稳损失函数、学习率调度与模型保存的完整流程4.1 损失函数和优化器选型categorical_crossentropy与Adam损失函数的选择不需要纠结多分类任务用categorical_crossentropy。对应的标签格式可以是one-hot或整数索引两种Keras里整数标签配sparse_categorical_crossentropy也可以。课程设计里我推荐用categorical_crossentropy搭配前面ImageDataGenerator.flow默认的class_modecategorical标签会自动转成one-hot和后续画混淆矩阵时的格式保持一致。优化器用Adam几乎不需要考虑别的选择。Adam对学习率不那么敏感自适应调整每个参数的学习率省去手动做学习率衰减的工程。但Adam不是万能钥匙它前期收敛快后期容易在低位震荡不退。解决办法不是换成SGD而是加学习率衰减策略。这里用ReduceLROnPlateau它会在验证集指标连续若干epoch不上升时自动降低学习率。from tensorflow.keras.callbacks import (ReduceLROnPlateau, EarlyStopping, ModelCheckpoint) reduce_lr ReduceLROnPlateau( monitorval_loss, factor0.5, patience3, min_lr1e-6, verbose1) early_stop EarlyStopping( monitorval_loss, patience8, restore_best_weightsTrue, verbose1) checkpoint ModelCheckpoint( best_model.keras, monitorval_accuracy, save_best_onlyTrue, verbose1) history model.fit( train_generator, steps_per_epochtrain_generator.n // 64, validation_dataval_generator, validation_stepsval_generator.n // 64, epochs50, callbacks[reduce_lr, early_stop, checkpoint], class_weightclass_weight_dict)这段代码是课设训练脚本的标准模板。ReduceLROnPlateau的factor0.5表示学习率乘以0.5patience3表示连续3个epoch验证集loss没有改善就触发衰减min_lr1e-6防止学习率降到不合理。EarlyStopping的patience8意味着容忍8个epoch没有改善触发后停止训练。restore_best_weightsTrue非常关键它会把模型权重自动回滚到验证集指标最好的epoch否则你保存的可能是训练末期已经过拟合的权重。ModelCheckpoint的save_best_onlyTrue确保磁盘上始终保留验证集准确率最高的模型文件。注意模型文件后缀新版Keras推荐.keras旧版习惯用.h5如果部署时加载报错先检查这里。steps_per_epoch和validation_steps的作用是告诉fit每个epoch需要跑多少批数据。train_generator.n拿到的是训练样本总数除以batch_size 64得到批次数。如果漏掉这两个参数Keras在某些版本下会从生成器的默认迭代逻辑里推算出不同的批次数导致每个epoch看到的样本数量不稳定。这里也是深度学习环境配置中常踩的坑Generator接口迭代长度在不同版本之间行为有差异显式指定最稳。4.2 Batch Size与Epochs的取舍小批量、大轮次和显存之间的平衡训练参数里最常被问到的就是batch size和epochs怎么设置。batch size选择受硬件限制。GPU上64到128都常见CPU上32到64比较稳妥因为CPU对大batch的矩阵运算加速有限每次迭代的等待时间反而更长。从梯度估计的角度看batch size越大梯度方向越稳定但达到相同收敛效果的epoch数可能不同小batch会引入更多噪声某些场景下反而有助于跳出局部极小值。课设里没有做严谨消融实验的必要取64即可。epochs的建议是先设50然后观察训练曲线。如果50轮结束后验证集准确率还在上升说明欠拟合继续训练如果验证集已经不再变化而训练集依然下降说明过拟合EarlyStopping会自动拦住。不要盲目追求训练集准确率那不是需要证明的东西——欠拟合时训练集准确率本来就不会高。建议训练时顺手把history里的loss和accuracy画出来作为答辩材料的一部分import matplotlib.pyplot as plt plt.plot(history.history[accuracy], labeltrain_acc) plt.plot(history.history[val_accuracy], labelval_acc) plt.xlabel(epoch) plt.ylabel(accuracy) plt.legend() plt.savefig(training_curve.png, dpi150)训练曲线图在课设说明文档里几乎是必备素材它能直观展示模型是否稳定收敛、有没有明显过拟合。如果val_acc曲线持续低于train_acc且差距越来越大说明泛化能力出了问题优先调整Dropout比例和数据增强强度而不是加深网络。看曲线时还要注意一个细节如果train_acc稳定上升但val_acc在某个点后开始震荡而EarlyStopping一直没触发说明val_loss可能在震荡中偶尔下降此时可以把EarlyStopping的monitor换成val_accuracy或者加大min_delta给训练多留一点空间。4.3 评估模型混淆矩阵和分类报告才是答辩的底气准确率是课设报告里必须有的指标但不能只有准确率。一个纯用accuracy指标评估的训练过程有严重盲区数据不均衡时看起来不错的准确率背后可能是少数类全部预测错误。建议训练结束后立即保存测试集上的分类报告和混淆矩阵图这两样东西几乎是课程设计评分表里最直观的加分项。from sklearn.metrics import classification_report, confusion_matrix import numpy as np y_true np.argmax(y_test, axis1) y_pred np.argmax(model.predict(x_test), axis1) print(classification_report( y_true, y_pred, target_names[Angry, Disgust, Fear, Happy, Sad, Surprise, Neutral])) cm confusion_matrix(y_true, y_pred)这里的前提是x_test和y_test来自第2章里用train_test_split或validation_split划出的测试集y_test经过flow后已经是one-hot格式所以用argmax还原成类别索引。classification_report会输出每个类别的precision、recall、f1-score和总体准确率。看报告时关注两个地方一是disgust和fear这些样本少的类别recall是否明显低于平均线二是happy类别是否出现过拟合比如precision很高但recall不高。混淆矩阵则能直观看出哪些类别容易互相混淆——fear和surprise经常被错分这是人脸表情本身在视觉上的相似性导致的即使更深的网络也难以完全解决。答辩被问到这个问题时可以从表情的肌肉运动模式解释恐惧和惊讶都会睁大眼睛、眉毛上提区别主要在嘴部形态模型学不到这种细粒度特征时自然容易混。5. 避坑指南从训练到实时推理的5个翻车现场5.1 数据不均衡模型把所有表情都判成happy现象训练出的模型在测试集上准确率接近70%但现场演示对着镜头做“厌恶”的表情界面显示“happy”检查输出概率发现模型对多数类happy、neutral的输出概率普遍偏高。原因FER2013中disgust样本只有几百张而happy和neutral各有近万张。模型学到的先验是“所有表情大概率是happy或neutral”因为这样预测loss最低。虽然compute_class_weight已经在损失层面做了修正但训练只改动了整体损失占比模型在真实摄像头数据上依然表现出较强的多数类偏好。解决双管齐下。训练阶段保留类别权重是底线部署阶段在softmax输出上乘一个类别补偿系数系数与训练样本数量成反比归一化到总和为1相当于在推理时人工提升少数类的输出概率。更实际的做法是收集自己或同学的照片涵盖各种表情做一个小规模fine-tune让模型适应真实摄像头的光照分布。课程设计答辩环境下fine-tune的效果往往比调系数更明显因为评委是在现场光照下做演示的。血泪经验课设交付前一周用自己实验室的灯光拍一百张照片做微调比在测试集上多调两天参数管用得多。5.2 训练与部署预处理不一致灰度、归一化与尺寸现象测试集准确率很高摄像头演示却总是输出错误标签预测概率分布很“平”没有明显峰值。典型表现是无论做什么表情七个类别的概率都在0.1上下浮动。原因训练时图像是灰度图、归一化到0-1、尺寸48x48部署时摄像头直接取BGR帧没有灰度转换也没有除以255或者resize时用了与原训练不同的插值方式。模型在0-255的输入分布下看到的是完全不同的数据分布输出概率自然接近均匀分布。解决把数据预处理封装成单一函数训练和部署共用同一个入口。在源码里建立utils.py定义preprocess_face函数功能固定为灰度转换、缩放48x48、除以255训练脚本和GUI脚本都调用它。这样能把这类问题的排查时间从两小时压缩到零。跟预处理绑定的另一个坑是验证集划分方式引发的数据泄露如果先做全局数据增强再划分train/val增强产生的副本可能同时出现在两边验证集准确率虚高实际部署效果掉一大截。正确的顺序是先划分、后增强第2章里通过validation_split完成的就是这个顺序。很多课程设计源码交付时说明文档里写“验证集准确率85%”但评委现场复现只有60%八成就是数据泄露或者预处理不一致导致的。5.3 实时推理的帧率太低、人脸框抖动与模型加载黑匣子现象摄像头画面卡顿帧率不到5帧/秒人脸检测框在相邻帧之间跳来跳去表情标签频繁切换模型文件加载后预测结果和训练时测试集的结果对不上。原因性能瓶颈主要在人脸检测器上Haar级联的detectMultiScale在640x480画面上的耗时通常在30到80毫秒但如果是每一帧都检测还要加上模型推理的耗时帧率自然上不去。人脸框抖动源于Haar级联对位置不敏感连续帧之间人脸微小的移动会导致检测框尺寸和位置跳动而检测框内容变化又直接导致分类结果抖动。模型加载结果对不上绝大多数情况是模型文件由不同版本的Keras保存和加载或者训练时输入做了归一化而加载后的脚本没有做。解决第一个问题是跳帧检测——不是每一帧都做人脸检测而是每5帧检测一次其余帧沿用上一次的人脸框坐标。第二个问题是帧间平滑——对连续多帧的表情概率做指数移动平均或滑动平均标签只在主表情持续超过0.5秒后变化界面显示不再闪烁。对于模型加载问题我现在的习惯是加载模型后先在测试集上跑一遍对比保存时输出的准确率一致了再接GUI。这个习惯不复杂但能避免“训练明明成功了演示却翻车”的尴尬。课程设计的源码包里也应该带上模型加载后的自检脚本这是说明文档里值得写到的一个验证步骤。另一个容易被忽略的是CPU推理速度如果上面这些都优化了还是卡看一下TensorFlow用的是不是CPU版本在GPU机器上训练出的模型到CPU机器部署时首次运行会有一段编译优化时间不是程序卡死等几秒就好。6. 把模型包装成系统Tkinter界面、摄像头实时推理与模型验证6.1 用Tkinter搭一个能演示的表情识别界面课程设计的基本盘是“完整系统”。模型是核心但演示效果决定了评分上限。GUI方案里Tkinter是Python自带库免安装、打包简单符合课设交付需求PyQt更专业但写起来重。布局建议左图右表左侧是带人脸框的摄像头画面右侧用水平长条实时显示七个表情的概率底部放一个标签显示当前主表情。实现时需要注意PIL.ImageTk.PhotoImage的引用要保持否则画面会被垃圾回收变成空白python基础语法不熟的话这里最容易卡住。6.2 跳帧检测、概率平滑与多线程实时推理摄像头读取和界面更新不能放在同一个阻塞循环里否则界面会卡死。常见做法是每30毫秒用after刷新读帧与预测串行执行。核心逻辑如下import cv2 import tkinter as tk import numpy as np import tensorflow as tf from collections import deque class ExpressionApp: def __init__(self, model_path): self.model tf.keras.models.load_model(model_path) self.cap cv2.VideoCapture(0) self.cap.set(cv2.CAP_PROP_FRAME_WIDTH, 640) self.cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 480) self.face_cascade cv2.CascadeClassifier( cv2.data.haarcascades haarcascade_frontalface_default.xml) self.prob_history deque(maxlen20) def predict_frame(self, frame): gray cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) faces self.face_cascade.detectMultiScale( gray, scaleFactor1.1, minNeighbors5, minSize(48, 48)) if len(faces) 0: return None, frame x, y, w, h sorted( faces, keylambda f: f[2] * f[3], reverseTrue)[0] face cv2.resize(gray[y:yh, x:xw], (48, 48)) prob self.model.predict( face.reshape(1, 48, 48, 1) / 255.0, verbose0)[0] self.prob_history.append(prob) smoothed np.mean(self.prob_history, axis0) cv2.rectangle(frame, (x, y), (xw, yh), (0, 255, 0), 2) return smoothed, frame def update(self): ret, frame self.cap.read() if ret: prob, frame self.predict_frame(frame) # 这里更新界面画面与右侧七根概率条 self.root.after(30, self.update)核心技巧是滑动平均deque的maxlen设为20保存最近20帧预测概率取均值作为显示结果避免标签频繁跳变。选取人脸框时用sorted按面积倒序取最大框适合单人演示场景。self.root.after(30, self.update)每30毫秒刷新一次同时这是跳帧检测的入口——可以把detectMultiScale放到一个帧计数器里每5帧执行一次其余4帧沿用最近一次的人脸框坐标。这样在CPU上也能稳定跑到20帧以上实测比逐帧检测流畅得多。模型推理部分也可以用predict_on_batch替代predict省去函数调用开销但输入格式要求更高课设阶段用predict就能满足要求。6.3 验证系统的三种方法测试集、视频回放和盲测最后验证系统是否值得提交从三个角度检查。一是测试集分类报告确认每个类别的recall都大于0.5重点看disgust这类少数类。二是准备一段包含连贯表情变化的视频回放逐帧查看标签是否平滑过渡这能提前暴露概率平滑窗口设得够不够。三是在不同光照条件下让同学现场做表情盲测换几个不同的人脸不要自己对着摄像头自测——自己测永远会放大模型优点。把这三组数据整理进说明文档比任何文字描述都有说服力。其实我第一次做课设时没有意识到这一点只在报告里放了混淆矩阵。答辩时评委问“这个系统在实际环境中表现如何”我只能口头回答“还行”评委明显不太满意。从那以后我再也不省掉盲测这一步——模型的代码写完了系统收尾才算完成。希望帮到你。本文还有配套的精品资源点击获取
返回列表