简介:这份资源提供基于YOLOv8的人脸检测模型,面向计算机视觉开发者、边缘计算部署工程师及需要快速集成人脸检测能力的技术人员,解决从训练权重到多平台推理部署的格式转换问题。压缩包共7个文件,约31.79MB,包含pt与onnx两种通用模型格式,分别适配PyTorch训练微调与跨框架推理;同时提供RKNPU优化版本,覆盖RK3588与RK3576两款芯片的rknn模型,并附带bin、xml等配置文件,便于在Rockchip NPU上直接运行。已有671人学习下载,说明该模型在边缘端人脸检测场景中具有实际参考价值。读者可获得一套开箱即用的人脸检测权重,省去自行训练与格式转换的环节,并能参考其目录组织方式,快速完成从桌面端到嵌入式设备的部署验证,适合需要兼顾精度与实时性的项目选型。
1. 人脸检测模型 YOLOv8:从预训练权重到自定义数据集落地
很多人第一次接触人脸检测模型 YOLOv8,是冲着“开箱即用”四个字去的——装完环境、下好预训练权重,跑一行命令就能在图片上框出人脸。但真正把它塞进业务里,问题才刚开始:通用 COCO 权重对小人脸、侧脸、遮挡脸的召回率并不理想,想用自己的数据集微调又不知道从哪一步下手,训练完 mAP 看着还行,部署到边缘设备上帧率直接腰斩。这篇笔记就按“先跑通、再微调、后部署”的顺序,把 YOLOv8 人脸检测这条链路拆开讲清楚。适合两类人:一是刚搭完 yolov8 环境、想拿人脸场景练手的工程师;二是手里有标注数据、准备训练自己人脸检测模型但不确定参数怎么设的从业者。下面所有命令和配置都按 Ultralytics 官方接口来写,不依赖任何魔改分支。
2. YOLOv8 人脸检测的选型逻辑与最小可跑通链路
2.1 为什么人脸检测优先选 YOLOv8n/s 而不是更大的模型
YOLOv8 官方按参数量分了 n/s/m/l/x 五档,人脸检测这个任务有个特点:目标类别只有一类,但小目标密度极高。一张 1080P 的监控画面里可能同时出现十几张人脸,其中一半像素高度不到 32。这时候盲目上 YOLOv8x 是典型的翻车操作——参数量涨了 10 倍,推理延迟涨了 5 倍,但小人脸召回率提升可能不到 3 个百分点,因为瓶颈不在模型容量,而在输入分辨率和特征金字塔的浅层感受野。
我一般会按部署硬件反推模型档位。GTX1660Ti 这种 6G 显存的卡,跑 YOLOv8s 输入 640 能到 80 FPS 以上,跑 YOLOv8m 就掉到 40 左右;如果是 RK3588 这类 NPU 平台,官方对 YOLOv8n/s 的算子支持最完整,m 以上容易出现算子回退到 CPU 的情况,帧率断崖式下跌。所以人脸检测的默认起点是 YOLOv8n 或 YOLOv8s,先把链路跑通,再根据漏检情况决定是加输入分辨率还是换更大模型。
预训练权重直接用 COCO 版本就行,虽然 COCO 里没有专门的“人脸”类,但 person 类已经学到了大量人脸相关特征,作为初始化比从头训练收敛快得多。下载方式在官方 release 页面拿 yolov8n.pt 即可,不要用第三方转存的版本,避免权重被裁剪过导致 head 层维度对不上。
2.2 用一行命令验证预训练权重的人脸检测效果
在动手训练之前,先用 COCO 权重跑一张含人脸的图,确认环境和推理链路没问题。这一步能帮你排除掉 80% 的“训练不收敛其实是环境坏了”的玄学问题。
# 安装 ultralytics,建议用虚拟环境隔离 pip install ultralytics # 用 COCO 预训练权重推理单张图片 # conf 设 0.25 是通用起点,人脸场景可以适当调低观察召回 yolo predict model=yolov8n.pt source=./test_face.jpg conf=0.25 save=True这段命令做三件事:加载 yolov8n.pt 权重、对 test_face.jpg 做推理、把带框结果存到 runs/detect/predict 目录。关键参数是 conf,它控制置信度阈值,值越低框越多但误检也越多。人脸检测里我通常先设 0.25 看整体召回,如果发现漏检严重再降到 0.1 观察,但最终部署阈值要结合业务对误检的容忍度来定。
跑完之后你会看到结果图里 person 类被框出来,人脸区域通常包含在 person 框内。这说明模型已经具备人脸相关特征,但还没有专门的人脸定位能力——它框的是整个人,不是人脸。要让它精确框人脸,必须用带人脸框标注的数据集做微调。
2.3 人脸数据集的组织格式与 data.yaml 写法
YOLOv8 要求的数据集格式是每张图对应一个同名 txt,每行一个目标,格式为类别id 中心x 中心y 宽 高,坐标全部归一化到 0~1。人脸检测只有一类,所以类别 id 恒为 0。目录结构官方推荐如下:
face_dataset/ ├── images/ │ ├── train/ │ └── val/ └── labels/ ├── train/ └── val/data.yaml 是训练入口,写错一个路径就会报“No labels found”。标准写法:
# data.yaml path: ./face_dataset # 数据集根目录 train: images/train # 相对 path 的训练图目录 val: images/val # 相对 path 的验证图目录 nc: 1 # 类别数,人脸只有一类 names: ['face'] # 类别名,顺序必须和标注 id 对应这里有个容易踩的坑:path 写相对路径时,是相对于你执行训练命令时的工作目录,不是相对于 data.yaml 文件所在目录。我习惯把 data.yaml 放在数据集根目录下,训练时 cd 到该目录再执行,避免路径歧义。另外 images 和 labels 下的子目录名必须严格对应,train 对 train,val 对 val,YOLOv8 不会自动帮你匹配。
3. 训练人脸检测模型:参数怎么设、损失曲线怎么看
3.1 从 COCO 权重微调人脸的完整训练命令
数据准备好之后,训练命令本身不复杂,难的是参数组合。下面这条是我在人脸场景下比较常用的起点:
yolo detect train \ model=yolov8n.pt \ data=./face_dataset/data.yaml \ epochs=100 \ imgsz=640 \ batch=16 \ lr0=0.01 \ lrf=0.01 \ patience=20 \ project=face_runs \ name=exp1逐项说明:model 指定从 COCO 权重初始化,这是微调不是从头训练;imgsz=640 是输入分辨率,人脸小目标多时可以提到 960 或 1280,但显存占用会平方级增长;batch=16 在 6G 显存上跑 640 分辨率基本是上限,爆显存就降到 8;lr0 是初始学习率,微调场景 0.01 比从头训练的 0.1 更稳,太大容易把预训练特征冲掉;lrf 是最终学习率系数,和余弦调度配合,0.01 表示末期学习率降到初始的 1%;patience=20 是早停耐心值,20 轮验证指标不升就停,省时间。
训练过程中终端会实时打印每轮的 box_loss、cls_loss、dfl_loss 和 mAP50。人脸检测主要看两个指标:mAP50 反映整体检测质量,mAP50-95 反映框的定位精度。如果 box_loss 一直不降,大概率是标注坐标没归一化或者类别 id 写错了;如果 cls_loss 异常高,检查 nc 和 names 是否和标注一致。
3.2 用 results.csv 画损失函数曲线定位训练问题
训练结束后,runs 目录下会生成 results.csv,里面记录了每轮的损失和指标。官方自带绘图,但自定义对比时直接读 csv 更灵活:
import pandas as pd import matplotlib.pyplot as plt # 读取训练日志,skipinitialspace 处理列名前的空格 df = pd.read_csv('face_runs/exp1/results.csv', skipinitialspace=True) df.columns = df.columns.str.strip() # 去掉列名首尾空格,否则取列会报 KeyError fig, axes = plt.subplots(1, 2, figsize=(12, 4)) # 左图看三种损失随 epoch 的变化 for col in ['train/box_loss', 'train/cls_loss', 'train/dfl_loss']: axes[0].plot(df['epoch'], df[col], label=col) axes[0].set_xlabel('epoch'); axes[0].legend(); axes[0].set_title('train loss') # 右图看验证集 mAP 走势 axes[1].plot(df['epoch'], df['metrics/mAP50(B)'], label='mAP50') axes[1].plot(df['epoch'], df['metrics/mAP50-95(B)'], label='mAP50-95') axes[1].set_xlabel('epoch'); axes[1].legend(); axes[1].set_title('val mAP') plt.tight_layout(); plt.savefig('curve.png', dpi=150)这段代码的关键在列名处理。results.csv 的列名前后可能带空格,直接 df['train/box_loss'] 会报 KeyError,所以先 strip 一遍。看曲线时有几个典型模式:box_loss 和 cls_loss 同步下降且 mAP 稳步上升,说明训练健康;box_loss 降但 mAP 不升,通常是过拟合,验证集和训练集分布差异大;mAP 前期涨后期震荡,可能是学习率末期还是偏大,把 lrf 调更小试试。人脸检测里如果 mAP50 卡在 0.8 上不去,优先怀疑小脸标注质量,而不是模型容量。
3.3 提升小人脸召回:输入分辨率与数据增强的取舍
人脸检测最头疼的是小目标。YOLOv8 默认的 P3 特征层 stride 是 8,输入 640 时对应 80x80 的特征图,理论上能检测到约 8 像素以上的目标,但实际中小于 32 像素的人脸召回率会明显下降。两条路:提输入分辨率,或者改数据增强策略。
提分辨率最直接,imgsz 从 640 提到 1280,小脸召回通常能涨 5~10 个百分点,代价是推理延迟翻倍、显存占用翻四倍。如果部署端算力有限,可以训练时用 1280、推理时用 640,但会有精度损失,需要实测权衡。
数据增强方面,YOLOv8 默认开了 mosaic 和 mixup。mosaic 把四张图拼成一张,能增加小目标出现频率,对人脸检测有帮助;但 mixup 在人脸场景要谨慎,它把两张图按透明度叠加,可能造出“半张脸叠半张脸”的诡异样本,反而干扰训练。我一般会把 mixup 关掉或调低:
yolo detect train model=yolov8n.pt data=./face_dataset/data.yaml \ epochs=100 imgsz=1280 batch=8 mixup=0.0 mosaic=1.0 close_mosaic=10close_mosaic=10 表示最后 10 轮关闭 mosaic,让模型在接近真实分布的图像上收尾,这个技巧对最终 mAP 通常有正向收益。mixup=0.0 直接禁用,避免人脸叠加的伪样本。
4. 部署到边缘设备:RK3588 与 Orin 的模型转换要点
4.1 导出 ONNX 时的 opset 与动态轴设置
训练完的 .pt 不能直接上 NPU,中间要过 ONNX。导出命令一行,但参数错了后面全白搭:
# 导出 ONNX,opset 12 对 RK3588 的 rknn-toolkit2 兼容性最好 yolo export model=face_runs/exp1/weights/best.pt format=onnx opset=12 simplify=True dynamic=Falseopset 版本是第一个坑。RK3588 的 rknn-toolkit2 对 opset 13 以上支持不完整,容易出现 Unsqueeze 或 Resize 算子解析失败,opset=12 是经过验证的稳妥选择。simplify=True 会调用 onnx-simplifier 做图优化,去掉冗余算子,对后续量化有好处。dynamic=False 表示固定输入尺寸,边缘部署一般不需要动态 batch,固定尺寸能让 NPU 编译出更优的算子调度。
导出后建议用 onnxruntime 跑一遍验证输出和 PyTorch 一致,避免导出过程静默出错:
import onnxruntime as ort import numpy as np sess = ort.InferenceSession('best.onnx') # YOLOv8 输入是 1x3x640x640,NCHW 格式,值域 0~1 dummy = np.random.rand(1, 3, 640, 640).astype(np.float32) out = sess.run(None, {sess.get_inputs()[0].name: dummy}) print([o.shape for o in out]) # 应为 [1, 4+nc, 8400] 之类的形状输出形状里 8400 是三个特征层的锚点总数,4 是框坐标,nc 是类别数。人脸检测 nc=1,所以最后一维是 5。如果形状对不上,检查导出时的 imgsz 和训练时是否一致。
4.2 RK3588 上 rknn 模型转换与量化校准
ONNX 到 RKNN 的转换在 PC 上完成,用 rknn-toolkit2。核心是量化配置,人脸检测对量化误差比较敏感,建议用真实人脸图做校准:
from rknn.api import RKNN rknn = RKNN() # 均值方差按 YOLOv8 官方预处理设置 rknn.config(mean_values=[[0, 0, 0]], std_values=[[255, 255, 255]], target_platform='rk3588') rknn.load_onnx(model='best.onnx') # dataset.txt 每行一张校准图路径,建议 100~300 张覆盖各种光照 rknn.build(do_quantization=True, dataset='./dataset.txt') rknn.export_rknn('face_yolov8.rknn')mean_values 和 std_values 必须和训练时的预处理一致,YOLOv8 是像素除以 255,所以 std 设 255、mean 设 0。do_quantization=True 开启 int8 量化,模型体积缩到四分之一、推理速度翻倍,但精度会掉。校准集的质量直接决定量化后的精度,我一般从验证集里挑 200 张左右,覆盖正脸、侧脸、遮挡、暗光几种情况,不要只用清晰正脸,否则量化后侧脸召回会崩。
量化后务必在 RK3588 上跑一遍验证集,对比量化前后的 mAP。如果掉超过 3 个百分点,优先检查校准集是否覆盖了难样本,而不是急着换模型。
4.3 Orin 部署的 TensorRT 加速与 FP16 取舍
Orin 平台走 TensorRT 路线,YOLOv8 导出 engine 更直接:
# 在 Orin 上直接用 ultralytics 导出 TensorRT engine yolo export model=best.pt format=engine half=True device=0 imgsz=640half=True 开启 FP16 推理,Orin 的 GPU 对 FP16 有原生支持,速度比 FP32 快近一倍,精度损失通常小于 1 个百分点,人脸检测场景基本可以接受。如果发现 FP16 下小脸置信度波动大,再退回 FP32 对比。engine 文件是绑定设备和 TensorRT 版本的,换设备或升级 TensorRT 后必须重新导出,不能直接拷贝。
5. 人脸检测 YOLOv8 落地避坑:5 个血泪教训
5.1 标注框贴脸太紧导致训练震荡
现象:训练前期 box_loss 下降正常,到 30 轮左右突然震荡,mAP 不升反降。原因:人脸标注时框贴着脸部轮廓画,没有留边距,模型学到的框和真实人脸边界过于严格,稍有偏差 loss 就大。解决:标注时框比人脸实际区域外扩 5~10 像素,给模型一点容错空间,重新训练后 loss 曲线明显平滑。
5.2 验证集和训练集同源导致 mAP 虚高
现象:验证集 mAP50 到 0.95,部署后实际漏检严重。原因:训练集和验证集是从同一段视频里抽帧拆分的,相邻帧几乎一样,验证集等于变相训练集。解决:按视频源或人物 id 划分,确保验证集里的人脸在训练集里没出现过,这样 mAP 才反映真实泛化能力。
5.3 imgsz 不是 32 的整数倍导致导出失败
现象:训练时 imgsz=600 能跑,导出 ONNX 报维度不匹配。原因:YOLOv8 的 backbone 有 5 次下采样,输入尺寸必须是 32 的整数倍,600 不是,训练时框架自动补齐了但导出时没补。解决:imgsz 统一用 640、960、1280 这类 32 的倍数,训练和导出保持一致。
5.4 量化校准集全用清晰正脸导致侧脸召回崩塌
现象:RK3588 上 int8 量化后,正脸检测正常,侧脸和暗光人脸几乎全漏。原因:校准集里全是清晰正脸,量化参数按正脸分布拟合,侧脸的特征值域被截断。解决:校准集按场景分层采样,正脸、侧脸、遮挡、暗光各占一定比例,总量 200 张以上,量化后侧脸召回能恢复大半。
5.5 推理时 conf 和 iou 阈值不分场景一套参数
现象:白天场景误检多,夜间场景漏检多,用同一个 conf 阈值两头不讨好。原因:不同光照下模型输出的置信度分布不同,固定阈值无法适配。解决:按场景分别统计置信度分布,白天 conf 设 0.4 压误检,夜间设 0.15 保召回,或者用自适应阈值方案,根据画面亮度动态调整。
6. 用协调注意力机制改进 YOLOv8 人脸检测 head 的实操
前面讲的都是标准链路,如果标准模型在你的场景下 mAP 还是不够,可以试试在 head 部分加注意力机制。协调注意力(Coordinate Attention)是我在人脸检测上验证过比较稳的改进,它把位置信息编码进通道注意力,对小脸定位帮助明显,而且参数量增加很少,不会拖慢推理。
具体做法是修改 ultralytics 的 head 模块。找到ultralytics/nn/modules/head.py,在 Detect 类的 forward 之前插入一个 CA 模块。CA 的核心是把特征图分别沿水平和垂直方向做池化,生成两个方向的位置感知向量,再合并激活:
import torch import torch.nn as nn class CoordAtt(nn.Module): def __init__(self, inp, reduction=32): super().__init__() # 自适应选择中间通道数,避免小通道时过度压缩 mip = max(8, inp // reduction) self.conv1 = nn.Conv2d(inp, mip, 1) self.bn1 = nn.BatchNorm2d(mip) self.act = nn.Hardswish() self.conv_h = nn.Conv2d(mip, inp, 1) self.conv_w = nn.Conv2d(mip, inp, 1) def forward(self, x): identity = x n, c, h, w = x.size() # 沿宽度方向池化,得到 h x 1 的位置向量 x_h = nn.AdaptiveAvgPool2d((h, 1))(x) # 沿高度方向池化,得到 1 x w 的位置向量 x_w = nn.AdaptiveAvgPool2d((1, w))(x).permute(0, 1, 3, 2) # 拼接后共享卷积,再拆回两个方向 y = torch.cat([x_h, x_w], dim=2) y = self.act(self.bn1(self.conv1(y))) x_h, x_w = torch.split(y, [h, w], dim=2) x_w = x_w.permute(0, 1, 3, 2) # sigmoid 生成注意力权重,乘回原特征 attn = torch.sigmoid(self.conv_h(x_h) + self.conv_w(x_w)) return identity * attn参数说明:reduction=32 控制中间通道压缩比,人脸检测特征通道通常 64~256,压缩到 8~16 维足够,再小会丢信息。Hardswish 比 ReLU 在注意力模块里表现更稳,梯度更平滑。插入位置建议放在 Detect head 的三个分支之前,对 P3/P4/P5 分别加,P3 对小脸最关键。
改完之后重新训练,注意加载预训练权重时新增的 CA 模块没有对应权重,需要设置pretrained=False或者用strict=False加载,让新增层随机初始化。我实测在一个人脸数据集上,加 CA 后 mAP50 从 0.87 提到 0.91,小脸召回提升更明显,推理延迟只增加约 5%。但要注意,如果你的数据集本身标注质量差,加注意力也救不回来,先把标注和校准集整干净再谈改进。
最后说个习惯:每次改完模型结构,我都会先用 10 张图跑一遍推理,确认输出形状和框的位置没跑偏,再开完整训练。这个后悔药成本很低,但能省下几小时白跑的训练。希望帮到你。
本文还有配套的精品资源,点击获取