ARTICLE DETAIL

资讯详情

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

YOLOv8人脸检测实战:从环境搭建到调优的完整指南

YOLOv8人脸检测实战:从环境搭建到调优的完整指南 1. 为什么人脸检测值得用 YOLOv8 重新做一遍如果你之前接触过人脸检测大概率绕不开 OpenCV 自带的 Haar 级联分类器或者 dlib 的 HOG 检测器。这两个方案在 2015 年前后确实是主流代码也就十来行跑起来也快。但实际用起来你会发现几个很烦的问题侧脸基本检测不到戴帽子或者光线暗一点就疯狂漏检误报还特别多——墙上一个圆形装饰画都能给你框成一张脸。我在一个门禁打卡的小项目里就吃过这个亏。当时用 Haar 做检测白天效果还行一到傍晚逆光场景识别率直接掉到六成以下用户投诉不断。后来换成基于 CNN 的方案同样的摄像头、同样的场景检测率稳定在九成五以上。这就是卷积神经网络带来的差距——它不是靠人工设计的边缘和纹理特征去匹配而是从大量数据里自己学出来的特征表达。YOLOv8 是 ultralytics 团队推出的目标检测框架它把检测任务做成了一个端到端的回归问题输入一张图网络直接输出所有目标的边界框和类别。人脸检测本质上就是目标检测的一个特例——只不过类别只有人脸这一个。所以用 YOLOv8 做人脸检测思路非常直接要么用官方预训练模型里自带的 person 类别间接推断要么直接找一个已经用人脸数据训练好的 YOLOv8 权重。这篇文章面向的是刚接触深度学习、想快速跑通一个人脸检测 Demo 的读者。你不需要懂反向传播的数学推导也不需要自己搭网络结构。只要你会装 Python 包、会写几行脚本就能跟着走完整个流程。我会把环境配置、模型选择、代码实现、效果调优这几个环节都拆开讲清楚包括我自己踩过的那些坑。2. 环境搭建ultralytics 安装与 OpenCV 的版本匹配2.1 ultralytics 安装的两种路径与常见报错装 ultralytics 最省事的方式就是 pippip install ultralytics这条命令会自动把 PyTorch、OpenCV、numpy 这些依赖一起装上。但实际执行的时候很多人会卡在依赖解析这一步。最常见的报错是Could not find a version that satisfies the requirement ultralytics这个问题的根因通常有两个一是 pip 版本太老不认识新版包的元数据格式二是 Python 版本和 ultralytics 要求的版本不匹配。我的建议是先把 pip 升到最新python -m pip install --upgrade pip然后确认你的 Python 版本在 3.8 到 3.11 之间。ultralytics 对 3.12 的支持是后来才跟上的如果你用的是 3.12 早期版本可能会遇到一些编译问题。另外如果你在国内网络环境下 pip 下载慢可以临时指定镜像源pip install ultralytics -i https://pypi.tuna.tsinghua.edu.cn/simple还有一种情况是你已经装了 PyTorch但版本和 ultralytics 要求的不一致。这时候 pip 会尝试重新安装 PyTorch如果你的 CUDA 环境是配好的重新装可能会把 GPU 版本换成 CPU 版本。遇到这种情况可以先单独装好对应 CUDA 版本的 PyTorch再用--no-deps参数装 ultralytics然后手动补上缺的依赖。2.2 OpenCV 装了却 import 失败的排查思路ModuleNotFoundError: No module named opencv和cv2.error这两个报错几乎每个做视觉项目的人都遇到过。前者通常是因为包名和导入名不一致——你 pip 装的是opencv-python但代码里写的是import cv2这本身没问题但如果装的是opencv这个包一个已经废弃的老包那就会出问题。正确的安装命令是pip install opencv-python如果你需要用到一些额外的功能比如 SIFT 特征或者视频编码可以装opencv-contrib-python。但注意不要同时装这两个包它们会互相覆盖导致一些奇怪的错误。cv2.error的报错信息通常很长里面会包含具体的函数名和错误原因。比如cv2.error: OpenCV(4.4.0) ... error: (-215:Assertion failed)这种一般是传入的图像路径不对或者图像为空。我的习惯是在读取图像后立刻加一个判断import cv2 img cv2.imread(test.jpg) if img is None: print(图像读取失败检查路径是否正确) exit()这个简单的检查能帮你排除掉一大半的 cv2.error。2.3 GPU 环境下的版本对齐问题如果你有一块 NVIDIA 显卡比如 GTX 1660 Ti 或者 RTX 3060想用 GPU 加速推理那需要确保三件事对齐显卡驱动版本、CUDA 版本、PyTorch 版本。这三者任意一个不匹配都会导致torch.cuda.is_available()返回 False。一个快速验证的方法是import torch print(torch.__version__) print(torch.cuda.is_available()) print(torch.cuda.get_device_name(0) if torch.cuda.is_available() else 无可用GPU)如果is_available()返回 False先别急着重装用nvidia-smi看一下驱动支持的 CUDA 版本然后去 PyTorch 官网找对应版本的安装命令。ultralytics 本身对 PyTorch 版本的要求比较宽松只要你的 PyTorch 能正常调用 GPUYOLOv8 就能跑在 GPU 上。3. 模型选型用预训练权重还是自己训练人脸数据3.1 官方预训练模型的 person 类别能直接当人脸检测用吗YOLOv8 官方提供的预训练权重是在 COCO 数据集上训练的包含 80 个类别其中有一个类别是 person。很多人会想既然 person 能检测出人那我从 person 的检测框里再截取上半部分不就能当人脸检测了吗这个思路在特定场景下能用但问题很明显。COCO 数据集里 person 类别的标注是整个人的边界框不是人脸框。当一个人只露出头部的时候person 检测器可能根本检测不到当多个人重叠的时候person 框会混在一起你没法准确切出每张脸。我实测过在一个人头密集的场景里用 person 框推算人脸位置准确率大概只有七成左右而且框的位置偏移很大。所以如果你的需求是正经的人脸检测不建议走这条路。它只适合那种大概知道有没有人的粗粒度场景。3.2 找一个已经训练好的人脸 YOLOv8 权重更靠谱的做法是直接用别人已经用人脸数据训练好的 YOLOv8 权重。GitHub 上有很多开源的人脸检测模型比如基于 WIDER FACE 数据集训练的 YOLOv8n-face 或者 YOLOv8s-face。这些模型的输入输出格式和官方 YOLOv8 完全一致你只需要把权重文件下载下来在代码里指定路径就行。选权重的时候注意两点一是看它用的 YOLOv8 版本n/s/m/l/xn 是最小的速度快但精度低s 是速度和精度的平衡点我一般推荐从 s 开始试二是看它的训练数据集WIDER FACE 是目前最常用的人脸检测基准数据集覆盖了各种尺度、姿态和遮挡情况用它训练出来的模型泛化能力比较好。3.3 自己训练人脸数据的完整流程如果你有特定场景的需求比如要检测戴口罩的人脸、或者要检测某种特定角度的人脸那自己训练是更好的选择。YOLOv8 的训练流程已经封装得很简单了from ultralytics import YOLO model YOLO(yolov8s.pt) model.train(dataface_dataset.yaml, epochs100, imgsz640, batch16)face_dataset.yaml里需要指定训练集和验证集的路径以及类别名称。人脸检测通常只有一个类别所以names里写{0: face}就行。数据标注用 labelImg 或者 Roboflow 都可以格式是 YOLO 的 txt 格式每行一个目标内容是类别编号 中心x 中心y 宽度 高度坐标都归一化到 0 到 1 之间。标注的时候有个经验人脸框不要贴得太紧稍微往外扩几个像素让模型学到一点上下文信息这样在检测模糊人脸的时候效果会更好。训练轮数方面如果你用的是预训练权重做微调50 到 100 个 epoch 通常就够了。学习率用默认的 0.01 起步如果 loss 震荡得厉害就降到 0.001。batch size 根据你的显存来定GTX 1660 Ti 的 6GB 显存跑 yolov8s 加 640 输入尺寸batch 设 8 比较稳。4. 几行代码跑通人脸检测从读图到画框4.1 最小可运行代码的逐行拆解先看最核心的代码整个推理过程其实就三行from ultralytics import YOLO model YOLO(yolov8s-face.pt) results model(group_photo.jpg) results[0].show()第一行导入 YOLO 类第二行加载权重文件第三行把图片路径传进去做推理。results是一个列表每个元素对应一张输入图片的检测结果。results[0].show()会弹出一个窗口显示带框的图片。但实际用的时候你肯定不想每次都弹窗而是想把结果保存下来或者做后续处理。这时候需要把 results 对象里的信息取出来for result in results: boxes result.boxes for box in boxes: x1, y1, x2, y2 box.xyxy[0].tolist() conf box.conf[0].item() cls box.cls[0].item() print(f人脸位置: ({x1:.0f}, {y1:.0f}) - ({x2:.0f}, {y2:.0f}), 置信度: {conf:.2f})box.xyxy是左上角和右下角的坐标box.conf是置信度box.cls是类别编号。人脸检测只有一个类别所以 cls 永远是 0。4.2 用 OpenCV 做后处理画框、裁剪、保存ultralytics 自带的show()和save()方法很方便但如果你想自定义画框的样式或者想把检测到的人脸裁剪出来做后续识别就需要用 OpenCV 来操作了import cv2 from ultralytics import YOLO model YOLO(yolov8s-face.pt) img cv2.imread(group_photo.jpg) results model(img) for box in results[0].boxes: x1, y1, x2, y2 map(int, box.xyxy[0].tolist()) conf box.conf[0].item() if conf 0.5: 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) face_crop img[y1:y2, x1:x2] cv2.imwrite(fface_{x1}_{y1}.jpg, face_crop) cv2.imwrite(result.jpg, img)这里有个细节box.xyxy返回的是浮点数画框的时候需要转成整数。另外裁剪人脸的时候要注意边界问题如果框超出了图片范围直接切片会报错或者得到空图。稳妥的做法是加一个 clamph, w img.shape[:2] x1, y1 max(0, x1), max(0, y1) x2, y2 min(w, x2), min(h, y2)4.3 批量处理和视频流检测的写法单张图片检测跑通之后扩展到批量图片或者视频流就是改一下输入源的事情。批量处理图片import os from ultralytics import YOLO model YOLO(yolov8s-face.pt) img_dir images/ for img_name in os.listdir(img_dir): if img_name.endswith((.jpg, .png)): results model(os.path.join(img_dir, img_name)) results[0].save(foutput/{img_name})视频流检测用 OpenCV 的 VideoCaptureimport cv2 from ultralytics import YOLO model YOLO(yolov8s-face.pt) cap cv2.VideoCapture(0) while True: ret, frame cap.read() if not ret: break results model(frame, verboseFalse) annotated results[0].plot() cv2.imshow(Face Detection, annotated) if cv2.waitKey(1) 0xFF ord(q): break cap.release() cv2.destroyAllWindows()results[0].plot()会返回一个画好框的 numpy 数组直接丢给 cv2.imshow 就行。verboseFalse是为了关掉控制台里每帧都打印的日志不然刷屏很烦。5. 调优实战置信度阈值、输入尺寸与推理速度的平衡5.1 置信度阈值设多少合适YOLOv8 默认的置信度阈值是 0.25也就是说置信度低于 0.25 的框会被过滤掉。在人脸检测场景里这个值偏低了会导致很多误报。我一般会把阈值提到 0.5 左右results model(img, conf0.5)但阈值也不是越高越好。如果你做的是安防监控漏检的代价比误报大那可以降到 0.3 到 0.4。如果是做相册自动归类误报会让用户觉得烦那就提到 0.6 以上。这个值没有标准答案需要根据你的具体场景去试。还有一个参数是iou控制非极大值抑制的重叠阈值默认 0.7。如果发现同一个人脸被框了好几次可以把这个值降到 0.5 左右。5.2 输入尺寸对速度和精度的影响YOLOv8 默认的推理尺寸是 640x640。如果你的图片里人脸比较大、比较清晰可以降到 320 或者 416速度会快很多精度损失也不明显。但如果图片里有很多小人脸比如集体照或者监控画面那就需要保持 640 甚至提到 1280。results model(img, imgsz1280)我实测过一组数据在 GTX 1660 Ti 上yolov8s 模型跑 640 尺寸单帧推理大概 15 毫秒跑到 1280 尺寸单帧要 50 毫秒左右。如果你的应用要求实时性比如 30 帧每秒的视频流那 640 是更稳妥的选择。5.3 模型量化与导出 ONNX 的加速效果如果你需要在没有 GPU 的设备上跑比如树莓派或者 RK3588 这类嵌入式板子可以考虑把模型导出成 ONNX 格式再用 ONNX Runtime 做推理。导出命令很简单model.export(formatonnx, imgsz640, halfTrue)halfTrue表示用 FP16 精度导出模型体积会减半推理速度也会快一些。但注意不是所有硬件都支持 FP16导出之前最好确认一下目标平台的兼容性。在 RK3588 上部署 YOLOv8通常需要把 ONNX 转成 RKNN 格式用瑞芯微提供的 RKNN Toolkit 来做转换。这个过程涉及的步骤比较多包括量化校准、算子兼容性检查等后面有机会可以单独写一篇来讲。6. 那些文档里不会写但一定会遇到的坑6.1 图片路径里有中文导致读取失败OpenCV 的imread函数在 Windows 上遇到中文路径会返回 None这是历史遗留问题。解决办法是用 numpy 先读文件再解码import numpy as np import cv2 def imread_unicode(path): data np.fromfile(path, dtypenp.uint8) return cv2.imdecode(data, cv2.IMREAD_COLOR)或者直接用 ultralytics 的接口它内部处理了这个问题传路径字符串进去就行。6.2 显存不够导致的 CUDA out of memory跑大模型或者大尺寸输入的时候很容易遇到显存不够。除了换小模型、降尺寸、减 batch 之外还有一个技巧是设置streamTrue让推理结果以生成器的方式返回而不是一次性全部加载到显存里results model(img_dir, streamTrue) for result in results: process(result)另外如果你在循环里反复调用模型记得把上一轮的 results 变量释放掉Python 的垃圾回收有时候不会及时清理 GPU 显存。6.3 检测框抖动与视频流平滑处理在视频流里做人脸检测你会发现框的位置在帧与帧之间会轻微抖动看起来很不舒服。这是因为每一帧都是独立推理的没有利用时序信息。一个简单的平滑方法是做指数移动平均smooth_boxes {} alpha 0.6 for box in results[0].boxes: track_id int(box.id) if box.id is not None else 0 x1, y1, x2, y2 box.xyxy[0].tolist() if track_id in smooth_boxes: prev smooth_boxes[track_id] x1 alpha * x1 (1 - alpha) * prev[0] y1 alpha * y1 (1 - alpha) * prev[1] x2 alpha * x2 (1 - alpha) * prev[2] y2 alpha * y2 (1 - alpha) * prev[3] smooth_boxes[track_id] (x1, y1, x2, y2)配合 YOLOv8 自带的跟踪功能model.track()效果会更好。跟踪功能会给每个检测框分配一个 ID这样你就能知道哪一帧的框对应的是同一个人。6.4 小脸漏检的几种补救方案小脸漏检是人脸检测的经典难题。除了提高输入尺寸之外还可以试试这几个方法一是用切片推理把大图切成若干小块分别检测再把结果合并二是用专门针对小脸优化的模型比如在 WIDER FACE 的 small 子集上做过微调的权重三是调整 NMS 的参数让小框不容易被大框抑制掉。切片推理的代码大概长这样def slice_inference(model, img, slice_size640, overlap100): h, w img.shape[:2] all_boxes [] for y in range(0, h, slice_size - overlap): for x in range(0, w, slice_size - overlap): patch img[y:yslice_size, x:xslice_size] results model(patch, verboseFalse) for box in results[0].boxes: x1, y1, x2, y2 box.xyxy[0].tolist() all_boxes.append([x1x, y1y, x2x, y2y, box.conf[0].item()]) return all_boxes这个方法会增加推理时间但在监控场景下对小人脸的召回率提升很明显。7. 从检测到识别人脸检测之后的扩展方向人脸检测本身只是一个中间步骤真正有价值的应用都在检测之后。比如人脸识别你需要把检测到的人脸裁剪出来送进一个特征提取网络比如 ArcFace 或者 FaceNet得到特征向量之后再做比对。YOLOv8 负责找到脸在哪特征网络负责这是谁的脸两者配合就能搭出一个完整的人脸识别系统。还有一个方向是人脸属性分析比如性别、年龄、表情、是否戴口罩。这些任务通常是在检测框的基础上再跑一个分类网络。你可以用 YOLOv8 的 classify 模式把裁剪出来的人脸图片送进去做分类。如果你做的是视频监控场景还可以结合 YOLOv8 的跟踪功能做人员轨迹分析。每个检测到的人脸分配一个 ID记录它在每一帧里的位置就能画出移动轨迹。这个在商场客流分析、工地安全监控里都有实际应用。我自己在项目里最常用的组合是YOLOv8 做人脸检测 简单的 IOU 跟踪做帧间关联 一个轻量级特征网络做人脸比对。整套流程在 GTX 1660 Ti 上能跑到实时对于中小规模的场景完全够用。如果你刚开始接触建议先把检测这一步跑稳再逐步往上叠加功能不要一上来就搞一个大而全的系统。
返回列表