简介:基于YOLOv5的人脸数据集标注工具,面向需要快速构建人脸数据集的算法工程师与开发者。其核心价值是自动化人脸标注流程,支持自定义人脸检测模型,并可将标注结果导出为PASCAL VOC XML、MS COCO JSON、YOLO TXT等主流格式,方便直接接入检测模型训练链路。工具提供webcam实时标注、批量图片标注与批量视频标注三种模式,覆盖常见数据采集场景。
资源包共59个文件,约78.68MB,以Python源码(face_labeling.py及util目录下的格式转换、模型管理等模块)、预训练模型(darkface-m.pt、widerface-m.pt)、说明文档(多个.md)和示例图片/视频为主,结构清晰,便于按需查阅。压缩包内还包含YOLOv5在脏脸、广域人脸等场景的适配说明,以及环境依赖requirements.txt,可降低部署门槛。
当前已有338人学习/浏览,适合需要低成本搭建人脸标注工具、或希望在YOLOv5基础上二次开发标注能力的开发者下载参考。
1. 基于YOLOv5的智能人脸标注工具:为什么标注这件事值得重做一遍
做目标检测的人脸方向项目,开头最磨人的不是模型选型,而是给几千张图出框。人工用 LabelImg 一张张框人脸,快的一分钟两三张,遇到密集人群、小脸、遮挡场景,一张图能折腾五分钟。数据标注工具和标注工具生态这几年已经成熟了不少,但工具解决的是“标得累不累”的问题,解决不了“从零开始出框”的效率问题。所谓智能人脸数据集标注工具,核心逻辑是用一个已经训好的人脸检测模型先跑一遍图片,自动生成一批高质量的候选框,再让标注人员在预标注基础上做修正而不是从头画框。这个方案落在 YOLOv5 上,是因为它的推理速度快、权重生态成熟、导出和部署都干净,跑通一个预标注流水线只需要一台普通 GPU 机器,甚至 CPU 也能勉强撑住小规模数据。这篇文章会把这个工具背后的原理、跑通流程、格式转换和踩坑点完整铺开,目标是让你拿到类似源码包后能自己动手搭一套,而不是停留在“有这个工具”的层面。
2. YOLOv5 预标注原理:检测结果是怎么变成可编辑标注的
2.1 预标注的完整流水线:从图片到可修正的标签文件
智能标注并不是什么神秘的黑匣子,它的工作流可以拆成四步:批量推理、置信度过滤、格式转换、人工修正。第一步把一批未标注图片送进 YOLOv5 检测模型,拿到每个目标的类别、置信度分数和边界框坐标,第二步按置信度阈值把低质量预测框丢掉,只保留高置信度的人脸框,第三步把 YOLOv5 输出的 txt 格式转成标注工具能直接打开的 XML 或 JSON 格式,第四步由标注人员在工具里加载预标注结果,只对有问题的框做位移、缩放或删除操作。
这四条链路里,真正决定预标注质量的是第三步的格式转换做得对不对。YOLOv5 的--save-txt输出是归一化坐标,格式是class x_center y_center width height,四个值全是 0 到 1 之间的小数,而 LabelImg 的 VOC XML 格式要求的是像素坐标xmin ymin xmax ymax,CVAT 又期望 COCO 或 YOLO 格式。不做转换直接导入,常见的后果是框全部错位到一个方向,或者类别编号错乱,标注工具打开后一片飘红。所以一个靠谱的标注工具源码,核心亮点不在“调用 YOLOv5 检测”这一步,而在格式转换和边界情况的处理上。
另一个容易被忽略的环节是检测结果的排序和去重。批量推理时,一张图里可能有多个模型预测框重叠,YOLOv5 的 NMS 已经做了第一次去重,但跨图片的重复框依然存在。比如视频抽帧得到的图片序列里,同一张人脸在连续帧中会被反复检测到,如果不做去重,标注人员会看到同一个身份被框了几十次,修正工作量大增。常见的做法是加入一个基于 IoU 的跨图去重步骤,或者在做数据集时按帧间隔抽帧,避开太接近的邻帧。
2.2 为什么是 YOLOv5 而不是 Faster R-CNN 和 SSD
人脸检测不是没有专精模型,SCRFD、RetinaFace 在 WIDER FACE 榜单上的表现都比通用 YOLOv5 好看,但在这个“标注工具”的场景里,选型逻辑完全不同。预标注工具要的是两件事:一是批量推理时单张图耗时可控,二是部署和二次开发成本低。YOLOv5 的工程化程度在这三者里是最高的,一个detect.py就能完成批量推理,超参数调整也友好,换个数据集重新训练的成本比 Faster R-CNN 低一个量级。
Faster R-CNN 的优势是检测精度上限高,尤其在小目标上,但它的两阶段结构在批量预标注时速度劣势明显。同样一张 1080P 图片,YOLOv5s 在消费级显卡上能跑到 50 FPS 以上,Faster R-CNN 通常在个位数 FPS。SSD 的速度不错,但它的训练生态和预训练权重不如 YOLOv5 丰富,二次微调人脸数据集的资料也少。从标注工具的开发者视角看,YOLOv5 的 GitHub 仓库本身就是一套完整的训练、验证、推理框架,直接拿来改要比从零搭一个推理管道省太多事。
还有一个现实因素是权重兼容性。YOLOv5 官方发布的yolov5s.pt、yolov5m.pt可以直接在 COCO 数据集上跑通用目标检测,虽然 COCO 里没有专门的人脸类,但可以用person类做粗筛,或者在此基础上用 WIDER FACE 人脸数据集做二次微调。用户拿到“源码+模型.zip”之后,最省心的路径就是直接用包里配套的权重跑推理,而不是自己从头训一个。这也是这类工具普遍选 YOLOv5 的原因:开箱即用,生态完整。
2.3 源码包里那几块必须理解的核心逻辑
一个标准的人脸数据集标注工具源码,无论打包成什么样,里面通常藏着四块逻辑:检测脚本、格式转换模块、数据校验模块和可视化回放模块。检测脚本封装的是 YOLOv5 的推理部分,负责批量处理图片目录并输出预测结果;格式转换模块把预测结果转成标注工具可读的格式;数据校验模块用来发现漏检和误检,比如统计每张图片的框数量、置信度分布、框面积占比,这些统计指标能帮你快速判断一批数据的预标注质量;可视化回放模块则把检测框画回原图,生成一张带框的预览图,方便人工抽查。
我拿到一个标注工具源码时,第一件事不是跑 demo,而是先看格式转换模块里类别映射是怎么写的。人脸标注看起来只有一个类别,但实际项目里经常区分“人脸”和“带口罩人脸”,WIDER FACE 数据集里也没有口罩类,如果模型是在 WIDER FACE 微调的,那类别编号表就要在转换模块里人工维护。很多标注工具翻车就翻在这里:模型输出class 0代表人脸,转换脚本里却没写类别映射表,导出的 XML 里类别名直接写成了数字 0,LabelImg 打开后显示“0”而不是“face”,后续训练时按类别名匹配就全乱了。
另一个值得关注的是图片读取路径的处理。YOLOv5 推理时用的是 OpenCV 读图,默认按 BGR 顺序加载,而格式转换模块如果用了 PIL 保存预览图,PIL 按 RGB 输出,两者混用时颜色通道会互换。人脸检测对这种通道差异不算敏感,但如果你在工具里叠加可视化回放功能,画出来的框和原图颜色对不上,会让人误以为检测结果飘了。这个细节不算难查,但没有经验的人排查起来可能要折腾很久。
3. 搭建环境到跑出第一批预标注:从零到能用的完整路径
3.1 环境准备:conda、PyTorch 与依赖安装
大部分类似的源码包里都会带上 requirements.txt,但直接在原有环境下装依赖经常把其他项目的依赖搞坏。我习惯先用 conda 建一个干净的独立环境,再在这个环境里安装 PyTorch 和 YOLOv5 的依赖。下面是一套可以照抄的最小命令:
conda create -n yolov5-label python=3.9 -y conda activate yolov5-label # 根据你的 CUDA 版本选择 torch 版本,这里以 cu118 为例 pip install torch==2.0.1 torchvision==0.15.2 --index-url https://download.pytorch.org/whl/cu118 git clone https://github.com/ultralytics/yolov5.git cd yolov5 pip install -r requirements.txt代码里的python=3.9不是必选,但 YOLOv5 官方在 3.8 到 3.10 之间的兼容性验证最充分,选 3.9 基本不会碰到语法兼容问题。PyTorch 版本选 2.0 系列是因为它在 CUDA 11.8 下有预编译 wheel,安装速度快,而且和 YOLOv5 的 ops 兼容性稳定。装依赖时注意 settings 文件默认会去下载一些预训练权重,如果网络环境受限,可以先手动把权重文件放到weights/目录下,再修改data/里的配置跳过自动下载。
环境装好后,先用一张真实图片跑一次detect.py,验证推理链路是通的,再进入下一步。这里有个小技巧:先用小图片测试,比如 320 分辨率,如果小图能跑通再切回实际用的 640 或 1280。小图推理耗时低,出问题容易定位,大图跑挂了反而难分清是显存不足还是代码配置问题。
3.2 准备人脸检测模型:从 COCO 权重到 WIDER FACE 微调
预标注工具的模型权重有两种来源。一种是直接用 YOLOv5 官方在 COCO 上训练的通用权重,检测person类,再通过几何先验筛选出人脸区域。这种做法精度上限低,因为 COCO 的 person 框是整个人的范围,不是人脸框,用这种权重做预标注,标注人员要把每个人框缩到脸部,等于还是手动修正大部分框,效率提升有限。
另一种是拿 WIDER FACE 数据集在 YOLOv5 上做二次微调,得到一个专门的人脸检测权重。WIDER FACE 有 3 万多张图片、约 40 万张标注人脸,覆盖了尺度、遮挡、姿态各种困难情况,是做人脸检测预标注最常用的公开数据集。微调时不要从头训练,用yolov5s.pt作为预训练权重加载,冻结前几层再训练,能大幅缩短训练时间:
python train.py --data widerface.yaml --weights yolov5s.pt --img 640 --batch-size 16 --epochs 50 --freeze 10这里的--freeze 10表示冻结模型前 10 层的参数,只训练后面的检测头和特征提取层。这样做的理由是人脸的基础视觉特征和通用物体特征差异不大,前几层提取的边缘、纹理信息可以直接复用,训练能更快收敛。--img 640是一个平衡点,WIDER FACE 里大量小脸目标,分辨率低于 640 会漏检严重,高于 640 会显存压力大。
微调完成后,用val.py在 WIDER FACE 验证集上测一下 mAP,正常情况下人脸类别的 mAP 应该在 0.7 以上。如果低于 0.5,不用怀疑模型问题,大概率是配置文件里的类别数写错了,比如 WIDER FACE 数据集的 YAML 文件里nc: 1写成了 80。这个错误很常见,因为很多人直接复制 COCO 的 YAML 改,忘了改类别数。
3.3 跑通批量预标注脚本:参数怎么设才有实际效率
模型准备好之后,批量预标注的核心命令是对整个图片目录跑推理。YOLOv5 的detect.py原生支持传入图片文件夹路径,自动遍历目录下所有图片并输出检测结果。实际使用时我会把几个关键参数固定下来,避免每次手敲错误:
python detect.py --weights runs/train/exp/weights/best.pt \ --source /path/to/unlabeled_images \ --img 1280 \ --conf-thres 0.35 \ --iou-thres 0.45 \ --save-txt \ --save-conf \ --project /path/to/output--img 1280是这里最关键的参数。YOLOv5 在 640 分辨率下训练,推理时放大到 1280 能显著提升小脸检测率,但会增加推理耗时。实际上推理耗时和分辨率不是线性关系,1280 的耗时大约是 640 的四倍。如果数据集以中近景人脸为主,640 就够用;如果包含大量远景人群场景,必须上 1280,否则漏检率高到预标注失去意义。
--conf-thres 0.35这个阈值我一般会调两轮。第一轮用 0.3 跑完看整体框数量和误检率,如果发现大量明显不是人脸的背景框,就把阈值提到 0.45 再跑一次;如果发现漏检严重,比如照片里明显的人脸没出框,那就要降阈值而不是强行提高分辨率。置信度阈值本质上是精度和召回率的权衡旋钮,做预标注时宁可让召回率略高一点,多出几个错框让人工删,也比漏检导致人工补画框要省力。补画一个框需要两三个操作,删一个框只要一次点击,这就是为什么预标注任务里阈值设置应该偏向召回。
--save-conf参数很多人会漏掉,但它对后续质量评估很重要。保存置信度信息后,你可以按置信度区间筛选框,比如只审核置信度低于 0.6 的框,高置信度框默认可信。这种分层审核策略能大幅减少人工工作量,后面第 5 章会细讲。
3.4 把检测结果转成 VOC 与 YOLO 格式:转换脚本与四个边界坑
YOLOv5 的--save-txt输出是归一化坐标,每张图片的检测结果保存在同名 txt 文件里。要让预标注结果能被 LabelImg 或 CVAT 直接打开,需要写一个转换脚本。下面这个脚本把 YOLO 格式转成 VOC XML 格式,并且把置信度信息写进 XML 的difficult字段,方便后续筛选:
import os from pathlib import Path from xml.etree.ElementTree import Element, SubElement, tostring from xml.dom import minidom def yolo_to_voc(txt_path, img_width, img_height, class_names, output_dir): boxes = [] with open(txt_path, 'r') as f: for line in f.strip().splitlines(): parts = line.split() if len(parts) < 5: continue cls_id = int(parts[0]) x_center, y_center, w, h = map(float, parts[1:5]) conf = float(parts[5]) if len(parts) > 5 else 0.0 xmin = int((x_center - w / 2) * img_width) ymin = int((y_center - h / 2) * img_height) xmax = int((x_center + w / 2) * img_width) ymax = int((y_center + h / 2) * img_height) xmin, ymin = max(0, xmin), max(0, ymin) xmax, ymax = min(img_width, xmax), min(img_height, ymax) if xmax <= xmin or ymax <= ymin: continue boxes.append((cls_id, xmin, ymin, xmax, ymax, conf)) # 构建 VOC XML 结构 root = Element('annotation') for cls_id, xmin, ymin, xmax, ymax, conf in boxes: obj = SubElement(root, 'object') name = SubElement(obj, 'name') name.text = class_names[cls_id] difficult = SubElement(obj, 'difficult') difficult.text = str(int(conf < 0.6)) # 低置信度标注为难例 xml_str = minidom.parseString(tostring(root)).toprettyxml(indent=' ') out_path = Path(output_dir) / (Path(txt_path).stem + '.xml') out_path.write_text(xml_str, encoding='utf-8')脚本逻辑本身不复杂,但四个边界情况必须处理。第一是坐标越界,模型偶尔会预测出完全超出图片范围的框,比如x_center是 1.05,转成像素坐标后超出图片宽度,如果不裁剪到[0, img_width]范围内,LabelImg 打开这种 XML 会直接报错或者框跑到图片外面。第二是宽度或高度为负数的框,这种情况通常出现在模型对极端宽高比的预测上,转换时判xmax <= xmin直接丢弃。第三是 txt 文件里的 class id 和 XML 里的类别名映射,前面的 3.1 节提到过,class id 是数字,XML 里要写可读的人脸类别名,映射表必须单独维护。第四是difficult字段的利用,这个字段在 VOC 数据集中原本表示难例,这里借用来保存置信度分层信息,标注人员打开 XML 时就能直观看到哪些框需要重点检查。
如果项目方用的标注平台是 CVAT 而不是 LabelImg,那转换脚本要输出的是 COCO 格式的 JSON 而不是 XML。COCO 格式更复杂一些,要维护 images、annotations、categories 三个顶层字段,并且 annotation 里的bbox是[x, y, width, height]格式,而不是 VOC 的[xmin, ymin, xmax, ymax]。这两个格式搞混是预标注接入 CVAT 时最常见的翻车点,转换脚本里写清楚两个独立函数,不要用一个函数内部做分支,否则后续维护很痛苦。
4. 预标注落地避坑:5 个最常见的翻车现场
4.1 小尺寸人脸在 640 分辨率下几乎全漏检
现象:一批全景活动照片,人离得远,脸部区域在整张图里只占几十个像素,预标注跑完发现图片里的框数远小于实际人脸数,大量漏检。
原因:YOLOv5 在 640 分辨率下训练时,anchor 尺寸设计是针对目标占总图比例较大情况的。人脸只有十几个像素时,特征提取层下采样之后只剩个位数的特征点,检测头根本没有足够信息判断这是不是人脸。
解决:推理时把--img参数从 640 提到 1280 甚至 1536,同时把--conf-thres降到 0.25。这两种手段先试前者。如果显存不足跑不动 1280,换用yolov5m或yolov5l这类参数量更大的权重,它们在小目标上的表现比yolov5s强不少。如果硬件实在带不动,最后一个选择是把图片切块,按 50% 重叠率切成四块分别推理再合并结果,代价是重复框需要二次 NMS。
4.2 背景被误检成人脸,置信度阈值调高后误检少了但漏检也多了
现象:预标注出来的框里夹杂大量背景误检,比如柱子上的纹理、树叶间隙、甚至光斑都被框成了人脸,人工审核时删除量巨大。
原因:模型在微调时如果没有专门做难例负样本挖掘,对类人脸纹理的判别力不够。WIDER FACE 数据集里本身以正样本为主,负样本占比低,模型容易把纹理特征误当成面容特征。
解决:先把--conf-thres从 0.25 提到 0.45,同时观察漏检变化。误检和漏检同时高的情况,说明问题不在阈值,而是模型本身判别力不足,这时候要回头用误检图片做难例回灌,重新训练一轮。做法是把误检图片整理成不含人脸标注的负样本图片,混入训练集,让模型学会区分“像人脸但不是人脸”的区域。
4.3 转换后的 XML 导入标注工具,类别名成了数字而不是文字
现象:LabelImg 打开预标注 XML,类别栏显示的是“0”或“1”,不是“face”,导入后标注人员无法按类别筛选。
原因:转换脚本里没有维护 class id 到类别名的映射表,直接把 YOLOv5 输出的数字类别写进了 XML。
解决:在转换脚本里显式定义类别列表,例如class_names = ["face"],确保类名可读。如果你的模型同时检测人脸和人脸关键点,或者区分普通人脸和戴口罩人脸,这个列表要按模型训练时的类别顺序写,顺序错了会导致所有标签串位。这里有个自查方法:用模型跑一张包含多类目标的图片,看 txt 输出里各类别的 id 分布,再对照映射表检查是否有 id 超出列表长度的异常值。
4.4 模糊和遮挡人脸的置信度低被过滤掉,导致难样本越标越少
现象:预标注跑完后,标注结果里全是清晰的正脸,侧脸、模糊脸、戴帽子遮挡的人脸全被低置信度过滤掉了,数据集变成“简单样本集合”,后续训练出的模型在真实场景泛化很差。
原因:置信度阈值过滤是无差别过滤,它不管目标是否重要,只按置信度一刀切。模糊和遮挡人脸的置信度天然低于清晰正脸,阈值一高就全被滤掉。
解决:改成双阈值策略。用高阈值(0.7)筛出可信框直接进标注结果,用低阈值(0.2)筛出候选框单独存到一个“待人工确认”目录。标注人员集中处理这批低置信度候选框,虽然辛苦但能保住难样本。这套做法在主动学习里叫难例挖掘,放到预标注里就是“低置信度候选框单独审核”策略。我在实际项目中,低置信度候选框的审核工作量约占整体的 20%,但贡献了训练集里一半以上的难例。
4.5 坐标归一化搞混,框全部偏移到图片左上角
现象:导入标注工具后,框全部堆在图片左上角,位置完全不对。
原因:YOLOv5 的 txt 里是归一化坐标,如果要转到 VOC 的像素坐标,必须乘上图片的宽和高。如果图片实际宽度是 1920,但是脚本里写死的img_width=640,那转换出来的坐标全部小了 3 倍,框就挤在左上角。
解决:转换脚本不能硬编码图片尺寸,要从图片文件本身读取宽高,用 PIL 或 OpenCV 的shape属性获取。涉及批量处理时还要注意,图片目录里可能混有 PNG 和 JPG,二者压缩格式不同但读取尺寸的方式一样,不要因为扩展名不同而走不同的读取分支。另外在转换结束后做一次自动校验:随机抽取 5% 的 XML,解析出所有框的坐标,检查是否有框坐标超出图片宽高范围、是否有框面积为 0,这两项检查能拦截 90% 的格式转换错误。
5. 让预标注越用越准的进阶玩法
预标注工具最大的价值不是第一次跑出来的结果,而是它能不能随着使用变得越用越准。一个可落地的进阶做法是把主动学习的闭环跑起来:人工审核完一批预标注结果后,把审核时删掉的高置信度误检框、修正过的人脸框、以及低置信度的难例框分别导出,混合进训练集做增量训练。增量训练不需要从头跑,直接用现有权重加新数据微调 20 到 30 个 epoch 就能看到效果。这样跑 3 到 4 轮之后,模型在你的特定数据分布上会明显比通用的 WIDER FACE 微调权重更准,预标注的人工修正工作量会逐轮下降。
验证预标注质量的方法也要同步做量化,不能只看感觉。一个可量化的指标是“框命中率”:人工审核时只调整位移而不新增或删除的框数量占总框数的比例。这个比例超过 80%,说明预标注质量已经适合直接进入训练环节;低于 50%,说明模型和数据分布不匹配,需要回炉重训而不是继续堆数据。另一个实用的习惯是把每轮预标注的置信度分布直方图保留下来,随着模型迭代,置信度分布会整体右移,这是模型在变准的最直观证据。我习惯在每轮增量训练后跑一次同一批测试图片,对比检测框数量和置信度分布的变化,用数据而不是直觉来决策。
这套方案本身的价值不需要太多包装,它解决的就是重复劳动问题。一个 10000 张图片的人脸数据集,纯人工标注大概需要 200 小时,预标注介入后能压缩到 60 小时以内,省出来的时间足够做两轮模型迭代优化。最后提醒一句:预标注工具输出的结果一定要有人工审核环节,完全无人化的自动标注在现阶段不容易落地,至少要把置信度低于 0.8 的框全部过一遍人眼。这个习惯帮我避开了很多数据质量事故,希望也能帮到你。
本文还有配套的精品资源,点击获取