ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

YOLOv8人脸检测实战:从数据准备到RK3588边缘部署全链路

YOLOv8人脸检测实战:从数据准备到RK3588边缘部署全链路 简介这份资源提供基于YOLOv8框架训练的人脸检测模型面向计算机视觉开发者、边缘计算部署工程师及深度学习学习者用于在图像或视频流中快速定位人脸区域。压缩包共7个文件约31.79MB包含pt与onnx两种通用模型格式便于PyTorch与跨框架部署同时提供rknn文件及配套bin、xml分别适配RK3588与RK3576的NPU加速另有tar打包的优化版本覆盖从通用推理到Rockchip边缘设备的多种落地场景。已有671人学习下载说明该模型在实时人脸检测任务中具备一定参考价值。读者可直接获得可加载的权重文件与多平台部署素材省去自行训练与格式转换的环节适合需要快速验证YOLOv8人脸检测效果或开展嵌入式部署实践的开发者参考使用。1. 人脸检测模型 YOLOv8从预训练权重到自定义数据集一条能跑通的落地路线很多人第一次接触人脸检测模型 YOLOv8是冲着“开箱即用”四个字去的装完环境、下载权重、跑一行命令图片上就框出了人脸。但真正把它放进业务里问题才刚开始——侧脸漏检、小脸糊成一团、戴帽子口罩就翻车、换到 RK3588 或 Orin 上帧率掉到个位数。YOLOv8 本身是通用目标检测框架人脸只是它众多类别中的一种官方 COCO 预训练权重里“person”类并不等于“face”直接拿来做人脸检测精度和召回都撑不住生产环境。这篇笔记面向的是想用 YOLOv8 做人脸检测、并且打算自己训练或部署的工程师从环境搭建、数据准备、训练参数、损失曲线判读到边缘设备落地把这条链路里真正会卡住人的地方讲清楚。如果你只是想在本地跑个 demo 看效果前两章够用如果你要训自己的数据集、上板子建议从头看到尾。2. 环境搭建与预训练权重把第一张人脸框出来2.1 环境配置的版本组合与安装命令YOLOv8 的环境搭建本身不复杂但版本组合不对后面训练和导出全是玄学问题。我一般用 Python 3.10 PyTorch 2.1 CUDA 11.8 这套组合在 GTX 1660 Ti 这种老卡上也能跑。Ultralytics 官方包更新很快建议锁一个稳定版本不要无脑装最新。# 创建虚拟环境Python 3.10 兼容性最好 conda create -n yolov8face python3.10 -y conda activate yolov8face # 安装 PyTorchCUDA 11.8 对应版本 pip install torch2.1.0 torchvision0.16.0 --index-url https://download.pytorch.org/whl/cu118 # 安装 ultralytics锁 8.0.x 系列避免 API 变动 pip install ultralytics8.0.200 # 验证环境 yolo checksyolo checks会输出当前 PyTorch、CUDA、设备信息。如果 CUDA 显示不可用先查驱动版本再查 PyTorch 是否装成了 CPU 版。GTX 1660 Ti 只有 6GB 显存训练时 batch 不能开大后面会讲怎么调。2.2 预训练权重下载与最小推理验证YOLOv8 的预训练权重在 Ultralytics 的 release 页面可以下载常见的有 yolov8n/s/m/l/x 五档。人脸检测建议从 yolov8n 或 yolov8s 起步n 版在边缘设备上更现实。下载后放到项目根目录用一行命令验证推理链路。from ultralytics import YOLO # 加载预训练权重首次运行会自动下载 model YOLO(yolov8n.pt) # 对单张图片推理conf 调低一点看召回 results model.predict( sourcetest_face.jpg, conf0.25, # 置信度阈值人脸检测建议 0.2~0.3 iou0.45, # NMS IoU 阈值 imgsz640, # 输入尺寸边缘设备可降到 416 device0 # 0 表示第一块 GPUCPU 写 cpu ) # 保存带框结果 results[0].save(output_face.jpg)这段代码跑通只说明推理链路没问题不代表能检测人脸。COCO 预训练权重里没有独立的 face 类它会把脸框成 person 的一部分。要真正做人脸检测必须换数据集重新训练或找现成的人脸预训练权重。常见做法是先用 WIDER FACE 数据集训练一版再在自己的业务数据上微调。提示imgsz不是越大越好。640 是精度和速度的平衡点416 适合边缘设备1280 只在服务器端且人脸特别小时才考虑。3. 数据集准备WIDER FACE 转 YOLO 格式与四个边界坑3.1 WIDER FACE 的标注结构与转换脚本WIDER FACE 是人脸检测最常用的公开数据集标注格式是每张图一个 txt每行一个框格式为x1 y1 w h blur expression illumination invalid occlusion pose。YOLO 需要的是class x_center y_center w h归一化坐标。转换脚本本身不长但边界情况很多。import os import cv2 def wider_to_yolo(img_dir, anno_dir, out_dir, img_size640): os.makedirs(out_dir, exist_okTrue) for anno_file in os.listdir(anno_dir): if not anno_file.endswith(.txt): continue img_name anno_file.replace(.txt, .jpg) img_path os.path.join(img_dir, img_name) if not os.path.exists(img_path): continue img cv2.imread(img_path) h, w img.shape[:2] lines [] with open(os.path.join(anno_dir, anno_file)) as f: for line in f: parts line.strip().split() if len(parts) 4: continue x1, y1, bw, bh map(float, parts[:4]) # 过滤无效框和过小框 if bw 10 or bh 10: continue # 转归一化中心坐标 xc (x1 bw / 2) / w yc (y1 bh / 2) / h nw bw / w nh bh / h # 裁剪到 [0,1] xc, yc min(max(xc, 0), 1), min(max(yc, 0), 1) nw, nh min(nw, 1), min(nh, 1) lines.append(f0 {xc:.6f} {yc:.6f} {nw:.6f} {nh:.6f}) if lines: out_name os.path.splitext(anno_file)[0] .txt with open(os.path.join(out_dir, out_name), w) as f: f.write(\n.join(lines)) wider_to_yolo(WIDER_train/images, wider_annotations, labels/train)转换逻辑的核心是坐标归一化和无效框过滤。bw 10 or bh 10这行很关键WIDER FACE 里有大量几个像素的小脸留着只会拉低训练稳定性。min(max(...))是防止标注越界导致 YOLO 报错。3.2 数据集划分与 data.yaml 配置转换完标签后按 8:1:1 划分训练、验证、测试集。目录结构要严格按 YOLO 要求来否则训练时找不到图。# data.yaml path: /data/face_dataset train: images/train val: images/val test: images/test nc: 1 names: [face]nc: 1表示只有一类names里写face。如果后面要加口罩检测、安全帽检测改这里就行。path用绝对路径最稳相对路径在不同工作目录下容易翻车。3.3 四个边界坑小脸、遮挡、灰度图、标签越界第一个坑是小脸。WIDER FACE 里 10x10 以下的框占比很高这些框在 640 输入下缩到 1~2 个像素模型根本学不到。我的做法是训练时过滤掉小于 16x16 的框推理时如果业务确实需要小脸把imgsz提到 1280 或改用切片推理。第二个坑是遮挡。WIDER FACE 的 occlusion 字段标了遮挡程度但转换时很多人直接忽略。如果业务场景遮挡严重建议把 occlusion2 的框也保留让模型学会从部分人脸推断整体。第三个坑是灰度图。WIDER FACE 全是彩色图但实际业务里摄像头可能出灰度流。训练时加hsv_h0.0并随机转灰度做增强能显著提升灰度场景的召回。第四个坑是标签越界。有些标注框的 x1w 超过了图像宽度归一化后大于 1YOLO 训练时会直接报错或静默丢弃。转换脚本里的min(max(...))就是防这个的但更稳妥的做法是转换后跑一遍校验脚本统计越界框数量。4. 训练参数怎么设从损失曲线判断模型有没有学进去4.1 训练命令与关键参数含义YOLOv8 的训练入口很简洁但参数含义必须搞清楚否则调参就是碰运气。yolo detect train \ datadata.yaml \ modelyolov8n.pt \ epochs100 \ imgsz640 \ batch16 \ lr00.01 \ lrf0.01 \ momentum0.937 \ weight_decay0.0005 \ warmup_epochs3 \ patience20 \ workers8 \ device0 \ projectruns/face \ nameexp1lr0是初始学习率人脸检测这种单类任务 0.01 起步没问题如果 loss 震荡就降到 0.005。lrf是最终学习率因子和lr0相乘得到训练结束时的学习率0.01 表示余弦退火到初始值的 1%。patience20表示 20 轮验证指标不提升就早停省时间。batch16在 6GB 显存的 1660 Ti 上刚好再大就 OOM。4.2 损失曲线怎么看三个典型形态与对应动作训练完在runs/face/exp1下有results.csv用 pandas 或 Excel 画曲线。重点看train/box_loss、train/cls_loss、val/box_loss、metrics/mAP50。import pandas as pd import matplotlib.pyplot as plt df pd.read_csv(runs/face/exp1/results.csv) df.columns df.columns.str.strip() fig, axes plt.subplots(1, 3, figsize(15, 4)) axes[0].plot(df[epoch], df[train/box_loss], labeltrain_box) axes[0].plot(df[epoch], df[val/box_loss], labelval_box) axes[0].legend(); axes[0].set_title(Box Loss) axes[1].plot(df[epoch], df[train/cls_loss], labeltrain_cls) axes[1].plot(df[epoch], df[val/cls_loss], labelval_cls) axes[1].legend(); axes[1].set_title(Cls Loss) axes[2].plot(df[epoch], df[metrics/mAP50], labelmAP50) axes[2].legend(); axes[2].set_title(mAP50) plt.savefig(loss_curve.png)第一种形态train 和 val 同步下降mAP 稳步上升说明学习率合适继续训就行。第二种train 持续降但 val 先降后升典型过拟合加数据增强或减模型容量。第三种train 和 val 都震荡不降学习率太大或 batch 太小先降lr0再考虑加 batch。4.3 数据增强参数对人脸检测的影响YOLOv8 默认开启 mosaic、mixup、hsv 增强。人脸检测里 mosaic 要慎用四张图拼一起会让小脸更小反而有害。我的配置是mosaic0.5、mixup0.0、hsv_h0.015、hsv_s0.7、hsv_v0.4、fliplr0.5。左右翻转对人脸没问题但上下翻转要关掉人脸倒过来不符合真实分布。注意close_mosaic10表示最后 10 轮关闭 mosaic让模型在真实分布上收尾这个参数对人脸检测提升明显建议保留。5. 避坑与排查训练和推理里最常见的五个翻车现场5.1 现象训练 loss 正常但 mAP 一直是 0原因通常是标签路径不对或类别索引错位。YOLO 读标签时如果找不到对应 txt会当成背景图训练loss 看着降但模型什么都没学到。解决检查data.yaml里train和val路径下的images和labels是否一一对应用脚本统计标签文件数量是否和图片数量一致。5.2 现象推理时框出一堆重叠框NMS 的iou阈值设太高了。人脸密集场景下iou0.45可能不够调到 0.3~0.4。另外检查是否误用了agnostic_nms多类检测时这个参数会让不同类互相抑制。5.3 现象GPU 显存够但训练报 CUDA out of memoryYOLOv8 的batch是全局 batch多卡时每卡分到的更少。单卡 6GB 跑batch16加imgsz640刚好如果开了ampFalse会翻倍。解决保持ampTrue或降batch到 8 并开梯度累积。5.4 现象验证集 mAP 很高但实际场景漏检严重验证集和实际场景分布不一致。WIDER FACE 以正脸为主实际场景侧脸、低头、戴口罩多。解决从业务视频里抽帧人工标 200~500 张加入训练集微调 20~30 轮效果立竿见影。5.5 现象导出 ONNX 后推理结果和 PyTorch 不一致导出时的imgsz和推理时的输入尺寸不一致或dynamic轴设置有问题。解决导出命令固定imgsz640推理时也用 640不要动态改。ONNX 的预处理要和训练时完全一致归一化参数差一点结果就偏。6. 边缘部署与进阶技巧RK3588 和 Orin 上的量化取舍6.1 RK3588 部署 YOLOv8 人脸的完整链路RK3588 的 NPU 只吃 RKNN 格式链路是 PyTorch → ONNX → RKNN。转换时量化方式是精度和速度的核心取舍。# 1. 导出 ONNX yolo export modelruns/face/exp1/weights/best.pt formatonnx imgsz640 opset12 simplifyTrue # 2. ONNX 转 RKNN用 rknn-toolkit2 python convert_rknn.py# convert_rknn.py from rknn.api import RKNN rknn RKNN() rknn.config( mean_values[[0, 0, 0]], std_values[[255, 255, 255]], target_platformrk3588, quantized_dtypeasymmetric_quantized-8 # int8 量化 ) rknn.load_onnx(best.onnx) rknn.build(do_quantizationTrue, datasetquant_dataset.txt) rknn.export_rknn(face_yolov8.rknn)quant_dataset.txt里放 100~200 张业务场景图量化校准用。int8 量化后模型缩小 4 倍NPU 上帧率能到 30fps 以上但小脸召回会掉 3~5 个点。如果业务对精度敏感用quantized_dtypew8a16混合量化速度慢一点但精度保住。6.2 Orin 上的 TensorRT 加速与 FP16 取舍Orin 走 TensorRT 路线yolo export formatengine halfTrue直接出 FP16 引擎。FP16 对精度影响很小人脸检测 mAP 掉 0.5 个点以内速度比 FP32 快近一倍。注意 TensorRT 引擎和硬件绑定换设备要重新导出。6.3 一个具体技巧用切片推理救小脸小脸漏检是所有人脸检测的痛点。切片推理SAHI的思路是把大图切成重叠小图分别推理再合并对小脸提升明显。from sahi import AutoDetectionModel from sahi.predict import get_sliced_prediction model AutoDetectionModel.from_pretrained( model_typeyolov8, model_pathbest.pt, confidence_threshold0.25, devicecuda:0 ) result get_sliced_prediction( crowd.jpg, model, slice_height320, slice_width320, overlap_height_ratio0.2, overlap_width_ratio0.2 ) result.export_visuals(export_dirsahi_output)slice_height/width设 320 是因为 640 输入下小脸缩得太小切到 320 后相对放大。overlap0.2 是防止边缘人脸被切断。代价是推理时间翻 4~6 倍适合离线分析或对帧率要求不高的场景。6.4 验证部署效果的两个硬指标部署完不要只看 demo 图跑两个指标一是业务视频抽 500 帧统计漏检率和误检率二是连续跑 1 小时看帧率波动和内存增长。RK3588 上如果帧率从 30 掉到 15多半是散热降频加散热片或限制 NPU 频率。我自己的习惯是每次训完模型先在验证集看 mAP再从业务视频抽 100 帧人工数漏检两个数对不上就以业务数据为准。模型指标再好看业务场景漏检就是白搭。希望帮到你。本文还有配套的精品资源点击获取
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进