无人机电力巡检杆塔检测:13790张图像从格式转换到训练实战
前阵子有位做电力巡检的朋友抱着一堆从无人机上导回来的航拍图来找我问我能不能帮他训练一个模型把图像里的电力杆塔自动框出来。数据量倒不小13790张标注格式也给了VOC和YOLO双格式单类别检测。我打开看了几眼发现这批图拍摄场景挺杂有农田、有丘陵、有城市边缘杆塔形态也不统一有的画面里只占很小一块有的则几乎撑满全图。我当时跟他说单类目标检测模型架构倒不复杂真正决定最后能不能用的是数据有没有被“喂”明白。这篇文章就把我们用这套无人机视角电力场景杆塔检测数据做训练的全过程写出来包括13790张图像的结构和划分逻辑、VOC与YOLO两种格式之间的转换细节、训练超参的选择和实测结果以及中间踩过的一些坑。不管你是刚接触目标检测还是已经在做工业巡检类算法这套数据相关的工程经验应该都能直接参考。1. 电力巡检为什么先拿杆塔定位开刀1.1 人工巡检的瓶颈与“先定位、再细看”的两级思路电力线路巡检过去靠人拿着望远镜在塔下看或者直接登塔检查危险不说效率也低。后来无人机普及了一个架次就能沿线路飞几十公里拍回来上千张照片。照片是有了瓶颈转移到了人工看片子上——一个熟练工一天能翻几百张就算不错要是赶上大风天拍出来的图有偏斜、模糊看片的效率还要再降。所以工业上的落地思路通常分成两级第一步先把杆塔从大量无关背景里“找出来”第二步再把杆塔区域放大去分析绝缘子、销钉、防震锤这些部件的状态。杆塔定位是整个自动巡检链路的入口这一步不做好后面所有精细化分析都没有稳定的输入。这也解释了为什么数据集只需要1个类别。它服务的不是“这是什么缺陷”而是“杆塔在哪里”。检测器输出一个框下游系统按这个框去裁剪局部图像、做部件级识别分工非常清晰。单类检测不等于任务简单它要求模型把“杆塔”这一类目标的召回率和定位精度做到足够高否则下游根本没法继续处理。1.2 T型杆塔在航拍视角下为什么既好检又难检题目里提到的T型杆塔在输配电线路里很常见塔身竖直横担水平伸展出去整体轮廓像一个放大的T字。这种结构在航拍视角下有天然优势线条硬朗、几何特征明显、与周围的树木和建筑区分度高比识别电线上挂着的异物要容易得多。难点也很现实。无人机飞行高度和拍摄角度一直在变同一个塔从正上方俯拍、从侧面斜拍、从远处平视拍外观差异非常大。再加上天气变化逆光时塔身被压成深色剪影雾天时横担和背景融成一片模型很容易把背景里的高塔、避雷针甚至大型施工机械误判成杆塔。我见过不少刚开始做这个方向的人以为单类检测随便训一下就能上98%的mAP结果在恶劣天候的样本上被现实狠狠教育了一顿。1.3 单类别的工程取舍类别边界比类别数量更重要数据标注里有一个经常被忽略的问题类别数量越少类别内部的边界反而越要定义清楚。比如“杆塔”这一类有没有包括塔基横担最外侧的高压线算不算水泥电杆和钢管铁塔要归成两类还是同一类如果标注人员没有统一标准每个人会按自己的理解去画框最后的训练数据里同一类目标的框风格千奇百怪模型学到的特征就会非常飘。从工程角度看把不同类型的杆塔统一归为1个类别没有毛病。这种归并减少了标注不一致的风险也让模型把注意力集中在“塔身横担”这个公共结构上。实际项目里如果后续需要区分杆塔类型完全可以在检测框基础上再做一层分类不需要把检测任务的类别体系一开始就搞得很重。2. 场景构成比张数更重要13790张数据里到底藏了哪些不均匀2.1 多场景与多尺度分布的拆解做目标检测的人常有一个误区:以为只要数据量够大模型就能自动变强。理论上数据多是好事但如果13790张图都集中在晴天、同一拍摄高度、同一类地形背景模型就相当于只在单一环境里“应试”换个场景立刻露馅。实际处理时我更关心这批数据的分布是否拉开得足够开。拆开来看一张无人机杆塔图里最关键的变量有三个。第一是拍摄距离杆塔在画面里可能只占5%也可能占30%以上这直接决定了检测器要面对的尺度和目标像素数量。第二是角度正俯、斜侧、平视三个方向的杆塔特征完全不同。第三是背景农田、丘陵、水域、城市近郊背景复杂度差异非常大。再加上光照和天气的变化这才构成了一套真实可用的训练分布。按这套逻辑去复盘样本构成可以大致参考下面这个分布形态维度分布特征说明拍摄高度30-80米为主少量百米以上高度变化会显著影响杆塔在画面中的占幅拍摄角度斜侧约占一半正俯和平视各约四分之一斜侧能同时看到塔身和横担信息最全背景类型农田平原、丘陵山地、水域、城市近郊背景越杂模型学到的“抗干扰”能力越强光照条件晴天、阴天、逆光、晨昏各占一部分逆光样本是检错率最高的类别必须有目标占幅5%以下的小目标样本约占三成小目标直接决定mAP0.5:0.95的上限这也是我拿到数据集后第一步要做的事先按拍摄元数据把全部图像粗分一遍看每个生命桶里的样本量是否均衡而不是直接拆开目录就开训。2.2 训练、验证、测试集究竟怎么划分才不算“数据泄漏”训练集、验证集、测试集的划分看起来是流水线操作恰恰是最容易埋雷的地方。有些人直接用随机划分把一万多张图按比例随机分结果验证集里和训练集混着来自同一架次的相邻帧画面内容高度相似验证集指标漂亮得吓人一到真实场景精度立刻掉下来。正确的做法是按拍摄航次或地理区域来分。同一架无人机在同一条线路上拍到的图片不管相隔多少张都应该划进同一个集合。比如把某几条线路的图整体留作测试另外几条线路用于训练和验证这样模型在测试阶段面对的是没见过的塔型和环境。我在实际处理时习惯按大约8:1:1的比例切分但前提是“按场景组”切而不是“按行切”。切完还要再做一道检查随机抽几张测试集图片去训练集里看一下有没有相似的“双胞胎”如果有说明分组粒度还不够大需要重划。2.3 从张数到“有效负样本”单类检测也要处理背景差异目标检测不光要学“什么是杆塔”还要学“什么不是杆塔”。那些完全不包含杆塔的负样本或者说背景样本它的价值常常被低估。好在这批13790张的数据本身是航拍连续帧里面天然有很多杆塔只出现在画面边缘或干脆没进画面的帧不需要专门去网上扒负样本。带背景多样性的负样本能大大降低误检率。尤其电力巡检场景航拍图里经常出现水塔、信号塔、交通标志塔这类“长得像杆塔但不是杆塔”的物体。如果训练数据里这些干扰物出现得太少模型就会把“竖直的长条结构”当成杆塔特征这是后期误检的主要来源。处理这类问题不能只靠调IoU阈值得靠数据分布去“喂”模型。3. VOC与YOLO双格式的落地细节字段、转换脚本与路径陷阱3.1 VOC格式的XML字段到底在描述什么VOC格式的核心是JPEGImages目录下放图像、Annotations目录下放同名XML文件每个XML里用标签块记录一个目标框的位置和类别。下面是我在处理过程中抽出来的一个典型标注内容结构annotation folderJPEGImages/folder filenametower_000123.jpg/filename size width4928/width height3264/height depth3/depth /size object nametower/name bndbox xmin1280/xmin ymin640/ymin xmax2048/xmax ymax1920/ymax /bndbox /object /annotation需要注意VOC的坐标是绝对值像素坐标宽度就是xmax减xmin高度就是ymax减ymin。这个格式的好处是可读性强人工检查非常方便坏处是文件体积大、解析慢批量处理一万多张图时每张图都要用XML解析器去遍历节点速度会比读纯文本慢不少。3.2 YOLO文本标注的归一化原理YOLO格式把坐标全部归一化到0到1之间一行文本代表一个目标框格式是class_id x_center y_center width height例如下面的内容表示类别0的杆塔框中心在图像横向62%、纵向39%的位置框宽占整图约22%框高占整图约47%0 0.6234 0.3912 0.2218 0.4685从VOC转YOLO的换算其实就几个公式关键在于归一化的顺序别搞反。先算中心点再算宽高最后统一除以图片宽度或高度。3.3 转换脚本与三个常见路径陷阱我用的转换逻辑很简单核心就是遍历XML文件逐框写入文本import os import xml.etree.ElementTree as ET CLASSES [tower] def voc_to_yolo(xml_path, classes): tree ET.parse(xml_path) root tree.getroot() w int(root.find(size/width).text) h int(root.find(size/height).text) lines [] for obj in root.iter(object): name obj.find(name).text.strip() if name not in classes: continue cls_id classes.index(name) box obj.find(bndbox) xmin int(box.find(xmin).text) ymin int(box.find(ymin).text) xmax int(box.find(xmax).text) ymax int(box.find(ymax).text) x_center (xmin xmax) / 2 / w y_center (ymin ymax) / 2 / h bw (xmax - xmin) / w bh (ymax - ymin) / h lines.append(f{cls_id} {x_center:.6f} {y_center:.6f} {bw:.6f} {bh:.6f}) return lines代码本身不难真正坑人是三个文件层面的问题。第一个坑是标注路径。YOLO训练时用到的train.txt、val.txt里记录的是每张图片的路径如果生成时写的是绝对路径比如/home/xxx/dataset/images/tower_000123.jpg把这套数据迁移到别的机器上路径直接失效。我的建议是统一用相对路径并且保持images和labels的顶层目录结构不变这样换机器后只要改一个数据根目录变量就行。第二个坑是classes.txt的类别顺序。转换时classes列表的索引就是YOLO标注里那个class_id训练配置里的nc数量和类别清单必须跟它保持严格一致。单类数据还好如果以后扩展成多类顺序一旦错位模型学到的类别标签就会整个乱掉。第三个坑是图片尺寸不一致。无人机拍摄的照片分辨率并不统一有4928x3264也有4000x2250还有少数是手机拍摄的缩略图。如果脚本里用固定宽高去归一化或者XML里的宽高和实际图像对不上YOLO的框就会漂移。转换完一定要做一次校验随机抽出几百组图像和标签把框画在图上人工看一遍。4. 用这套数据实测训练分辨率、锚框与增强策略调整复盘4.1 输入分辨率先跑通640再上1280对电力杆塔这类场景模型输入分辨率几乎直接决定结果上限。杆塔本身是竖长结构如果目标只占画面5%缩放到640分辨率后塔身可能只有二三十个像素高横担更细小目标特征被压缩得很厉害。我建议第一次训练先用640x640把整个流程跑通确认数据、划分、训练脚本都没有问题再切到1280x1280精调。这样能快速排查工程问题不需要一上来就让网络适配大分辨率导致显存吃紧、训练时间翻倍。实测中从640切到1280单类杆塔检测的mAP0.5一般能有几个点的提升mAP0.5:0.95提升更明显因为小目标定位精度变高了。代价也很直接显存占用和训练耗时同步上升。如果显卡不够大备选方案是做切片推理把原图划分成多个重叠patch分别检测再把结果合并。这条路线不需要改训练分辨率效果也不错但推理逻辑会复杂一些。4.2 锚框设置默认值不一定适合单类检测YOLO系列默认锚框是在COCO数据集上聚出来的覆盖80个类别、各式各样的目标尺寸。我们的任务只有杆塔一个类别目标的长宽比有很强的规律性塔身高、横担宽整体框通常偏向竖长形长宽比稳定在1到3之间和COCO里那些横着的车、横着的日常物体差异不小。所以训练前最好对数据集重新做一次K-means锚框聚类看看数据自己的分布是什么样的。在之前的一次模拟项目X里我们拿到的聚类结果是大约[(10, 18), (16, 30), (24, 48), (35, 78), (55, 110), (90, 160)]这一量级的组合明显更贴合无人机视角下的杆塔尺度分布。把聚类后的锚框写进模型配置训练初期的损失下降会更快最终精度通常也更高。训练时把锚框设成“不自动重新调整”避免训练中途锚框漂移尤其当你已经手工聚过类之后。4.3 数据增强的取舍哪些增强会伤害杆塔检测数据增强不是越多越好。我在试验中发现过大的随机旋转对杆塔检测是负优化。杆塔有明确的垂直结构横担水平伸展这是它区别于其他杆状目标的核心特征。如果旋转增强的角度范围给到90度模型就会看到大量“横担朝下”的离谱画面真实场景根本不存在这种视角白白消耗模型的拟合能力。个人经验是把旋转范围限制在±10度以内主要应对无人机飞行姿态导致的轻微倾斜即可。Mosaic增强要保留它能把不同场景的杆塔塞进同一张图里对提升小目标检测和理解复杂背景都有帮助。HSV颜色增强建议适度开模拟不同天气光照变化但饱和度变化范围不要拉太大否则会让模型依赖不真实的颜色特征。4.4 一组可供参考的训练配置与结果下面是我在处理类似电力杆塔检测任务时用过的一套配置可以直接作为起点参考不用原样照抄跑通以后按自己数据的情况再调。配置项数值说明输入尺寸640起步精调时切1280先用小分辨率跑通工程再上大分辨率提精度批次大小16大分辨率时适当调小防止显存溢出训练轮数200单类数据收敛较快100轮后重点看验证集曲线初始学习率0.01配合Warmup避免前几个batch震荡锚框策略K-means聚类用当前数据集聚类而不是沿用默认COCO锚框Mosaic0.9保留对多样性和小目标有帮助旋转增强±10度超过这个范围容易产生虚假样本负样本比例约10%控制无目标图的占比训练更稳定按这套配置我们当时跑出来的参考结果大概是mAP0.5在98%上下mAP0.5:0.95在87%上下。表面看起来已经挺好但把错误样本拆开看就会发现漏检主要集中在远景小目标错检则集中在和杆塔结构相似的高塔、避雷针。这说明数据分布里的场景多样性还有提升空间模型单靠现有数据已经学得足够充分了。5. 精度上不去时先别动模型从标注统计反查数据问题5.1 排查顺序数据正确性优先于模型结构训练结果不理想时工程师的第一反应往往是换更大的模型、调学习率、改损失函数。但我见过的很多问题根子根本不在模型上面而藏在数据里。排查的顺序应该反着来先验证数据文件配对是否正确再统计标注本身的分步情况然后才是模型结构。先检查最基础的图像和标签文件名是否一一对应有没有空标签文件标签里有没有越界坐标。这类问题不需要任何算法知识单纯写个脚本扫一遍就能发现。我写完转换脚本后必做的一步是从13790个标签里统计框的总数、平均宽高、长宽比分布一旦发现哪项指标离群就知道对应的那批图像一定有问题。5.2 一次“横担误检”的定位过程这里分享一个实际经历。某次训练完我发现验证集里有个规律错检框几乎都集中在横担周围模型会在横担左右两侧多框出一些奇怪的区域。一开始以为是模型定位不准后来把训练集里所有标注框的宽高比拉出来排序发现有一批约几百张图标注框明显比正常框“胖”——它们把两边的横担整个包进了框里有的甚至连带了一截背景树木。正常标注应该从塔身两侧切进去只框住塔体和横担主体的核心区域。问题很快就查出来了这批图在生成标签时用的是一个旧版本的标注文件框的风格和其余数据完全不一致。比例不大只有几百张但对模型的影响被放大了——它们相当于告诉模型“横担两侧的背景也是杆塔的一部分”。把对应标签重新标注并回归统计后横担误检立刻大幅下降。5.3 分组评估如何找出模型真正的短板整体mAP好看不代表模型没有短板。平均指标会被大量简单样本“稀释”真正危险的场景错误反而被掩盖。更好的做法是把测试集按拍摄场景、光照条件或目标尺度分组分别计算每组指标。比如把测试图分成远景、中景、近景三组会发现远景组的mAP可能比近景组低十几个点这很正常但能告诉我们下一步优化方向要么在训练数据里补充更多远景样本要么针对小目标设计专门的检测分支。再比如把逆光样本单独拉出来评估如果召回率明显偏低说明训练数据里逆光图的比例不够单靠HSV增强无法替代真实逆光图的纹理特征。我现在的习惯是训练完第一次评估就把错误样本按“小目标漏检、遮挡漏检、相似物错检、标注边界误差”四类打标做成一个小型错误分析表。这比盯着loss曲线猜原因要高效得多因为loss是粗颗粒信息只能告诉你“有问题”不能告诉你“问题具体在哪个场景”。拿到标注统计和分组评估后大部分数据层面的问题都能快速定位。如果这些检查全部通过指标还是不行再开始调模型结构和训练策略这时候的调优才是有意义的。在做这类航拍检测项目时我最深的体会是模型架构本身能带来的提升越来越有限数据质量和分布设计才是最终决定模型能否在真实场景里落地的那道天花板。希望我上面这些经验能帮你少踩几个坑尤其在处理同类单类检测数据时先把数据盘清楚再谈训练。