☰
YOLOv8人脸检测实战:从环境搭建到SAHI小脸召回优化
2026/9/29 15:16:08 网站建设 项目流程

简介:这份资源提供基于YOLOv8的人脸检测模型,面向计算机视觉开发者、边缘计算部署工程师及深度学习学习者,解决从训练权重到多平台推理落地的完整需求。压缩包共7个文件,约31.79MB,包含pt与onnx两种通用模型格式,分别适配PyTorch训练微调与跨框架部署;同时提供RKNPU优化版本,含rknn、bin、xml及tar打包文件,可直接用于RK3588、RK3576等瑞芯微NPU平台,输入尺寸为640×640。资源覆盖从桌面端到嵌入式端的多种部署路径,开发者可依据硬件条件选择合适格式,省去模型转换与算子适配的重复工作。目前已有671人学习下载,适合需要快速验证人脸检测效果或搭建边缘端实时检测方案的中高级开发者参考使用。

1. 从一张合影里把人脸框出来:yolov8 人脸检测到底能解决什么

上周帮朋友处理一批活动合影,三百多张照片,需要把每张里的人脸位置标出来做后续裁剪。手动框?不现实。用传统 Haar 级联?侧脸和遮挡基本歇菜。这时候 yolov8 人脸检测就是那个能让你少加两天班的方案。它本质上是把 YOLOv8 这个通用目标检测框架,通过人脸数据集微调或直接加载人脸专用权重,让模型只对"人脸"这一个类别做检测。相比通用 COCO 预训练权重,人脸专用权重在密集小脸、侧脸、口罩遮挡场景下的召回率明显更高。适合谁?做安防监控截图分析、活动照片批量处理、嵌入式设备端人脸计数、以及拿人脸检测当毕业设计题目的同学。你不需要从零训一个检测器,但需要知道权重从哪来、输入尺寸怎么设、置信度阈值怎么调。

2. 环境搭建与权重选择:别在第一步就把自己埋了

2.1 为什么优先用 ultralytics 而不是自己搭 Darknet

YOLOv8 和 YOLOv5 最大的工程差异在于,v8 官方主推ultralytics这个 pip 包,把训练、推理、导出、验证全部封装成统一 API。你不需要像当年玩 Darknet 那样编译 Makefile、改 cfg 文件、手动算 anchor。常见做法是直接pip install ultralytics,然后三行代码跑推理。但这里有个选型理由要说清楚:如果你只是做人脸检测推理,用ultralytics加载人脸专用权重是最短路径;如果你要改网络结构(比如加协调注意力机制),那还是得把源码 clone 下来改ultralytics/nn/modules里的模块定义。新手建议先走 pip 路线,把流程跑通再动结构。

2.2 环境配置的具体步骤

先确认你的 CUDA 版本,这决定了 PyTorch 装哪个。GTX 1660 Ti 这类卡跑 yolov8n 或 yolov8s 的人脸检测完全够用,6GB 显存下 batch size 设 8 到 16 问题不大。RK3588 或 Orin 这类边缘设备则是另一套流程,后面单独说。

# 创建独立环境,别在 base 里乱装 conda create -n face_yolo python=3.10 -y conda activate face_yolo # 根据你的 CUDA 版本装 PyTorch,这里以 CUDA 11.8 为例 pip install torch torchvision --index-url https://download.pytorch.org/whl/cu118 # 装 ultralytics pip install ultralytics # 验证安装 yolo checks

yolo checks会打印出你的 PyTorch 版本、CUDA 是否可用、以及 ultralytics 版本。如果 CUDA 显示不可用,别急着往下走,先把驱动和 PyTorch 版本对齐。这一步翻车的人最多,血泪经验是:显卡驱动版本、CUDA 运行时版本、PyTorch 编译版本三者必须匹配,缺一个就是torch.cuda.is_available()返回 False。

2.3 人脸检测权重从哪来

