简介:一篇关于无人机巡检图像销钉故障检测的深度学习期刊论文,面向电力巡检、计算机视觉等领域的研究人员与工程师。资源包共1个PDF文件,大小1.67MB,内容为《计算机测量与控制》2019年发表的论文,聚焦输电线路销钉脱落故障的自动识别问题。文中系统介绍了基于Faster R-CNN的目标检测方法,并与ACF+Adaboost、Hough+LSD两种传统检测方案进行比较;实验结果显示,该方法对销钉脱落故障的识别率可达96%,对正常销钉的识别率最高为98%。读者可从中获取完整的算法流程、实验设计与分类器对比结论,也可将其作为深度学习在电力工业场景落地应用的案例,用于课题研究、毕业设计或技术调研。这份资源已有278人学习下载,对希望快速了解该方向技术路线与核心指标的读者具有参考价值。
1. 无人机巡检图像里的销钉故障检测:小目标、长尾与误检这三座山
在输电线路和铁塔的无人机巡检图像里,销钉是最不起眼却最要命的部件。一颗直径不过几厘米的销钉,在 4K 巡检原图里往往只占几十到一两百像素,松脱、缺失、锈蚀的差异在肉眼下都容易看漏,更别说让模型在飞巡一个架次产生的上千张图像里稳定找出来。基于深度学习的无人机巡检图像销钉故障检测,要解决的不是“能不能识别出销钉”这个泛泛问题,而是三件具体的事:小目标怎么不被漏掉、故障样本太少怎么训练、现场误检怎么压下去。这套做法适合电力巡检算法工程师、无人机数据服务商和刚接触深度学习视觉落地的人,先搭数据底座,再选模型,最后用推理和部署把精度变成可用率。
2. 数据底座:拍摄规范、标注格式与数据集划分三件事
2.1 拍摄与采集:无人机巡检图像的成像约束
销钉检测在天上就输了一半,往往不是因为模型不行,而是因为图像里压根看不清。无人机巡检图像的成像质量直接决定了后续所有工作的上限,这一步很少有人愿意回头补拍,所以采集规范比模型选型更早介入。常见做法是:无人机悬停在铁塔横担外侧,云台俯仰角控制在 15° 到 45° 之间,镜头光轴尽量垂直于销钉所在的角钢平面,避免极端侧视把销钉压成一条线。飞行高度与焦距要配合,让销钉在画面中的短边不小于 40 像素,做不到时就靠变焦镜头靠近,而不是靠后期超分硬猜。
光照是另一个容易被低估的变量。晴天正午拍摄时,角钢和销钉的反差最大,但阴影区里的销钉几乎全黑;阴天或薄暮时整体光照均匀,检出率反而更稳。我一般会要求采集时尽量覆盖不同光照时段,并单独留一组逆光样本。飞行路径上还要保证同一基塔有至少两个拍摄角度,正面一张、侧面一张。原因很直接:销钉缺失这个故障,从正面看可能被螺母遮挡,侧面才能确认;松动则相反,正面能看到缝隙,侧面不明显。这里的图像配准和时间序列对比也很有用——同一基塔不同月份的照片对齐后,销钉状态变化会非常直观。不过配准是后话,采集阶段先保证每个塔位多角度覆盖,后面做变化检测才有素材。
2.2 标注格式与转换脚本:从 VOC/自制 JSON 到 YOLO 格式
标注环节最常见的工作流是:先用 LabelImg 或 X-AnyLabeling 标成 VOC XML 或 JSON,再统一转成 YOLO 的 txt 格式。转换脚本是每次做数据都得过一遍的坎,这里给出一个我常用的转换片段,兼容 VOC 和常见 JSON 标注结构。
import os import xml.etree.ElementTree as ET def voc_to_yolo(xml_path, out_dir, class_map): tree = ET.parse(xml_path) root = tree.getroot() img_w = int(root.find("size/width").text) img_h = int(root.find("size/height").text) objects = root.findall("object") txt_name = os.path.basename(xml_path).replace(".xml", ".txt") with open(os.path.join(out_dir, txt_name), "w") as f: for obj in objects: cls = obj.find("name").text if cls not in class_map: continue # 跳过未定义类别,避免脏标签进训练 box = obj.find("bndbox") x1 = float(box.find("xmin").text) y1 = float(box.find("ymin").text) x2 = float(box.find("xmax").text) y2 = float(box.find("ymax").text) cx = ((x1 + x2) / 2) / img_w cy = ((y1 + y2) / 2) / img_h w = (x2 - x1) / img_w h = (y2 - y1) / img_h # 过滤异常框:宽或高小于5像素的标注视为无效 if w * img_w < 5 or h * img_h < 5: continue f.write(f"{class_map[cls]} {cx:.6f} {cy:.6f} {w:.6f} {h:.6f}\n")这个脚本做了三件事:把像素坐标归一化到 0~1 区间,筛掉小于 5 像素的无效框,同时跳过不在类别表里的名称。归一化是 YOLO 格式的硬性要求,模型内部不管原图多大,都在归一化坐标上算损失;不归一化轻则训练不收敛,重则训练时 loss 直接变成 nan。5 像素这个阈值是我自己的习惯,销钉再小,标注框小于 5 像素时连人都很难判断类别,强行保留只会给模型送噪声。
类别表的定义要提前定死。我建议按“正常销钉、销钉缺失、销钉松动、销钉锈蚀”四类起步。很多团队喜欢只标“缺失”和“正常”二分类,但松动的视觉特征介于正常和缺失之间,不加类别会让模型把松动学成正常,这是现场误检的一大来源。另外备注框也很有用:巡检原图里大量铁塔构件长得像销钉,比如螺栓头和垫片,第一次标注时最好单独建一个“hard negative”目录存放,不参与训练,但后续测试专门用来压误检。
2.3 训练集划分:按塔位分组,别按图片随机划分
数据划分看起来是不起眼的步骤,却是最容易翻车的地方。很多项目把同一基塔的几十张图像随机分进训练集和验证集,结果验证集 loss 很漂亮,现场一测就崩。原因是同一塔位的图像背景几乎一样,只是无人机悬停位置稍有偏移,随机划分相当于让模型在“开卷考试”里背答案,验证集里全是训练集的近亲。
正确做法是先把所有图像按塔位编号分组,再按组划分。比如你有 120 基塔的数据,可以按 96 / 12 / 12 切分成训练集、验证集和测试集,保证同一基塔的所有图像只出现在其中一个集合里。这个操作我一般用文件目录名做 group 字段,一次划分后固定下来,后续加数据时重新跑一遍分组脚本。划分之后统计一下各类别的框数量:销钉缺失的框如果只有几十个,后面训练时要么做重采样,要么先别把它当独立类别,合并成“异常销钉”跟“正常销钉”二分类起步,否则模型大概率学不到有效特征。
这里顺带说一句标注质量抽检。巡检图像的标注员在疲劳状态下,会把“缺失”标成“松动”,把锈蚀漏掉。训练前我会抽 5% 的标注框,用图像算法做一次简单校验:把每个标注框裁出来,按类别归档成网格图,人眼扫一遍。这个习惯看起来土,但比任何自动清洗都管用。给模型喂数据之前,先确认数据自己是干净的。
3. 模型选型:YOLOv8 还是 RT-DETR,深度学习 CNN 的巡检基线怎么定
3.1 检测框架对比:CNN 基线、Transformer 与轻量化部署的取舍
销钉检测的模型选型,本质是在“小目标召回率”和“机载或边缘设备推理速度”之间做权衡。过去几年电力巡检领域最常见的基线是 YOLO 系列,到 YOLOv8 这一代,CNN 检测器在中等尺寸模型上已经能把销钉这类小目标处理得比较稳。RT-DETR 这类基于 Transformer 的检测器精度上限更高,尤其在光照复杂、遮挡严重的图像上表现更稳,但模型体积和推理延迟也更大。对于先跑通流程的项目,我建议用 YOLOv8n 或 YOLOv8s 做基线,跑通之后再决定要不要换大模型。
下面这张表是我在同类巡检项目里常用的对比口径,具体数字会因数据而异,但量级可以参考。
| 模型 | 输入尺寸 | 参数量 | 推理速度(GPU) | 小目标表现 | 部署难度 |
|---|---|---|---|---|---|
| YOLOv8n | 640 | 约 3M | 极快 | 一般,容易漏小销钉 | 低 |
| YOLOv8s | 640 / 1280 | 约 11M | 快 | 1280 输入下明显改善 | 低 |
| YOLOv8m | 1280 | 约 26M | 中等 | 好,显存占用较高 | 中 |
| RT-DETR-L | 640 | 约 32M | 中等 | 好,遮挡场景更稳 | 中高 |
| RT-DETR-X | 1280 | 约 67M | 慢 | 最好,现场落地成本高 | 高 |
从这张表能看出一个关键结论:对小目标检测来说,输入分辨率往往比模型参数量更起作用。YOLOv8n 在 1280 输入下的表现可能好过 YOLOv8s 在 640 输入下的表现。所以选型的顺序不该是“先定模型再定输入”,而是“先估计算力能承受多大输入,再在同一输入下比模型”。边缘部署场景我一般会优先 YOLOv8s,1280 输入跑 TensorRT FP16,单卡能跑到接近实时;服务器推理则直接上 YOLOv8m 或 RT-DETR-L,把精度顶上去。
3.2 预训练权重与迁移学习:用 COCO 还是现场数据预处理
销钉不是 COCO 里的常见类别,但 COCO 预训练权重仍然值得用。原因在于预训练模型已经学会了边缘、纹理、形状组合这些通用视觉特征,销钉检测需要的小目标纹理响应,恰好可以从这些底层特征迁移过来。常见做法是直接加载 ultralytics 提供的 YOLOv8 预训练权重,然后把类别数改成自己的四类,前几轮冻结骨干只训练检测头。损失下降正常后再解冻全部层,用较小学习率微调。
这里有一个必须注意的匹配问题:预训练模型在 COCO 上训练时的输入尺寸是 640,如果你直接改成 1280 输入,位置编码和特征金字塔的感受野都会变化,前几个 epoch 的 loss 可能不降反升。我一般会分两阶段:先用 640 输入训练 50 个 epoch 让检测头收敛,再把输入尺寸切到 1280 微调 30 个 epoch。这种做法比一步到位直接训 1280 更稳,也更容易判断是数据问题还是模型问题。
迁移学习的另一个隐性收益是小样本容错。当销钉缺失样本只有几十张时,完全从头训练几乎必然过拟合,但用预训练权重微调时,模型至少能把“销钉”和“非销钉”区分开,再把“缺失”“松动”这些细粒度差异学出来。换句话说,预训练权重把学习的起点往前推了一大段,后续只需要在已有特征上做增量调整。
3.3 在电力巡检场景下先跑通一个最小可用的训练命令
不管选哪个模型,落地的第一步永远是跑通一个最小训练流程。这里以 YOLOv8 为例,假设数据已经按前面步骤转成了 YOLO 格式,并且目录结构如下。
yolo detect train \ data=/data/split/dataset.yaml \ model=yolov8s.pt \ epochs=100 \ imgsz=640 \ batch=16 \ lr0=0.01 \ cos_lr=True \ scale=0.3 \ mosaic=0.8 \ project=/output/pole_pin \ name=exp01这个命令的每个参数都有实际意义。data 指向数据集 YAML,里面写明类别名、类别数和 train/val 路径;model 用预训练权重作为初始化;imgsz 是输入分辨率,第一轮先用 640 建立基线;batch 从 16 开始,如果显存报错就降到 8,梯度累积可以弥补 batch 减小带来的抖动;mosaic 是马赛克增强,把四张图拼在一起训练,有助于模型学会在不同背景下识别销钉,但到了训练后期要关掉,否则小目标被拼接边界切割,反而损害精度。
训练启动后要看三样东西:第一是 loss 曲线是否平稳下降,第二是训练集和验证集的 loss gap 是否过大,第三是每个类别的 recall 是否均衡。如果“销钉缺失”这个类的 recall 明显低于其他类,说明正样本太少,先别急着调模型,回到数据层面重采样或加增强更有效。
4. 训练三件套:图像尺寸、损失权重与数据增强怎么调
4.1 图像尺寸:1280 起步,检测精度与显存的平衡
销钉检测的训练第一课就是图像尺寸。很多从公开数据集入门的人习惯用 640 输入做所有检测,这在销钉这类小目标场景下几乎必然漏检。以一张 4K 巡检原图为例,销钉直径在图像里可能只有 15 到 20 像素,下采样到 640 时只剩 2 到 3 像素,等于直接消失了。与之相对,保持 1280 输入时销钉还有 6 到 10 像素,虽然仍然小,但至少进了模型可感知的范围。
用 1280 输入的直接代价是显存翻倍。以 YOLOv8s 为例,batch 16、1280 输入大概需要 16GB 到 20GB 显存,单张 3080 或 4090 能跑,但边缘设备上的推理会吃力。所以我的做法是训练时用 1280,部署时在算力允许的情况下也用 1280,如果边缘设备只能跑 640,那就必须配合切图推理(后面第 6 章专门讲),而不是让模型直接看整张大图。
调整输入尺寸还会影响锚框尺度。YOLOv8 是 anchor-free 的,但特征金字塔的输出层仍对应不同尺度的特征图。输入从 640 变成 1280 后,同一特征图上的每个格子覆盖的原始像素范围变大了,小目标的响应分布会变化。训练时如果发现小目标类别的 precision 高但 recall 低,通常不是模型容量问题,而是输入分辨率不够或者特征金字塔的高分辨率分支没有被充分利用。
4.2 损失与后处理参数:cls 权重、NMS IoU 和置信度阈值的联动
销钉检测的类别不均衡是损失函数调整的直接理由。“正常销钉”样本可能有几万个框,“销钉缺失”可能只有两三百个框。YOLOv8 默认给所有类别相同的损失权重,小样本类别在总损失里占比极低,模型自然倾向于把所有销钉都预测成“正常”。常见的做法是给少数类提高分类损失权重,或者用 focal loss 让模型更关注难分类样本。
yolo detect train \ data=/data/split/dataset.yaml \ model=yolov8s.pt \ epochs=100 \ imgsz=1280 \ batch=8 \ class_weight=1.0 \ cls=0.8 \ box=7.5这里 cls 参数控制的是分类损失在总损失中的占比,box 控制回归损失占比。对于销钉这种定位精度要求高的场景,box 权重要给得比通用检测大一些,否则预测框抖动会直接影响后续故障判断。class_weight 是针对类别重采样的权重,但 ultralytics 的 CLI 里并不直接暴露逐类别权重,通常要写继承类或者用数据重采样实现。更简单的方案是在数据集层面复制少数类样本,让每个 epoch 里模型看到更多“缺失”和“松动”的实例,我实测下来比调损失权重更可控。
后处理参数同样不能照抄默认值。置信度阈值默认 0.25,在销钉场景下往往太激进,会引入大量背景误检。现场部署时我通常把置信度阈值提到 0.35 到 0.45,同时把 NMS 的 IoU 阈值从默认的 0.7 降到 0.5。原因是销钉之间距离很近,同一个销钉可能产生多个重叠框,IoU 阈值太松时 NMS 会把相邻但不同的销钉合并掉,阈值太紧又会保留重复框。一般来说,置信度阈值负责压误检,IoU 阈值负责去重,两个参数要一起调,不能只动一个。
4.3 数据增强与样本均衡:马赛克增强、空图过滤与困难样本
数据增强对销钉检测是把双刃剑。马赛克增强(Mosaic)和混合增强(MixUp)能显著提升模型的泛化能力,但对小目标有致命副作用:图像拼接后,销钉可能被切到拼接边界上,标注框跨图导致训练信号错乱。我的处理方式是训练前 60 个 epoch 开启马赛克,后 40 个 epoch 关闭,让模型在最后阶段回到原图分布上精调。实测这一改动比调任何损失函数都更能挽回小目标召回率。
空图过滤是另一个容易被忽略的参数。巡检图像里有大量不包含任何销钉的背景区域,比如天空、草地、绝缘子串。YOLO 训练默认会保留这些图参与训练,它们对分类损失几乎没有贡献,却会拉长训练时间并让置信度校准发生偏移。我一般通过数据集配置里的 skip_empty 参数过滤掉训练集中不含目标框的图像,验证集则保留空图——因为验证集一旦没有空图,误检率就永远是零,现场的真实表现会被严重高估。
困难样本挖掘在这个阶段也值得做。第一轮模型训练完成后,把验证集里漏检和误检的图像挑出来,按图像算法聚类一下,看看是逆光、远距离还是遮挡。如果集中在某个光照条件下,就去采集端补数据;如果集中在某个塔型上,就检查是不是标注框有问题。补数据优先补现场真实样本,合成数据和图像超分只能作为临时手段,因为巡检图像的背景复杂度远不是合成数据能模拟的。
5. 部署与避坑:从 ONNX 到 TensorRT 的导出流程与四条踩坑记录
5.1 推理管线:模型导出到 TensorRT 与批次推理
训练完成后,模型要落到实际巡检流程里才能产生价值。常见做法是先把 PyTorch 权重导出成 ONNX,再转成 TensorRT 引擎部署到 GPU 服务器或 Jetson 设备上。导出这一步有几个参数必须固定:动态输入、批大小、精度。
yolo export model=/output/pole_pin/exp01/weights/best.pt \ format=onnx \ imgsz=1280 \ dynamic=True \ simplify=True \ opset=17 trtexec \ --onnx=best.onnx \ --saveEngine=best_fp16.engine \ --fp16 \ --minShapes=images:1x3x1280x1280 \ --optShapes=images:4x3x1280x1280 \ --maxShapes=images:8x3x1280x1280导出到 ONNX 时,dynamic=True 保留动态 batch 维,simplify 会去掉一些冗余计算节点,opset 版本建议 17 以上,太低会导致部分算子无法映射到 TensorRT。trtexec 生成引擎时,min/opt/max 三组 shapes 必须按实际推理时的 batch 范围设定,比如服务器端一次要处理 8 张图,maxShapes 就设为 8。FP16 精度对销钉检测的影响很小,框的坐标误差在 1 到 2 个像素以内,对于故障判断完全够用,但速度能提升接近一倍。
推理管线里还有一个批次大小的问题。巡检图像分辨率通常很高,比如 5472×3648,直接整图推理会撑爆显存。常见做法是先做自适应缩放,把长边缩到 1280 或 1600,再送入 TensorRT 引擎。如果还想保留原图细节,就得用切图推理,把原图切成多个 1280×1280 的 patch,推理后再把检测框映射回原图坐标,这一步在下一章细说。
5.2 四条高频踩坑记录:现象、原因与解决
第一条:训练集 mAP 很高,现场测试召回率却大幅下降。原因是训练集和现场图像的分布不同——现场有逆光、有雾、有不同塔型,而训练集中这些场景占比较少。解决思路不是去调模型,而是去采集现场困难样本并做增量训练。我在项目里经历过最夸张的一次,训练集 mAP 到 0.93,新区域现场测试直接掉到 0.6,问题就出在训练集里单一塔型占了 70%。
第二条:导出的 ONNX 转 TensorRT 时报不支持算子。常见原因是 opset 版本太低,或模型里带了自定义算子。解决方式是先升级 opset 到 17 以上重新导出,如果仍报错,就用 TensorRT 的 polygraphy 工具做逐层定位,找到具体是哪一层不支持,再回到模型侧修改。还有一个容易被忽略的点:导出前把模型切到 eval 模式,否则 BatchNorm 层的统计量会是训练状态,ONNX 里的参数就是错的。
第三条:同一个销钉被检测出 3 个重叠框,NMS 之后还是乱。原因是销钉在图像中纹理重复度高,特征图上多个位置同时产生高响应。解决方式是把 NMS 的 IoU 阈值降到 0.5,同时把置信度阈值提到 0.4 以上。如果仍有问题,检查一下是不是训练时 NMS 后处理参数没有固定,导致训练和部署时的后处理行为不一致。
第四条:推理速度达标,但 GPU 利用率只有 20%,大部分时间浪费在图像预处理上。原因是 Python 端的 resize、归一化和 BGR 转 RGB 都在 CPU 上做,成了瓶颈。解决方式是把预处理放到 GPU 上用 CUDA 算子完成,或者用 NVIDIA DALI 做数据加载和增强。这一步做完,推理吞吐通常能提升 2 到 3 倍。
注意:上面这四条踩坑记录来自实际项目的高频问题,前两条影响精度,后两条影响性能。先按顺序排查,不要一上来就换模型。
6. 小目标再进阶:切图推理、多尺度测试与伪标签回灌
前面所有工作都在解决“怎么把模型训好、部署好”,最后这一步是专门针对销钉这种小目标的进阶打法,也是很多团队止步于“demo 能用,现场不好用”的突破口。
切图推理(sliding window inference)是提升小目标召回率最直接的手段。做法是把原图按固定步长切成若干 patch,每个 patch 独立送入模型推理,再把检测结果合并回原图坐标系。常见参数是 patch 尺寸 1280、重叠率 25% 到 50%。重叠的目的是避免销钉刚好被切在 patch 边界上导致检测框被截断。合并时对重叠区域出现的重复检测框做 NMS,阈值同样用 0.5。切图推理的代价是推理次数变多,但效果显著,一个 50 像素的销钉在 1280 patch 里相当于 1280 分辨率下的正常尺度,远比整图缩放到 1280 时清晰。我一般在边缘设备上对关键塔位启用切图,普通塔位用整图推理,兼顾速度与精度。
多尺度测试是另一个低投入高回报的技巧。推理时同时把图像缩放到 960 和 1280 两个尺寸,分别检测后合并结果。大尺寸能抓到小销钉,小尺寸能抓到被放大的模糊目标,两者互补。注意合并时要按图像原始尺寸做坐标映射,并用较低的置信度阈值(比如 0.3)来保留更多候选框,最后统一 NMS。这一招能让召回率提升 2 到 3 个百分点,代价只是推理时间翻倍。
伪标签回灌适合在积累了大量未标注巡检图像时使用。用已有模型对未标注图像做预测,筛选出置信度高于 0.9 且两个相邻 patch 预测一致的检测框,作为伪标签加入训练集,再重训一轮。这里有一个纪律:伪标签只加“正常销钉”和“锈蚀”这类特征明确的类别,缺失和松动必须有真实标注兜底,否则容易把错误固化进模型。我见过团队用伪标签把错误检测当正样本回灌,三轮迭代后模型误检率翻了倍。伪标签是省标注成本的工具,不是替代标注的手段。
这几招做完,销钉检测的精度基本到了工程可用线:正常销钉召回率 95% 以上,缺失和松动召回率 85% 以上,误检率控制在每百张图不超过 3 个。回看整个流程,数据、模型、部署、进阶各占四分之一,没有哪一步可以跳过。我自己踩过最大的坑就是第一次只盯着模型结构调参,忽略了切图推理和样本均衡,结果在一条真线路上被销钉缺失的漏检狠狠上一课。从那以后,每次新项目都先问数据能不能看清,再谈模型应该多复杂。这个顺序希望你也能在项目里用上,希望帮到你。
本文还有配套的精品资源,点击获取