☰
基于YOLOv5的半自动人脸数据集标注:从预标注到训练闭环
2026/9/30 6:24:06 网站建设 项目流程

简介:面向人脸数据集构建与标注场景,这份基于YOLOv5的智能标注工具源码包,适合算法工程师、CV开发者及标注团队使用。工具内置YOLOv5人脸检测模型,支持加载自定义模型,实现图片、视频及webcam实时标注,并能一键导出PASCAL VOC XML、MS COCO JSON、YOLO TXT等多种格式标签,可灵活对接主流训练框架,有效提升标注效率。资源共59个文件,涵盖Python源码、配置文件、模型权重(pt)、示例图片与视频、说明文档等,压缩包整体约78.68MB。其中py脚本覆盖检测模型调用、标签格式转换、参数配置等模块,pt权重文件提供预训练模型,sample与jpg/mp4则用于快速体验标注流程,md文档附有使用与环境配置说明。目前已有338人学习下载,适合需要自动化标注人脸数据、减少人工标注成本的开发者参考使用。

1. 先回答最该问的问题:为什么做数据集还要用YOLOv5来标

手工一张张拖框标注人脸数据集,一整天下来可能连三百张都处理不完,脖子先受不了。基于YOLOv5的智能人脸数据集标注工具,核心思路是把标注这个苦力活的顺序颠倒一下:先让YOLOv5把图里能看见的人脸全部框出来,生成初步标注,再由人去改错,而不是从零开始画框。它解决的是新项目冷启动时最缺标注、又最耗人工的那段路,适合正在做人脸检测、人脸识别、表情分类这类任务,手里有一批未标注图片但不想在LabelImg里耗一个星期的开发者。前置条件不苛刻:一张能跑PyTorch的显卡,或者干脆用CPU推理,慢一点但不至于卡死。注意这个"智能"不是全自动,它替代的是初标环节,复核仍然要人来做,但它能把标注时间压缩到原来的五分之一甚至十分之一。

2. 半自动标注的运行逻辑:为什么是YOLOv5,而不是纯手动工具或大型标注平台

2.1 手动标注的瓶颈和半自动标注的价值

先算一笔账。假设你手里有 2000 张人脸图,平均每张图里 3 到 5 张脸,用 LabelImg 这类纯手动工具标,单张图拖框加微调平均要 40 到 60 秒,遇到遮挡、侧脸、模糊的图更慢。一小时满打满算标 60 到 80 张,2000 张就是 25 到 33 个小时——这还没算中间休息、返工和标注一致性检查。更要命的是,这种纯体力活做久了人会疲劳,框的位置开始漂移,标签点错也没注意到,等训练时发现数据有问题,回头改标注比重标还痛苦。

半自动标注的思路就一句话:让模型先干 80% 的活,人只干那 20% 的判断题。YOLOv5 跑一遍推理,把置信度高的框直接落成标注,人工只需要检查漏检的、删掉误检的、微调不准确的框。同样 2000 张图,YOLOv5 用一张 3060 显卡跑推理,几分钟就出完所有预标注,剩下的人工复核时间大约 4 到 6 小时,工作量直接少一个数量级。这个数字对比就是半自动标注存在的全部理由。

2.2 YOLOv5 作为标注引擎的四个选型理由

为什么偏偏是 YOLOv5,而不是 Faster R-CNN、SSD 或者别的检测模型?第一个理由是推理速度快。标注工具的使用场景是批量刷图,不是单张精测,YOLOv5s 权重在 640 分辨率下跑一张图只要几十毫秒,低显存配置也能用 batch 分批跑,不像两阶段检测器那样显存吃紧、速度还慢。第二个理由藏在 YOLOv5 的 detect.py 里:官方推理脚本自带--save-txt参数,一行命令就把检测结果落成 YOLO 格式的 txt 标注文件,坐标是归一化之后的中心点加宽高,和训练格式完全一致。这意味着预标注结果可以直接进入训练流程,中间不需要做检测框到标注格式的复杂转换,这是三个理由里最关键的一个。

