十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

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

YOLOV7 + WIDER Face 实战:从数据预处理到部署的人脸检测全流程

YOLOV7 + WIDER Face 实战:从数据预处理到部署的人脸检测全流程 人脸检测这活儿说难不难说简单也不简单。如果你做过几年视觉落地大概率会碰到这种场景视频流里的人脸不大、遮挡多、光照还忽明忽暗用传统Haar级联或者老一代的MTCNN漏检误检能把你逼疯。后来转深度学习试了SSD、RetinaFace精度上去了但推理速度在边缘设备上又拉胯。直到YOLOV7出来我才算找到一个相对舒服的平衡点。这篇文章想分享的就是我用WIDER Face YOLOV7做人脸检测的完整过程。WIDER Face 是人脸检测领域绕不开的benchmarkYOLOV7 则是速度和精度兼顾的目标检测模型两者组合起来既能跑学术指标又能直接推上线做产品。适合正在做人脸检测、目标检测落地或者刚入门YOLO系想找一套正经数据集练手的同学。我会从数据预处理、模型选型、训练调参、常见坑位到部署导出把能说的细节都摆出来。1. 整体方案与模型选型为什么是WIDER Face YOLOV71.1 人脸检测的核心挑战人脸检测之所以比通用目标检测更讲究是因为人脸的分布极具“欺骗性”。一个画面里可能同时出现5米外的半张侧脸、2米外戴着口罩的正脸、还有几乎贴镜头的自拍大脸尺度和姿态跨度极大。WIDER Face 这个数据集恰恰把这些极端情况都覆盖了训练集、验证集、测试集分别有12880、3226、16097张图片累计标注超过40万个人脸框。我第一次拿WIDER Face训练时最大的感觉就是它故意为难你。大量的人脸只有十几个像素宽要检测出来模型必须对小目标非常敏感。同时有些样本用文字标注了“遮挡”“模糊”“夸张姿态”等属性这些信息虽然不会直接喂给YOLOV7但在数据筛选和后续针对性增强时非常有用。另一个难点是评估标准严格。WIDER Face 按检测难度把验证集分成Easy、Medium、Hard三档Easy档都是清晰的大脸Hard档几乎就是“视力测试”——小脸、遮挡、暗光混在一起。很多人用YOLOV7在COCO上刷得挺开心一换到WIDER Face的Hard子集mAP立刻露馅。1.2 YOLOV7凭什么能扛事YOLOV7是2022年提出的单阶段检测器它的核心优势不是某个花哨的新模块而是把一堆已有技巧做了有效的工程化组合。比如重参数化卷积、动态标签分配、辅助训练头这些机制单拆开看都不新鲜但YOLOV7把它们整合得很好在相近算力下比YOLOV5系列有明显提升。和人脸检测常见的RetinaFace、SCRFD这些专用模型相比YOLOV7最大的好处是生态成熟。仓库自带完善的训练、验证、导出、部署脚本资料多、踩坑答案多换场景也方便。我做项目时还要顺带检测人头、口罩、手部等目标RetinaFace专注人脸反而不够灵活YOLOV7只需换数据集就能扩展。如果你追求的是“移动端轻量级模型”YOLOV7的tiny版本会更合适如果精度优先且显卡允许直接用YOLOV7x。我这边用的是普通版YOLOV7配合输入尺寸640x640在RTX 3090上单卡训练约18小时能收敛推理速度在端侧设备上也有较大调整空间。1.3 模型可迁移到细分领域的潜力人脸检测模型最大的魅力在于它是一个很好的“底座”。比如做智慧医疗场景下的皮肤病检测病灶区域和人脸在形态上有差异但“定位目标区域”这个任务本质是一样的。你完全可以用人脸数据集把模型的基础特征训练好再用皮肤病灶数据集做迁移学习收敛速度比从COCO预训练权重起步更快。当然医疗场景涉及数据合规和伦理问题这里只讨论技术路径。实操时你只需要把皮肤病灶数据集整理成YOLO格式保留YOLOV7预训练权重的前面若干层做微调即可。这类迁移在实际项目中帮我省了不少事。2. WIDER Face 数据集处理从原始标注到YOLO格式2.1 数据下载与目录结构WIDER Face 的官方下载方式是把图片和人脸标注分开下载。训练集、验证集、测试集各有一个zip包标注文件则是单独的文本压缩包。下载完先不要急着解压后开干建议先建立清晰的目录widerface/ ├── images/ │ ├── train/ │ │ └── 0--Parade/xxx.jpg │ └── val/ ├── labels/ │ ├── train/ │ └── val/ ├── wider_face_split/ │ ├── wider_face_train_bbx_gt.txt │ └── wider_face_val_bbx_gt.txt └── wider_to_yolo.py这里的wider_face_split是官方标注目录wider_face_train_bbx_gt.txt就是标注文件包含了图片路径、人脸数量、每个人脸的四个坐标和模糊/遮挡等属性标注。YOLOV7 绘图数据的标签不会直接用这个原始格式需要转换。注意WIDER Face 原始图片本身分辨率不低但很多人脸真的很小。做目标检测训练时我建议保留原始分辨率喂给模型不要为了省显存无脑降到224x224否则Hard档基本废掉。2.2 标注格式转换脚本YOLOV7 需要的标签格式是每行一个目标内容是class x_center y_center width height其中中心坐标和宽高都是相对于图片宽高的归一化数值。WIDER Face 原始标注是x1 y1 x2 y2所以转换代码塌塌实实写上就好。import os def convert_annotation(in_file, img_dir, out_dir): with open(in_file, r, encodingutf-8-sig) as f: lines f.readlines() idx 0 total len(lines) while idx total: img_path lines[idx].strip() idx 1 num_faces int(lines[idx].strip()) idx 1 # 对应图片的宽高这里需要从图片中获取实际分辨率 img_full os.path.join(img_dir, img_path) from PIL import Image w_img, h_img Image.open(img_full).size # 输出文件保持相同子目录结构 out_txt os.path.join(out_dir, img_path.replace(.jpg, .txt)) os.makedirs(os.path.dirname(out_txt), exist_okTrue) with open(out_txt, w) as fout: for _ in range(num_faces): x1, y1, w, h, blur, expression, illumination, invalid, occlusion, pose lines[idx].strip().split() idx 1 x1, y1, w, h float(x1), float(y1), float(w), float(h) # 跳过无效标注 if invalid 1: continue cx (x1 w / 2.0) / w_img cy (y1 h / 2.0) / h_img nw w / w_img nh h / h_img # 防止归一化后越界做一次裁剪 cx min(max(cx, 0.0), 1.0) cy min(max(cy, 0.0), 1.0) nw min(max(nw, 0.0), 1.0) nh min(max(nh, 0.0), 1.0) fout.write(f0 {cx:.6f} {cy:.6f} {nw:.6f} {nh:.6f}\n) print(f完成{out_dir})这里有个细节WIDER Face 的标注是x1 y1 width height不是x1 y1 x2 y2不要套用COCO格式的惯性。我第一次转换时就栽在这里把宽高当成了右下角坐标模型训练出来输出框全偏。另外标注里最后几个字段是blur、expression、illumination、invalid、occlusion、pose其中invalid为1表示这个框是无效标注转换时直接跳过。2.3 训练集/验证集划分与ignored区域处理很多教程会把训练和验证直接按官方划分来用这没问题。但如果你的目标是做项目落地而不是刷基准我建议从训练集里再匀出一部分做验证因为官方验证集的Hard档基本是“地狱难度”用验证集来指导早停容易过拟合到Hard子集。我当时是按 9:1 从训练集内部划分了一个小验证集最终在官方验证集上做最终评估。还有一个容易被忽略的点WIDER Face 提供了ignore区域也就是原始标注文件里invalid字段对应的区域。这些区域不是人脸的负样本直接丢弃会导致模型在困难区域产生过多误检。更稳妥的做法是把 invalid 区域转成YOLO的“忽略区域”不过在YOLOV7的原生训练流程里并不直接支持忽略框所以大多数情况下还是“先跳过”。如果你的业务对误检要求极高建议额外收集一些包含密集人脸的负样本把它们标记为背景图加入训练。3. 训练环境与实操配置3.1 环境依赖与仓库准备YOLOV7 官方仓库以 PyTorch 为基础我对环境的要求是Python 3.8、PyTorch 1.12、CUDA 11.x。不要一上来就装最新版PyTorch有些老算子兼容性反而麻烦。直接克隆官方仓库git clone https://github.com/WongKinYiu/yolov7.git cd yolov7 pip install -r requirements.txt如果你只要人脸检测一个类别建议把cfg/training/yolov7.yaml里的nc改成 1。注意修改自己使用的配置文件别只改 data yaml 里的类别数不然加载预训练权重时会报维度不匹配。下载预训练权重时我推荐用YOLOV7官方给的yolov7_training.pt这是训练中途的checkpoint做迁移学习比用yolov7.pt更好。如果只想快速验证流程也可以用yolov7.pt冷启动但收敛会慢一些。3.2 数据配置文件与超参数在YOLOV7仓库的data/目录下新建widerface.yamltrain: /path/to/widerface/train.txt val: /path/to/widerface/val.txt test: /path/to/widerface/test.txt nc: 1 names: [face]train.txt和val.txt是图片路径列表每行一个路径。写个脚本扫描images/train下所有jpg文件就自动生成这里不再展开。关键点是路径要写绝对路径避免训练到一半报找不到文件。超参数方面我几乎没有动hyp.scratch.yaml里的默认值只调了两个地方hsv_h、hsv_s这类颜色增强保持默认但因为WIDER Face里存在大量模糊人脸我把degrees从0调到5让模型轻微旋转另外把mosaic保持默认开启。人脸场景中用mosaic的好处是能拼接不同尺度的人脸到同一张图里强化小目标检测能力缺点是训练前期loss波动大这是正常的。注意不要一开始就把epochs设成300。WIDER Face单类别训练收敛很快我习惯先跑50个epoch看曲线模型如果在验证集上停止提升再决定是否加训练轮次避免浪费时间。3.3 训练命令与过程监控训练命令比较直接python train.py \ --data data/widerface.yaml \ --cfg cfg/training/yolov7.yaml \ --weights yolov7_training.pt \ --batch-size 16 \ --img 640 \ --epochs 100 \ --workers 8 \ --device 0 \ --name wider_facebatch size 的选择跟显存直接挂钩。我的3090是24G显存batch size 16 是稳定值如果是12G显存的卡先把 batch size 降到8输入尺寸降到 512再考虑要不要开--cache-images。尽量别为了凑batch size把图片缩到 320人脸检测对分辨率太敏感小图训练基本等于白训。训练过程中我盯三个东西GIoU loss、obj loss、val mAP。YOLOV7的obj loss在人脸数据集上会降得比较曲折如果发现它每隔几个epoch反弹一次别慌多半是mosaic变换导致正样本数量波动。真正需要警惕的是val mAP在30个epoch后仍然低于0.3这说明数据转换或配置大概率出了问题。训练结束后runs/train/wider_face/weights/目录下会有best.pt和last.pt。best.pt是验证集上表现最好的权重导出部署时首选它last.pt是最后一轮权重一般用不上除非你想继续训练。4. 常见问题与排查心得4.1 小目标漏检与误检在小脸、远脸场景里漏检是最常见的问题。线条粗暴的解决方式是提高输入分辨率从640提到1280能显著改善Hard档的召回率但推理时间翻倍。如果不想牺牲速度可以先在训练时开启YOLOV7的--img 800让模型看到更大的特征图再在导出模型时做INT8量化来补偿速度损失。另一种针对小目标的思路是多尺度训练。在train.py中设置--multi-scale训练时图片会随机在0.5倍到1.5倍之间拉伸。我用WIDER Face做实验时开启多尺度训练后Hard档的mAP涨了大约2个百分点效果明显。但要注意多尺度训练会让batch内样本分辨率不稳定显存不太够的机器建议别开。误检则是另一个极端。WIDER Face里背景复杂容易在皮肤纹理、圆形图案上产生误检。训练时提升负样本比例是办法但如果标签里没有显式负样本可以在后处理阶段提高置信度阈值比如从0.25提到0.4配合NMS的IoU阈值从0.45调到0.5。这个操作有机会让精确率涨代价是召回率微降具体看业务更在意哪头。4.2 训练不收敛或显存不足训练loss不降先别怀疑模型先检查标签。打开一个转换后的txt文件确认坐标都是0到1之间的数值并且没有空文件。WIDER Face里有些图片标注文件可能因为invalid区域过多被过滤成空文件YOLOV7遇到空标签会直接报错或导致训练异常。我的处理方式是写脚本把所有空标签和对应的图片从训练列表中移除。显存不足时除了调低batch size还可以把rect模式关掉。YOLOV7默认rectFalse如果开了矩形训练批量处理时会调整图片尺寸改动不好预测。另外--cache-images会把图片一次载入内存省去IO等待但吃内存加上--workers 0可以降低数据加载时对显存的额外占用代价是训练变慢。4.3 指标提升的几个实用技巧如果验证集mAP卡在某个值上不动我一般按下面顺序排查先调anchor。YOLOV7默认anchor是在COCO上聚类得到的对WIDER Face这种小脸密集场景未必合适。用仓库里的tools/kmeans_anchors.py对人脸标签重新聚类把anchor的宽高分布调得更贴近真实人脸框。人脸框通常都是扁方形聚出来的anchor和COCO的差距不小。然后是Warm-up轮次。YOLOV7的warmup默认3个epoch在人脸单类别上可以适当缩短到1个epoch因为类别少模型不需要那么长的warmup来稳定训练。这个细节是我翻阅社区经验时试出来的实际训练中loss下降更快。最后是TTA。如果只是刷指标测试时可以用--img 640 --augment开启测试时增强把多尺度推理结果合并。但对生产环境TTA在延迟上不划算我只在最终评估时用。5. 从ONNX到实际部署5.1 模型导出训练好best.pt之后第一步是转成ONNX。YOLOV7仓库的export.py已经封装好直接执行python export.py \ --weights runs/train/wider_face/weights/best.pt \ --img 640 \ --batch 1 \ --simplify \ --topk-all 100 \ --iou-thres 0.45 \ --conf-thres 0.25 \ --include onnx导出时要注意--simplify参数它会去掉ONNX里冗余的计算节点减少推理框架的兼容问题。如果之后要做TensorRT部署最好在--include里直接加上engine让仓库脚本生成适配你显卡的TensorRT引擎比自己手动用trtexec更方便。检测器导出后预处理和后处理不能丢。ONNX模型输出的是原始预测结果需要做解码、过滤、NMS。YOLOV7的detect.py里提供了参考实现直接移植到ONNX Runtime即可。5.2 推理示例下面是一个基于ONNX Runtime的推理代码片段方便你集成到服务或边缘设备import cv2 import numpy as np import onnxruntime as ort session ort.InferenceSession(best.onnx, providers[CUDAExecutionProvider, CPUExecutionProvider]) input_name session.get_inputs()[0].name def preprocess(img, size640): h, w img.shape[:2] r min(size / h, size / w) new_h, new_w int(round(h * r)), int(round(w * r)) img_resized cv2.resize(img, (new_w, new_h)) canvas np.full((size, size, 3), 114, dtypenp.float32) canvas[:new_h, :new_w] img_resized canvas canvas[:, :, ::-1].transpose(2, 0, 1) / 255.0 return canvas[np.newaxis, ...] img cv2.imread(test.jpg) input_tensor preprocess(img) outputs session.run(None, {input_name: input_tensor}) # 后处理省略可参考仓库 detect.py 中的非极大值抑制逻辑注意YOLOV7导出模型时输出层可能包含三个尺度的特征检测结果的顺序因导出版本而异。强烈建议导出后用小图验证一次输出尺寸避免部署到服务上才发现问题。5.3 部署场景延伸从通用人脸检测到特定领域检测人脸检测模型往往不直接单独上线而是作为上层业务的第一步。比如在门禁场景人脸检测框出来后要接人脸质量评估、人脸对齐、人脸识别在客流统计场景可能需要统计画面中人头的数量。YOLOV7检测出的目标框相对稳定在后续接入跟踪算法时能有效减少ID Switch。我之前试着用相同流程把WIDER Face训练好的模型迁移到皮肤病灶检测的数据集上。数据量不大只有几千张标注图片但从预训练权重开始微调后模型在病灶定位上的表现比从零训练好一大截。这说明人脸检测训练出的底层特征泛化性确实强。当然了具体业务一定要重新标注和验证不能直接把模型拿去当医疗诊断工具。6. 一些值得注意的细节6.1 数据闭环比模型结构更影响上限训练YOLOV7也好换其他backbone也好最终在人脸检测上拼的还是数据分布是否贴近真实场景。WIDER Face 是一个很好的公共基准但它覆盖的分布和你的业务场景往往有偏差。我在项目里习惯的做法是先在WIDER Face上训练出基础模型然后专门去拍摄目标场景的视频抽帧后用人脸检测模型预测一轮再把置信度高且正确的框作为自动标注样本结合人工抽检扩充训练集。这种方式能让模型在特定角度、特定光照下快速适应。6.2 后处理参数要在业务场景里调很多同学直接把conf-thres0.25和iou-thres0.45用在所有场景这是不合适的。如果场景是远距离人脸抓拍框漏得厉害优先把conf降低到0.15甚至0.1再用NMS去重如果场景是柜台前的人脸识别误检会导致后面识别模块白算conf就该提到0.45以上。不同业务对召回率和精确率的容忍度完全不同参数没有绝对的最好。6.3 不要迷信单一大模型YOLOV7有多个尺寸x版本精度确实高但实际部署时我经常对同一份数据同时训练普通版和tiny版。线上服务先用tiny版跑一版评估精度损失和性能收益如果差距不大就保留tiny版只有hard样本占比高时才考虑上大模型。这样既控制了推理成本又保证了体验。最后再分享一个小技巧。WIDER Face的验证集标注本身包含框的宽高你可以统计一下自己训练集里人脸框的尺寸分布然后根据分布去设定anchors数量。我试过把默认3组anchor改成4组模型在小脸上的召回率提升非常直接但训练时间也相应增加。这个取舍嘛看你有多少显存和耐心。
返回列表