这是检索里问得最多的问题之一。YOLOv8 官方 COCO 预训练权重里没有"人脸"这个类别,它只有 person 类。所以你有两条路:第一条是找社区已经用人脸数据集(比如 WIDER FACE)微调好的权重,直接加载做推理;第二条是下载官方 yolov8n.pt 作为起点,自己用人脸数据集训练。如果你只是要快速出结果,走第一条;如果你要针对特定场景(比如工地安全帽下的人脸、夜间红外)优化,走第二条。

from ultralytics import YOLO # 加载人脸专用权重(假设你已经拿到了 face_yolov8n.pt) model = YOLO("face_yolov8n.pt") # 对单张图片推理 results = model.predict( source="group_photo.jpg", conf=0.4, # 置信度阈值,人脸检测建议 0.3~0.5 iou=0.5, # NMS 的 IoU 阈值 imgsz=640, # 输入尺寸,小脸多的话可以提到 1280 device=0 # 用 GPU 0 ) # 保存带框的结果图 results[0].save("output.jpg")

conf参数是人脸检测里最需要调的。设太高(比如 0.7),侧脸和戴口罩的脸会被漏掉;设太低(比如 0.1),背景里的纹理会被误检成人脸。我一般先用 0.4 跑一批看效果,再根据漏检和误检情况微调。imgsz也关键,合影里人脸像素少,640 可能不够,提到 1280 能明显改善小脸召回,但推理速度会下降。

3. 训练自己的人脸数据集:从标注到损失曲线

3.1 数据标注格式与目录结构

YOLOv8 用的是 YOLO 格式标注:每张图对应一个 txt 文件,每行是类别 x_center y_center width height,坐标都归一化到 0 到 1。人脸检测只有一个类别,所以类别 id 全是 0。常见做法是用 labelImg 或 Roboflow 标注后导出 YOLO 格式。目录结构必须长这样,不然训练脚本找不到数据:

dataset/ ├── images/ │ ├── train/ │ │ ├── 001.jpg │ │ └── ... │ └── val/ │ └── ... └── labels/ ├── train/ │ ├── 001.txt │ └── ... └── val/ └── ...

3.2 data.yaml 的写法与参数含义

# face_data.yaml path: /home/user/dataset # 数据集根目录 train: images/train # 训练集图片相对路径 val: images/val # 验证集图片相对路径 nc: 1 # 类别数,人脸检测就是 1 names: ['face'] # 类别名称

path写绝对路径最稳,写相对路径时容易因为工作目录不同而报File not found。nc和names必须对应,如果你标了多个类别但 nc 写 1,训练时标签会被截断。

3.3 启动训练与关键参数

yolo detect train \ data=face_data.yaml \ model=yolov8n.pt \ epochs=100 \ imgsz=640 \ batch=16 \ lr0=0.01 \ patience=20 \ project=face_runs \ name=exp1

epochs是训练轮数,人脸检测数据集如果只有几千张,100 轮通常够收敛。patience=20表示验证集指标 20 轮不提升就早停,省时间。lr0是初始学习率,0.01 是默认值,如果你用预训练权重微调,可以降到 0.001 避免把预训练特征冲掉。batch根据显存调,GTX 1660 Ti 跑 yolov8n 用 16 没问题,跑 yolov8m 可能得降到 8。

3.4 看损失函数曲线判断训练状态

训练完在face_runs/exp1/下会有results.csv和results.png。results.png里包含 box_loss、cls_loss、dfl_loss 三条训练损失和对应的验证损失。正常情况是训练损失和验证损失同步下降,最后趋于平稳。如果训练损失还在降但验证损失开始往上走,那就是过拟合了,需要加数据增强或减模型复杂度。如果两条都居高不下,检查标注是不是有问题——我见过有人把坐标没归一化就丢进去训,loss 从头到尾不降。

