
简介YOLOv8人脸表情识别训练权重与配套数据集面向有目标检测基础、希望快速落地表情识别项目的开发者。资源不仅提供了训练好的权重还包含已划分train/val/test的完整数据集并内置data.yaml配置文件支持YOLOv5、YOLOv7、YOLOv8、YOLOv9等算法直接训练标签为txt格式涵盖anger、happy、sad、surprise四类表情。压缩包共2000个文件大小114.27MB其中主要包含678个jpg图像、602个txt标签以及yaml配置、Python训练脚本、Markdown说明文档、C推理部署源码等方便对照学习与二次开发。目前已有1432人学习目录结构清晰数据路径已预先配置适合科研、课程设计或工程落地。通过配套博文可进一步了解检测结果与调参思路帮助快速验证模型效果。1. YOLOv8 做人脸表情识别为什么先要解决“权重数据集”的问题很多工程师初次接触 YOLOv8 算法人脸表情识别训练权重数据集第一反应是赶紧把模型跑通结果在真实摄像头前一测就翻车人脸稍微侧一点、光线一暗识别结果就乱成一团。这个任务的关键其实不在 YOLOv8 的网络结构而在三个容易看走眼的地方——你手里的权重是通用目标权重还是表情微调权重数据集是人脸检测格式还是分类格式以及训练和部署时预处理是否一致。这篇笔记把这三条线串起来讲先讲模型选型与训练参数再讲数据集如何做成 YOLO 格式最后收在避坑和部署上。适合自己标数据、用 CPU 或单块显卡训练、想把模型落到边缘设备上的工程师。不要急着下 demo先把权重和数据集变成能用的东西。2. 目标检测还是纯分类YOLOv8 表情识别的建模与权重选型2.1 表情识别为什么常被做成检测任务而不是七分类纯分类的思路是给一张已经裁剪好的人脸图输出 angry、happy、neutral 这类标签。这个思路单看准确率并不差因为输入不需要框模型可以把全部算力放在表情细节上。但真实场景里的人脸位置是未知的摄像头画面里可能有多个人、不同距离、不同角度。如果你先做一个分类模型还得再写一套人脸检测逻辑去把脸“抠”出来相当于需要两套模型工程复杂度立刻涨了一截。所以更常见的做法是把人脸表情识别当成目标检测任务输入一帧完整图像输出每个人脸框框的类别直接是表情。YOLOv8 是 anchor-free 检测器它的输出头可以直接表达“位置 类别”不需要额外做人脸检测和表情分类的串联。看 YOLOv8 网络结构图时Backbone 提取特征Neck 做多尺度融合Head 输出三个尺度的检测结果。人脸表情属于中低层特征敏感的细粒度任务因此训练时不要盲目改结构先把输入尺寸和训练数据做扎实比任何结构改进都有效。如果项目标题里写的是“YOLOv8 算法人脸表情识别”我一般按下述方式理解输入一整张图片模型同时输出人脸框和表情标签。如果你的输入已经是对齐后的人脸图那可以直接用 YOLOv8 的分类版yolov8-cls但大多数落地场景不是这样的检测做法更通用。2.2 预训练权重选型yolov8n.pt 到 yolov8m.pt 怎么取舍YOLOv8 官方提供在 COCO 大图上预训练过的通用权重比如 yolov8n.pt、yolov8s.pt、yolov8m.pt。训练人脸表情识别时不要从头初始化模型复用 COCO 预训练权重能显著缩短收敛时间尤其是数据量只有几千张时。这里说的“训练权重”有两层含义一是你手里有没有官方预训练权重文件二是项目最终训练出来的 best.pt。先要把这两个概念分开。权重文件模型规模我对它的使用场景yolov8n.pt最小CPU 训练和调试先跑通流程验证数据和标注没问题yolov8s.pt小单张普通显卡训练效果和速度比较均衡yolov8m.pt中等数据量 1 万张以上或者需要部署到边缘设备m 是性价比上限yolov8l/x.pt大人脸表情这种细粒度任务提升有限显存消耗大不推荐一上来就用下载预训练权重最常见的做法是安装 ultralytics 后直接指定 model 名称程序会自动下载。如果网络容易中断我更习惯先手动把 .pt 文件放到工作目录训练命令写成相对路径避免每次都在线拉取。例如先把 yolov8n.pt 放到~/face_expr/weights/下然后用modelweights/yolov8n.pt指定。选型上有一条血泪经验不要因为显存大就直接上 yolov8x。人脸表情数据集中很多是近距离单人脸网络参数大了反而不容易在小样本上收敛。我一般先用 n 跑通再用 s 或 m 做正式训练。对于部署到 RK3588 这类边缘设备的场景m 的大小也差不多够用转成 INT8 后体积和延迟都能接受。2.3 把最小训练命令跑通参数怎么设才算合理在 Ubuntu 20.04 上搭建 YOLOv8 环境CPU 版本的最小安装是装好 ultralytics 和对应 torch 即可。GPU 环境则要先确认显卡驱动和 CUDA下面给出训练命令cd ~/face_expr # 如果还没装环境CPU 版本可这样装GPU 版本请到 PyTorch 官网选 cu121/cu118 等命令 pip install ultralytics pip install torch torchvision --index-url https://download.pytorch.org/whl/cpu # 从 yolov8n.pt 开始训练 yolo train modelweights/yolov8n.pt \ dataface_expr.yaml \ epochs100 \ imgsz640 \ batch16 \ device0 \ patience15 \ cacheTrue命令里每个参数都值得提前理解否则后期排查会很痛苦。model指定预训练权重文件data是数据集配置文件下一章会专门讲。epochs100是最大训练轮数表情识别数据量不大100 轮足够看到趋势再多可能过拟合。imgsz640是输入图片尺寸人脸在画面中占比较大时继续用 640如果人脸是远距离小目标可以提高到 960但训练时间会明显增加。batch16表示每批 16 张图8G 显存跑 640 分辨率可能不够降到 8 或 4 都可以。device0是使用第一张显卡CPU 环境改成devicecpu。patience15是连续 15 轮验证指标不提升就提前停止避免周六日白跑两轮。cacheTrue适合总量不大的数据集把图片提前加载到内存减少磁盘读取瓶颈如果数据集超过 10G建议去掉这个参数。训练过程中会看到 train/box_loss、train/cls_loss、train/dfl_loss 三个损失值。box_loss 负责框位置cls_loss 负责表情分类dfl_loss 负责框分布。三者整体下降只有轻微波动就不用慌张。很多 repo 里给“YOLOv8 模型训练参数含义”的表格核心其实就是上面这七个参数其余参数大多保持默认就够了。3. 数据集制作公开数据集、LabelMe 标注与 YOLO 格式转换3.1 优先选用公开表情数据集别急着自己标自己做数据集非常耗时一张图如果有多个人脸加上表情歧义一小时能标 100 张就算不错了。所以第一步先看有没有可复用的公开人脸表情数据集。常见的有 FER2013、RAF-DB、AffectNet 等但它们的格式差异很大FER2013 是 48x48 灰度小图只有分类标签没有人脸框不能直接训练检测模型RAF-DB 是真实场景图片带基本表情标注部分样本有人脸框AffectNet 数据量大但标签噪音也大。我的原则是优先找“带人脸框”的数据集因为检测模型需要知道脸在哪里。如果拿到的是纯分类数据集就只能先用人脸检测器把人脸区域抠出来再通过裁剪得到训练图等于多绕一步。另一种方式是直接采自己的视频用公开人脸检测器辅助预标注再用 LabelMe 修正。这样数据与你的摄像头视角更贴近模型落地效果通常更好。无论从哪个来源拿数据最终都要统一成 YOLO 格式每张图片对应一个同名.txt文件每一行是类别ID x_center y_center width height坐标用归一化后的 0~1 小数表示。这是 YOLOv8 训练的标准输入别自己发明格式。3.2 LabelMe 转 YOLO 格式转换脚本与关键参数LabelMe 是常用的图像标注工具保存的 JSON 里有shapes每个 shape 包含标注框points和标签label。下面是我常用的转换脚本把 LabelMe 的矩形标注转成 YOLO 格式# labelme_json_to_yolo.py import json import os from glob import glob # 类别顺序一旦确定后续 data.yaml 和训练结果都依赖这个顺序 CLASSES [angry, disgust, fear, happy, sad, surprise, neutral] def convert_labelme(json_path, out_dir): with open(json_path, r, encodingutf-8) as f: data json.load(f) img_w data[imageWidth] img_h data[imageHeight] lines [] for shape in data[shapes]: if shape[shape_type] ! rectangle: continue # 只处理矩形框多边形先跳过 label shape[label] if label not in CLASSES: print(f{json_path}: unknown label {label}) continue (x1, y1), (x2, y2) shape[points] # 保证左上角在右下角之前 x1, x2 min(x1, x2), max(x1, x2) y1, y2 min(y1, y2), max(y1, y2) # 归一化到 0~1 cx ((x1 x2) / 2.0) / img_w cy ((y1 y2) / 2.0) / img_h w (x2 - x1) / img_w h (y2 - y1) / img_h # 防止越界YOLO 不处理越界框 cx max(0, min(cx, 1)) cy max(0, min(cy, 1)) w max(0, min(w, 1)) h max(0, min(h, 1)) lines.append(f{CLASSES.index(label)} {cx:.6f} {cy:.6f} {w:.6f} {h:.6f}) if not lines: return base os.path.splitext(os.path.basename(json_path))[0] with open(os.path.join(out_dir, base .txt), w, encodingutf-8) as f: f.write(\n.join(lines)) if __name__ __main__: in_dir labelme_json out_dir labels os.makedirs(out_dir, exist_okTrue) for jp in glob(os.path.join(in_dir, *.json)): convert_labelme(jp, out_dir) print(converted:, jp)这段脚本的核心是把像素坐标的矩形转成 0~1 归一化坐标。注意CLASSES顺序很关键它必须与后续data.yaml里的names完全一致。如果顺序不一致训练时类别就会错位比如 angry 被当成了 happy这种翻车最难排查。LabelMe 里如果画的是多边形脚本会直接跳过更稳妥的做法是标注时就只画矩形或者把多边形转成最小外接矩形后再转换。转换完成后还需要把图片和标签文件放到同一目录下。建议目录结构为images/train、images/val、labels/train、labels/val。图片名和标签名必须完全一致扩展名一个是.jpg/.png另一个是.txt。3.3 写 data.yaml类别顺序、路径和验证集比例YOLOv8 训练时读一个data.yaml内容很简单但很多返工都出在路径写错或类别顺序不一致。下面是我常用的模板# face_expr.yaml path: /home/user/face_expr # 数据集根目录 train: images/train val: images/val nc: 7 names: [angry, disgust, fear, happy, sad, surprise, neutral]path是绝对路径train和val是相对根目录的子目录。如果训练时提示找不到图片第一件事就是检查这个路径下是否真的存在对应目录。nc必须等于names的长度多一个少一个都会报错。验证集的比例建议取 15%~20%但更重要的是按“人”划分而不是按“图片”划分。如果同一个人的不同表情帧同时出现在训练集和验证集模型相当于提前见过这个人验证集分数会虚高部署到新用户脸上立刻露馅。我一般先把人物 ID 列出来按 ID 切分再随机分配到两个集合。类别顺序要固定最好在项目开始前就确定并写进文档。七类表情的顺序没有强制规定但一旦训练了 best.pt后续推理时输出类别索引就是按这个顺序映射的。如果你后续要多加 contempt 类别不要直接改 names必须重新训练否则旧权重会错位。3.4 样本不均衡与数据增强让少样本类别活下来公开表情数据集普遍存在 happy 样本多、fear 和 disgust 样本少的问题。如果不处理模型会倾向于把不确定的脸全判成 happy 或 neutral整体准确率看起来不错混淆矩阵里少数类召回率却低得可怜。我常用的方法是重采样把少样本类别的图片在训练文件夹里复制几份使每个类别的样本量接近。复制本身会引入过拟合风险所以还要配合数据增强。YOLOv8 默认开启 mosaic、随机平移、HSV 扰动等但对人脸表情任务mosaic 把多张不同人脸拼在一起可能让脸的位置变得奇怪需要适当控制。可以在训练命令里加参数yolo train modelweights/yolov8s.pt \ dataface_expr.yaml \ epochs120 \ imgsz640 \ batch16 \ device0 \ mosaic0.3 \ hsv_h0.02 \ hsv_s0.3 \ hsv_v0.2mosaic0.3表示 30% 的样本使用 mosaic 增强比默认低一些让人脸保持相对完整。hsv_h0.02控制色相变化幅度太大脸色就失真表情特征也会受影响。光照变化倒是可以保留因为真实场景光照本来就不确定。另一个有效增强是随机平移和缩放让模型对人脸位置不那么敏感。如果重采样之后少数类还是不行就要回到标注质量上检查。fear 和 disgust 本身就是易混淆表情眉毛和嘴角的细微差别才是决定性特征。如果标注框太松模型学不到这些细节再调增强也没用。4. 常见问题与踩坑训练到部署的五个真实事故4.1 最高精度很高但恐惧和厌恶永远查不出来现象验证集 mAP50 到 0.85 以上但打开混淆矩阵fear 召回率只有 0.2disgust 几乎全被判断成 angry。原因数据集里 happy、neutral 占大头模型学习时“多数类”主导了梯度。fear 和 disgust 不仅样本少且和 sad、angry 在面部肌肉特征上高度相似模型没有足够样本去区分。解决先把这两个类的样本量通过复制或采集提升到与 happy 相当的规模。然后单独跑一次验证打印每个类别的 recall 和 precision不要只盯总指标。如果采集新数据成本高可以只针对这两个类做更强的局部增强比如随机遮挡嘴部、轻度旋转。4.2 人脸框里掺了头发和背景模型学到的是“头”现象用真实摄像头测试模型对一张只有后脑勺的画面输出了高置信度“happy”对戴帽子的人频繁漏检。原因标注时为了省事把整个头框进去了。框内包含大量头发、帽子、背景模型采集到的特征是“头部轮廓头发颜色”而不是面部表情。解决重新检查标注规范矩形框要贴合脸颊左右到耳朵边缘上到额头下到下巴不要把头发和衣领框进去。如果已经标完数据可以用脚本把矩形框向内收缩 5%~8%。框架之后仍要人工抽检 20% 的标注看看是否真的贴合面部。4.3 CPU 训练跑了一个周末还没收敛现象devicecpubatch16数据集只有 3000 张一个 epoch 要十几分钟跑了 60 轮损失还在高位。原因CPU 跑 640 分辨率的目标检测非常吃力mosaic 增强又额外增加了计算量。batch 太大还会导致内存和 CPU 缓存频繁交换速度更慢。解决先用 yolov8nimgsz480batch8cacheTrue训练 30 轮验证数据流程是否正确。如果损失能明显下降再决定是否购买 GPU 云实例。CPU 环境更适合做小规模实验完整训练还是交给单张 RTX 3060 级别以上的卡。4.4 训练 loss 很漂亮导出 ONNX 后推理结果对不上现象PyTorch 端测试同一张图都是正确结果转成 ONNX 后同样这张图出现框偏移、类别概率完全混乱。原因最常见的是预处理不一致。训练时 ultralytics 用的是 RGB 且归一化到 0~1而自己写的部署脚本用了 BGR还忘记除以 255。另外导出时imgsz640如果实际推理输入是 416输入分辨率变化会影响结果。解决统一导出和推理时的尺寸。导出命令用yolo export modelbest.pt formatonnx imgsz640 opset17推理端读取 ONNX 时先做一次预处理测试读图、转 RGB、除以 255、resize 到 640再和 PyTorch 输出逐字节比较。也可以在部署代码里打印输入张量的 shape 和 mean/std先用一张固定的调试图片和 PyTorch 结果对齐再接入摄像头。4.5 小脸漏检imgsz640 对远距离人脸不友好现象人离摄像头 3 米以上脸在画面里只有 50x50 像素推理时漏检或置信度很低。原因训练数据里大多是被标注框充满画面的近距离人脸模型没有见过这么小的目标。YOLOv8 对 16x16 以下的目标本身就不敏感。解决最直接的办法是训练时调imgsz960但显存和时间成本上升。另一个常见方案是拆成两步先用轻量人脸检测模型框出人脸区域再把该区域裁剪放大后送进表情识别模型。两步方案的缺点是会增加一次模型调用但对小脸场景往往比单模型更稳。5. 训练、评估与损失曲线怎么判断权重真的可用5.1 从训练日志里读关键指标loss、mAP50、混淆矩阵训练完成后输出目录runs/detect/train下有weights/best.pt和weights/last.pt以及results.csv。best.pt是根据验证集指标保存的最优权重last.pt是最后一轮权重一般部署用 best。results.csv每一列对应一个指标包括 epoch、train/box_loss、train/cls_loss、train/dfl_loss、metrics/precision、metrics/recall、metrics/mAP50、metrics/mAP50-95、val 对应损失等。我判断权重能不能用的顺序是先看 train/box_loss 是否降到 0.05 以下量级再看 val/cls_loss 是否收敛最后看 mAP50 和混淆矩阵。很多人只看 mAP50 高于 0.9 就部署这是给自己挖坑。因为人脸表情类别分布不均mAP50 高可能只是多数类强。5.2 用验证集评估 best.pt命令与阈值参数训练结束后单独做一次验证会输出混淆矩阵和每类的 PR 曲线yolo val modelruns/detect/train/weights/best.pt \ dataface_expr.yaml \ imgsz640 \ batch1 \ conf0.25conf0.25是置信度阈值低于这个值的检测会被当作背景。验证集上conf设多少推理时最好也保持多少。如果部署时发现误检多可以提高到 0.4如果漏检多降到 0.15。这个参数不是越高越好需要结合你的业务场景权衡。results.confusion_matrix.png是表情识别项目里最值得看的图它会把每个真实类别和预测类别做成矩阵。对角线越亮越好如果某一整列特别亮说明模型在把所有脸都往这个类别上猜。5.3 用 Python 画损失曲线results.csv 的使用训练过程中想实时看曲线可以用 TensorBoard也可以直接读 results.csv 画图后者更直观且不依赖环境。下面是一个最小脚本# plot_loss.py import pandas as pd import matplotlib.pyplot as plt df pd.read_csv(runs/detect/train/results.csv) # 表头里列名带空格先清理 df.columns df.columns.str.strip() plt.figure(figsize(8, 5)) plt.plot(df[epoch], df[train/box_loss], labeltrain/box_loss, linewidth1) plt.plot(df[epoch], df[val/box_loss], labelval/box_loss, linewidth1) plt.xlabel(epoch) plt.ylabel(loss) plt.legend() plt.grid(alpha0.3) plt.savefig(box_loss_curve.png, dpi150)执行后生成box_loss_curve.png。如果 train/box_loss 一直下降但 val/box_loss 在 20 轮后回升说明过拟合需要回到第 3 章做数据增强或减少训练轮数。如果 train/box_loss 和 val/box_loss 都在缓慢下降说明学习率合适不必急着改参数。CLI 训练时也可以加plotsTrue让 ultralytics 自动保存更多曲线图但要等训练结束才可见。5.4 部署前还要做一次硬样本测试验证集上的指标只是第一关。我习惯在部署前额外准备一段没有参与训练的视频包含室内、室外、侧脸、低头、多人、光线变化等场景用 best.pt 跑一遍统计误检和漏检。这个视频不要选训练集的帧截图至少隔两周再录才能反映真实分布。如果这一步能稳定通过再谈 ONNX 导出和边缘部署否则改训练数据比改部署代码更有效。6. 用训练好的权重做实时推理以及一个提速的小技巧实时推理时ultralytics 的 API 已经封装好了摄像头读取、预处理、后处理和可视化。下面是最简代码# webcam_infer.py from ultralytics import YOLO model YOLO(runs/detect/train/weights/best.pt) # streamTrue 表示使用生成器逐个返回结果避免内存积压 for result in model.predict(source0, streamTrue, conf0.25, imgsz640): if result.boxes is None: continue names result.names for box in result.boxes: cls_id int(box.cls[0]) conf float(box.conf[0]) print(bbox:, box.xyxy[0].tolist(), label:, names[cls_id], conf:, conf)source0是读取摄像头第一个通道。streamTrue情况下循环会不断输出新一帧的结果。如果想要更高帧率可以把imgsz降到 480但小脸场景不建议降太多。另一个实用技巧是关闭可视化不调用showTrue改用saveFalse在服务器上跑推理时能减少一小部分 CPU 开销。如果项目要部署到 RK3588 这类边缘设备我一般先把 best.pt 导出成 ONNX再转到 RKNN。导出时用imgsz640, opset17转换后做 INT8 量化。表情识别这种细粒度任务INT8 后准确率会有轻微下降我习惯先在原设备上用 FP16 跑一遍验证再决定是否上 INT8。这是从很多次部署里换来的教训先确认权重本身没问题再做量化否则你会分不清掉点来自模型还是量化过程。希望帮到你。本文还有配套的精品资源点击获取