第三个理由是权重生态成熟。通用场景下 COCO 预训练的 yolov5s.pt 能直接拿来用,但要做人脸数据集标注,一般会换用专门在 WIDER FACE 或 FFDD 上训练过的人脸权重,这些权重对人脸这个类别输出更准,不会出现"框住整个人"这种哭笑不得的结果。第四个理由是环境成本低,YOLOv5 的依赖就是标准 PyTorch 生态,一个 conda 环境就够,不需要像 CVAT 那样起一堆 Docker 服务。四个理由叠加下来,"基于 YOLOv5"不是噱头,而是给标注这件事找了个便宜、快速、输出格式友好的发动机。

2.3 三种常见"智能标注"方案的取舍:LabelImg脚本化、CVAT、LabelStudio

既然提到 CVAT 和 LabelStudio,就顺手把这几个目标检测常用标注工具的边界说清楚。方案一,继续用 LabelImg,但写个 Python 脚本把 YOLOv5 的推理结果批量灌进去,这个做法最轻,但 LabelImg 本身不支持批量导入外部预标注,需要自己做 XML 注入,维护脚本的功夫不小。方案二,用 CVAT,它自带自动标注插件,可以挂深度学习模型做预标注,功能全,但部署要起 PostgreSQL、Redis、Docker 容器,一个人做小项目属实有点杀鸡用牛刀。方案三,LabelStudio,有 ML backend 能接 YOLOv5,界面也现代,但配置一条模型推理链路要写后端代码,初学者容易被卡在环境上。

这三条路我实际都走过,最后回归到的方式是:YOLOv5 的 detect.py 负责推理出框,一个自己写的转换脚本负责把 txt 转成 PascalVOC XML 或直接转成 YOLO 训练格式,需要人工复检时用 LabelImg 打开 XML 修正,或者干脆在代码里做可视化检查。这个组合既没有平台依赖,又保留了人工复核入口。下表是四个方案的取舍对比:

方案是否需要独立服务自动标注能力人工复核便利度适合人群
LabelImg 纯手动否无高,界面直接拖框数据量小于 500 张
LabelImg + 脚本注入预标注否中,需自己写注入中,XML 兼容有编程能力的小团队
CVAT + 自动标注插件是,Docker 全家桶强,插件丰富高,在线协作多人团队、数据量大
YOLOv5 detect.py + 自定义转换脚本否强,直接出 YOLO 格式中,用可视化脚本或 LabelImg个人开发者、临时项目

结论很直白:手头这批数据如果是一次性任务,别去折腾 CVAT 和 LabelStudio,YOLOv5 推理加脚本转换是投入产出比最高的路。

3. 跑通最小可用链路:环境、目录与一条推理命令

3.1 环境准备:Python版本、PyTorch和推理依赖怎么装

YOLOv5 对环境的要求不算苛刻,但版本坑不少。我一般用 Python 3.8 到 3.10 之间的版本,太高容易出现 torch 算子兼容问题,太低又装不上新版本依赖。创建环境时用 conda 能少踩很多坑,具体做法是:

conda create -n yolo_annotate python=3.8 -y conda activate yolo_annotate pip install torch torchvision --index-url https://download.pytorch.org/whl/cu118 git clone https://github.com/ultralytics/yolov5 cd yolov5 pip install -r requirements.txt

这里有两个参数值得解释。--index-url指向 PyTorch 官方 wheel 源,CUDA 11.8 版本的 torch 在默认 PyPI 源里不一定能装对,指定这个源能避免装成 CPU 版或者版本不匹配。pip install -r requirements.txt装的是 YOLOv5 的运行依赖,包括 opencv-python、pyyaml、tqdm、pandas、matplotlib 这些,缺了后面跑 detect.py 会直接报模块找不到。如果显存小于 4GB,可以考虑 CPU 推理或者把 batch 调到 1,--device cpu也能跑,只是 2000 张图会从几分钟变成一两个小时。

3.2 工具目录结构:权重、图片、输出各放哪里

一个顺手的标注工具目录,从开始就按"输入、推理、输出、转换"四个区域分开。常见的布局长这样:

annotate-tool/ ├── weights/ # 预训练权重 │ ├── yolov5s.pt # 通用 COCO 权重 │ └── face_v5s.pt # 人脸专用权重 ├── images/ # 待标注原始图片 │ ├── img_0001.jpg │ └── img_0002.jpg ├── runs/detect/ # YOLOv5 推理输出目录 ├── labels_auto/ # 转换后的最终标注 ├── check/ # 可视化抽检输出 └── convert.py # 格式转换脚本

