ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

YOLOv8人脸检测实战:从预训练权重到自定义数据集微调与边缘部署

YOLOv8人脸检测实战:从预训练权重到自定义数据集微调与边缘部署 简介这份资源提供基于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 modelyolov8n.pt source./test_face.jpg conf0.25 saveTrue这段命令做三件事加载 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 对 trainval 对 valYOLOv8 不会自动帮你匹配。3. 训练人脸检测模型参数怎么设、损失曲线怎么看3.1 从 COCO 权重微调人脸的完整训练命令数据准备好之后训练命令本身不复杂难的是参数组合。下面这条是我在人脸场景下比较常用的起点yolo detect train \ modelyolov8n.pt \ data./face_dataset/data.yaml \ epochs100 \ imgsz640 \ batch16 \ lr00.01 \ lrf0.01 \ patience20 \ projectface_runs \ nameexp1逐项说明model 指定从 COCO 权重初始化这是微调不是从头训练imgsz640 是输入分辨率人脸小目标多时可以提到 960 或 1280但显存占用会平方级增长batch16 在 6G 显存上跑 640 分辨率基本是上限爆显存就降到 8lr0 是初始学习率微调场景 0.01 比从头训练的 0.1 更稳太大容易把预训练特征冲掉lrf 是最终学习率系数和余弦调度配合0.01 表示末期学习率降到初始的 1%patience20 是早停耐心值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, skipinitialspaceTrue) 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], labelcol) axes[0].set_xlabel(epoch); axes[0].legend(); axes[0].set_title(train loss) # 右图看验证集 mAP 走势 axes[1].plot(df[epoch], df[metrics/mAP50(B)], labelmAP50) axes[1].plot(df[epoch], df[metrics/mAP50-95(B)], labelmAP50-95) axes[1].set_xlabel(epoch); axes[1].legend(); axes[1].set_title(val mAP) plt.tight_layout(); plt.savefig(curve.png, dpi150)这段代码的关键在列名处理。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 modelyolov8n.pt data./face_dataset/data.yaml \ epochs100 imgsz1280 batch8 mixup0.0 mosaic1.0 close_mosaic10close_mosaic10 表示最后 10 轮关闭 mosaic让模型在接近真实分布的图像上收尾这个技巧对最终 mAP 通常有正向收益。mixup0.0 直接禁用避免人脸叠加的伪样本。4. 部署到边缘设备RK3588 与 Orin 的模型转换要点4.1 导出 ONNX 时的 opset 与动态轴设置训练完的 .pt 不能直接上 NPU中间要过 ONNX。导出命令一行但参数错了后面全白搭# 导出 ONNXopset 12 对 RK3588 的 rknn-toolkit2 兼容性最好 yolo export modelface_runs/exp1/weights/best.pt formatonnx opset12 simplifyTrue dynamicFalseopset 版本是第一个坑。RK3588 的 rknn-toolkit2 对 opset 13 以上支持不完整容易出现 Unsqueeze 或 Resize 算子解析失败opset12 是经过验证的稳妥选择。simplifyTrue 会调用 onnx-simplifier 做图优化去掉冗余算子对后续量化有好处。dynamicFalse 表示固定输入尺寸边缘部署一般不需要动态 batch固定尺寸能让 NPU 编译出更优的算子调度。导出后建议用 onnxruntime 跑一遍验证输出和 PyTorch 一致避免导出过程静默出错import onnxruntime as ort import numpy as np sess ort.InferenceSession(best.onnx) # YOLOv8 输入是 1x3x640x640NCHW 格式值域 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, 4nc, 8400] 之类的形状输出形状里 8400 是三个特征层的锚点总数4 是框坐标nc 是类别数。人脸检测 nc1所以最后一维是 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_platformrk3588) rknn.load_onnx(modelbest.onnx) # dataset.txt 每行一张校准图路径建议 100~300 张覆盖各种光照 rknn.build(do_quantizationTrue, dataset./dataset.txt) rknn.export_rknn(face_yolov8.rknn)mean_values 和 std_values 必须和训练时的预处理一致YOLOv8 是像素除以 255所以 std 设 255、mean 设 0。do_quantizationTrue 开启 int8 量化模型体积缩到四分之一、推理速度翻倍但精度会掉。校准集的质量直接决定量化后的精度我一般从验证集里挑 200 张左右覆盖正脸、侧脸、遮挡、暗光几种情况不要只用清晰正脸否则量化后侧脸召回会崩。量化后务必在 RK3588 上跑一遍验证集对比量化前后的 mAP。如果掉超过 3 个百分点优先检查校准集是否覆盖了难样本而不是急着换模型。4.3 Orin 部署的 TensorRT 加速与 FP16 取舍Orin 平台走 TensorRT 路线YOLOv8 导出 engine 更直接# 在 Orin 上直接用 ultralytics 导出 TensorRT engine yolo export modelbest.pt formatengine halfTrue device0 imgsz640halfTrue 开启 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 的整数倍导致导出失败现象训练时 imgsz600 能跑导出 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, reduction32): 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], dim2) y self.act(self.bn1(self.conv1(y))) x_h, x_w torch.split(y, [h, w], dim2) 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参数说明reduction32 控制中间通道压缩比人脸检测特征通道通常 64~256压缩到 8~16 维足够再小会丢信息。Hardswish 比 ReLU 在注意力模块里表现更稳梯度更平滑。插入位置建议放在 Detect head 的三个分支之前对 P3/P4/P5 分别加P3 对小脸最关键。改完之后重新训练注意加载预训练权重时新增的 CA 模块没有对应权重需要设置pretrainedFalse或者用strictFalse加载让新增层随机初始化。我实测在一个人脸数据集上加 CA 后 mAP50 从 0.87 提到 0.91小脸召回提升更明显推理延迟只增加约 5%。但要注意如果你的数据集本身标注质量差加注意力也救不回来先把标注和校准集整干净再谈改进。最后说个习惯每次改完模型结构我都会先用 10 张图跑一遍推理确认输出形状和框的位置没跑偏再开完整训练。这个后悔药成本很低但能省下几小时白跑的训练。希望帮到你。本文还有配套的精品资源点击获取
返回列表