
简介一份面向电力行业与工地场景的人头检测数据集适合计算机视觉学习者、算法工程师及安监智能化项目开发者用于目标检测模型的训练与验证。数据规模为7035张现场图像标注类别统一为head共26424个矩形框全程使用labelImg人工画框标注规则明确虽不对模型精度作保证但能提供准确且合理的标注基础。压缩包共2000个文件以1999个xml标注文件和1个txt使用说明为主压缩后约226.32MBxml对应Pascal VOC格式txt对应YOLO格式二者配套使用便于直接接入主流检测框架也方便完成格式转换和数据划分。包内附使用前必读说明可快速了解标注规范与目录结构并支持安全帽佩戴检测、人员闯入预警、作业区域人数统计等典型应用场景。目前已有392人学习适合需要真实电力工地场景数据进行预训练或算法验证的读者。1. 一份电力工地人头检测数据集能帮你少走多少弯路拿到“电力工地场景下的人头检测数据集VOCYOLO格式7035张1类别.7z”这类压缩包时多数人第一反应是解压、扔进YOLO训练脚本、等mAP。真这么做大概率会在验证集看到不错的结果却在现场视频里被安全帽反光、脚手架遮挡和低机位视角连续漏检。这个数据集的价值不在“大”7035张对深度学习训练来说不算夸张而在于它锁定了电力工地这一细分场景同时给VOC和YOLO两套格式省掉了从标注工具到训练框架之间最琐碎的格式转换环节。适合正在做电力行业安全违章识别、需要先定位人头再做安全帽状态判断或人员计数的人。它能让你第一版模型在两周内跑起来但替代不了现场补拍。2. 拿到数据先做三件事解压、摸目录结构、验标签2.1 先解压再把“7035张”当成数字而不是事实7z压缩包在Linux服务器上同样常见解压命令并不复杂。文件名里带中文和多个点号shell下建议整体加引号避免通配符和分词捣乱。# 解压到指定目录避免在当前目录散落一堆文件 7z x 电力工地场景下的人头检测数据集VOCYOLO格式7035张1类别.7z -o./head_det如果服务器没有7z命令先装p7zipapt install p7zip-full或yum install p7zip。解压完成后别急着看标注格式先核对图片数量# 统计jpg数量验证是否与标题宣称的7035张一致 find ./head_det -type f -name *.jpg | wc -l # 统计所有标注文件数量分别看txt和xml的规模 find ./head_det -type f -name *.txt | wc -l find ./head_det -type f -name *.xml | wc -l这个动作花不到一分钟。我习惯在拿到任何数据集时先做这一步因为发布者打包时漏文件、重复文件、标注中断留下的半成品都会在训练阶段变成莫名其妙的警告或路径报错。发现数量对不上时后续所有操作都要以实际目录为准而不是标题里的数字。2.2 双格式目录到底长什么样VOC与YOLO的组织方式标题写了“VOCYOLO格式”意味着数据集里同时存在两套标注。常见的发布结构是VOC风格目录配上YOLO风格目录或者共用一份图片把不同标注放在两个目录下。先看一级目录再写配置不要凭经验猜。目录/文件用途格式说明JPEGImages/原始图片.jpg通常包含施工人员、铁塔、脚手架背景Annotations/VOC标注.xmlPascal VOC格式含object name与bndbox坐标labels/YOLO标注.txt每行 class x_center y_center width height归一化坐标ImageSets/Main/VOC划分文件.txt每行一个不带后缀的文件名train/val分开VOC格式主要面向视觉工具链比如LabelImg标注流水线、MMDetection、Detectron2YOLO格式则直接被ultralytics和darknet系框架消费。一份数据保留双格式是有现实理由的——不同阶段用不同工具VOC标注可以当作原始证据长期留存txt坏了随时能重新生成。后面讲转换脚本时会展开。2.3 训练前先做标签冒烟测试别把隐患带进训练这一步是很多新手跳过的。用一个短脚本检查jpg、xml、txt三者是否存在一一对应关系尤其是“有jpg但没标注”和“有txt但找不到图片”两类问题。# label_smoke_test.py # 检查JPEGImages、Annotations、labels三目录的文件对应关系 import os from pathlib import Path jpg_dir Path(./head_det/JPEGImages) xml_dir Path(./head_det/Annotations) txt_dir Path(./head_det/labels) jpg_list sorted(jpg_dir.glob(*.jpg)) missing_ann [] missing_label [] for jpg in jpg_list: stem jpg.stem xml_path xml_dir / f{stem}.xml txt_path txt_dir / f{stem}.txt if not xml_path.exists(): missing_ann.append(stem) if not txt_path.exists(): missing_label.append(stem) print(fjpg总数: {len(jpg_list)}) print(f缺xml: {len(missing_ann)} 个 - {missing_ann[:10]}) print(f缺txt: {len(missing_label)} 个 - {missing_label[:10]})这里为什么要同时盯两个目录因为“有jpg但没xml”意味着这张图在VOC体系里是废图后续做评估时会被排除“有jpg但没txt”则直接影响YOLO训练txt缺失的文件如果留在训练列表里训练时会产生empty labels警告。另外还要抽查txt里class id的范围。单类别数据集的合法class id只有0如果出现1说明发布者的类别映射不是从0开始的YOLO读入后当成第二类处理最终结果会错得非常隐蔽。3. 从VOC到YOLO的格式转换坐标归一化、类别对齐与四个高频坑3.1 为什么要保留一套VOC标注很多人拿到数据集第一反应是删掉Annotations目录省空间这是把原始素材扔掉了。实际项目中VOC标注更适合做可视化排查、边界框审核以及作为多类别扩展的底稿。比如后续想把“未戴安全帽”从单类别中细分出来LabelImg打开xml重新标注比对着txt里的归一化坐标改要直观得多。我一般会把VOC标注视作“母版”YOLO格式视作“导出产物”。训练脚本需要的txt坏了或需要调整类别时随时能重跑转换脚本。这也是双格式数据集的核心价值——后悔药在手。接下来给出一套转换脚本可以用于校验现有txt是否与xml一致也可以用来对新增图片做格式导出。3.2 转换脚本xml里每个object对应一行YOLO标注# voc2yolo.py # 将Pascal VOC xml转为YOLO txt单类别且支持类别名映射 import xml.dom.minidom as minidom from pathlib import Path # 类别名映射把标注里的目标名统一到class id CLASS_MAP { person: 0, worker: 0, person_head: 0, } def xml_to_yolo_line(xml_path): 从单个xml提取所有object返回YOLO格式的归一化坐标行 dom minidom.parse(str(xml_path)) size_node dom.getElementsByTagName(size) if not size_node: return None, no_size width float(size_node[0].getElementsByTagName(width)[0].firstChild.data) height float(size_node[0].getElementsByTagName(height)[0].firstChild.data) if width 0 or height 0: return None, bad_size lines [] for obj in dom.getElementsByTagName(object): name_node obj.getElementsByTagName(name)[0] name name_node.firstChild.data.strip() class_id CLASS_MAP.get(name) difficult_node obj.getElementsByTagName(difficult) if difficult_node and int(difficult_node[0].firstChild.data) 1: continue # 过滤难例避免训练与评估基准不一致 bndbox obj.getElementsByTagName(bndbox)[0] xmin float(bndbox.getElementsByTagName(xmin)[0].firstChild.data) ymin float(bndbox.getElementsByTagName(ymin)[0].firstChild.data) xmax float(bndbox.getElementsByTagName(xmax)[0].firstChild.data) ymax float(bndbox.getElementsByTagName(ymax)[0].firstChild.data) # 坐标归一化中心点坐标和宽高都除以图片宽高 x_center (xmin xmax) / 2.0 / width y_center (ymin ymax) / 2.0 / height box_w (xmax - xmin) / width box_h (ymax - ymin) / height lines.append(f{class_id} {x_center:.6f} {y_center:.6f} {box_w:.6f} {box_h:.6f}) return lines, ok # 批量处理所有xml xml_dir Path(./head_det/Annotations) out_dir Path(./head_det/labels) out_dir.mkdir(exist_okTrue) for xml_file in sorted(xml_dir.glob(*.xml)): lines, status xml_to_yolo_line(xml_file) if lines is None: print(f跳过 {xml_file.name}: {status}) continue if lines: with open(out_dir / f{xml_file.stem}.txt, w) as f: f.write(\n.join(lines))逻辑说明脚本从xml的size节点读取图片宽高遍历每个object把bndbox的xmin/xmax/ymin/ymax换算成YOLO需要的中心点坐标和宽高。归一化坐标全部保留6位小数相比常规的4位在4096像素大图上能避免约0.25像素的量化误差放大到YOLO默认训练尺寸后差异虽小但保留精度是稳妥习惯。参数说明CLASS_MAP是本脚本最容易改的地方。以后想扩展成“戴帽/未戴帽”二分类只需把类别名映射改成多组。difficult节点显式跳过原因会在下一节展开。xml里的bndbox如果出现xmin大于xmax这类畸形数据脚本不会报错但生成的行会是负数或反向框这种问题要在脚本外加一层数值校验。3.3 电力工地数据集转YOLO常碰到的四个边界坑第一个坑是difficult1的object。Pascal VOC里difficult对象意思是“建议忽略”常用于被遮挡、难以确认的工人。但YOLO格式不支持这个字段转换脚本若不显式过滤难例会被混进训练集而评估基准又和它不一致mAP会被拉低。解决方式就是上文代码里的continue逻辑同时把被过滤的文件名打印出来便于人工复核。第二个坑是xml缺失size节点或尺寸写成0。这通常发生在标注过程中途被人工改过文件的情况下。遇到这种xml归一化坐标会全部变成0或负值YOLO训练时直接忽略这些框。可靠的兜底策略是检测到尺寸非法时用PIL读同目录jpg宽高回填。代价是速度变慢但能保住标注数据不丢失。第三个坑是类别名不一致。同一个工地场景有人把目标标成person有人标成worker_head还有标成head的。转换脚本最忌讳硬编码单一类别名使用别名映射表才能稳。第四个坑是路径里的中文与空格。数据集文件名本身带中文Windows和Linux之间迁移时很容易出现反斜杠与正斜杠混用。生成训练列表时统一用Path对象的as_posix()方法转成正斜杠绝对路径能少许多低级报错。4. 用YOLOv8/11跑通这份数据集data.yaml、训练命令与7个必调参数4.1 写data.yaml的三种写法YOLO系列框架的训练入口是data.yaml它告诉脚本图片在哪里、标签在哪里、类别叫什么。以这份电力工地数据集为例最省事的写法是相对路径# electric_person.yaml path: ./head_det train: images/train val: images/val names: 0: person_headpath字段是数据集根目录train和val是相对根目录的子目录名。如果你的目录不是images/train这种标准结构改成实际路径即可。第二种写法是把train和val写成本地txt文件的路径txt每行含一张图片的完整路径适合需要精细控制每张图归哪一边的情况。第三种写法是直接用绝对路径好处是不受当前工作目录影响坏处是换机器时必须改配置。无论哪种names里的class id必须从0开始连续编号。单类别数据集只有0号类如果误写names为{1: person_head}训练不会报错但推理输出会偏移一个类别属于极易漏掉的细节。4.2 训练命令与参数解读data.yaml写好之后用ultralytics的命令行启动训练。下面的命令适用于YOLOv8和YOLO11两个版本在参数命名上保持一致。yolo detect train \ dataelectric_person.yaml \ modelyolov8n.pt \ epochs300 \ imgsz640 \ batch16 \ device0 \ workers8 \ patience30 \ optimizerAdamW \ lr00.001 \ close_mosaic10 \ project./runs \ namehead_det_640参数说明逐个拆开讲epochs设300但开了patience30意思是验证集mAP连续30轮不涨就提前结束。数据量只有七千多张单类别任务收敛快固定100轮可能不够固定300轮又会白等所以交给early stopping更科学。batch16是单张V100或3090级别显卡的稳妥值显存不够就降8或4没有固定标准。workers8对应CPU线程数服务器核心少就降到4避免数据加载瓶颈。optimizer选AdamW而不是默认的SGD是因为小数据集上AdamW收敛更稳不容易因为初始学习率没调好而发散。lr00.001是相对保守的起点如果你的验证集loss在头几十轮就开始震荡说明学习率偏大降到0.0005重跑。close_mosaic10意思是在最后10个epoch关掉mosaic增强让模型在接近真实分布的数据上微调这是YOLOv8引入的技巧能避免mosaic产生的拼接伪影影响最终精度。初次跑通可以用yolov8s.pt替换n版本精度会高一些但训练时间翻倍。我的习惯是先n跑通全流程再s或m冲最终指标。4.3 单类别训练的损失、验证与混淆矩阵观察点单类别检测和常规多类检测在训练流程上没有区别但验证时要换一套关注点。多类别任务习惯看各类别混淆矩阵单类别任务里矩阵只有一列正类加上背景类统计口径不同“混淆矩阵总行和不等于1”是正常现象不要被这个指标吓到。真正值得盯的是P曲线和R曲线在同一置信度阈值下的交叉点以及F1-confidence曲线的最优点。训练结束后在runs/head_det_640/weights/目录下会生成best.pt和last.ptbest.pt按验证集mAP50-95最优选择不代表现场最佳置信度。想按F1最优阈值选权重需要单独做验证推理这在第6章详细展开。5. 电力工地人头检测的避坑清单5个让测试集崩掉的细节5.1 验证集mAP有0.85现场视频漏检到没法用现象模型在数据集划分的测试集上表现很好一放到现场拍摄的监控视频里工人只要背对镜头头部就被漏掉。原因这类数据集里的图片多来自高机位固定相机或近景拍摄而现场测试视频常是低机位手机或无人机斜向视角人头尺度、遮挡比例和背景分布完全不同。数据集再真实也覆盖不了所有部署视角。解决单独保留一段工地现场录制素材作为测试集训练过程中不要动它最终评估以这段素材为准。如果现场素材因为隐私原因不能入库至少要统计人头大小分布判断是否和训练集一致。5.2 安全帽反光和黄色苫布卷被当成人头现象误检目标集中在圆形、半圆形物体上比如安全帽顶部、反光的灭火器罐、黄色苫布卷模型把它们输出为person_head框。原因电力工地背景里存在大量形似人头的弧形结构而单类别模型只区分“头”和“非头”在纹理信息不足时模型会退化成按形状判断。解决收集现场误检帧挑出50到100张硬负样本单独建一个bg目录并标注为空文件混入训练集重新训练。同时把数据增强里的hsv_h、hsv_s从默认值调大一点迫使模型多依赖颜色以外的特征。5.3 同一个工人被连续帧重复计数现象检测结果本身没问题但计数模块把每一帧出现的人头都当成新目标一个工人走过镜头被统计了三到五次。原因检测器没有时序概念逐帧输出本来就允许同一目标重复出现。把计数需求直接挂到检测结果上遗漏了跟踪关联这一步。解决检测输出后接IoU匹配或DeepSORT做轨迹关联给每个目标分配track id计数时按track id去重。人头属于小目标跟踪时tracks容易丢失id现场宁可少计数也不要多计数因为违章记录里假阳性比漏报更干扰管理决策。5.4 训练时频繁出现empty labels警告现象日志里出现“image xxx has empty labels”查下来这些图片确实存在于train列表里但labels目录没有对应txt。原因转换脚本半路中断或原始标注里这张图就没有object。数据集页面声称双格式完整实际文件并不完全对齐。解决训练列表只保留“labels目录中存在且文件非空”的图片不保留JPEGImages全量。清洗逻辑不过几行# filter_empty_labels.py # 过滤掉没有有效txt标注的图片生成干净的train/val列表 from pathlib import Path labels_dir Path(./head_det/labels) image_dir Path(./head_det/images/train) valid_images [] for txt in labels_dir.glob(*.txt): if txt.stat().st_size 0: stem txt.stem img_path image_dir / f{stem}.jpg if img_path.exists(): valid_images.append(str(img_path)) with open(./head_det/train_clean.txt, w) as f: f.write(\n.join(valid_images)) print(f清洗后有效图片: {len(valid_images)} 张)这段脚本先把空txt过滤掉再检查jpg是否存在输出一行一行的绝对路径列表。data.yaml里train改成train_clean.txt路径即可。注意别用glob来匹配jpg后缀以外的格式电力工地数据集偶尔会出现png或bmp混存的情况稳妥做法是同时检查多个后缀。5.5 BN崩溃batch太小还开大分辨率现象loss前几十轮正常某个epoch后突然变成nan之后loss曲线一路向下消失模型输出一片灰或全黑。原因batch太小加上imgsz偏高时BatchNorm统计量不稳定遇上一批以暗色铁塔为背景的图像就会崩。电力工地场景暗区占比大更容易触发。解决batch低于8时不建议硬train大模型先换yolov8n或者固定batch4的同时把imgsz从640降到480。另一种做法是开启数据增强里的brightness扰动让网络适应亮度变化减少BN对小batch极端分布的敏感度。6. 用PR曲线定置信度阈值把单类别模型调到现场可用训练完成不是终点最容易被忽略的一步是确定推理置信度阈值。ultralytics默认conf0.25这个值对现场场景往往不是最优选择。先看训练日志目录里的PR_curve.png横轴是recall纵轴是precision曲线越靠近右上角越好。不同置信度阈值对应曲线上不同的点现场允许漏检多一点就调高置信度允许误检多一点就往低调。更可靠的步骤是拿一段现场视频做多次推理分别用conf0.1、0.2、0.3、0.4跑一遍统计每个阈值下的误检数和漏检数。以我自己的习惯误检的麻烦程度远高于漏检因为误检会打断施工、产生无效报警所以最终阈值经常选在PR曲线交点偏右的位置而不是默认的0.25。# 用best.pt对现场素材做推理比较不同置信度下的输出 yolo detect predict \ modelruns/head_det_640/weights/best.pt \ source./site_video.mp4 \ conf0.15 \ saveTrue跑完换conf0.3、0.4再跑对比每遍视频里的框数量和明显错检数。这套做法比单独看mAP指标有说服力因为现场素材和训练集分布差异往往是最大的翻车来源。单类别任务没有“几个类别取最大得分”的分离过程阈值几乎一票决定最终输出质量。如果后续要做安全帽状态判断把这份数据集里“未戴帽”的人头框重新标注成no_helmet再叠加一个helmet类别就能直接在现有权重上做多类别微调。VOC格式的xml可以在LabelImg里打开继续编辑无需重建工程文件。这就是当初保留VOC标注的意义。我现在拿到这类数据集第一件事永远是花10分钟做标签冒烟测试再花10分钟过一小批标注图看数据分布然后才开始训练配置。这三步省下的排错时间远比训练本身多。希望帮到你。本文还有配套的精品资源点击获取