images 目录放原始图片,命名建议统一用img_0001.jpg这种定长格式,避免后面标注文件与图片文件名对不上。weights 目录里最好同时留通用权重和人脸专用权重,先用哪个取决于你的图片场景——如果是大合影、证件照这类正脸多的,人脸专用权重效果明显更好;如果是生活照、街拍,通用权重反而能兜底。runs/detect 是 YOLOv5 自动生成的输出目录,里面会按--name参数分子目录存放推理结果。labels_auto 是转换脚本的产物,也就是真正会被训练流程使用的标注文件。

3.3 最小可用命令:用detect.py批量生成预标注

环境就绪后,第一件要做的事不是写脚本,而是先把 YOLOv5 自带的 detect.py 跑通。最小命令长这样:

python detect.py \ --weights weights/face_v5s.pt \ --source images/ \ --conf 0.25 \ --iou 0.45 \ --imgsz 640 \ --save-txt \ --save-conf \ --project runs/detect \ --name batch1

执行完这行命令,YOLOv5 会遍历 images 目录下所有图片,把检测结果保存到runs/detect/batch1/下。--save-txt是半自动标注的核心参数,它会让 YOLOv5 把每个检测框写入 labels 子目录下的 txt 文件,每行格式是class x_center y_center width height,全部是 0 到 1 之间的归一化数值。--save-conf把置信度追加到每行末尾,转换脚本后续可以用它做置信度分层,这个信息后面调标注质量时特别有用。

--conf 0.25和--iou 0.45这两个参数直接决定预标注的质量,也是后续最容易来回调整的旋钮。conf 是置信度阈值,0.25 属于偏保守的设置,漏检少但误检多,适合第一次初标先全覆盖,人工复核时再删;如果图片比较干净,可以直接拉到 0.4 以上减少误检。iou 是 NMS 的 IoU 阈值,0.45 是 YOLOv5 默认值,主要影响重叠框的合并力度,人脸密集的合影场景建议调到 0.4,减少两个人脸靠太近时框被错误合并的情况。--imgsz 640是输入分辨率,这个参数对小脸漏检影响巨大,后面避坑章节细说。

3.4 从txt到可编辑标注:把推理结果转成常见标注格式

detect.py 生成的 txt 文件还不能直接用 LabelImg 打开,因为 LabelImg 默认读写 PascalVOC XML 格式。转换脚本的作用就是把归一化坐标还原成绝对像素坐标,并组装成 XML。核心代码逻辑如下:

import os import glob from PIL import Image def convert_txt_to_xml(txt_path, img_path, xml_out_dir, class_name="face"): img = Image.open(img_path) img_w, img_h = img.size base = os.path.splitext(os.path.basename(img_path))[0] xml_lines = [] xml_lines.append('<annotation>') xml_lines.append(f' <filename>{os.path.basename(img_path)}</filename>') xml_lines.append(f' <size><width>{img_w}</width><height>{img_h}</height></size>') with open(txt_path, 'r') as f: for line in f: parts = line.strip().split() if len(parts) < 5: continue cls_id, xc, yc, w, h = parts[:5] # 归一化中心点+宽高 -> 绝对左上角+右下角 x1 = int((float(xc) - float(w) / 2) * img_w) y1 = int((float(yc) - float(h) / 2) * img_h) x2 = int((float(xc) + float(w) / 2) * img_w) y2 = int((float(yc) + float(h) / 2) * img_h) xml_lines.append(' <object>') xml_lines.append(f' <name>{class_name}</name>') xml_lines.append(f' <bndbox>') xml_lines.append(f' <xmin>{max(0, x1)}</xmin>') xml_lines.append(f' <ymin>{max(0, y1)}</ymin>') xml_lines.append(f' <xmax>{min(img_w, x2)}</xmax>') xml_lines.append(f' <ymax>{min(img_h, y2)}</ymax>') xml_lines.append(f' </bndbox>') xml_lines.append(' </object>') xml_lines.append('</annotation>') os.makedirs(xml_out_dir, exist_ok=True) xml_path = os.path.join(xml_out_dir, base + '.xml') with open(xml_path, 'w') as f: f.write('\n'.join(xml_lines)) # 示例调用:把 runs/detect/batch1/labels/ 下的每个 txt 都转一遍 for txt in glob.glob('runs/detect/batch1/labels/*.txt'): image_path = os.path.join('images', os.path.basename(txt).replace('.txt', '.jpg')) convert_txt_to_xml(txt, image_path, 'labels_auto/xml/')

