
简介面向YOLO系列目标检测学习与面部表情识别开发这份数据集包含200张已标注人脸图像覆盖愤怒、悲哀、中立、幸福四类适用于yolov5/v7/v8/v9/v10/v11等主流模型快速训练和验证。数据集已完成训练集与验证集划分并附带data.yaml配置文件下载后可直接调用YOLO工程进行训练无需额外整理数据同时提供yolo格式txt和VOC格式xml两种标签文件分别保存在独立文件夹中标签字段包含目标框的归一化中心点坐标与宽高格式规范清晰方便不同工具链接入。整个压缩包共601个文件包括200张JPG图片、200个txt文本标签、200个xml标签和1个yaml配置体积仅4.25MB轻量便携适合快速跑通完整训练流程也可作为课堂实验或算法对比的基础数据。目前已有136人学习下载对于需要规范表情标注数据、减少数据预处理工作量的开发者而言是一个实用的小型数据集。 去年整理硬盘时翻出一个旧项目包文件名很直白yolo算法-面部表情数据集-200张图像带标签-愤怒-悲哀的-中立的-幸福的.zip。这是之前做的一个小规模人脸表情识别项目基于YOLO目标检测框架用200张带标签的图片训练四分类模型愤怒、悲哀、中立、幸福。这个包虽然小但五脏俱全正好可以拿来说说YOLO在表情识别这类细粒度分类任务上的玩法以及小数据集训练的各类坑和技巧。先说清楚这东西到底能做什么把一张包含人脸的图片喂给模型模型会输出每个人脸的位置框bounding box同时给出表情类别——愤怒、悲哀、中立、幸福。它属于目标检测的一个垂直应用不是单纯的人脸分类而是“定位分类”一步到位。适合想要快速入门YOLO目标检测、接一个能跑的实战项目、或者需要在低算力设备上做实时表情分析的朋友参考。200张图虽然少但如果标注质量高、训练策略得当依然能做出一个效果可用的轻量级表情识别器跑在CPU上也能达到每秒几十帧的检测速度。1. 项目整体思路为什么用YOLO做表情识别1.1 人脸表情识别的两条技术路径业内做人脸表情识别主流上有两条路一条是传统的人脸分类路线先用MTCNN、RetinaFace这类人脸检测器把脸抠出来再送给一个分类网络比如ResNet、MobileNet判断表情这是“先检测后分类”的两段式流程。另一条就是把表情识别直接做成目标检测任务用YOLO这类单阶段检测器同时回归出人脸位置和表情类别一段式搞定。两条路我都实际跑过。两段式的好处是每一段都可以单独调优人脸检测器成熟稳定分类模型也有大量预训练权重可用但缺点是流程长、速度慢在边缘设备上累积延迟明显。YOLO一条路的好处是端到端训练和推理部署简单模型天然带着位置信息检测和分类同时输出而且在小目标、多人脸场景下抗干扰能力不错。代价是——YOLO的类别特征提取能力和纯分类网络比还是有差距尤其是表情这种类别间差异很小、类内差异很大的任务对数据质量和训练技巧要求更高。1.2 200张图的以小博大逻辑这个数据集只有200张标注图像说实话拿来做深度学习训练属于“极度贫血”的配置。常规目标检测数据集动辄上万张比如COCO是12万张图、80个类别。但小数据集不是不能做关键是目标和策略要对。用200张图训YOLO做表情识别要达成的不是“刷榜级别的准确率”而是把整个YOLO训练、标注、验证、部署流程跑通同时在小样本条件下逼出足够好的效果。我在这个项目里的预期是在测试集上达到70%以上的mAP能稳定区分幸福和愤怒这两个差异较大的表情中性和悲哀尽力而为。最终实测mAP50在75%左右幸福类AP最高悲哀类最低基本符合预期。如果你的目标也是“快速落地一个能用的原型”那这个项目的参考价值就很高。2. 数据集结构与标签格式拆解2.1 目录组织YOLO项目基础的根拿到这个zip解压后目录结构是这样的face_expression_dataset/ ├── images/ │ ├── train/ │ │ ├── img_001.jpg │ │ ├── img_002.jpg │ │ └── ... │ ├── val/ │ └── test/ ├── labels/ │ ├── train/ │ │ ├── img_001.txt │ │ ├── img_002.txt │ │ └── ... │ ├── val/ │ └── test/ ├── data.yaml └── README.md这是YOLO系列通用的数据组织方式images存放图片labels存放同名的txt标签文件。图片和标签不能随便扔必须一一对应且文件名完全一致不包括扩展名。我第一次做数据集的时候犯过新手错误图片叫img_001.jpg标签叫img_001.txt但一个在images/train一个在labels/val结果就是模型训练时找不到对应的ground truth直接报错。后来养成习惯每处理一批图先跑一个脚本校验两边文件名是否对齐。2.2 归一化坐标YOLO标签的底层逻辑YOLO的标签格式和COCO那种JSON格式完全不同。每个.txt文件里一行代表一个目标格式是class_id x_center y_center width height对应到这张图里的人脸框。这里有几个关键词需要理解坐标全部是归一化的范围在0到1之间。也就是说x_center 人脸框中心点的x坐标 / 图片宽度y_center 人脸框中心点的y坐标 / 图片高度width 人脸框宽度 / 图片宽度height 人脸框高度 / 图片高度。为什么要归一化一是让模型不依赖输入图片的绝对尺寸无论输入是640x640还是1280x1280坐标空间统一二是数值范围小模型更容易收敛。举个例子一张1920x1080的图片里有一个人脸框左上角坐标是(500, 300)右下角坐标是(900, 700)。换算成YOLO格式框宽 900 - 500 400框高 700 - 300 400中心点x 500 400/2 700中心点y 300 400/2 500归一化x_center 700 / 1920 ≈ 0.3646y_center 500 / 1080 ≈ 0.4630width 400 / 1920 ≈ 0.2083height 400 / 1080 ≈ 0.3704所以这一行就是2 0.3646 0.4630 0.2083 0.3704假设类别序号是2对应“中立”。这个计算过程看起来简单但手动算很容易出错。我一般直接写个脚本用OpenCV读取图片尺寸然后从标注工具导出的XML或JSON里读坐标自动完成转换。如果是从KITTI或其他数据集改标注格式过来的核心思路一样先读原始标注再读图片宽高套公式归一化。2.3 数据集划分train/val/test怎么分200张图我按大约8:1:1的比例划分也就是train约160张、val约20张、test约20张。这个比例在数据量极少的情况下比较稳妥。val集用来观察训练过程中的模型表现决定是否早停early stoppingtest集是最后用来评估的“没见过的题目”模拟真实场景效果。有两点值得强调。第一划分时要保证类别分布均衡。比如happy有80张、angry有50张、sad有20张、neutral有50张那val和test里也要按相近的比例抽样不能让val里全是happy导致验证loss失真。我写了个小脚本先按类别分组再每组随机抽样到val和test里。第二划分后就不能再动了训练过程中绝对不能把val的信息通过某种手段“喂”给模型。我见过有朋友为了刷高验证指标反复调整验证集这是自欺欺人上线后立刻现原形。3. 训练配置与关键参数解读3.1 核心训练过程在YOLO中如何配置YOLO系列这里以YOLOv5/v8为例的项目核心文件是data.yaml它告诉模型三件事训练集在哪儿、验证集在哪儿、有哪些类别。我的配置如下train: face_expression_dataset/images/train val: face_expression_dataset/images/val test: face_expression_dataset/images/test nc: 4 names: [angry, sad, neutral, happy]这里有几个点必须注意nc是类别数量number of classes必须和names列表长度一致我在刚开始时把nc写了6而names只列了4个结果训练直接报错names的顺序要和标签文件里的class_id一一对应标签里写0就代表names[0]即angry写3就代表happy顺序错了模型会学到完全错误的东西还不容易察觉。训练命令我用的很简单以YOLOv8为例yolo detect train datadata.yaml modelyolov8n.pt epochs200 imgsz640 batch16解释一下各参数modelyolov8n.pt是加载YOLOv8 nano预训练权重对200张图的小数据集来说nano模型是最合理的起点参数少、不容易过拟合、速度快epochs200是小数据集需要的训练轮数因为数据少模型要反复“看”这些图才能学到特征但也不能太多后面会讲怎么判断何时停下来imgsz640是输入分辨率YOLO默认640x640这个值和标注归一化无关模型会自动resizebatch16是批大小如果显存不够就调小到8或4我在一张8GB显存的GTX 1070上跑这个配置稳定。3.2 模型选型为什么选yolov8n而不是更大的模型YOLO系列有不同规模的版本nanon、smalls、mediumm、largel、xlargex。参数规模依次增大理论精度也越高但对训练数据量的要求水涨船高。在只有200张图的情况下选yolov8l或yolov8x纯粹是给自己找麻烦——模型容量太大很快就死记硬背住了这200张图验证和测试性能反而断崖式下跌。我实际对比过在同样200张图、100个epoch的条件下yolov8n的val mAP50约0.72yolov8s约0.75看着高一点但yolov8s的训练时间长了近一倍推理也慢了。综合来看小数据场景下性价比最高的是nano和small之间的选择。如果后续数据量扩充到几千张再换成s或者m版本效果会有实打实的提升。另外YOLO的预训练权重非常重要。直接用modelyolov8n.pt是加载在ImageNet或COCO上预训练过的特征提取层这些通用特征边缘、纹理、形状对小数据集训练是极好的“先验知识”。如果从零开始训练modelyolov8n.yaml200张图基本训不出什么有用的结果。这里强烈建议不要随便去掉预训练权重除非你有几万张图。3.3 损失函数与训练机制的关键点热词里有“yolo损失函数”这里一并说清楚。YOLO的损失函数由三部分组成边界框回归损失box loss、置信度损失obj loss和分类损失cls loss。在YOLOv5中box loss用的是CIOU Loss在YOLOv8中用的是DFLDistribution Focal Loss和CIOU的组合。分类损失用BCEWithLogitsLoss置信度损失也是BCE。对小数据集来说分类损失往往占比最高因为四类表情之间的特征差异很小模型很容易把“中性”和“悲哀”搞混。我的经验是训练时盯着cls_loss看如果它降不下去大概率是数据本身有问题——要么标注不一致要么某类样本太少。YOLOv8的loss有三个加权项loss box_gain * box_loss cls_gain * cls_loss dfl_gain * dfl_loss默认的box_gain7.5, cls_gain0.5, dfl_gain1.5适用于大多数场景一般不建议新手改。我唯一一次调整是为了提高表情分类的权重把cls_gain从0.5提到0.8mAP略有提升但val loss震荡变剧烈。所以调loss权重属于双刃剑数据少的时候更要谨慎。3.4 训练过程中的数据增强策略小数据集必须靠数据增强“变出”更多样本。YOLO内置了丰富的在线增强马赛克mosaic、随机翻转flip、HSV色域变换、缩放、平移、旋转等。这些增强默认开启但有几个参数在小数据集下建议手动调。马赛克增强mosaic会把4张图拼成一张训练等于让模型在更复杂的背景中学习。200张图时我建议保留但对表情任务要小心拼图会导致人脸可能被切割如果表情的判别区域比如嘴部恰好被切掉一半模型反而可能学到错误特征。所以我把mosaic的概率从默认1.0调低到0.5。随机翻转fliplr默认0.5对表情识别我有一个特别提醒水平翻转会改变表情的生物学特征吗人脸左右基本对称表情基本对称所以fliplr可用。但如果你将来扩展更多类别比如侧脸、闭眼、吐舌头翻转就要慎重。HSV色域变换我调高了一点hsv_h: 0.015, hsv_s: 0.5, hsv_v: 0.5因为不同摄像头、不同肤色、不同光照下表情区域的颜色差异很大色域增强可以提升泛化能力。4. 实操过程与效果验证4.1 基础训练流程的完整演示这里我把整个实操流程捋一遍方便直接照着做。环境部分假设已经装好了PyTorch和Ultralytics YOLO安装命令一行pip install ultralyticsCUDA版本对应好就行。AMD显卡要看ROCm或者用CPU跑也不是不行就是慢。准备数据集的脚本我用Python写了一个核心逻辑就是遍历原始标注做归一化写文件顺便打印类别统计。这个脚本不复杂关键是校验每生成一个txt就检查坐标是否在0~1范围内有没有负数或大于1的。训练之后模型会在runs/detect/train目录下输出一系列文件。YOLO的训练日志包含每个epoch的box_loss、cls_loss、dfl_loss、precision、recall、mAP50、mAP50-95等指标。我在这个项目里到第100个epoch时mAP50已经到0.7左右140个epoch后基本稳定在0.73~0.75。如果继续训到200mAP不再明显涨损失也不再降那就说明模型已经收敛了。4.2 从损失曲线到模型质量的判断方法训练完成后不要急着拿模型去跑先看两样东西results.png和confusion_matrix.png。results.png里包含三张子图box_loss、cls_loss、obj_loss在train和val上的曲线。如果train loss持续下降但val loss在某个epoch后开始上升就是过拟合的典型信号如果val loss震荡剧烈可能是学习率太大或者batch太小可以尝试把lr0从默认0.01调到0.005或者增大batch。我这里val loss曲线总体平滑下行说明数据集和超参匹配度尚可。confusion_matrix.png能直观看到哪些类被搞混了。我的模型里最严重的混淆是“悲哀”和“中立”在20张test图像上sad被预测成neutral的次数有3次neutral被预测成sad有2次。这在天然语义上也能理解因为“面无表情”和“轻微难过”本来就没有明确边界。从标注层面看我需要对这两类重新过一遍标注把靠近边界的样本重新分类。4.3 推理测试与真实场景验证训练完用一段简短的推理代码在test集上跑一遍yolo detect predict modelruns/detect/train/weights/best.pt sourceface_expression_dataset/images/test模型会把检测结果画框后输出到runs/detect/predict目录。我习惯逐张看图不是只看mAP。mAP是统计指标可能掩盖个别严重错误。逐张看图能发现这样一些问题漏检人脸很小模型完全没框出来、误检把嘴巴当成人脸、重复框一个人脸出了两个框、类别错乱明明是愤怒却标成幸福。解决漏检和误检的几个常用手段调低conf_thres置信度阈值默认0.25到0.1可以让更多低置信度的框出现当然也会带来更多误检调低iou_thresNMS的IoU阈值默认0.7可以让靠得很近的框被合并如果小脸漏检多可以试着把imgsz从640提到960但推理时间会相应增加。我最终把conf_thres定在0.2iou_thres保持0.7在速度和准确率之间取了一个平衡。5. 小数据集的坑与避坑技巧5.1 过拟合200张图最容易掉进去的陷阱训练过程中的过拟合几乎是必然发生的只是程度问题。特征表现在训练集的loss趋近于0mAP接近1.0但验证集指标停滞在0.5左右甚至下降。在YOLO里判断过拟合最直接的方式是看results.png里train和val两条loss曲线之间的距离。200张图时距离会拉开得特别快通常50个epoch后就明显了。面对过拟合我的策略有三个。第一数据增强往强了调把mosaic保留、scale范围拉大scale: 0.9、增加一点旋转degrees: 5。第二加正则化YOLO的weight_decay默认是0.0005小数据集时我会调到0.001有效抑制权重爆炸。第三用早停机制Ultralytics默认有patience100的早停意思是如果100个epoch内val指标没有更好就自动终止。200张图时我把patience设成50因为模型到后期提升空间本来就小没必要等太久。5.2 类别不均衡训练结果偏向多数的应对之道200张图分成四类几乎不可能均匀。我的数据集里happy有70多张sad只有30张左右。训练出来的模型在happy类上的APAverage Precision远高于sad这就是类别不均衡的直接后果——模型见过足够多happy样本学得扎实sad样本太少特征学不到位。处理类别不均衡的常见方案有几种。第一离线扩充少数类用翻转、亮度调整、裁剪等手工生成更多sad样本。我实际做了把sad从30张扩到60张虽然数量还偏少但效果改善明显。第二在线给少数类加权YOLO的class_weights参数可以设置每个类别的loss权重比如把sad的权重设成2.0。第三最笨但有效的办法是——继续收集数据。小数据集项目做到最后瓶颈永远不在算法而在数据。5.3 标注质量比图像数量更关键的变量200张图的标注质量直接决定模型的天花板。我踩过的最大一个坑是标注框大小不一致。有一个阶段我让人帮忙标注一部分框紧贴人脸包括头发和下巴一部分框只框到脸中间训练出来的模型在检测阶段就飘忽不定——回归框一会儿大一会儿小。后来我强制用标准要求框的边界为额头最上沿到下巴最下沿、左耳到右耳才算合格。这一条规范立下来之后模型检测框的稳定性明显变好。另一件值得注意的事是标签错标。200张图里如果有一两张happy被标成了angry模型会有两个感觉一是happy的特征被污染二是angry的特征被污染。在小数据集里一张错标图的破坏力被放大得极其严重。我后来的经验是标注完成后必须有第二个人复核至少逐张过一遍有歧义的样本。如果没有第二个人就自己隔一天再回来看一遍能发现不少当时没注意的问题。6. 后续扩展与部署思路6.1 从YOLOv8n到更高精度的模型演进路线这个200张图的项目跑通之后如果要把它做成产品第一个要做的不是换更大的模型而是扩充数据。就我的感受而言把数据从200张扩到2000张比把模型从nano换成large带来的精度提升大得多。数据达到几千张之后再考虑从yolov8n切成yolov8s或者yolov8m同时把imgsz从640调整到800检测小脸的能力会上一个台阶。如果还想压榨性能可以尝试在其他YOLO变体上做迁移。热词里有“yolo改进”其实常见改进点包括用更高效的主干网络比如在YOLOv8里换用P2层检测小目标、加注意力机制SE、CBAM对表情这种细粒度小目标有效、改进NMS策略等。但每次改进都要用实验说话不要迷信网上说的“加了XX模块涨了5个点”。在小数据集上改进效果波动很大换一个随机种子可能就冲掉了。6.2 边缘设备部署与实时推理优化表情识别最典型的落地场景是摄像头实时分析——比如课堂专注度统计、人机交互情感判断、公共场所人群情绪监测。这时需要在边缘设备树莓派、Jetson Nano、手机端上跑YOLO。我在这类设备上踩过不少坑总结下来顺序是这样先转成onnx格式用onnxruntime或TensorRT推理能比PyTorch原版快2~5倍如果还不够再把模型量化成int8精度显存和内存占用能砍掉70%左右但精度会掉几个点必须实测验证。转换命令很简单yolo export modelruns/detect/train/weights/best.pt formatonnx dynamicTrue yolo export modelruns/detect/train/weights/best.pt formatengine device0第二行的engine格式是TensorRT的专用权重只能在NVIDIA GPU上跑但实测推理速度能到几百帧每秒这种场景下200张图训练出来的轻量模型反而成了巨大优势——不挑硬件跑哪儿都流畅。6.3 数据闭环让小模型越跑越准最后分享一个我认为做CV项目最值得养成的习惯建立数据闭环。模型上线后把推理置信度低于某个阈值比如0.3的检测结果全部保存下来定期人工标注补充进训练集。这种“难例挖掘”积累的数据质量远超随机补充的图片。我后来把200张原始图的项目迭代到几千张mAP从0.75涨到了0.86绝大功劳来自难例。做算法这么多年越来越确信一句话模型结构决定了下限数据质量才是决定上限的钥匙。这个道理在200张图的小项目里体现得比大项目更淋漓尽致。本文还有配套的精品资源点击获取