简介:这是一份面向计算机专业毕业设计、课程设计与期末大作业场景的深度学习面部表情识别项目资源,适合正在完成相关课题的学生及需要项目实战练习的开发者。包内提供完整源码、论文文档、数据集与答辩演示文稿,覆盖卷积神经网络、视觉几何组网络、残差网络等多种模型实现,并附有代码注释、部署说明、数据处理与可视化脚本,可帮助读者理解从数据预处理到模型训练评估的完整流程。资源共三十六个文件,以源码脚本、交互式笔记、论文文档、答辩演示及压缩数据集为主,并包含模型权重文件与示例视频,整体压缩包大小约四百四十六兆字节,目录结构清晰,便于按模块查阅。已有二百七十六人学习下载,项目经导师指导并获九十八分评审,适合作为高分毕业设计的参考范本。
1. 表情识别没那么玄:先搞懂数据长什么样再做模型
很多人拿到“基于深度学习的面部表情识别系统”这个课题,第一反应是找模型、调参、看准确率。但我做了几个类似的人脸相关项目后,最深的感触是:表情识别真正难的不是CNN搭不出来,而是数据层面的坑一个接一个——标签分布极度不均衡、人脸没对齐、光照和姿态干扰大,这些因素比网络结构更影响最终效果。这套系统本质上是一条完整流水线:从数据集读取、人脸检测与裁剪、图像预处理,到CNN训练、评估指标、可视化,再到论文图表和答辩PPT。如果你的目标是高分毕业设计,那重点不是模型有多花哨,而是每个环节都有据可查、可复现、能说清楚为什么这么选。适合谁?适合准备做图像分类类课设或毕设的学生,也适合想快速在本地跑通一个完整深度学习项目的从业者。
2. 数据集与模型选型:为什么多数人首选FER2013和mini-Xception
2.1 FER2013和CK+的取舍:公开数据集该怎么挑
表情识别最常用的两个公开数据集,一个是FER2013,一个是CK+。FER2013来自Kaggle,包含35887张48x48灰度图,分成愤怒、厌恶、恐惧、开心、悲伤、惊讶、中立七类,它的特点是数据量大、噪声多、标签由人工标注,贴近真实场景,但训练难度也高,很多人第一次跑只有六成多准确率。CK+是实验室环境采集的视频帧序列,样本量小得多,大约只有几百个序列,但每帧是正面人脸、表情变化清晰,标签质量高,适合做微调验证或论文里的对比实验。
实际选题时我的建议是:主数据集用FER2013,因为论文需要大样本训练曲线和混淆矩阵;CK+可以作为第二个数据集来做跨数据集验证,证明模型的泛化能力。很多高分论文都采用这种“一主一辅”的搭配,比只用一个数据集显得工作量大且论证充分。
数据集下载后通常是CSV格式,FER2013的每一行是“label + 48x48灰度像素值”,需要自己解析成图像矩阵。这里要特别注意:图像是灰度图,不是三通道,很多新手在这里就搞错了,拿RGB网络去跑,最后输入维度对不上。
2.2 模型选型:为什么推荐mini-Xception而不是大残差网络
表情识别是一个细粒度分类任务,类别之间差异小,比如“恐惧”和“惊讶”经常混淆。常见的做法是直接用VGG16或ResNet50做迁移学习,但这类大模型在FER2013这种小尺寸灰度图上容易过拟合,而且训练一轮就要很久,对毕设来说时间成本不划算。我一般会用一个轻量级的mini-Xception结构,它的核心是深度可分离卷积,参数少、训练快,在FER2013上能达到接近大模型的准确率,非常适合只有单张GPU(甚至只有CPU)的毕设环境。
mini-Xception的设计思路是:先用几层普通卷积把通道数提上去,再用深度可分离卷积堆叠残差块,最后接全局平均池化和全连接层输出7个类别。相比普通CNN,它能更好地捕捉局部纹理差异,而表情识别恰恰依赖眼睛、嘴角这些局部区域的变化。
这个选型的另一个好处是显存占用小,batch size可以开大一些,训练更稳定。如果你的电脑是Mac或普通笔记本,这个模型也能跑,只是时间稍长;如果有NVIDIA显卡,那整个训练过程可以控制在半小时到一小时。
2.3 表情标签的分布陷阱:先统计再训练
拿到数据后不要直接开训,先用脚本统计一下七个类别的样本数。FER2013中“开心”和“中立”样本最多,“厌恶”最少,这种不均衡分布会直接导致模型偏向多数类。常见的处理方法有两种:一是用class_weight给少数类更大的损失权重,二是在数据增强时对少数类做更多随机变换。
统计标签分布的代码很简单,但对后续训练策略影响很大。如果发现某一类样本特别少,后续无论怎么调参,这类别的召回率都会偏低,这是数据决定的,不是模型不行。论文里最好放进一张分布条形图,答辩时解释一下你如何处理不均衡,这比单纯说“我用了一个CNN”有说服力得多。
3. 从原始图片到可训练数据:预处理与数据集划分
3.1 灰度图读入与归一化:先让像素值归一到0到1之间
FER2013以CSV形式存储,第一步是把它解析成numpy数组。标签列是0到6的整数,后面是2304个像素值,按48x48还原成矩阵。注意像素值范围是0到255,直接喂给神经网络会导致梯度更新不稳定,必须先除以255归一化。
这一步我见过不少翻车现场:有人忘了归一化,导致loss震荡不收敛;也有人用OpenCV读图后默认得到BGR三通道,跟灰度图对不上。所以最稳妥的方式是直接从CSV解析,不要中间转图片文件再读一遍,少一个环节就少一个出错点。
归一化之后还需要做one-hot编码,把标签从整数变成7维向量,才能在分类交叉熵损失函数里计算。这一步在代码里用to_categorical一次搞定。
import pandas as pd import numpy as np from sklearn.model_selection import train_test_split from tensorflow.keras.utils import to_categorical # 读取FER2013的CSV文件 df = pd.read_csv('fer2013.csv') # 提取像素列并还原为48x48灰度图像矩阵 pixels = df['pixels'].tolist() X = np.zeros((len(pixels), 48, 48, 1), dtype=np.float32) for i, p in enumerate(pixels): img = np.array(p.split(), dtype=np.float32).reshape(48, 48) X[i, :, :, 0] = img / 255.0 # 归一化到[0,1] # 标签one-hot编码 y = to_categorical(df['emotion'].values, num_classes=7) # 分层划分训练集和验证集,保证每个类别的比例一致 X_train, X_val, y_train, y_val = train_test_split( X, y, test_size=0.2, stratify=df['emotion'].values, random_state=42) print(X_train.shape, X_val.shape)逻辑说明:代码的核心是第8行到第10行的像素解析循环,把字符串按空格切分、转float、reshape成48x48,然后扩一个通道维度变成(48,48,1)。这一步用的是float32而不是float64,主要为了节省内存,35万张图float64会占用更多内存。归一化在构建数组时就完成了,不是后面单独跑一遍。
参数说明:test_size=0.2表示留出20%作为验证集,毕设中这个比例比较常用;stratify参数是sklearn提供的分层抽样,保证7个类别在训练集和验证集中的分布一致,这对不均衡数据很重要;random_state=42固定随机种子,确保每次运行划分结果相同,论文里的结果才能复现。如果后面要加测试集,可以在划分一次,得到train/val/test三份。
3.2 数据增强:用随机变换对抗过拟合
表情识别模型很容易过拟合,因为FER2013里同一张人脸可能有多个相似帧,模型会把背景和身份信息也学进去。常见做法是用ImageDataGenerator做在线增强,每次训练时随机对图像做旋转、平移、缩放、水平翻转,相当于把训练集扩大了若干倍,而且增强后的样本参与每个epoch的训练,模型见过更多变体。
增强参数要看任务来定:旋转角度太大人脸会变形,角度太小又起不到作用。我一般设置在10度到20度之间。另外一定要关掉水平翻转,很多人在这里踩坑——水平翻转会让部分表情语义翻转,这是数据读取层面的问题,后续会在避坑章节展开。
from tensorflow.keras.preprocessing.image import ImageDataGenerator # 在线数据增强,只在训练时使用 datagen = ImageDataGenerator( rotation_range=15, # 随机旋转±15度 width_shift_range=0.15, # 水平平移比例 height_shift_range=0.15, # 垂直平移比例 zoom_range=0.15, # 随机缩放 horizontal_flip=False, # 不做水平翻转 fill_mode='nearest' # 超出边界用最近邻填充 ) # 搭配flow方法,在生成器内部完成增强并分批输出 train_generator = datagen.flow(X_train, y_train, batch_size=64) val_generator = ImageDataGenerator().flow(X_val, y_val, batch_size=64)逻辑说明:数据增强不是把图片提前生成好存到磁盘,而是每个epoch在内存里随机变换后直接送入训练,这样每次迭代看到的样本都不一样,变相增大了数据量。fill_mode='nearest'处理旋转或平移后产生的空白区域,用最近的像素填充,避免出现黑色边框干扰特征提取。
参数说明:rotation_range=15表示图像最多旋转15度,角度过大会让眼睛和嘴巴的位置偏移太多,模型学不到稳定特征。width_shift_range和height_shift_range是位移比例,0.15就是上下左右最多移动15%的像素宽度。zoom_range控制在15%以内,太大的缩放会导致人脸在画面中过小或过大。验证集不用增强,因为评估时需要用原始图像反映模型的真实能力。
3.3 为什么不建议先做人脸检测再裁剪
很多参考代码会在预处理环节调用OpenCV的人脸检测器,把每张图的人脸区域裁剪出来再训练。对FER2013来说这是多余的——数据集本身已经是以人脸为中心的图像,再检测一次反而可能误检或裁剪偏移,把眉毛额头切掉。我在初版代码里也加过这一步,结果准确率掉了两个点,后来直接去掉。
如果你用自采数据集或真实摄像头画面,那做人脸检测是必要的,因为画面里不止一张脸,需要先定位再识别。常见做法是用OpenCV的Haar级联检测器,或者用MTCNN获取五个关键点做对齐。但毕设里用FER2013,没必要给自己增加这个变量。论文里如实写“数据集本身已做人脸的粗对齐”,这是合理的交代。
如果你确实想做更完整的系统,可以把人脸检测放到推理阶段而不是训练阶段——训练用原始FER2013图,部署时摄像头画面先裁剪人脸再送到模型。这样训练和推理的输入分布更一致。
4. 训练与评估:从搭建mini-Xception到画出混淆矩阵
4.1 模型搭建与损失函数选择
模型部分建议直接用mini-Xception,而不是自己从零发明网络结构。原因是毕设需要的是“可解释、可复现”,你选一个公开验证过的结构,论文里讲清楚它的设计动机,比自己拼一个网络、效果却解释不清要加分。mini-Xception的完整实现在Keras里大约六七十行,核心就是用SeparableConv2D替代普通卷积来减少参数。
分类层输出7个节点,激活函数用softmax,把输出变成7个类别的概率分布。损失函数用categorical_crossentropy,这是多分类任务的标准选择,它计算预测分布与真实分布之间的交叉熵,梯度更新方向明确。优化器我一般选Adam,初始学习率设1e-3,因为Adam对学习率的敏感度比SGD低一些,更适合课设环境。
from tensorflow.keras.models import Model from tensorflow.keras.layers import Input, Conv2D, SeparableConv2D, MaxPooling2D from tensorflow.keras.layers import GlobalAveragePooling2D, Dense, Dropout, BatchNormalization, Activation, Add def mini_xception(input_shape=(48, 48, 1), num_classes=7): inputs = Input(shape=input_shape) # 第一层普通卷积,把通道数提到8 x = Conv2D(8, (3, 3), padding='same', use_bias=False)(inputs) x = BatchNormalization()(x) x = Activation('relu')(x) # 堆叠4个残差块,使用深度可分离卷积 for filters in [16, 32, 64, 128]: # 残差主分支 residual = x x = SeparableConv2D(filters, (3, 3), padding='same', use_bias=False)(x) x = BatchNormalization()(x) x = Activation('relu')(x) x = SeparableConv2D(filters, (3, 3), padding='same', use_bias=False)(x) x = BatchNormalization()(x) x = MaxPooling2D((2, 2), strides=(2, 2))(x) # 残差旁路:对输入做卷积和池化,使shape与主分支一致 residual = Conv2D(filters, (1, 1), strides=(2, 2), use_bias=False)(residual) x = Add()([x, residual]) # 全局平均池化替代全连接层,减少参数量 x = GlobalAveragePooling2D()(x) x = Dropout(0.5)(x) outputs = Dense(num_classes, activation='softmax')(x) model = Model(inputs, outputs) return model model = mini_xception() model.summary()逻辑说明:深度可分离卷积把标准卷积拆成空间卷积和逐点卷积两步,参数量大约是普通卷积的十分之一。残差连接解决了深层网络的梯度消失问题,让梯度可以直接从输出层传到浅层。全局平均池化替代Flatten加全连接层,能大幅减少参数量,同时保留空间信息的全局统计特征。
参数说明:for循环里filters从16到128逐层翻倍,这是比较常见的通道数增长策略,浅层学边缘纹理,深层学语义特征。每个残差块内部做两次SeparableConv2D,然后接2x2最大池化缩小特征图尺寸——48x48经过4个残差块后变成3x3。Dropout放在最后一层之前,比例0.5,训练时随机丢弃一半神经元来抑制过拟合。BatchNormalization放在卷积和激活之间,可以让每层输入分布更稳定,同时允许模型使用更大的学习率。
4.2 模型训练与关键回调:早停、学习率衰减、模型保存
训练时不要裸跑fit,要配合几个callback。我最常用的是这三个:ModelCheckpoint保存验证集准确率最高的权重,EarlyStopping在监控指标不再提升时提前结束训练,ReduceLROnPlateau在loss陷入平台期时自动降低学习率。这三个组合下来,基本不需要手动盯着训练过程。
问题是怎么判断“不再提升”。我通常监控验证集准确率,patience设为5,意味着连续5个epoch没有提升就停止。ReduceLROnPlateau的patience设2到3,意思是连续两三个epoch没提升就降学习率,让模型在更小的步长下继续优化。
from tensorflow.keras.callbacks import ModelCheckpoint, EarlyStopping, ReduceLROnPlateau # 训练过程自动保存最优权重 checkpoint = ModelCheckpoint( 'best_model.h5', monitor='val_accuracy', save_best_only=True, mode='max', verbose=1 ) # 验证集准确率连续5个epoch不提升就停止 early_stop = EarlyStopping( monitor='val_accuracy', patience=5, restore_best_weights=True ) # 验证集loss连续2个epoch不下降就把学习率减半 reduce_lr = ReduceLROnPlateau( monitor='val_loss', factor=0.5, patience=2, min_lr=1e-6 ) history = model.fit( train_generator, validation_data=val_generator, epochs=50, batch_size=64, callbacks=[checkpoint, early_stop, reduce_lr] )逻辑说明:fit阶段把train_generator直接传进去,模型会自动按batch_size从增强器里取数据。这里没有手动写循环,所有数据流转交给框架处理。history对象会在训练结束后保存每个epoch的loss和accuracy,后面可以直接画曲线。
参数说明:save_best_only=True保证磁盘上只保留验证集准确率最好的那一份权重,而不是每个epoch都覆盖。restore_best_weights=True是后悔药,意思是早停之后把模型权重回滚到历史最优状态,而不是用最后一个epoch的参数。factor=0.5每次降一半学习率,min_lr限制下限防止无限衰减。epochs设50,但通常early stopping会在15到25个epoch触发,这里50是一个上限。
如果你训练时发现验证集准确率一直持平甚至下降,先不要怪模型不行,优先检查这三件事:归一化是否做了、class_weight是否生效、数据增强是否过强导致样本失真。这三项比换网络结构对结果的影响大得多。
4.3 评估指标怎么算:从准确率到混淆矩阵
训练完成后你需要回答“模型到底哪里好、哪里不好”,这时只用一条准确率是不够的。表情识别里“愤怒”和“厌恶”经常互相误判,“恐惧”经常被分到“惊讶”,这些模式的分布只有混淆矩阵能展示。论文里放一张带颜色深浅的混淆矩阵图,答辩时能直接指着说“当前最大的混淆出现在恐惧和惊讶之间”,这比念准确率数字有说服力太多。
评估代码里还要注意一个细节:测试数据不要用增强器,要用原始图像。增强只服务于训练,验证和测试必须和真实场景一致。
from sklearn.metrics import confusion_matrix, classification_report import numpy as np # 模型对验证集做预测 y_pred = model.predict(X_val, batch_size=64) y_pred_classes = np.argmax(y_pred, axis=1) y_true_classes = np.argmax(y_val, axis=1) # 打印每个类别的精确率、召回率、F1 emotion_labels = ['Angry', 'Disgust', 'Fear', 'Happy', 'Sad', 'Surprise', 'Neutral'] print(classification_report(y_true_classes, y_pred_classes, target_names=emotion_labels)) # 生成混淆矩阵,行是真实标签,列是预测标签 cm = confusion_matrix(y_true_classes, y_pred_classes) print(cm)逻辑说明:predict返回的是每个样本属于7个类别的概率分布,要取argmax得到预测类别才能和真实标签比较。classification_report一次给出每个类别的precision、recall、F1-score,这正好用来定位“哪个类拉低了整体准确率”。混淆矩阵的行列含义要记清楚,画图时如果行列弄反了,整个论文的解读就错了。
参数说明:batch_size=64是推理时的批次大小,主要影响内存占用,不改变结果。emotion_labels的顺序必须和训练时one-hot编码的类别顺序一致,否则输出标签会错位。FER2013原始的类别顺序就是0到6对应的这七个词,不要自己改顺序。
如果某个类别recall特别低,比如厌恶只有30%,不要慌。先在论文里如实写“该类别在数据集中样本量较少,导致模型难以充分学习其特征”,然后展示你的解决尝试——class_weight是否试过、增强是否加强过。答辩老师更看重你分析和解决问题的过程,而不是一个完美的数字。
5. 避坑手册:表情识别训练中我踩过的五个坑
5.1 水平翻转让模型“左右不分”
现象:开启horizontal_flip=True后验证集准确率不升反降,而且训练曲线一直在震荡。
原因:表情在水平翻转后语义会发生改变——人脸左右不对称性虽然不强,但部分表情比如“厌恶”在翻转后特征分布被打乱,标签却没有跟着变。FER2013的标注是假设人脸的原始朝向,翻转等于给模型喂了“同一张脸两种标签”的错误样本。
解决:训练时关闭水平翻转。如果一定要做翻转增强,只翻转那些标签在翻转后语义不变的类别,但这实现起来非常复杂,毕设里不值得。我直接把horizontal_flip固定为False,之后再没在这个问题上翻车。
5.2 从CSV解析时少了一个维度
现象:报错ValueError: operands could not be broadcast together with shapes (48,48) (48,48,1),或者模型第一层输入维度不匹配。
原因:读CSV时像素字符串转成numpy数组后形状是(48,48),但Keras的Conv2D层期望输入是(高, 宽, 通道数),灰度图也要有最后一维。
解决:用np.expand_dims(img, axis=-1)或者reshape(48,48,1)补上通道维度。类似的错误还发生在把RGB数据集直接传给输入形状为(48,48,1)的模型时,两个问题是同一个根源:形状描述不一致。写完数据加载代码后,先print(X.shape)看一眼再继续往下写,这一步能帮你省半小时排查时间。
5.3 验证集准确率高但实际拍一张照片就识别错
现象:valid训练阶段准确率一直不错,但用摄像头拍一张真人照片去预测,结果错得很离谱。
原因:评测出来的高准确率是在FER2013的验证集上得到的,这套数据来自互联网图像,图像风格和你摄像头的画面差异不小。另外验证集本身也有标注噪声,有些图表情本来就模棱两可。
解决:训练完成后一定要建一个“自采测试集”——用摄像头或者手机拍自己几张不同表情,按同一套预处理流程推断。这一步在论文里叫“真实场景评估”,它能证明你的模型不止能跑通公开数据集,还能落地。如果自采测试准确率低,不要急着改模型,先检查预处理是否和训练时一致,包含灰度和归一化。
5.4 训练loss降到0.3左右就不动了
现象:loss在某个值附近震荡,准确率也跟着上不去,学习率调到很小也没用。
原因:大概率是当前loss达到了模型容量和数据类型下的一个局部平坦区。FER2013的标注噪声比较大,少数样本连人眼都难分辨,模型很难继续收敛到更低loss。另一种可能是数据增强强度太大,图像被旋转缩放后变形严重,模型根本学不到干净的特征。
解决:先打印几个增强后的样本,肉眼确认图像没有明显形变。然后确认学习率在训练中是否已经降到很小的值,如果已经到1e-5附近还停滞,那就是模型容量到底了,继续训练半天收益很小。对这种数据集,验证准确率55%到65%是正常区间,不要被网上那些90%以上准确率带偏——很多是只选用CK+采样出来的结果,和FER2013不具可比性。
5.5 训练时显存不够,OOM报错
现象:训练到某个epoch直接崩掉,报错Killed或者ResourceExhaustedError。
原因:batch_size开太大,或者数据增强在内存里同时处理了过多图像。尤其是在CPU环境下跑深度学习项目,内存溢出的概率比GPU显存溢出更高。
解决:把batch_size从64调成32甚至16,看看峰值内存有没有降下来。另一个常见做法是关掉图形界面、关掉其他占用内存的程序。如果你用的是TensorFlow,可以加一句限制显存增长的代码:
import tensorflow as tf # 按需分配显存,而不是一次性占满 gpus = tf.config.experimental.list_physical_devices('GPU') if gpus: try: for gpu in gpus: tf.config.experimental.set_memory_growth(gpu, True) except RuntimeError as e: print(e)逻辑说明:默认TensorFlow会预占全部显存,用set_memory_growth改成按需增长后,显存只有实际计算时才分配。这在同时跑多个任务时尤其实用,是破解OOM最简单的手段。
参数说明:如果显存还是不够,就再降低batch_size,同时把图像尺寸保持48x48不要放大。有些参考代码会把图缩放到128或224,准确率提升有限但内存暴增,毕设环境往往撑不住。
6. 把模型用起来:摄像头实时推理与答辩素材制作
训练部分完成后,整个项目还差最后两块拼图:一是让模型能跑起来识别摄像头画面,二是把训练过程和结果转成答辩用的图表。很多人把模型训完就停了,结果答辩现场只能展示一张准确率曲线,这不够。让模型实时识别你的脸,效果远比静态图震撼。
推理脚本的核心是:读取摄像头画面 → 人脸检测 → 预处理成48x48灰度图 → 模型预测 → 在画面上叠加标签。人脸检测用OpenCV的Haar级联,它虽然不算最精准,但速度快、免训练,对毕设足够。预测时注意每次都要走跟训练时完全相同的预处理流程——灰度化、缩放、归一化,任何一步不一致都会显著影响结果。
import cv2 import numpy as np from tensorflow.keras.models import load_model # 加载训练好的最优权重 model = load_model('best_model.h5') emotion_labels = ['Angry', 'Disgust', 'Fear', 'Happy', 'Sad', 'Surprise', 'Neutral'] # 加载OpenCV自带的人脸检测器 face_cascade = cv2.CascadeClassifier( cv2.data.haarcascades + 'haarcascade_frontalface_default.xml' ) cap = cv2.VideoCapture(0) while True: ret, frame = cap.read() if not ret: break gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) faces = face_cascade.detectMultiScale(gray, scaleFactor=1.1, minNeighbors=5) for (x, y, w, h) in faces: # 从灰度图裁剪人脸区域并缩放到48x48 face = gray[y:y+h, x:x+w] face = cv2.resize(face, (48, 48)) face = face.astype(np.float32) / 255.0 face = face.reshape(1, 48, 48, 1) # 预测类别并得到置信度 pred = model.predict(face, verbose=0)[0] emotion_idx = np.argmax(pred) confidence = np.max(pred) # 在画面上画框和标签 cv2.rectangle(frame, (x, y), (x+w, y+h), (0, 255, 0), 2) label = f'{emotion_labels[emotion_idx]}: {confidence:.2f}' cv2.putText(frame, label, (x, y-10), cv2.FONT_HERSHEY_SIMPLEX, 0.8, (0, 255, 0), 2) cv2.imshow('Facial Expression Recognition', frame) if cv2.waitKey(1) & 0xFF == ord('q'): break cap.release() cv2.destroyAllWindows()逻辑说明:整个循环体就是“读一帧 → 检测人脸 → 预处理 → 预测 → 画框画字”的通用流程。detectMultiScale返回的是人脸矩形框坐标,这里直接从灰色图中裁剪,因为模型输入是灰度图,不需要把彩色图再转换一次。置信度confidence取预测分布中的最大值,它代表模型对这个判断的把握程度,但不是一个校准过的概率,不要拿它跟精确率混为一谈。
参数说明:scaleFactor=1.1表示检测时图像每次缩小10%,数值越小检测越精细但速度越慢;minNeighbors=5表示一个候选区域至少要有5个邻近检测框才算有效,数值越大误检越少但漏检可能增加。这两个参数在答辩时经常被问,要能说清楚它们各自控制什么。
关于答辩PPT素材,这里有三个最值得放进去的图:训练和验证的loss曲线、准确率曲线,以及混淆矩阵。前两张直接从history对象里画,不需要额外收集数据。另外自采测试集的结果做成一个小表格,每个表情一行,列出精确率和召回率,再附上几张实时识别的截图。这一套素材下来,答辩内容基本就完整了。
我的习惯是训练完成当天就把log和图片存好,用matplotlib一次性生成所有图表,不要拖到答辩前一周再补。整个项目走下来,最大的感受是:表情识别的难点不在网络结构和代码技巧,而在于数据处理的每道工序是否扎扎实实做对了。把数据管好,准确率自然会上去;把过程记录下来,答辩才有话可说。希望帮到你。
本文还有配套的精品资源,点击获取