做输电线路巡检的人应该都有感觉:防外力破坏这个场景,算法本身不是瓶颈,数据才是。外破检测说白了就是识别线路走廊保护区内出现的挖掘机、吊车、塔吊这类施工机械,但目标种类杂、现场环境变化大、遮挡又多,没有一批足够踏实的数据集,模型训练出来就是空中楼阁。这次我整理的项目,就是一套面向电力场景输电线路防外力破坏检测的数据集,共1106张图,5个类别,同时提供VOC标注和YOLO格式标注两种版本。
这套数据集的定位很明确:给YOLO系列这类单阶段目标检测模型做训练和微调用。适用对象包括刚入门的算法工程师、做巡检系统集成的小伙伴,以及需要快速验证外破识别方案的产品团队。1106张不算海量,但配合合理的标注质量和针对性的数据增强,完全足够把模型拉到工程可用的水平。接下来我从方案设计、标注规范、格式转换到训练验证,把整个流程完整拆一遍,顺带把我踩过的坑也交代清楚。
1. 项目背景与方案选型
1.1 输电线路防外力破坏,为什么需要视觉检测
输电线路的故障原因里,外力破坏一直是占比很高的一项。施工机械超高作业碰线、塔吊吊臂进入安全距离、挖掘机误挖杆塔基础,每一类都是实打实的电网事故隐患。传统手段靠人工巡视和视频监控,但监控画面动辄几十上百路,靠人眼盯着不现实。视觉检测的价值就是把"人盯屏幕"变成"算法看画面",发现施工机械进入线路保护区后自动告警。
这个场景对检测模型有两个硬要求。第一是目标要认得准,机械误报和漏报都会直接拉低系统的可信度,误报多了现场人员会直接关掉告警。第二是推理要够快,很多边缘盒子用的是嵌入式GPU,模型不能太重。YOLO系列天然契合这个需求,所以数据集的标签格式也优先考虑YOLO。VOC格式主要用于标注阶段的通用性,方便在多个标注工具之间流转,也方便兼容一些老平台。这也是我为什么坚持双格式都保留的原因。
1.2 五类目标怎么定,边界在哪里
外破场景里的施工机械种类不少,但并不是类别越多越好。类别太多会导致标注成本上升、类别间特征混淆加剧,尤其像吊车和塔吊这种带长臂的目标,稍微标注不仔细就容易串。我最终锁定5个类别:挖掘机、吊车、塔吊、桩工机械、工程车辆。
- 挖掘机:履带式或轮式,带挖斗和动臂,特征是底盘加铲斗结构。
- 吊车:汽车吊为主,有伸缩吊臂和吊钩,吊臂和车身之间有明显的转台。
- 塔吊:固定式高塔,塔身很高,顶部有长横臂,多出现在工地场景。
- 桩工机械:打桩机、旋挖钻机、静压桩机等,结构特征以立柱导轨和重锤为主。
- 工程车辆:自卸车、混凝土搅拌车、平板运输车等。
类别之间的边界在标注时要特别小心。吊车和塔吊都带"臂",但吊车的臂在车身上,塔吊的臂在高空,两者在画面中的长宽比例差别很大。挖掘机和桩工机械一般不容易混,但远景低分辨率下两类外形都可能糊成一团,标注时需要放大图片确认结构。五类的划分不是拍脑袋定的,我对照了电力安监部门对外破施工的常见分类,这样模型输出结果和业务上报口径可以对齐。
1.3 1106张的数据规模够不够
很多人拿到数据集第一反应是:1106张是不是太少了?这个数量放在通用检测任务里确实不算多,但外破检测有它的特殊性。这些施工机械外观结构比较固定,不像行人、车辆那样有大量姿态和款式变化,模型不需要从零学习"什么是挖掘机",它只需要在预训练权重的基础上做领域适配。用迁移学习的思路来看,1106张精选图像足够完成这个适配过程。
不过这个数量有个前提:每一张都得是能用的图。掉帧、过曝、严重压缩的图我宁可删掉也不留在集合里。最终1106张里面,有一些大图是无人机航拍的高分辨率视角,还有一部分是现场枪机的低机位视角,两者结合让模型在不同视角下都见过样本。实际训练时,配合数据增强和多尺度训练,这批数据能把模型支撑到工程落地的水平。等后续项目扩展到新的地区,再按需补充几个类别或特定工况的图像即可。
1.4 为什么双格式都保留
VOC格式是Pascal VOC的标注规范,一个XML文件对应一张图,里面记录图片路径、尺寸、目标类别和边框坐标。YOLO格式则是每个目标一行,格式为"类别ID 中心点X 中心点Y 宽度 高度",所有坐标都归一化到0到1之间。两者各有各的适用场景。
我习惯标注阶段先用LabelImg产出VOC格式,因为XML结构清晰,人工检查和修改都方便。YOLO格式是给训练脚本直接吃的,但纯文本格式可读性差,如果标注框有问题,在txt里很难一眼看出来。保留VOC版本等于留了原始标注档案,发现问题还能追溯修改。因此数据集目录里两个版本都保留,互不干扰,转换关系是单向的:VOC是源,YOLO是衍生。后文我会给出完整的转换脚本,方便读者自己重建。
2. 数据采集与标注规范
2.1 图像来源与场景覆盖
数据采集环节决定了数据集的天花板。这批图主要来自三个渠道:线路巡检无人机航拍、杆塔视频监控截图、以及同行之间交流脱敏后的现场图片。无人机图多是俯视或斜视视角,能拍到线路走廊两侧较大范围的施工活动;枪机截图是平视视角,构图更接近实际部署后的真实画面;脱敏素材则补充了一些特殊天气和光照条件下的样本。
场景覆盖上我刻意做了均匀化处理。白天强光、阴天漫射光、清晨逆光、黄昏低照度的图都有分布。背景也尽量拉开,包括农田、山地、城镇边缘、在建工地等。模型在训练时如果只见过单一背景,部署到新现场就会频繁误检。我的经验是宁可每类目标数量少一点,也要保证背景和光照的多样性,这样模型的泛化能力才靠得住。
2.2 标注工具与标注流程
标注工具我用的LabelImg,它支持Pascal VOC格式导出,界面操作直观,团队里新手也能快速上手。标注流程分三步走:
- 粗筛阶段:把所有采集到的原始图像做去重和筛选,去掉画面模糊、目标过小、严重遮挡导致无法辨别的图。
- 标注阶段:严格按照类别定义框选目标,全程锁定边框四角尽量贴合目标边缘。
- 复核阶段:每张图至少经过第二个人复核,重点检查类别是否标错、边框是否过大或过小。
流程看起来简单,但复核这一步最关键。单人标注容易出现"越标越随意"的问题,比如挖掘机的挖斗被截掉一半、吊车吊臂只框了半截。多人复核能把这个趋势拉回来。最终1106张图中标注框总数约4600个,平均每张图4个左右目标,属于比较典型的施工密集场景分布。
2.3 标注细则和边界情况
标注过程中有几条细则必须前置约定,否则后期返工成本很大。
- 目标只要出现在画面中就要标,哪怕被电线杆、树木遮挡了一部分。遮挡目标的语义信息对模型训练很有价值,强制删除反而会让模型在真实遮挡场景下更脆弱。
- 边框要框住目标的整体主体结构,而不是局部肢臂。比如吊车就把车身和吊臂一起框住,塔吊则框住塔身和横臂整体,不单独框某一段。这样目标类别特征完整,模型更容易学到区分类别的关键结构。
- 在无法判断类别时,先放大图像确认;遮挡太严重实在无法确认的,这个框可以删除,不要硬标。
- 多目标重叠的场景,比如挖掘机旁边停着一辆自卸车,两个目标都要分别标注,哪怕边框高度重叠,模型需要学会在拥挤场景中区分实例。
这些边界情况的处理,直接影响模型在真实巡检画面上的表现。很多数据集训练结果不理想,不是模型不行,而是标注阶段就把类别和边框搞乱了,模型学到的全是错误的映射关系。
3. VOC转YOLO的完整实现
3.1 数据集目录结构
双格式数据集的目录结构建议如下布置,简单清晰,也方便后续脚本处理。
dataset/ ├── voc_format/ │ ├── JPEGImages/ │ ├── Annotations/ │ └── ImageSets/ ├── yolo_format/ │ ├── images/ │ │ ├── train/ │ │ └── val/ │ ├── labels/ │ │ ├── train/ │ │ └── val/ │ └── data.yamlJPEGImages放所有原图,Annotations放与图片同名的XML标注文件,ImageSets下面是划分好的训练集和验证集文件名列表。yolo_format目录下,images和labels各自再分train和val子目录,这是YOLO训练脚本默认的读取结构。data.yaml文件里指定类别数量nc和类别名称names,这些都必须和转换脚本里的类别顺序保持一致,顺序错了模型训练就是灾难。
3.2 解析XML并归一化坐标
VOC转YOLO的核心逻辑不复杂:读XML里的bndbox坐标,除以图片宽高得到归一化值,再换算成中心点格式。这里有一个隐藏细节:有的XML里 坐标已经超出图片边界,这是标注时鼠标拖过头造成的。转换脚本里可以直接做一次clamp,把坐标限制在0到1之间,以免训练时YOLO解析出非法框直接报错。
直接贴一个可用的转换脚本:
import os import xml.etree.ElementTree as ET from PIL import Image CLASSES = ['excavator', 'crane', 'tower_crane', 'pile_driver', 'truck'] def voc_to_yolo(xml_path, img_dir, out_dir): tree = ET.parse(xml_path) root = tree.getroot() img_name = root.find('filename').text img_path = os.path.join(img_dir, img_name) with Image.open(img_path) as im: img_w, img_h = im.size lines = [] for obj in root.findall('object'): cls_name = obj.find('name').text if cls_name not in CLASSES: print(f"未知类别: {cls_name} in {img_name}") continue cls_id = CLASSES.index(cls_name) box = obj.find('bndbox') xmin = max(0, float(box.find('xmin').text)) ymin = max(0, float(box.find('ymin').text)) xmax = min(img_w, float(box.find('xmax').text)) ymax = min(img_h, float(box.find('ymax').text)) x_center = (xmin + xmax) / 2.0 / img_w y_center = (ymin + ymax) / 2.0 / img_h box_w = (xmax - xmin) / img_w box_h = (ymax - ymin) / img_h lines.append(f"{cls_id} {x_center:.6f} {y_center:.6f} {box_w:.6f} {box_h:.6f}") txt_name = os.path.splitext(img_name)[0] + '.txt' with open(os.path.join(out_dir, txt_name), 'w') as f: f.write('\n'.join(lines) + '\n')注意脚本里对坐标做了max和min的边界约束,就是为了应对标注出界的情况。转换完成后,每个XML对应生成一个同名txt文件,文件里每行对应一个目标。我建议单独写一个转换日志,把遇到"未知类别"和"坐标异常"的图片名都打出来,后续统一处理,而不是边转边改。
3.3 转换后的自检脚本
转换完成不等于万事大吉,一定要做一次全量自检。主要检查几类问题:图片没有对应txt、txt内容为空、坐标越界、类别ID超出范围、文件名对不上。自检脚本可以这样写:
import os from PIL import Image def check_yolo_labels(img_dir, label_dir, nc=5): problems = [] for img_name in os.listdir(img_dir): stem = os.path.splitext(img_name)[0] label_path = os.path.join(label_dir, stem + '.txt') if not os.path.exists(label_path): problems.append((img_name, "缺少标注文件")) continue with Image.open(os.path.join(img_dir, img_name)) as im: img_w, img_h = im.size with open(label_path) as f: lines = f.read().strip().split('\n') if lines == ['']: problems.append((img_name, "空标注文件")) continue for i, line in enumerate(lines): parts = line.split() if len(parts) != 5: problems.append((img_name, f"第{i+1}行字段数错误")) continue cls_id = int(parts[0]) cx, cy, bw, bh = map(float, parts[1:]) if cls_id < 0 or cls_id >= nc: problems.append((img_name, f"第{i+1}行类别ID越界")) if cx < 0 or cx > 1 or cy < 0 or cy > 1: problems.append((img_name, f"第{i+1}行中心点越界")) if bw <= 0 or bh <= 0: problems.append((img_name, f"第{i+1}行宽高非法")) return problems这段脚本跑一遍,如果有问题会直接打印图片名和具体症状。我自己转过一次,当时就排查出几十张图是"坐标系没归一化",原因是没有读取XML里的size而是硬编码了固定分辨率,视觉效果很诡异。这类低级错误没有自检脚本根本发现不了。
3.4 数据集标签统计
转换完成后一定要看一眼标签分布。标签分布直接反映数据集的类别均衡性,也决定训练时要不要做类别权重或者补充样本。下表是我们这批数据集的框数统计,供参考。
| 类别 | 标注框数量 | 占比 | 典型出现场景 |
|---|---|---|---|
| excavator 挖掘机 | 约1450 | 31% | 基坑开挖、护坡整修等 |
| crane 吊车 | 约780 | 17% | 立杆、吊装设备 |
| tower_crane 塔吊 | 约310 | 7% | 线路附近工地施工 |
| pile_driver 桩工机械 | 约420 | 9% | 桩基施工、场地打桩 |
| truck 工程车辆 | 约1640 | 36% | 土方运输、混凝土浇筑 |
可以看出塔吊的框数偏少,原因是很多图像采集区域附近并没有在建工地,塔吊出现频率天然就低。如果模型在塔吊类别上表现不佳,优先想到的不是换网络结构,而是去补充几批带塔吊的图像再做微调。数据不均衡靠训练技巧只能缓解,治本还是得靠补数据。
4. 数据增强与训练验证
4.1 增强策略:不同目标要有不同的处理方法
数据增强不是把OpenCV的变换全都套上就行,要结合目标的结构特点选。挖掘机和工程车辆这类目标比较"皮实",翻转、缩放、亮度扰动都可以上。吊车和塔吊这类带长臂结构的机械,旋转角度要控制。旋转90度以后吊臂竖直朝上,现实场景里很少出现这种构图,模型反而会学到错误的姿态特征。我这边把旋转角度限制在±30度,再配合水平翻转,既能增加样本多样性,又不破坏目标的结构语义。
光照扰动对电力场景尤为重要。现场图片常有逆光、阴影、黄昏低照度,训练时如果不做亮度增强,模型部署到阴天场景大概率扑街。直方图均衡和对比度拉伸我各加了一组,用随机概率触发。对缩小和放大目标,我让短边的缩放范围保持在0.5到1.5倍之间,太大的缩放会导致目标特征严重畸变。
4.2 训练集划分与训练参数
划分训练集和验证集时,我严格保证同一来源的连续帧图片不会被分到两个集合里。视频监控截图里相邻几帧几乎一模一样,如果训练集和验证集里出现这种"近似重复",验证集指标会虚高,部署后才会露馅。所以划分的单位不是单张图片,而是图片的采集片段来源。
实际训练时我用YOLOv5s和YOLOv8s各跑了一轮。以YOLOv8s为例,关键参数如下:
nc: 5 imgsz: 640 epochs: 120 batch: 16 patience: 20 workers: 8图片输入分辨率为什么要用640而不是更高?外破场景中吊车、塔吊这类目标体积比较大,640的图像尺寸已经能让小目标的语义特征保留到可辨识程度。追求极致小目标检测可以上960或者1280,但推理耗时增加明显,边缘盒子扛不住。项目当前阶段优先保帧率,所以640是性价比比较高的选择。
4.3 训练结果与漏检分析
一轮完整训练后,验证集上的mAP50大概在0.85到0.9之间,这个结果对工程落地是可以接受的。各类别表现有差异,工程车辆和挖掘机的识别效果最好,证据是它们的姿态相对固定、背景干扰少;塔吊和桩工机械的召回率偏低,因为这两种目标要么数量不足、要么外观和背景有近似结构。
训练过程中我统计了一批典型漏检情况。逆光环境下挖掘机的铲斗和土壤背景融为一体,模型容易漏检;塔吊的吊臂如果正好穿过大片白云,模型偶尔会把吊臂当成云层纹理的一部分。这两类场景我后续单独补了约100张针对性图像,重新微调后漏检率才明显下降。所以说,1106张只是起点,跑到部署场景里发现短板再补数据,这种迭代思路比一口气堆几千张图更务实。
5. 常见问题与避坑记录
5.1 外接框过大
标注时手一抖,边框把目标外的一大片背景也框进来了,这类问题在复核阶段最容易漏过去。外接框过大对训练的影响比想象中大,因为模型会学到"背景也属于目标",轻则导致预测框偏大,重则误检率飙升。我在复核时会专门把放大倍率调到200%以上检查框的贴合度,重点盯吊车和塔吊这种外形不规则的机械,它们的边框往往最难标准。
5.2 类别混淆
吊车和塔吊、挖掘机和桩工机械是两组容易混淆的类别。吊车臂灵活多变,样式也更多,塔吊的横臂相对固定;但远景小目标上这两类区别不明显。我的对策是给容易混淆的类别补充了更丰富的角度样本,同时在标注规范里明确"看不清就放大看结构",避免靠猜测标注。经过一轮修正后,模型在吊车和塔吊之间的混检率明显降低。
5.3 转换脚本的常见坑
VOC转YOLO看似简单,坑一点都不少。第一坑是XML中的filename和实际文件名不一致,有的图片做过重命名,但XML里还是旧名,转换脚本直接读错文件。第二坑是XML文件里size标签缺失或填错,导致归一化坐标错误,YOLO训练直接崩或mAP惨不忍睹。第三坑是类别顺序在转换脚本和data.yaml里不一致,比如脚本里索引0是excavator,yaml里索引0却是crane,模型整个训练过程都是在错乱中进行的。这三个坑我全踩过,解决方式就是写自检脚本,在训练前把所有txt文件重新扫一遍,坐标和类别ID都确认无误再开跑。
5.4 标注质量问题最大的来源
回看整个项目,数据质量最大的隐患其实是标注疲劳。一个人连续标几个小时,到后面框的位置开始飘、类别选择越来越随意。我的对策有两个:一是每天标注量控制在合理范围,二是强制第二人交叉复核。宁可用更多工时换数据质量,也不要靠后期算法"清洗"弥补。标注是纯人工环节,机器替代不了,而数据集的质量上限在标注阶段就已经决定了。
这批数据集目前已经在我们项目组内部跑通了完整的训练和部署流程,后续迭代时我打算再补两个方向:一是增加夜晚红外和热成像视角的图像,提升夜间巡检能力;二是补充更多不同地域的施工现场背景,降低区域差异带来的误检风险。数据集的整理和维护会是一个持续的过程,真正的好用,都是靠一次次现场反馈磨出来的。