1. 为什么人脸检测值得用 YOLOv8 重新做一遍
人脸检测这件事,说它老吧,确实老。OpenCV 自带的 Haar 级联分类器十几年前就能跑,cv2.CascadeClassifier加载一个 xml 文件,几行代码就能框出人脸。但真到项目里用,你会发现 Haar 的误检率在复杂背景下高得离谱,侧脸、遮挡、小尺寸人脸基本靠运气。后来 DNN 模块出来,大家开始用 SSD、Caffe 模型,效果好一些,但配置起来又是一堆 prototxt、caffemodel 的路径问题,换个环境就报错。
YOLOv8 把这件事的门槛直接拉到了地板。ultralytics这个库封装得极其干净,装完之后YOLO("yolov8n.pt")一行加载模型,model.predict()一行推理,人脸检测就出来了。你不需要自己写 NMS,不需要手动处理 anchor,不需要纠结输入尺寸的 letterbox 填充。对于刚接触 CNN 和深度学习的小白来说,这是一个非常理想的切入点——你能在几分钟内看到神经网络的实际输出,而不是花三天配环境最后卡在某个 DLL 缺失上。
这篇文章面向的是零基础或者刚入门的朋友。我会从环境安装开始,把 YOLOv8 做人脸检测的完整流程拆开讲清楚,包括模型怎么选、代码怎么写、结果怎么解析、常见报错怎么排查。中间会穿插一些我实际踩过的坑,比如ultralytics安装时版本冲突、OpenCV 找不到cv2模块、GPU 和 CPU 推理速度差异这些。看完之后你应该能独立跑通一套人脸检测流程,并且知道每一步在干什么。
需要提前说明的是,YOLOv8 官方预训练模型是在 COCO 数据集上训练的,COCO 里面本来就有 person 这个类别,但没有人脸这个独立类别。所以直接用yolov8n.pt检测人脸,它会把整个人框出来,而不是只框脸。要精确检测人脸,有两条路:一是用专门在人脸数据集上训练过的 YOLOv8 权重,二是自己拿人脸数据集微调。这篇文章两条路都会讲,先跑通通用检测,再讲怎么换成人脸专用模型。
2. 环境搭建:把 ultralytics 和 OpenCV 装明白
2.1 Python 版本和虚拟环境的选择
ultralytics对 Python 版本有要求,官方推荐 3.8 以上,我实测 3.9 到 3.11 都比较稳。如果你用的是 3.12,部分依赖包可能还没跟上,会遇到编译错误。所以第一步,确认你的 Python 版本:
python --version如果版本不对,建议用 conda 或者 venv 建一个干净的环境。我个人的习惯是用 conda,因为后面如果要装 PyTorch 的 GPU 版本,conda 处理 CUDA 依赖会省心很多:
conda create -n yolo-face python=3.10 conda activate yolo-face用 venv 也行:
python -m venv yolo-face # Windows yolo-face\Scripts\activate # Linux / macOS source yolo-face/bin/activate注意:不要在你系统全局的 Python 环境里直接装 ultralytics。这个库会连带安装 torch、torchvision、opencv-python 等一堆包,版本冲突起来非常麻烦。虚拟环境是底线。
2.2 安装 ultralytics 和 OpenCV
装ultralytics本身很简单:
pip install ultralytics这一条命令会自动把torch、torchvision、opencv-python、numpy、pillow这些依赖都装上。但这里有个坑:默认装的是 CPU 版本的 PyTorch。如果你有 NVIDIA 显卡,想用 GPU 加速,需要先手动装 CUDA 版本的 torch,再装 ultralytics。
先看你的显卡支持什么 CUDA 版本:
nvidia-smi右上角会显示 CUDA Version,比如 12.1。然后去 PyTorch 官网找到对应的安装命令,比如:
pip install torch torchvision --index-url https://download.pytorch.org/whl/cu121装完 torch 之后再装 ultralytics,它就不会重复安装 CPU 版的 torch 了。验证 GPU 是否可用:
import torch print(torch.cuda.is_available()) print(torch.cuda.get_device_name(0))如果输出True和你的显卡型号,说明 GPU 环境没问题。如果是False,检查一下显卡驱动和 CUDA 版本是否匹配。
OpenCV 一般会随 ultralytics 自动装上,但有时候你会遇到ModuleNotFoundError: No module named 'cv2'。这种情况通常是装了opencv-python但环境不对,或者装了opencv-contrib-python和opencv-python两个包冲突了。解决办法:
pip uninstall opencv-python opencv-contrib-python opencv-python-headless -y pip install opencv-python实操心得:如果你在服务器上没有显示器,用
opencv-python-headless更合适,它不依赖 GUI 库,体积也小。但如果你要用cv2.imshow()显示图片,就必须用完整的opencv-python。
2.3 验证安装是否成功
装完之后跑一段最小验证代码:
from ultralytics import YOLO import cv2 import torch print("ultralytics OK") print("opencv version:", cv2.__version__) print("torch version:", torch.__version__) print("cuda available:", torch.cuda.is_available()) model = YOLO("yolov8n.pt") print("model loaded:", model.model_name)第一次运行会自动下载yolov8n.pt,大概 6MB 左右。如果网络没问题,几秒钟就下完了。下载路径默认在当前目录,你也可以手动指定。
如果这一步报错Could not find a version that satisfies the requirement ultralytics,大概率是 pip 源的问题。换清华源或者阿里源:
pip install ultralytics -i https://pypi.tuna.tsinghua.edu.cn/simple3. YOLOv8 检测人脸的核心代码拆解
3.1 用通用模型先跑通检测流程
先不管人脸不人脸,用 COCO 预训练的yolov8n.pt跑一张图,看看整个流程长什么样:
from ultralytics import YOLO import cv2 model = YOLO("yolov8n.pt") results = model.predict(source="test.jpg", conf=0.5, save=True) for result in results: boxes = result.boxes for box in boxes: cls_id = int(box.cls[0]) conf = float(box.conf[0]) xyxy = box.xyxy[0].tolist() label = model.names[cls_id] print(f"{label} {conf:.2f} {xyxy}")这段代码做了几件事:加载模型、对test.jpg做推理、把结果保存到runs/detect/目录下、打印每个检测框的类别、置信度和坐标。
conf=0.5是置信度阈值,低于 0.5 的框会被过滤掉。这个值可以调,调低会检出更多目标但误检也会增加,调高则相反。人脸检测场景下,我一般从 0.4 开始试,根据实际效果微调。
save=True会把画了框的图片保存下来,方便你直接看效果。如果你不想保存,设成False就行。
3.2 换用人脸专用权重
刚才说了,COCO 模型没有人脸类别。要检测人脸,最直接的办法是找一个在人脸数据集上训练好的 YOLOv8 权重。网上有一些开源的人脸检测 YOLOv8 模型,比如基于 WIDER FACE 数据集训练的版本。你可以从相关开源仓库下载.pt文件,然后直接加载:
from ultralytics import YOLO model = YOLO("yolov8n-face.pt") results = model.predict(source="group_photo.jpg", conf=0.4, save=True)yolov8n-face.pt这个权重只有人脸一个类别,输出就是人脸框。如果你找不到现成的权重,也可以自己训练,后面会讲。
提示:下载第三方权重时注意来源可靠性。有些权重可能是在很小的数据集上训练的,泛化能力差。建议优先选择在 WIDER FACE 或者 FDDB 这类标准人脸数据集上训练、且有明确评估指标的模型。
3.3 解析检测结果并绘制自定义框
results对象的结构需要搞清楚。每个result对应一张图,result.boxes里面存了所有检测框。常用的属性:
| 属性 | 含义 | 类型 |
|---|---|---|
box.xyxy | 左上角和右下角坐标 | tensor,shape (1,4) |
box.xywh | 中心点和宽高 | tensor,shape (1,4) |
box.conf | 置信度 | tensor,shape (1,) |
box.cls | 类别 ID | tensor,shape (1,) |
如果你想自己画框,不用save=True,可以拿xyxy坐标用 OpenCV 画:
import cv2 from ultralytics import YOLO model = YOLO("yolov8n-face.pt") img = cv2.imread("group_photo.jpg") results = model.predict(source=img, conf=0.4, verbose=False) for result in results: for box in result.boxes: x1, y1, x2, y2 = map(int, box.xyxy[0].tolist()) conf = float(box.conf[0]) cv2.rectangle(img, (x1, y1), (x2, y2), (0, 255, 0), 2) cv2.putText(img, f"{conf:.2f}", (x1, y1 - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 0), 2) cv2.imwrite("output.jpg", img)这样你就能完全控制画框的样式、颜色、线宽和文字。实际项目里经常需要根据置信度用不同颜色标注,或者只保留置信度最高的几个人脸,这些都可以在这段逻辑里改。
3.4 处理视频流和摄像头
图片检测跑通之后,视频就是逐帧处理。用 OpenCV 读视频或者摄像头,每帧送进模型:
import cv2 from ultralytics import YOLO model = YOLO("yolov8n-face.pt") cap = cv2.VideoCapture(0) # 0 表示默认摄像头 while True: ret, frame = cap.read() if not ret: break results = model.predict(source=frame, conf=0.4, verbose=False) for result in results: for box in result.boxes: x1, y1, x2, y2 = map(int, box.xyxy[0].tolist()) cv2.rectangle(frame, (x1, y1), (x2, y2), (0, 255, 0), 2) cv2.imshow("YOLOv8 Face Detection", frame) if cv2.waitKey(1) & 0xFF == ord("q"): break cap.release() cv2.destroyAllWindows()这段代码在 CPU 上跑yolov8n大概能到 10-15 FPS,GPU 上能到 60 FPS 以上。如果你觉得卡,可以把输入尺寸调小,比如imgsz=320,速度会明显提升,但小脸检测效果会下降。
实操心得:视频流处理时,不要每帧都做推理。可以隔帧检测,比如每 3 帧检测一次,中间帧沿用上一次的框,这样能大幅降低计算量,视觉上也不会有明显卡顿。
4. 自己训练一个人脸检测模型
4.1 数据集准备和标注格式转换
如果你找不到合适的人脸权重,或者想针对特定场景(比如戴口罩人脸、监控角度人脸)优化,自己训练是最靠谱的路。YOLOv8 支持的数据格式是 YOLO 格式的 txt 标注,每行是class_id x_center y_center width height,坐标都归一化到 0-1。
WIDER FACE 是人脸检测最常用的公开数据集,但它原始标注是框的左上角坐标和宽高。需要写脚本转成 YOLO 格式:
import os from PIL import Image def wider_to_yolo(img_path, anno_path, output_path): img = Image.open(img_path) w, h = img.size with open(anno_path, "r") as f: lines = f.readlines() yolo_lines = [] for line in lines[1:]: # 第一行是图片数量,跳过 parts = line.strip().split() if len(parts) < 10: continue x1, y1, x2, y2 = map(float, parts[:4]) # 转成中心点+宽高,并归一化 cx = (x1 + x2) / 2 / w cy = (y1 + y2) / 2 / h bw = (x2 - x1) / w bh = (y2 - y1) / h yolo_lines.append(f"0 {cx:.6f} {cy:.6f} {bw:.6f} {bh:.6f}") with open(output_path, "w") as f: f.write("\n".join(yolo_lines))数据集目录结构要按 YOLOv8 的要求组织:
dataset/ images/ train/ val/ labels/ train/ val/然后写一个data.yaml:
path: ./dataset train: images/train val: images/val nc: 1 names: ["face"]4.2 训练参数配置和启动
训练命令很简单:
yolo detect train data=data.yaml model=yolov8n.pt epochs=100 imgsz=640 batch=16几个关键参数的解释:
model=yolov8n.pt:从预训练权重开始微调,比从头训练收敛快很多。人脸检测任务和 COCO 的通用目标检测有共通特征,迁移学习效果很好。epochs=100:训练轮数。人脸检测一般 50-100 轮就够了,太多会过拟合。imgsz=640:输入图片尺寸。人脸通常占图片比例较小,640 是精度和速度的平衡点。如果小脸特别多,可以上到 1280,但显存消耗会翻倍。batch=16:批次大小。根据显存调整,8G 显存跑 640 尺寸大概能到 16,不够就降到 8。
训练过程中会输出 loss 曲线、mAP 等指标。训练完成后,最好的权重保存在runs/detect/train/weights/best.pt。
4.3 训练效果评估和调优
训练完之后,用验证集跑一下评估:
yolo detect val model=runs/detect/train/weights/best.pt data=data.yaml重点看mAP50和mAP50-95两个指标。人脸检测在 WIDER FACE 的 easy 子集上,mAP50 通常能到 0.9 以上,medium 子集 0.85 左右,hard 子集(小脸、遮挡、模糊)会低一些,0.6-0.7 是正常水平。
如果效果不理想,可以从这几个方向调:
- 数据增强:YOLOv8 默认开了 mosaic、HSV 抖动、翻转等增强。人脸场景下,mosaic 有时候会把人脸切得太碎,可以试试关掉
mosaic=0.0。 - 学习率:默认
lr0=0.01,微调时可以降到 0.001,避免破坏预训练权重。 - 输入尺寸:小脸检测效果差就加大
imgsz。 - 模型规模:
yolov8n换成yolov8s或yolov8m,精度会提升,但速度下降。
注意:训练人脸检测模型时,负样本(没有人脸的图片)也很重要。如果全部是含人脸的图片,模型容易在任何纹理丰富的地方都框出人脸。建议负样本占比 10%-20%。
5. 常见报错和排查速查
5.1 安装类报错
| 报错信息 | 原因 | 解决办法 |
|---|---|---|
Could not find a version that satisfies the requirement ultralytics | pip 源问题或 Python 版本不兼容 | 换国内源,确认 Python >= 3.8 |
ModuleNotFoundError: No module named 'cv2' | OpenCV 未安装或环境不对 | pip install opencv-python,确认在正确虚拟环境中 |
cv2.error: OpenCV(4.4.0) ... | OpenCV 版本过旧或与 numpy 不兼容 | 升级pip install --upgrade opencv-python numpy |
torch.cuda.is_available() 返回 False | CUDA 版本不匹配或驱动问题 | 检查nvidia-smi,重装对应 CUDA 版本的 torch |
5.2 推理类报错
CUDA out of memory是最常见的。原因是输入尺寸太大或者 batch 太大。解决办法:把imgsz从 640 降到 320,或者把batch降到 1。推理时用model.predict(source=..., imgsz=320)即可。
另一个常见问题是检测不到人脸。先确认你用的权重是不是人脸专用模型。如果用yolov8n.pt,它只会框出 person,不会框脸。其次检查conf阈值是不是设太高了,试试降到 0.25。最后确认图片本身人脸是否足够清晰、尺寸是否太小。
5.3 性能优化技巧
如果你在边缘设备上部署,比如树莓派或者 RK3588,yolov8n可能还是太慢。这时候可以考虑:
- 导出 ONNX 模型,用 ONNX Runtime 推理,比 PyTorch 快 20%-30%。
- 导出 TensorRT 引擎,在 NVIDIA 设备上能快 2-3 倍。
- 用量化把 FP32 转成 INT8,速度提升明显,精度损失通常在 1%-2% 以内。
导出命令:
yolo export model=yolov8n-face.pt format=onnx imgsz=640 yolo export model=yolov8n-face.pt format=engine imgsz=640 half=True实操心得:RK3588 上部署 YOLOv8,建议用 RKNN 工具链把 ONNX 转成 RKNN 模型,能充分利用 NPU。直接跑 PyTorch 模型在 RK3588 上帧率很低,转 RKNN 之后能到 30 FPS 以上。
6. 几个实际项目中的经验补充
人脸检测跑通之后,通常会接人脸识别、人脸属性分析、活体检测这些下游任务。这时候检测框的稳定性就很重要。YOLOv8 在视频流里逐帧检测,框会有轻微抖动。解决办法是在检测后面加一个简单的跟踪算法,比如 ByteTrack 或者 SORT,把帧间的检测框关联起来,输出平滑的轨迹。
另一个经验是,实际场景中的人脸尺寸变化很大。近处的人脸可能占画面 1/3,远处的人脸只有几十个像素。单一尺度的模型很难同时处理好这两种情况。YOLOv8 本身有 FPN 结构,对小目标有一定支持,但如果你的场景里小脸特别多,建议在训练时把imgsz设大一些,并且在数据增强里加入缩放变换,让模型见过各种尺度的人脸。
最后说一个容易被忽略的点:颜色空间。OpenCV 读进来的图片是 BGR 格式,而 YOLOv8 内部会转成 RGB。如果你自己做了预处理再送进模型,要注意颜色通道顺序。我见过有人用 PIL 读图(RGB)然后直接送进 OpenCV 的函数,结果颜色全反了,检测效果一塌糊涂。统一用 OpenCV 读图,或者统一用 PIL,不要混着来。
代码写到最后,你会发现 YOLOv8 做人脸检测的核心逻辑其实就三行:加载模型、推理、解析结果。剩下的都是围绕这三行的工程化处理。把这三行跑通,再逐步加上视频流、跟踪、部署优化,一套完整的人脸检测系统就成型了。