import pandas as pd import matplotlib.pyplot as plt df = pd.read_csv("face_runs/exp1/results.csv") df.columns = df.columns.str.strip() # 列名可能有空格 plt.figure(figsize=(10, 5)) plt.plot(df["epoch"], df["train/box_loss"], label="train box_loss") plt.plot(df["epoch"], df["val/box_loss"], label="val box_loss") plt.xlabel("epoch") plt.ylabel("loss") plt.legend() plt.savefig("loss_curve.png")

这段脚本把 box_loss 单独画出来,比看整张 results.png 更清楚。df.columns.str.strip()这行别省,ultralytics 导出的 csv 列名有时带前导空格,不处理会 KeyError。

4. 推理部署与边缘设备适配:从服务器到 RK3588

4.1 批量图片推理与结果解析

训练完拿到best.pt后,实际用的时候往往不是单张推理,而是批量处理。下面这段代码遍历文件夹,把每张图的人脸框坐标存成 json,方便后续裁剪或入库。

import os import json from ultralytics import YOLO model = YOLO("face_runs/exp1/weights/best.pt") img_dir = "photos" out = {} for fname in os.listdir(img_dir): if not fname.lower().endswith((".jpg", ".png", ".jpeg")): continue path = os.path.join(img_dir, fname) results = model.predict(source=path, conf=0.4, imgsz=1280, verbose=False) boxes = results[0].boxes faces = [] for i in range(len(boxes)): xyxy = boxes.xyxy[i].tolist() # 左上右下坐标 conf = float(boxes.conf[i]) faces.append({"bbox": xyxy, "conf": conf}) out[fname] = faces with open("face_results.json", "w") as f: json.dump(out, f, indent=2)

verbose=False关掉每张图的日志输出,批量处理时不然刷屏。boxes.xyxy是绝对像素坐标,如果你要归一化坐标用boxes.xywhn。conf存下来方便后续按置信度过滤。

4.2 导出 ONNX 与 RK3588 部署路径

RK3588 这类 NPU 设备不能直接跑 PyTorch 模型,需要先导出 ONNX,再用 RKNN Toolkit 转成 rknn 格式。Orin 则是走 TensorRT。导出 ONNX 的命令很简单:

yolo export model=face_runs/exp1/weights/best.pt format=onnx imgsz=640 opset=12

opset=12是兼容性比较好的选择,RKNN Toolkit 对 opset 版本有要求,太高可能不支持。导出后在 RK3588 上用 rknn_model_zoo 里的 yolov8 示例做后处理,注意人脸检测只有一个类别,后处理里的类别数要改成 1,否则解析会错位。这一步的坑在于:ONNX 导出的输出层名字和 RKNN 示例里写死的不一样,需要自己打印模型输出确认。

4.3 实时视频流人脸检测与标注

检索里有人问实时摄制视频的人脸检测与标注,这其实就是把model.predict的 source 换成摄像头索引或 RTSP 流。

import cv2 from ultralytics import YOLO model = YOLO("face_runs/exp1/weights/best.pt") cap = cv2.VideoCapture(0) # 0 是默认摄像头 while True: ret, frame = cap.read() if not ret: break results = model.predict(source=frame, conf=0.4, imgsz=640, verbose=False) annotated = results[0].plot() # 自带画框和置信度 cv2.imshow("face detect", annotated) if cv2.waitKey(1) & 0xFF == ord("q"): break cap.release() cv2.destroyAllWindows()

results[0].plot()返回的是画好框的 numpy 数组,直接 imshow 就行。如果帧率不够,把 imgsz 降到 416 或者换 yolov8n。GTX 1660 Ti 上 640 尺寸大概能跑到 40 到 60 FPS,够实时用。

5. 避坑与常见问题排查

5.1 训练 loss 不下降,标注文件有问题

