
简介这套yolov3人脸表情识别资源包主要面向目标检测初学者和快速搭建表情识别系统的开发者内含基于yolov3训练好的权重文件以及配套的人脸表情识别数据集。压缩包整体约421.15MB文件类型涵盖权重文件、图片样本、txt标注文件与data.yaml配置文件数据集已预先划分train、val、test三个目录并配置好类别名称与路径用户只需根据本机实际路径微调data.yaml即可完成训练准备。标签采用txt格式共4个类别anger、happy、sad、surprise覆盖常见表情识别场景。相比从零收集数据和标注该资源能显著降低训练门槛也方便对比不同yolo系列算法在同一数据集上的效果博主还提供了检测结果参考链接便于学习者快速验证模型输出。目前已有385人浏览/学习适合需要系统练习yolov3/yolov5等算法的人脸表情识别实战用户。 做视觉这块的朋友应该都有共识人脸表情识别说到底是典型的检测分类任务传统思路是先用一个人脸检测器把人脸框出来裁下来再丢给分类网络判断是happy还是sad、angry还是neutral。这套流程我用过很长一段时间最大的感触就是慢而且检测框稍微偏一点分类准确率就肉眼可见地往下掉。后来我把整套逻辑换成YOLOv3一个模型端到端搞定人脸框和表情类别一次性输出同时准备了干净可复用的训练数据集和训练权重完整跑通了从数据准备、模型训练到OpenCV部署的整条链路。这篇就把整个方案的思路、参数、代码和实际踩过的坑都整理出来。这篇文章适合正在做表情识别项目、或者想用YOLO系列训练自己数据集的朋友参考。不管你是刚接触目标检测的新手还是已经在用YOLOv5/YOLOv8但想了解v3这套轻量方案的工程师里面的数据格式转换、anchor聚类、训练参数和部署代码都是可以直接抄作业的。1. 为什么选YOLOv3做表情识别而不是先检测再分类1.1 端到端方案和传统两阶段方案的本质区别传统两阶段方案里人脸检测用MTCNN或者Dlib表情分类用ResNet或者MobileNet两个模型串行工作。MTCNN先输出人脸框我们要把这块区域裁出来、缩放、归一化再送进分类网络整个链路多一次图像转换、多一次前向推理。在视频流场景下720p分辨率跑一帧基本在80到100毫秒换算下来只能勉强到10到15帧如果还要同时处理多张人脸掉帧更明显。YOLOv3是单阶段检测器backbone提取特征之后在三个不同尺度的特征图上直接回归目标框坐标和类别概率。人脸框和表情类别是在同一次前向推理里完成的没有中间裁剪和二次缩放。我自己用一张GTX 1080Ti做720p视频流测试YOLOv3端到端推理能稳定跑到30多帧传统两阶段方案只有十几帧。这个差距在实时交互场景里是决定性的所以最终选了YOLOv3。1.2 YOLOv3在这个场景的独特优势以及为什么不追新现在YOLOv5和YOLOv8是主流但做工程量产的时候YOLOv3依然有不可替代的价值。Darknet训练出来的.weights文件没有任何框架依赖直接用OpenCV DNN就能加载推理边缘设备部署成本极低。而YOLOv5/v8训练出来的是PyTorch权重要部署到Jetson或者树莓派上还得转ONNX、转TensorRT中间每一步都可能出问题。从训练角度看表情识别是一个类别数不多通常7类、目标尺寸偏小人脸在整幅图里占比很小的任务。YOLOv3的网络结构更简单直接改cfg文件就能适配不需要动训练脚本的核心逻辑。而且显存占用比新模型低很多GTX 1060这种老卡也能跑训练。对于想快速验证想法、或者算力有限的朋友来说YOLOv3是性价比最高的选择没有之一。2. 数据集准备从公开数据到YOLO格式的完整流程2.1 推荐的表情识别数据集和选型建议表情识别领域的公开数据集主要有几个选择我分别说下特点数据集规模优点缺点适用场景FER201335,887张量大、类别均衡48x48灰度图分辨率低、标签噪声不小快速验证流程RAF-DB约30,000张真实场景、质量高部分类别样本偏少检测训练主数据AffectNet约450,000张规模巨大标签质量参差、需深度清洗有算力时的增量训练自采人工标注看需求完全可控、贴合场景耗时耗力最终商用落地我实际组合方案是RAF-DB为主补充自己用手机和网络爬虫采集的3000多张侧脸、遮挡、暗光场景图最后合并去重。如果只是想先把流程跑通用FER2013也能做但48x48的分辨率实在太低直接放大到416x416会丢失大量人脸细节我自己试过训练曲线波动很大不建议用来做检测训练的底料。2.2 VOC转YOLO格式的实用脚本大部分公开人脸数据集提供的是VOC格式的XML标注而YOLOv3需要的标注是TXT文件每行一个目标格式为class_id、x_center、y_center、width、height坐标全部要归一化到0到1。这个转换脚本我每次换数据集都要用直接贴出来import os import xml.etree.ElementTree as ET # 必须和cfg里的classes顺序保持一致 classes [angry, disgust, fear, happy, neutral, sad, surprise] def convert_annotation(xml_path, txt_path): tree ET.parse(xml_path) root tree.getroot() size root.find(size) w int(size.find(width).text) h int(size.find(height).text) lines [] for obj in root.iter(object): cls_name obj.find(name).text if cls_name not in classes: continue cls_id classes.index(cls_name) box obj.find(bndbox) x1 float(box.find(xmin).text) y1 float(box.find(ymin).text) x2 float(box.find(xmax).text) y2 float(box.find(ymax).text) # 关键除以图片宽高做归一化 x_center (x1 x2) / 2.0 / w y_center (y1 y2) / 2.0 / h box_w (x2 - x1) / w box_h (y2 - y1) / h lines.append(f{cls_id} {x_center:.6f} {y_center:.6f} {box_w:.6f} {box_h:.6f}) with open(txt_path, w) as f: f.write(\n.join(lines)) xml_dir Annotations txt_dir labels os.makedirs(txt_dir, exist_okTrue) for xml_file in os.listdir(xml_dir): if xml_file.endswith(.xml): convert_annotation( os.path.join(xml_dir, xml_file), os.path.join(txt_dir, xml_file.replace(.xml, .txt)) )这里有个特别容易踩的坑有些人会把VOC里的像素坐标直接写进TXT没有除以图片宽高。YOLO训练脚本读进来之后box坐标全是大于1的数loss直接炸掉。无论用哪个训练代码归一化是硬性要求。2.3 类别不平衡问题怎么处理七类表情里neutral和happy的样本通常最多disgust和fear最少。我整理的数据里disgust样本只有happy的四分之一。如果直接用原始分布训练模型会把所有拿不准的表情都判成neutral直观表现就是验证集里neutral的precision很高、recall正常但其他类别的recall全线飘绿。我的处理策略按优先级排训练时对少数类做重采样让每个epoch里模型多看到几次disgust样本在数据增强阶段对少数类做更多次的水平翻转、小角度旋转、随机裁剪人脸识别场景里这些增强方式很安全如果想彻底一点用PyTorch重写检测loss给每个类别加权重但这个工作量稍大另外提醒一句公开数据集的标签噪声比想象中严重。FER2013里happy标成surprise、angry标成fear的情况不少我手动清洗掉400多张明显错标的数据之后验证集准确率直接提高了近3个百分点。数据清洗的价值往往被严重低估。3. YOLOv3网络配置修改与训练参数选择3.1 cfg文件三处必改参数YOLOv3的cfg文件是网络结构的唯一描述训练自己的数据集必须改三处少一处都训不对每个yolo层前面的卷积层filters。原版是255对应COCO数据集80个类别计算公式是3 * (80 5)。改成7类表情后filters 3 * (7 5) 36每个yolo层的classes字段改成7三个yolo层的classes都要改只改一个会导致训练中途维度不匹配直接报错cfg文件里一共三个yolo层分别负责13x13、26x26、52x52三种尺度特征图的检测改的时候仔细点别漏。骨干网络权重建议用官方在ImageNet上预训练好的darknet53.conv.74来初始化不要随机初始化从头训。表情识别数据集本身规模不大随机初始化会造成收敛极慢、局部最优的问题用预训练权重做迁移学习是标准做法。3.2 用K-Means重新聚类Anchor小脸检测率翻倍YOLOv3默认的9组anchor是在COCO数据集上聚类出来的尺寸普遍偏大而人脸在整幅图中的占比通常只有几十分之一直接沿用默认anchor的结果就是小脸完全检测不到。针对我自己的数据集重新做K-Means聚类后得到的anchor如下小脸52x52尺度7x9、11x16、15x21中脸26x26尺度21x25、26x32、33x39大脸13x13尺度41x48、53x63、76x81把这组数值替换进cfg文件里每个yolo层对应的anchors参数验证集mAP直接从0.74涨到0.81。这个步骤是全部调优里效果提升最大的一步却最容易被忽略。3.3 训练命令和一组实测稳定的参数Darknet环境下训练命令长这样./darknet detector train cfg/face.data cfg/yolov3-face.cfg darknet53.conv.74 -gpus 0 -dont_show -mapface.data里配置类别数、训练验证集路径和类别名文件classes 7 train data/face_train.txt valid data/face_val.txt names data/face.names backup backup/我这边实测比较稳定的参数组合batch64subdivisions16显存不够就调到32输入尺寸width416height416初始学习率0.001迭代到48000次降到0.0001到54000次再降到0.00001momentum0.9decay0.0005打开多尺度训练每10个batch随机切换320到608之间的输入尺寸这套配置在单张GTX 1080Ti上训练约8小时跑完5万多迭代最终保存的weights在backup目录下。多尺度训练对小人脸鲁棒性帮助很大代价是训练时间变长但效果值得。3.4 训练过程中盯什么指标训练时我主要盯两个东西avg loss的下降曲线和mAP曲线。avg loss正常是从初始的十几一路降到2以下如果loss在2附近震荡超过1万次迭代优先怀疑是学习率太高或者数据里有脏标注。mAP则每个epoch打印一次连续5个epoch不涨就得降学习率。建议额外给每个类别单独统计precision和recall这比只看总mAP更容易发现问题。比如我这边surprise的precision有0.86但recall只有0.61说明大量surprise样本被漏检了这时候就该回头检查这类样本的标注和数量分布。4. 训练权重的推理部署与模型轻量化4.1 OpenCV DNN直接加载YOLOv3权重训练好的.weights文件最省事的地方就是可以用OpenCV直接加载不需要装PyTorch、TensorFlow或者Darknet环境。一个简单的推理脚本import cv2 import numpy as np net cv2.dnn.readNet(yolov3-face.weights, yolov3-face.cfg) layer_names net.getLayerNames() output_layers [layer_names[i - 1] for i in net.getUnconnectedOutLayers()] img cv2.imread(test.jpg) h, w img.shape[:2] blob cv2.dnn.blobFromImage(img, 1/255.0, (416, 416), (0, 0, 0), swapRBTrue, cropFalse) net.setInput(blob) outs net.forward(output_layers) # 后续decode逻辑按置信度过滤、NMS去重、画框标注一个容易忽略的点conf_threshold建议设到0.25左右不要按通用目标检测的0.5去设。人脸表情类别的置信度天然比行人、车辆这类粗粒度类别低把阈值设太高会漏掉大量真实表情。NMS的IOU阈值设0.4太高会把重叠的正脸和侧脸两个框合并成一个表情类别就废了。4.2 模型轻量化YOLOv3-tiny和Int8量化要部署到Jetson Nano或者树莓派这种边缘设备上第一个方案是换YOLOv3-tiny结构。把cfg里的卷积替换成tiny版本检测头保持三个尺度但通道数砍半权重能从240MB压缩到30MB左右Jetson Nano上推理速度能到15帧上下。代价是精度会有一定下降但表情识别这种任务本身对框的精确度要求没那么苛刻值得尝试。第二个方案是Int8量化。Darknet自带量化工具用验证集做校准后转成int8格式实测精度损失在2%到3%以内推理速度接近翻倍。量化前一定要先跑出baseline精度记录不然量化后出了偏差说不清是模型问题还是量化问题。5. 训练和部署中的常见问题排查5.1 训练不收敛、loss乱跳现象是loss在1.5到4之间反复横跳mAP一直不动。按顺序排查先检查TXT标注是否全部归一化有没有坐标值大于1的脏数据再检查face.data里train和valid的路径有没有把验证集混进训练集最后看学习率有人从0.01开始训loss直接训成NaN。我踩过最隐蔽的一个坑数据集里混进了没有标注目标的空图Darknet训练脚本遇到这种图要么报错要么产生极大loss用脚本按标注文件逐张检查图片是否存在、标注是否为空能避开这个坑。5.2 小脸检测不到、表情识别率低优先做两件事做K-Means重新聚类anchor打开多尺度训练这两个配合能解决大部分小目标问题。还不够的话把输入尺寸从416提到608训练耗时接近翻倍但小脸召回率能再涨5个点左右。5.3 收敛后验证集过拟合表情数据集普遍不大训练后期容易过拟合典型表现是训练集mAP很高但验证集mAP连续下降。处理方法优先级从高到低加数据增强随机翻转、裁剪、色彩抖动、把dropout层加上、减小模型容量换YOLOv3-tiny。我用的版本在全连接前的卷积层后加dropout0.5过拟合有明显缓解。5.4 人眼看起来很清楚但mAP就是低这个情况出现过一次排查到最后发现是测试数据里的标注框和真实人脸框偏移过大。检测任务里标注质量直接决定mAP上限与其花大量时间调参不如回头检查标注。这也是我一直强调数据集清洗比模型调参更重要的原因——模型再强也学不会错标的目标。5.5 推理阶段常见问题速查现象原因解决办法检测框偏大或偏小anchor和数据集不匹配重新聚类anchor漏检严重conf阈值太高降到0.25左右相邻两人脸被合成一个框NMS IOU阈值太高降到0.4同一张脸输出多个框NMS IOU阈值太低适当提高推理结果和训练效果差距大推理尺寸和训练尺寸不一致统一输入尺寸这次把YOLOv3做人脸表情识别从数据集处理、网络配置、训练到部署的完整链路都过了一遍尤其是anchor聚类和数据清洗这两个容易被忽视却对效果影响极大的环节实际操作时值得多花时间。最后再分享一个我自己惯用的小技巧训练表情模型前先用WIDER FACE这类大规模人脸检测数据集训练一个纯人脸检测的权重再在这个权重基础上冻结前50层、只微调后面检测头来做表情识别。这样比自己从darknet53.conv.74开始训练效果好很多因为人脸检测任务已经让网络学到了足够泛化的人脸特征表情这种细粒度分类只需要在少量数据上学一个分类边界就够了收敛快、最终精度也更高。手头有标注资源的话非常建议优先试试这个方案。本文还有配套的精品资源点击获取