转换脚本里有两个细节值得注意。第一个是max(0, x1)这种边界裁剪,因为归一化坐标在极端情况下会产生越界坐标,比如检测框中心靠近图片边缘时,还原后的 x1 可能是负数,LabelImg 打开这种 XML 会报错。第二个是宽高读取用了 PIL 的img.size,这个值必须来自原始图片,不能来自 detect.py 处理过的缩放图,否则坐标会整体偏移。脚本跑完后,用 LabelImg 打开labels_auto/xml/目录,就能看到所有预标注框,直接拖拽修正即可。

4. 把标注结果喂回训练闭环:格式、类别与坐标转换

4.1 YOLOv5的txt标注格式与归一化坐标

半自动标注最后产出的,是一批 YOLO 格式的 txt 文件。YOLOv5 训练时要求图片和同名 txt 放在同一目录,txt 每行代表一个目标,五列分别是类别编号、归一化中心点 x、归一化中心点 y、归一化宽、归一化高。理解这个格式的关键在于"归一化"三个字。坐标不是像素值,而是除以图片宽高之后的相对值,范围永远在 0 到 1 之间。这样做的本意是让模型训练时对图片尺寸不敏感,但反过来,当你需要可视化检查或转成其他标注格式时,就必须乘回原始图片的宽高。

这里有一个容易翻车的点:YOLOv5 的--save-txt输出的坐标除以的是模型输入的imgsz分辨率,不是原始图片的分辨率。如果转换脚本里直接乘以原图宽高,得到的就是错误坐标。好在 YOLOv5 更新后,detect.py 在保存 txt 时会按原图尺寸重新归一化,坐标已经是相对原始图的 0 到 1 数值。但如果你拿到的工具源码是旧版本,或者有人改过 detect.py,一定要先在图上画框验证一遍坐标准不准,再做批量转换。

4.2 绝对坐标还原与XML格式生成

上一章的转换脚本已经实现了最基础的 txt 到 XML 转换,但实际标注流程里还会遇到一个需求差异:短期项目用 LabelImg 复核,长期项目则希望直接生成训练集。两种需求都跑一遍也不冲突,只是输出路径分开而已。训练集准备其实更简单,因为 txt 已经是 YOLO 格式,唯一要做的是把runs/detect/batch1/labels/下的文件拷贝到训练集目录,再和原图放在一起:

mkdir -p dataset/images dataset/labels cp images/*.jpg dataset/images/ cp runs/detect/batch1/labels/*.txt dataset/labels/

执行前注意文件名一致性。YOLOv5 要求同名图片和 txt 的文件主名完全一致,img_0001.jpg对应img_0001.txt。如果 detect.py 因为某些原因没有为每张图生成 txt 文件——比如图片里完全没检测到目标——那就需要补一个空 txt 文件占位,否则训练时会报找不到标注文件。一个三行小命令就能处理:

for img in dataset/images/*.jpg; do base=$(basename "$img" .jpg) [ -f "dataset/labels/$base.txt" ] || touch "dataset/labels/$base.txt" done

所有非空标注都是有效的目标样本,空 txt 表示这张图没有目标,YOLOv5 训练时会把它当作负样本处理,不会报错。

4.3 类别映射与目标过滤

类别编号是另一个隐蔽的坑。COCO 预训练权重里 person 类别的编号是 0,但人脸专用权重里 face 类别可能也是 0,也可能不是。自动生成的 txt 里class这一列写的是模型输出空间的类别编号,它不一定等于你训练时想要的类别编号。做人脸检测,最终训练集的类别一般都是 0,但如果权重输出的是person类,就得手动改编号;如果是自定义人脸权重且只有 face 一类,那大概率已经是 0,不需要改动。

保险做法是写一个小脚本,统计一个批次里所有 txt 的类别分布:

cat runs/detect/batch1/labels/*.txt | awk '{print $1}' | sort | uniq -c

输出结果会告诉你这个模型实际输出了哪些类别编号。如果出现多个不同编号,说明权重是在多类别数据集上训练的,你只能保留 face 那一类的行,其他类别全部过滤掉。过滤操作用一行 grep 就能完成:

mkdir -p dataset/labels_filtered for txt in runs/detect/batch1/labels/*.txt; do awk '$1==0 {print $0}' "$txt" > dataset/labels_filtered/$(basename "$txt") done

这里假设目标类别编号是 0,按实际输出调整即可。这一步不做的话,后续训练时类别标签错乱会直接导致模型收敛出问题,而且这个问题隐蔽到 loss 曲线正常、但预测结果完全不可用。

4.4 人工复核的验收标准

自动标注不是终点,人工复核才是决定数据集质量的关键环节。验收标准我一般定三条:第一,漏检率要低于 5%,逐张翻图太累,抽样检查后面第 6 章有具体方法;第二,误检框(背景当脸、手臂当脸)数量不能超过总数 2%;第三,框与脸的贴合度,也就是框的边界和脸的边界误差,在 3% 的图片宽高范围内,肉眼看着不明显偏移就行。满足这三条,这批预标注就算合格,可以直接进训练。

复核时的效率技巧是:用程序先把预标注结果画到图上,按置信度从低到高排序输出,检低置信度区域只看漏检,检高置信度区域只看误检,不要每张图都从头看到尾。画框检查用 OpenCV 写一个十几行的可视化脚本就能完成,比打开 LabelImg 一个个改更快,因为大部分框本来就不需要改。

5. 避坑指南:自动标注的5个典型翻车现场

5.1 框住的是整个人,不是脸

现象:用 COCO 预训练的 yolov5s.pt 跑标注,生成的框把整个人都框进去了,脸只占据框的一小块区域。

原因:COCO 数据集的 person 类别是"人"的全身框,模型学到的语义就是整个人体,不是人脸。它确实"看到"了人,但输出框的语义和你要的"脸"不是一回事。

解决:换人脸专用权重,比如在 WIDER FACE 数据集上训练过的权重。WIDER FACE 是人脸检测领域的标准 benchmark,里面的标注就是人脸框,模型学出来的语义天然贴合需求。如果你手里只有通用权重,另一个权宜办法是保留这个不精确的初标,在人工复核时统一把框缩到脸部区域,但这样效率损失不小,建议直接换权重。

5.2 一张脸被框了两次:置信度与NMS的拉扯

现象:转换后的标注里,同一张脸出现两个重叠 90% 以上的框,训练时这个目标被重复计算,loss 表现异常。

原因:YOLOv5 推理流程里有 NMS(非极大值抑制)负责合并重叠框,但 NMS 的--iou阈值设置太宽松,或者--conf阈值太低,导致模型同时输出的多个相似候选框没有被合并掉。

解决:把--iou从默认 0.45 调低到 0.35 到 0.4,加强合并力度;同时把--conf从 0.25 适当提高到 0.3。调完之后重新跑一遍推理,抽几张之前的密集人脸图检查,确认每个脸只剩一个框。顺带提一句,NMS 是模型后处理的关键环节,YOLOv5 里这个参数直接影响标注质量,不要和训练时的 NMS 设置搞混。

5.3 小图片上的脸全部漏检

现象:图片分辨率小,或者人脸占图片比例特别小,比如集体照里后排的人脸,检测结果里一个框都没有,漏检率奇高。

原因:--imgsz 640的输入分辨率把小图缩得更小,人脸区域缩到十几个像素,模型特征提取阶段就直接丢掉了。YOLOv5 的训练分辨率一般是 640,但推理时对低分辨率小目标并不友好。

解决:把--imgsz调大到 960 甚至 1280,输入图像变大后小脸对应的像素区域也会变大,漏检率明显下降。代价是推理速度变慢、显存占用变高,低显存环境下可以把 batch 降到 1 来换取空间。如果图片里人脸实在太小,调整 imgsz 也救不回来,那就考虑切片推理,把大图切成多个 640 子图分别推理再合并结果,这个做法对小目标检测非常有效。

5.4 转换后的XML在LabelImg里错位

现象:LabelImg 打开转换出的 XML 文件,框的位置明显偏了,有的框跑到图片外面,有的框和脸对不上。

原因:大概率是坐标还原时乘以了错误的图片尺寸。转换脚本如果从 detect.py 的输出目录读取宽高,而不是从原始图片读取,拿到的就是缩放后的尺寸。

解决:转换逻辑里必须用 PIL 或 OpenCV 读取原始图片获取宽高。注意检测一下 YOLOv5 版本,新版 detect.py 的--save-txt输出已经是针对原图归一化的,但老版本或魔改源码不保证。解决方法是转换脚本里加一步验证:随机抽三张图,把还原出的坐标画框覆盖到原图上保存成 jpg,肉眼比对框和脸的位置关系,这一步几十秒就能排查完所有坐标换算问题。

5.5 阈值设太低,背景被当成脸

现象:置信度阈值设为 0.1 时,墙面纹理、树叶阴影、远处的路人背影全被标成人脸,一个批次里误检框数量爆炸。

原因:低置信度阈值让模型把大量低分候选框也输出了。标注工具追求低漏检率没有错,但阈值过低会让误检比例失控,人工复核删框的时间和漏检补框的时间可能一样长,半自动标注的提速效果被抵消。

解决:先跑一批 0.25 阈值的预标注,统计置信度分布,把 log 文件或可视化中置信度低于 0.2 的框占比拉出来看。如果误检框太多,把阈值提到 0.35 重新跑;如果净是漏检,把阈值降到 0.15 同时加大复核力度。阈值这东西属于每批数据都要重新调的参数,没有一劳永逸的值,我的习惯是先用一次小批量试跑,再决定全量批次的参数。

6. 置信度分层与二次标注:把人工成本降到最低的实测打法

6.1 置信度分层复核策略

先按置信度把标注框分成三档。第一档是置信度高于 0.7 的框,这些基本不用看,直接保留进训练集;第二档是 0.3 到 0.7 之间的框,这是人工复核的重点,需要逐帧检查框的贴合度和类别是否正确;第三档是低于 0.3 的框,里面混着大量误检,直接丢弃,或者只看漏检补漏。操作上可以写个简单脚本把每张图的框按置信度排序输出统计:

awk '{print $6}' runs/detect/batch1/labels/*.txt | sort -n | awk 'NR==1{min=$1} {sum+=$1} END{print "min:", min, "avg:", sum/NR}'

注意这里$6对应的是--save-conf追加到 txt 行尾的置信度列。如果当时没加--save-conf,txt 每行只有五列,那这步统计就做不了。所以跑推理时务必把这个参数加上,它是后期所有质量分析的数据基础。

6.2 漏检率与误检率的快速验证

不要凭感觉判断标注质量,抽样做定量检查。随机从图片目录里抽 100 张,不看自动标注结果,人工数一遍每张图的脸数,得到一个"真实脸数";再统计这 100 张图里自动标注的框数。漏检率等于真实脸数减检出框数再除以真实脸数,误检率等于"标注了但根本不是脸"的框数除以总检出框数。这个半小时就能做完的抽样验证,能直接告诉你这批预标注能不能进训练集,也能暴露上面第 5 章说的那些坑到底踩了哪一个。

6.3 用模型标注模型:迭代式标注的完整闭环

进阶打法里最有效的一招,是用这批初标数据先训练一个初版模型,再用这个模型去标注下一批新图。第一轮人工复核后的数据大概有几百张高质量标注,训练出的模型虽然精度有限,但已经能自主地框出大部分清晰人脸;用它跑第二批预标注,置信度分布会比第一次更集中,误检率更低。第二轮人工复核只需要修漏检和边界不齐的框,工作量又降一截。三轮迭代之后,手上会攒出一个规模可观、质量稳定的数据集,而总人工投入可能只有纯手动标注的三成。

我现在做新数据集时,已经习惯先让 YOLOv5 跑一遍再动手改框,而不是直接开 LabelImg 从零画。这个习惯最初只是在偷懒,但用久了发现它最大的价值是逼着你在标注之前先想清楚检测目标的长相、分布和难点,毕竟模型漏检的地方,往往就是你最初没预料到的那些边角场景。这套流程不复杂,一份能跑的 YOLOv5 环境、一个转换脚本、一批带着置信度做分层的耐心,就够了。希望帮到你。

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

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

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

立即咨询