简介从WIDER Face中筛选出的B子集大目标人脸检测数据集包含8188张jpg图片及对应标注仅face一个类别总标注框14649个。所有目标的像素面积均大于3500专门面向近距离、大脸检测场景可显著减少远距离小目标带来的误检适合刚接触目标检测的学习者快速上手也便于中高级开发者做数据增强与模型调优。压缩包共2000个文件含1999个VOC格式xml标注文件和1个txt使用说明xml可直接转为YOLO等格式配合原图即可接入主流检测框架包体862.16MB已有468人学习下载。标注由labelImg完成矩形框规范并附使用前必读说明便于快速了解目录与格式。对需要补充大目标人脸样本、验证检测精度的项目这份筛选后的数据能节省大量清洗与标注时间让研究者更专注于模型训练和效果优化。人脸检测数据集从零搭建WIDER FACE大目标子集8188张的VOC转YOLO全流程1. 先聊聊这个数据集能帮你解决什么问题做人脸检测的工程师几乎都绕不开WIDER FACE但真正动手时你会发现原版数据集32203张图像里大量都是小目标——街拍路人、密集人群一个脸可能只有十几个像素。训练出来的模型在监控、门禁、闸机这种人脸占比大的场景下反而表现平平。标题里这个8188张大目标子集就是把WIDER FACE里人脸面积占比较大的样本筛出来统一转成VOC和YOLO两种标注格式省掉你自己写脚本筛数据、转格式的时间。这套东西适合三类人一是刚入门目标检测、想拿人脸数据练手YOLOv5/v8的二是被小目标折磨、需要一个纯大目标数据集做对比实验的三是公司项目急着上线、标注预算有限想白嫖公开数据的。我拿到这个.7z解压后第一反应是训练集、验证集、测试集分好了没有、标注坐标是绝对坐标还是归一化坐标、类别名是face还是person——这三个问题直接决定你要不要额外写脚本。下面从数据构成、格式转换、训练验证到踩坑排查一条线讲完。2. WIDER FACE大目标子集的数据构成先搞清8188张和类别1的真面目2.1 原版WIDER FACE与这个子集的差异原版WIDER FACE按事件类型分成61个场景训练集、验证集、测试集比例大约是40%训练、10%验证、50%测试。但原版测试集的标注是不公开的你只能通过提交到评测服务器拿结果。这个8188张的大目标子集应该是从原版训练集和验证集里筛出来的——注意如果作者公开了测试集标注那另说但大多数第三方处理版本只动train和val。我做目标检测数据集处理时第一件事永远是数图像张数和标注框数是否对得上。8188张1类别意味着标注里只有face这一类不像VOC原始数据集有person、car、dog等20类。你要是有强迫症解压后跑一条命令核对find . -name *.xml | wc -l find . -name *.txt | wc -l如果XML文件数和TXT文件数不一致说明转换脚本漏了某些样本或标注为空被跳过了。我遇到过转换时丢掉空标注样本导致总数对不上的情况——不是bug是脚本逻辑里刻意忽略了无目标的图像这在训练时其实合理因为YOLO不支持负样本图像。但你要是拿验证集做mAP评估丢了空标注图像会虚高——硬编码往每张图塞一个背景框才是错上加错。2.2 训练集、验证集、测试集怎么分大多数这类打包好的数据集目录结构是images/train、images/val、images/test、labels/train、labels/val、labels/test这样的对位结构。YOLO训练时你需要的是images和labels两个分支下的对应子目录一致。我自己习惯的做法是拿8188张按8:1:1切但前提是作者没有已经切好。如果已经有划分就用它的划分不要自己重新随机打乱——除非你要做跨数据集对比必须保证划分方式和原论文一致。这里有个关键点WIDER FACE原版的验证集有3226张压根不是8188张按比例切出来的。这个子集如果是从原版val里筛大目标那验证集就小得多。你训练完模型如果想在原版WIDER FACE验证集上对比SOTA得找原版标注。我一般把两个验证集都留一份一个用于日常迭代一个用于最终对比。2.3 图像分辨率分布和目标的大是怎么定义的大目标的定义通常有三种口径一是目标框面积占图像总面积的比例二是目标框短边像素数三是目标框面积绝对值。WIDER FACE原版有个有趣现象高度在30像素以下的人脸占了相当比例很多脸在20×20以下。我自己做行人检测时习惯用短边大于等于32像素作为小目标和大目标的分界线。这个子集如果要叫大目标作者很可能是按面积比例筛的——比如人脸框面积占图像面积10%以上。解压后你可以自己验证一下用Python快速统计import os from PIL import Image img_dir images/train min_face_ratio 0.05 stats {total: 0, big: 0} for name in os.listdir(img_dir): img Image.open(os.path.join(img_dir, name)) w, h img.size # 这里根据自己的标注格式读取 GT # 统计人脸框面积 / 图像面积 的分布 stats[total] 1 print(stats)这个统计有两个用途确认数据符合大目标预期后面训练时如果发现模型收敛异常回头查这个分布。很多人拿到数据集不检查分布就直接train结果训练集里混了一些极小目标导致Anchor尺寸设置失配。你要真图省事记住一条结论大目标数据集用默认Anchor基本没问题但如果你要把这个数据集和其他小目标数据混合训练Anchor就得重新聚类。3. 从WIDER FACE原格式到VOC再到YOLO转换脚本与格式边界3.1 WIDER FACE原始标注的存储方式WIDER FACE官方给的标注是一个txt文件格式是图像文件名、人脸框数量、然后每个框一行x1, y1, w, h, blur, expression, illumination, invalid, occlusion, pose。其中x1、y1是左上角坐标w、h是框宽高。blur、occlusion这些属性在做人脸检测时大部分工程师直接忽略——你是做检测不是做属性分类只有invalid标记值得注意invalidTrue的框说明这张脸几乎看不清或严重遮挡严格做评测时应该剔掉。标题里的8188张大目标子集如果已经帮你过滤了invalid框那就省事了。如果没过滤你转换时必须决定全保留会造成模型学到脏标注全部过滤可能把难例都丢了模型泛化变差。我的经验是训练集保留部分困难样本验证集过滤掉invalid不然mAP会有虚低。3.2 VOC标注格式的XML结构VOC格式就是把标注写成XML每个object节点包含name、pose、truncated、difficult、bndboxxmin、ymin、xmax、ymax。注意bndbox是左上角右下角绝对值不是中心点。WIDER FACE给的x1,y1,w,h转换时要用xmax x1 wymax y1 h。好多人在这一步翻车把w直接当xmax用结果训练时损失爆炸。我自己的习惯是转换脚本里打印几个样本的坐标做人工核对解压一张图画框看看。3.3 从VOC转YOLO格式的Python脚本假设你已经有了VOC格式的XML转YOLO就是把bndbox归一化成中心点坐标和宽高。YOLO的标签文件每行是class_id x_center y_center width height其中x_center、y_center、width、height都是除以图像宽高后的浮点数。核心代码就这几行import os import xml.etree.ElementTree as ET from PIL import Image def voc_to_yolo(xml_path, img_path, out_label_path, class_list): tree ET.parse(xml_path) root tree.getroot() img Image.open(img_path) img_w, img_h img.size with open(out_label_path, w) as f: for obj in root.iter(object): cls_name obj.find(name).text if cls_name not in class_list: continue cls_id class_list.index(cls_name) bndbox obj.find(bndbox) xmin float(bndbox.find(xmin).text) ymin float(bndbox.find(ymin).text) xmax float(bndbox.find(xmax).text) ymax float(bndbox.find(ymax).text) # 越界裁剪防止标注超出图像边界 xmin max(0, min(xmin, img_w - 1)) ymin max(0, min(ymin, img_h - 1)) xmax max(0, min(xmax, img_w - 1)) ymax max(0, min(ymax, img_h - 1)) box_w xmax - xmin box_h ymax - ymin if box_w 0 or box_h 0: continue x_center (xmin xmax) / 2 / img_w y_center (ymin ymax) / 2 / img_h norm_w box_w / img_w norm_h box_h / img_h f.write(f{cls_id} {x_center:.6f} {y_center:.6f} {norm_w:.6f} {norm_h:.6f}\n) # 使用示例 class_list [face] voc_to_yolo(000001.xml, 000001.jpg, 000001.txt, class_list)这段脚本做了三件容易被忽略的事第一越界裁剪——WIDER FACE原版标注偶尔有框超出图像边界的情况不裁剪YOLO训练时会报错或产生错误anchor第二过滤掉宽高非正的框——某些标注文件里会出现width0的脏数据第三归一化输出保留6位小数——精度太高文件变大太低两个目标坐标太接近会合并。class_list只放face一个类也就对应标题里的1类别。3.4 批量转换脚本目录结构与文件命名对齐单张图转换没问题后批量转换要注意保持文件名一一对应。常见目录结构是这样dataset/ ├── images/ │ ├── train/ │ ├── val/ │ └── test/ ├── voc_xmls/ │ ├── train/ │ ├── val/ │ └── test/ └── labels/ ├── train/ ├── val/ └── test/批量脚本加上进度输出和日志跑完检查零字节文件import os from glob import glob def batch_voc_to_yolo(xml_root, img_root, out_root, class_list): xml_files sorted(glob(os.path.join(xml_root, *.xml))) total, skipped 0, 0 for xml_path in xml_files: base os.path.splitext(os.path.basename(xml_path))[0] img_candidates [.jpg, .jpeg, .png] img_path None for ext in img_candidates: cand os.path.join(img_root, base ext) if os.path.exists(cand): img_path cand break if img_path is None: skipped 1 print(f[SKIP] no image for {xml_path}) continue voc_to_yolo(xml_path, img_path, os.path.join(out_root, base .txt), class_list) total 1 print(fconverted: {total}, skipped: {skipped})注意我特意加了找不到对应图像时的跳过分支——在实际数据集中偶尔会有标注文件存在但原图丢失的情况。这类坏样本如果直接灌给YOLO训练会在读取图像时报FileNotFoundError打断整个训练流程别问我怎么知道的跑过一次半夜的分布式训练你就懂了。4. 用YOLOv5/v8训练大目标人脸检测data.yaml与超参数调整4.1 数据集配置文件data.yaml怎么写有了images和labels目录YOLO训练还需要一个data.yaml文件指定路径和类别信息# data.yaml train: /path/to/dataset/images/train val: /path/to/dataset/images/val test: /path/to/dataset/images/test nc: 1 names: [face]三个关键细节路径用绝对路径还是相对路径——我一般用绝对路径避免工作目录切换导致相对路径失效train和val不能指向同一个目录否则验证结果没有参考意义nc必须和names列表长度一致这里nc1、names[face]。有些人从别人项目里复制data.yaml没改路径训练半天发现迭代了0张图就是这个原因。4.2 Anchor尺寸:大目标数据集要不要重新聚类YOLO默认anchor是针对COCO数据集聚类的里面从小到大的目标都有。这个大目标数据集人脸占比大默认anchor可能偏小。YOLOv5训练时如果用了--autoanchor参数会自动用K-Means重新聚类。YOLOv8不暴露anchor参数它在Decoder里用Anchor-Free的方式对anchor的依赖小得多。如果你用YOLOv5我建议开--autoanchor省得手动改anchor。聚类前先想想你的大目标到底多大——如果人脸占比大通常聚出来的anchor会偏大模型收敛更快。如果你在训练日志里看到指标震荡可以试试固定anchor看看是不是聚类结果导致训练不稳定。4.3 训练命令和参数调优训练命令用YOLOv5举例cd yolov5 python train.py --data /path/to/data.yaml --weights yolov5s.pt --img 640 --batch 16 --epochs 100 --autoanchor这里有几个参数值得解释。--img 640输入分辨率大目标数据集用640够用没必要上1280除非你的部署设备性能余量大且目标对细节要求高。--batch 16按你的显存来16G显存跑yolov5s是够的如果显存不足降batch但别降分辨率分辨率对精度影响更大。--epochs 100这个数据量级别100轮能收敛配合早停机制可以提前结束。数据集只有1个类别且目标较大yolov5s这个骨干就够用直接上yolov5x反而容易过拟合。4.4 Loss曲线怎么看训练过程中主要盯三件事box_loss、obj_loss、cls_loss。因为只有face一个类别cls_loss会很低重点看box_loss和obj_loss是否同步下降。如果box_loss下降但obj_loss不变大概率是正负样本不平衡——大目标数量少但面积大模型倾向于预测背景这时可以调大anchor阈值或检查数据增强是否翻转了目标过多次导致目标移出图像边界。还有个常见现象train_loss下降但val_loss震荡这是过拟合信号可以减少epoch或加大数据增强里的flip、mosaic概率。大目标数据集的mosaic增强特别有用因为拼接后目标相对于新图变小了等于免费补充了一部分中等目标样本。5. 训练与验证中的数据检查避坑、常见问题与排查清单5.1 标注错位图像方向导致的人脸框偏移现象训练时loss一直在高位降不下来或验证集mAP极低但loss正常。原因某些图像样本是旋转过的尤其是手机拍摄的竖屏照片而标注没有跟着旋转或者转换脚本归一化时用了错误的图像宽高。解决训练前写脚本随机抽样100张图把GT框画上去人眼检查是否有明显错位。YOLOv5自带一个plot_labels工具训练时会在runs目录生成标注可视化图第一轮训练完先看这个再决定要不要停。5.2 零字节标签文件现象labels目录里存在0字节txt文件YOLO训练时某些实现会报错提示corrupt label。原因原标注为空但XML仍然生成了或者原图本身没有目标转换脚本没过滤。解决批量删除空文件再训练。YOLOv5训练时遇到空标签文件不会崩但会跳过该图像的loss计算导致有效样本数减少。用find命令处理find /path/to/labels -name *.txt -size 0 -delete删除之后和images目录对比一下文件数量确认图像和标签一一对应。5.3 类别编号错误现象训练正常但验证时结果全是0或全是背景框。原因data.yaml里names是[face]而转换脚本里class_id用的0没问题但如果数据集作者给的label是face1你没改class_id就开始训练模型学的是背景。解决打开一个txt标签文件看class_id是多少——0是YOLO默认从0开始计数的人做法如果看到1说明作者按VOC的1-based class标注方式做你得批量减1或改data.yaml。这个坑我踩过不止一次尤其是混合多个数据源时最容易犯。5.4 图像尺寸不一致的batch训练报错现象训练到几个epoch时报错类似Expected 3D tensor but got 4D。原因数据集中混入了一通道灰度图或带Alpha通道的PNG而YOLO默认输入是三通道RGB。WIDER FACE本身全是JPEG彩色图但如果这个子集后来被人转存过就可能混入变体。解决训练前统一转换写一条脚本把非RGB图转成RGB后另存为jpgimport os from PIL import Image def unify_rgb(src_dir, dst_dir): os.makedirs(dst_dir, exist_okTrue) for name in os.listdir(src_dir): img Image.open(os.path.join(src_dir, name)) if img.mode ! RGB: img img.convert(RGB) out_name os.path.splitext(name)[0] .jpg img.save(os.path.join(dst_dir, out_name), quality95)这里convert(RGB)会把灰度图变成三通道复制Alpha通道会被丢弃正好满足YOLO的输入要求。注意保存时统一成jpg格式免得后面加载时遇到. png大小写混乱的问题。5.5 验证标准不一致导致mAP虚高现象自测mAP有0.95换到别的评测脚本只剩0.8差距大到无法接受。原因验证时是否过滤了difficult样本、IoU阈值用的0.5还是0.75、是否把忽略区域(ignore regions)当成了负样本这些都直接影响mAP数值。WIDER FACE原版评测有Easy/Medium/Hard三档Hard档大量是极小人脸并明确说模型不该在某些区域输出检测框对应ignore。如果你用这个8188大目标子集做验证事情简单了目标都大没有ignore区域。但如果你想对比原版WIDER FACE的论文指标就得换原版评测脚本重新评估。最稳妥的做法是训练用这份大目标数据验证用同一份数据的val子集做日常迭代最终再拿原版WIDER FACE验证集做一次横向对比。6. 从单卡实验到部署验证TensorRT推理与实时性检查训练完模型后只盯着mAP不够还要确认推理速度能满足实际场景。视频流人脸检测项目里动不动就要回答640分辨率用TensorRT的YOLO能跑多少路这里给个我自己的基准思路T4显卡上用TensorRT FP16的YOLOv5s单路640×640输入大概1.5到3毫秒——纯GPU推理时间不算前后处理和图像解码。如果要求25帧每秒单路40毫秒预算一路大概占GPU算力10%-20%实际能扛多路取决于图像解码方式和CPU绑定。如果每路都解H.264CPU先爆轮不到GPU瓶颈。建议直接软解码到BGR再喂给推理引擎CPU核心数不够时降分辨率到416或480反而更划算——你数据集是大目标降到416也大概率不掉点。验证方法用TensorRT导出的Python接口批量测试import tensorrt as trt import numpy as np def infer_on_dir(engine_path, img_dir): logger trt.Logger(trt.Logger.WARNING) with open(engine_path, rb) as f: engine_data f.read() runtime trt.Runtime(logger) engine runtime.deserialize_cuda_engine(engine_data) context engine.create_execution_context() import glob from PIL import Image imgs sorted(glob.glob(os.path.join(img_dir, *.jpg)))[:100] for img_path in imgs: img Image.open(img_path).resize((640, 640)) arr np.array(img).astype(np.float32) / 255.0 arr arr.transpose(2, 0, 1)[None] # 这里绑定输入输出 buffer # 调用 context.execute_v2 推理实际做部署时我习惯先批量跑100张求平均耗时再套一个多线程框架模拟多路推流观察显存是否波动过大和是否出现偶发延迟尖峰。人脸检测这种任务精度差两三个点通常能接受延迟抖动反而更影响用户体验。最后的经验做数据集转换和训练验证时每个环节都保留一份中间产物和一行日志跑挂了能立刻定位到是哪一步出了问题。这类WIDER FACE大目标子集的价值在于比原版更贴合实际业务中的大脸场景但你拿它训练之前一定要先花20分钟做标注可视化核对——数据错漏导致的返工成本远高于这20分钟。希望帮到你。本文还有配套的精品资源点击获取