现象:训练跑了 50 轮,box_loss 一直在 2.0 以上震荡,mAP 接近 0。原因:标注坐标没有归一化,或者类别 id 写成了 1 但 data.yaml 里 nc=1 只允许 id 0。解决:用脚本检查每行标注的五个值是否都在 0 到 1 之间,类别 id 是否为 0。

5.2 推理时 CUDA out of memory

现象:训练能跑,推理大图时爆显存。原因:imgsz设太大,或者 batch 推理时一次塞了太多图。解决:推理时把imgsz降到 640,或者用stream=True逐张处理。GTX 1660 Ti 6GB 显存跑 1280 尺寸的 yolov8m 会爆,换 yolov8n 或降尺寸。

5.3 导出 ONNX 后 RK3588 上结果全错

现象:PC 上推理正常,转 rknn 后框全乱或没有框。原因:RKNN 后处理里的 anchor 或输出层解析和实际导出模型不匹配,或者输入归一化方式不同。解决:先用onnxruntime在 PC 上验证 ONNX 输出是否正常,再对比 RKNN 示例里的后处理代码,重点检查输出 tensor 的 shape 和顺序。

5.4 小脸漏检严重

现象:合影里远处的人脸框不出来。原因:输入分辨率不够,人脸在特征图上只剩几个像素。解决:推理时imgsz提到 1280 或 1536,或者用 SAHI 切片推理,把大图切成小块分别检测再合并。

5.5 误检把背景纹理当人脸

现象:窗户格子、树叶缝隙被框成人脸。原因:置信度阈值设太低,或者训练集里负样本太少。解决:把conf从 0.25 提到 0.5,训练时加入不含人脸的背景图作为负样本。

6. 进阶技巧:用 SAHI 切片推理把合影小脸召回拉满

最后一章说一个我实际项目里反复用的技巧。前面提到合影小脸漏检,提 imgsz 是最直接的办法,但显存和速度代价大。更聪明的做法是 SAHI(Slicing Aided Hyper Inference),把原图切成有重叠的小块,每块单独推理,再把结果合并回原图坐标。这样每块里人脸相对变大,小脸召回率能提升一截,而单次推理尺寸不用拉满。

from sahi import AutoDetectionModel from sahi.predict import get_sliced_prediction # 加载 yolov8 人脸模型 detection_model = AutoDetectionModel.from_pretrained( model_type="ultralytics", model_path="face_runs/exp1/weights/best.pt", confidence_threshold=0.4, device="cuda:0" ) # 切片推理 result = get_sliced_prediction( "group_photo.jpg", detection_model, slice_height=640, slice_width=640, overlap_height_ratio=0.2, # 切片间重叠比例 overlap_width_ratio=0.2 ) # 导出带框结果 result.export_visuals(export_dir="sahi_output/")

slice_height和slice_width决定每块大小,一般设成和模型训练尺寸一致(640)。overlap_height_ratio是重叠比例,0.2 表示相邻切片有 20% 重叠,防止人脸正好卡在切片边界被切掉。这个比例太低会漏边界脸,太高会重复检测增加 NMS 负担。我一般用 0.2 到 0.3。

验证 SAHI 效果的方法很简单:拿同一张合影,分别用普通推理和 SAHI 推理,数一下检出的人脸数量。如果 SAHI 多检出 10% 以上,说明你的场景确实需要切片。但注意 SAHI 的推理时间是普通推理的 3 到 5 倍,实时视频流慎用,适合离线批量处理。

还有一个参数是postprocess_type,默认是 NMS,如果切片重叠区域出现重复框,可以换成NMM或调低postprocess_match_threshold。这个阈值控制合并时多大 IoU 算同一个目标,人脸检测一般设 0.5 到 0.6。

从那以后我每次处理合影类项目,都强制先跑一遍普通推理看基线,再跑 SAHI 对比召回,最后根据时间预算决定用哪个。这个习惯帮我避免了好几次"以为模型不行、其实是推理策略没跟上"的误判。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询