1. 先把问题定义清楚这个项目到底在识别什么1.1 人工登塔看片的老路卡在哪儿了做电网绝缘子缺陷识别的第一课不是搭模型而是搞清楚这件事原来是怎么干的。我接触过的巡检流程大致是这样无人机沿着航线飞一基杆塔拍下上百张照片回来之后由班组里的老师傅一张张翻看到伞裙缺了一块、玻璃件爆了、表面有放电烧蚀痕迹就截图归档、开缺陷单。一个架次动辄三五百张图一个班组一天要看几千张眼睛花、效率低更麻烦的是漏看——绝缘子自爆这种缺陷在整张四千像素的大图上可能只占几十个像素翻快了就过去了。这套流程的瓶颈特别明显一是人力线性增长杆塔数量涨了看片的人也得涨二是判断标准依赖个人经验同一张图两个人可能给出不同结论三是缺陷从发生到被发现的时间窗口太长等季度巡检发现时可能已经发展成掉串隐患。深度学习在这个场景里真正有价值的地方不是炫技而是把从海量图里挑出可疑的那几十张这件事变成秒级的事人只需要复核少量候选工作量和漏检率都能压下来。所以项目一开始我就把定位说死了这是一套辅助筛查系统不是全自动判决系统。它负责从几千张图里把疑似的挑出来并排好优先级最终定性还是人说了算。这个边界定清楚了后面很多技术选型的纠结就迎刃而解——比如要不要为了 0.5 个点的 mAP 上一个超大模型答案是不值得因为召回率和推理速度才是这个场景的硬约束。1.2 检测、分类、分割别一上来就选最贵的新手最常见的误区是什么任务都往分割上靠。我见过不少方案一上来就要做像素级分割理由是缺陷边界要精确。听起来合理实际落地时几乎都会被标注成本和推理开销拖死。我一般按缺陷类型分层处理不同任务用不同形式任务形式适合的缺陷标注成本推理开销落地建议图像分类整串绝缘子是否存在污秽、放电痕迹低一张图一个标签最低适合做粗筛先过滤 80% 正常图水平框检测自爆、破损、异物悬挂、销钉缺失中画框即可中主力方案覆盖绝大多数缺陷旋转框检测倾斜航拍下的绝缘子串定位中偏高需要画带角度框中偏高绝缘子串本身定位用它更准实例分割裂纹走向、闪络面积统计高逐像素勾勒高只在少数需要量化的场景上我的实际选择是分类粗筛 检测精定位的两级结构。第一级用轻量分类网络把明显正常的图扔掉第二级用检测器在剩下的图上找缺陷。这个结构的收益很直接原本一千张图全要跑检测器现在可能只有两百张进第二级整体吞吐直接提升三到四倍而正常图本来就不需要精细定位。代价是分类器本身要足够保守宁可多放一些正常图进第二级也不能把带缺陷的图误判成正常扔掉——这是一个单向的错误代价问题阈值必须往召回那边偏。1.3 把缺陷清单翻译成算法能听懂的语言拿到业务方给的缺陷描述第一件事是翻译。业务上说绝缘子破损算法上要拆成玻璃件的自爆伞盘缺失一整块、瓷件的伞裙崩边边缘小缺口、串体整体倾斜可能是掉串前兆。这三类在图像上的表现完全不同标注规则、样本量、识别难度也完全不同混成一个破损类别是自找麻烦。同样异物这个词也很笼统。鸟巢、塑料薄膜、风筝线、气球残片在图上形态差异巨大颜色、尺寸、纹理都不同硬塞进一个类别模型会学出一个什么都有可能的模糊边界置信度分布会非常难看。我一般是先拆细样本量实在撑不起来的再合并并且合并时优先合并视觉特征相近的比如把几种挂线状异物归为一类而不是把鸟巢和塑料布放一起。这一步做完你会得到一张带优先级的缺陷清单哪些必须查出来自爆、掉串哪些可以容忍漏检轻微污秽哪些先不做需要专用传感器才能判定的内部缺陷。这份清单直接决定了后面损失函数的权重设计和评估指标的计算方式是整条链路上最不能偷懒的一步。2. 数据这一关直接决定项目天花板2.1 三条采集渠道各有各的坑数据来源我试过三条路实际项目里基本都是混着用。自有巡检图是主力。它的优点是视角、设备、光照和上线时完全一致分布贴得最紧缺点是缺陷样本天然稀疏可能几万张里只有几百个真缺陷而且这些图是给人工看片用的构图未必适合算法。我踩过的一个坑是早期直接用原图缩放训练结果模型学到的是绝缘子在画面中央这种伪相关因为巡检航线是居中对准杆塔的模型很快就学会偷懒不看细节。公开数据集能快速把流程跑通适合做骨架验证和预训练但和实际场景的差异往往大到不能用。我在验证阶段用公开数据训出来的模型换到自有数据上 mAP 直接腰斩原因就是背景、拍摄高度、器件型号全变了。仿真合成是我认为被低估的一条路。用三维重建的杆塔模型在虚拟场景里换光照、换天气、换拍摄角度渲染专门生成缺陷样本。它解决的是长尾问题不是替代真实数据。合成数据的价值在于把缺样本的类别补到能训起来但绝对不能只用合成数据训练否则模型会学到渲染器的伪影——我曾经遇到过模型对某个特定阴影极度敏感查了半天发现是渲染引擎的出图特征。注意任何合成数据进入训练集之前必须做一次真伪可分性检验——用一个小分类器试着区分真实图和合成图如果轻松分开说明渲染特征太明显这批数据要么加更强的域随机化要么降低采样权重。2.2 标注规范不写死后期返工能返到你怀疑人生标注这件事最贵的不是人力是返工。我现在的做法是先写一页纸的标注细则逐条给正例和反例截图标完一百张先做一致性验收通过率不到 90% 就停下来改细则绝不批量铺开。细则里必须写死的东西包括框的边界怎么贴贴外轮廓还是贴可见区域被遮挡一半怎么处理、最小可标尺寸我一般定在原图短边千分之四低于这个像素数不标因为模型也学不会、相邻同类目标粘连时是分开标还是合并标、以及最容易扯皮的疑似怎么处理。疑似缺陷我单独设一个suspicious类不混进正类训练时权重降一半这样既保留了信息又不会污染主类别的分布。另一个容易被忽略的点是标注一致率的量化。我的做法是从标注集里随机抽 200 张让两个人独立标算 IoU 大于 0.5 的匹配率和类别一致率。这两个数如果低于 0.85说明细则本身有歧义不是标注员的问题。很多人一上来就怀疑模型不行其实模型只是忠实反映了标注里的噪声——你给它一堆自相矛盾的标签它学出来的决策边界必然是糊的。2.3 长尾分布怎么补别只盯着过采样绝缘子缺陷数据的长尾程度用数字说比较直观我手上的一批数据里正常串体样本六万多自爆样本不到四百比例接近 150:1。这种分布下你直接训练模型会倾向于把所有东西都判成正常因为这样 loss 最小。我试过的组合拳按性价比排序复制粘贴增强把已标注的缺陷区域抠出来贴到正常图的合理位置上绝缘子串区域配合色彩、亮度、缩放扰动。这个手段对小目标缺陷特别有效我靠它把自爆样本从四百扩到了四千多召回涨了将近二十个点。关键细节是粘贴位置要限制在串体掩膜内否则模型会学到缺陷出现在奇怪位置的错误先验。损失函数重加权分类分支用 focal loss 压低易分样本的贡献回归分支对稀有类别的样本加权。权重别拍脑袋设我一般按类别频次的平方根倒数来定再手动微调。切片采样把大图切小之后含缺陷的切片在采样时重复更多次。这个办法的副作用是训练集分布变了验证集和测试集一定要用原始未采样分布否则指标虚高。难例挖掘每轮训练后把假阴性样本捞出来人工确认后加进训练集。这条最慢但最有效我的经验是三轮迭代能解决大部分模型死活学不会的个案。3. 模型选型为什么最后落在单阶段检测器上3.1 两阶段和单阶段的取舍本质是速度换精度的账两阶段检测器在精度上确实有优势尤其是小目标和密集目标。但这个项目里我没有选它理由很实在部署端是边缘盒子算力有限巡检队希望的是落地就能出结果而不是传回机房慢慢算。我做过对比测试同一批数据、同样的输入分辨率两阶段方案的 mAP50 比单阶段高大概 1.5 到 2 个点但单帧推理时间差了三倍以上。放到实际业务里这三个点换不来任何决策变化——因为最终还有人工复核这一层模型多找一个少找一个复核员的工作量差别不大但推理慢三倍意味着一个架次的图要跑几个小时这个体验是灾难性的。所以我的结论是在保证召回的前提下速度优先。单阶段检测器的精度损失可以用输入分辨率、数据增强和切片推理补回来但速度损失补不回来。3.2 输入分辨率这笔账得算清楚再定输入分辨率是这个项目里最需要精打细算的参数。原图一般是 4000×3000 这个量级直接缩到 640 输入绝缘子自爆的缺口在特征图上可能只剩两三个像素等于直接抹掉了。我算过一笔账一架次典型拍摄高度下绝缘子串在图中长度约 1200 像素自爆缺口宽度约 60 像素。如果输入缩到 640缩放系数是 0.16缺口只剩不到 10 像素勉强能学但极不稳定输入 1280 时缺口有 19 像素召回明显稳。但 1280 输入的计算量是 640 的四倍边缘盒子扛不住。最终的解法是切片推理训练和推理都按 1280 的窗口在原图上滑动切块相邻块重叠 20%保证跨界的缺陷至少完整出现在一个块里。重叠率的算法很简单要求重叠长度大于目标最大尺寸即可——我按最大目标 300 像素算1280 窗口下重叠 256 像素20%已经够用再高只是浪费算力。这条路的代价是后处理复杂了同一个缺陷可能被两个块同时检出需要做跨块的框合并。我用的是按类别分组做 NMS重叠阈值 0.5配合位置还原把块内坐标加上块的偏移量映射回原图坐标。这块逻辑写起来不难但坐标换算一定要反复验我第一版就是因为没处理图像 padding 导致的偏移结果所有框整体偏了一百多像素排查了大半天。3.3 水平框还是旋转框看你的绝缘子串歪不歪航拍视角下绝缘子串在画面里经常是斜的长宽比能到 4:1 甚至 6:1。用水平框标它框里会塞进大量背景模型学到的特征里掺了一半是干扰。如果只做缺陷检测、不做串体定位水平框其实够用——因为缺陷本身自爆缺口、破损形状不规则水平框的包围误差不影响判断。但如果你的流程里需要先定位整串绝缘子、再在串内找缺陷那旋转框值得上它能把背景压到最小串体的定位精度提升很明显。我的折中方案是串体定位用旋转框缺陷检测用水平框。两套头分开训共享骨干网络。这样既拿到了串体定位的精度又不用为小缺陷承担旋转框的标注和回归复杂度。训练时注意两套任务的损失权重要平衡我的经验是串体定位损失权重给 1.0缺陷检测给 2.0因为后者是主任务。3.4 骨干网络选型的三个硬指标选骨干网络我一般看三个指标不看论文里的 FLOPs 排名一是小目标特征保真度具体看网络最深层的下采样倍数。总下采样 32 倍的网络1280 输入下特征图只有 40×40小缺陷根本留不下我倾向于用下采样 16 倍的输出层做检测头必要时再加一个下采样 8 倍的高分辨率分支代价是显存涨得厉害。二是边缘设备的实测帧率不是 GPU 上的理论值。同一个网络在桌面卡和边缘盒子上差距能有五倍必须以目标硬件实测为准。三是量化的友好度有没有大量对量化敏感的结构比如某些归一化层、特殊的激活函数。我吃过亏选了个结构花哨的骨干INT8 量化之后精度掉了六个点只能退回 FP16速度优势全没了。4. 训练实操从配置到收敛的完整记录4.1 环境与目录组织环境这块我不迷信最新版本能用稳定版就用稳定版因为训练脚本跑通之后没人愿意再动它。我用的组合是 Python 3.10 PyTorch 2.x CUDA 11.8显卡单卡 24G 显存1280 输入、批大小 8 能跑满。目录结构我固定成这套多人协作时能少吵很多架insulator_defect/ ├── data/ │ ├── images/{train,val,test} │ ├── labels/{train,val,test} │ └── dataset.yaml ├── configs/ │ ├── train_1280.yaml │ └── infer_slice.yaml ├── weights/ │ └── pretrain_backbone.pth ├── scripts/ │ ├── train.py │ ├── slice_infer.py │ └── eval.py └── runs/dataset.yaml里最容易被写错的是类别顺序。类别 id 和名字必须和标注文件里的 id 严格对齐我见过一次因为顺序错位模型把自爆学成了异物训了两天才发现白烧了几十块电费。path: ./data train: images/train val: images/val test: images/test nc: 6 names: 0: insulator_string 1: self_explosion 2: broken_shed 3: flashover_trace 4: foreign_object 5: missing_pin4.2 训练启动与关键超参的设定逻辑启动命令本身很简单难的是超参怎么定。我一般分两段训先冻结骨干、只训检测头学习率给 1e-3跑 20 轮让头部快速收敛再解冻全部、学习率降到 1e-4配合余弦退火跑 150 轮。这个两段式的好处是避免一开始就用大学习率把预训练权重冲坏。python scripts/train.py \ --cfg configs/train_1280.yaml \ --weights weights/pretrain_backbone.pth \ --img-size 1280 \ --batch 8 \ --epochs 170 \ --lr0 0.001 \ --lrf 0.01 \ --freeze 10 \ --mosaic 0.8 \ --mixup 0.1几个参数值得单独说mosaic 增强给 0.8不给 1.0。四图拼接对小目标友好但拼得太狠会让缺陷出现在不符合物理规律的位置比如绝缘子悬在半空模型会学到错误的空间上下文。我留 20% 的样本用原图训当作纠偏。mixup 只给 0.1。这个增强对分类任务友好对检测任务其实有副作用因为两张图叠加后框的归属会变模糊。给小值主要是为了平滑决策边界不能给大。学习率不设 warmup 太长。我一般 warmup 3 轮就够设长了在冻结阶段会浪费大量轮次。训练日志我主要盯三个数分类损失、回归损失、以及验证集的召回率。分类损失和回归损失如果长期不同步一个降一个不降基本可以判定是标注质量或者类别不平衡的问题而不是模型结构问题。召回率我按类别分开看因为平均值会掩盖稀有类的糟糕表现——总召回 0.9 但自爆类召回只有 0.5这模型是没法上线的。4.3 评估指标怎么看才不骗自己评估这件事我最反对的是只看 mAP50 一个数。这个指标在类别不平衡的数据上特别容易好看因为优势类别会把平均值拉上去。我实际用来做决策的指标是这几个指标关注点我的参考阈值说明各类别召回率漏检关键类 ≥ 0.92自爆、掉串类必须达标各类别精确率误报≥ 0.75太低会导致复核工作量爆炸mAP50-95定位质量≥ 0.45看趋势不看绝对值每张图平均误报数可用性≤ 0.3直接影响复核体验单张全图推理耗时效率≤ 1.5 秒含切片和后处理每张图平均误报数这个指标很多方案里不写但它才是决定系统能不能被一线接受的关键。你想想如果一个架次五百张图每张蹦出三个误报复核员要处理一千五百个框他第二天就会把这系统关掉。宁可少报不可乱报这个原则贯穿了我的阈值选取。阈值怎么定我的做法是在验证集上画 PR 曲线找到满足关键类召回 ≥ 0.92的最低置信度点然后把这个阈值再往下压 0.05 作为上线值留一点余量应对真实场景的分布漂移。这不是最优的 F1 点但在业务上是合理的——漏检的代价远大于多报几个让人看一眼。5. 部署落地把模型塞进边缘设备5.1 导出链路和踩过的坑训练完的模型不能直接扔到边缘设备上跑中间要走一遍导出。我的链路是 PyTorch → ONNX → TensorRT每一步都有坑。导出 ONNX 时最常见的问题是动态维度。切片推理的输入尺寸是固定的1280×1280所以我直接固定 shape 导出避免动态轴带来的优化损失。另外一个坑是后处理里的 NMS如果写进模型图里某些算子在不同版本的推理引擎上行为不一致我现在的做法是把 NMS 挪到 CPU 侧用自己写的代码做模型只输出原始预测这样跨平台一致性最好。import torch model.eval() dummy torch.randn(1, 3, 1280, 1280) torch.onnx.export( model, dummy, insulator_1280.onnx, input_names[images], output_names[preds], opset_version12, do_constant_foldingTrue, )TensorRT 转换时工作空间给足FP16 是默认选项。我在边缘盒子上实测FP16 相比 FP32 大概快 2.2 倍精度损失在 0.3 个点以内完全可以接受。INT8 更快但要准备校准集而且小目标检测对量化误差特别敏感我试过一次自爆类召回掉了四个点最后放弃了。5.2 切片推理的工程实现细节切片推理的代码不长但每一行都有讲究。核心逻辑是按窗口滑动切图每块单独推理把结果映射回原图坐标最后跨块合并。def slice_infer(img, model, win1280, stride1024, conf0.25): H, W img.shape[:2] all_boxes [] for y in range(0, max(H - win, 0) 1, stride): for x in range(0, max(W - win, 0) 1, stride): patch img[y:ywin, x:xwin] ph, pw patch.shape[:2] if ph win or pw win: patch cv2.copyMakeBorder( patch, 0, win-ph, 0, win-pw, cv2.BORDER_CONSTANT, value(114,114,114)) preds model(patch) for b in preds: if b.score conf: continue all_boxes.append([ b.x1 x, b.y1 y, b.x2 x, b.y2 y, b.score, b.cls ]) return nms_per_class(all_boxes, iou_thr0.5)两个细节必须注意。一是边缘补边图的最右侧和最下侧往往不足一个窗口直接切会导致尺寸不对。补边用的填充值要和训练时的 letterbox 填充值一致我用 114否则边缘区域的特征分布会突变容易产生虚假检测。二是坐标裁剪映射回原图后要把超出图像边界的框裁掉我第一版忘了这步结果出现了大量负坐标的框下游系统直接报错。滑动步长我设成 1024也就是 1280 窗口的 80%重叠 256 像素。这个值是按最大目标尺寸倒推的目标最大长度约 240 像素只要重叠大于它任何目标至少完整落在一个窗口里。5.3 吞吐量估算别等上线才发现跑不完部署前一定要做一次端到端的吞吐估算我见过太多项目在这个环节翻车。假设一张原图 4000×30001280 窗口、步长 1024横向需要ceil((4000-1280)/1024)1 4个窗口纵向ceil((3000-1280)/1024)1 3个窗口总共 12 个切片。单切片在边缘设备上推理耗时 60 毫秒12 个就是 720 毫秒加上前后处理大概 900 毫秒一张图。一个架次 500 张串行处理约 7.5 分钟。这个数字是可以接受的因为巡检回来本来就要等七分钟出结果比人工看两小时强太多。但如果你的设备单切片要 200 毫秒那 500 张就是 20 分钟体验就会开始变差。这时候的优化方向不是换模型而是先用分类器粗筛把明显正常的图直接跳过通常能砍掉 70% 的图整体时间降到 6 分钟左右。提示估算的时候一定要留 30% 的余量因为真实场景的图片尺寸不统一、磁盘 IO 和内存拷贝都会拖慢整体速度纸面算出来的数字在实际部署时普遍要打个七折。6. 上线之后的坑问题排查实录6.1 漏检和误检要分开归因模型上线后出的问题八成可以归到两类漏检和误检。这两类的排查路径完全不同混在一起查会浪费大量时间。漏检我先看三件事一是看这个缺陷在原图里到底有多大如果小于训练时设定的最小尺寸那不是模型的问题是任务定义的问题二是看这个样本的形态是否在训练集里出现过比如侧视角的自爆如果训练集全是正视角模型不认识很正常三是看置信度分布如果大量真缺陷的置信度卡在 0.1 到 0.2 之间说明模型看到了一点但不确定这通常是类别不平衡或者标注噪声导致的不是能力问题。误检的排查更依赖可视化。我一般会把误检框抠出来做成一张拼图一眼就能看出规律。最常见的三种一是把正常的伞裙边缘阴影当成裂纹这类在强光斜射时特别多二是把杆塔上的螺栓、金具当成绝缘子缺陷本质是上下文特征不够三是把远处的植被纹理当成异物这是切片边缘的填充区域带来的伪影。针对第一种我加了一组针对性的负样本——专门挑强光斜射的正常图标成背景然后重新训。这个手段效果非常直接一轮就能把这类误检压下去大半。针对第三种把填充值改成镜像填充伪影基本消失。6.2 常见问题速查表这张表是我踩坑攒下来的基本覆盖了这个项目百分之八十的线上问题现象可能原因排查手段处理办法某类缺陷召回突然下降新批次图片拍摄条件变化按拍摄日期分组统计召回补充新条件样本做域适配训练置信度整体偏低阈值与训练分布不匹配画分数直方图重新标定阈值不要硬压同一位缺陷重复报多次切片重叠导致重复检出检查跨块 NMS提高合并 IoU 阈值框位置整体偏移坐标还原时漏加偏移或 padding抽图叠加可视化修正映射逻辑加单元测试夜间/逆光图大量误报训练集缺少该类光照按光照条件分桶评估补数据或直接跳过不判小目标全部漏检输入分辨率不足统计漏检目标像素尺寸提高输入分辨率或加高分辨率分支模型在验证集好、线上差数据泄漏或分布不一致检查验证集是否参与过增强调参重建独立测试集量化后精度骤降校准集分布不匹配对比 FP16 与 INT8 输出换校准集或退回 FP166.3 告警分级和人工复核闭环才是系统的终点模型输出一堆框直接丢给业务系统是没有意义的。我做的分级是这样置信度高于 0.7 的直接生成待复核工单标记为高优先级置信度在 0.35 到 0.7 之间的进低优先级队列复核员有空再看低于 0.35 的直接丢弃不展示。这个分级让复核员的工作量从每张图都要看变成每天看几十个高优工单接受度完全不同。闭环的另一半是误报回流。复核员标记为误报的图我要求必须回传并且记录误报类型。这些样本每周汇总一次人工确认后加进训练集做一轮增量训练。这个机制建立起来之后模型的误报率在三个月里从每图 0.8 个降到了 0.2 个效果比任何一次架构调整都明显。这套闭环里最容易断的一环是人懒得标。我的解决办法是把回传做成复核流程的必填项不填就不能关闭工单同时每个月给复核员看一次因为你标的这些样本模型进步了多少的对比图。人看到自己的标注真的有用配合度会高很多这比发文件强调重要性管用。7. 最后分享几条实打实的心得做这类项目我最深的体会是算法只占三成数据工程和流程设计占七成。我见过太多团队把精力全砸在调模型上换了五六个骨干网络mAP 涨了一个点结果上线之后因为误报太多被一线弃用。反过来数据清洗干净、标注规范统一、阈值定得合理用一个结构很普通的检测器也能跑出让人满意的效果。第二条心得是别追求一次性做全。缺陷类型有十几种一上来全做每类样本都不够模型哪类都学不好。我的做法是先做两三类高频、高危害的把整条链路跑通、跑稳让业务方看到价值然后再一类一类往上加。每加一类只针对这一类补数据、调阈值、做验证风险可控进度也好汇报。第三条是测试集一定要独立而且要按场景分桶。我现在的测试集按拍摄高度、光照条件、器件型号分了几个桶每次评估都出分桶报告。这样模型在哪个场景弱一眼就能看出来不用等线上出问题才知道。这个习惯帮我省了无数次返工。如果这个项目还要往下走我下一步想试的是把检测和缺陷程度分级结合起来——不只是判断有没有缺陷还要判断缺陷发展到什么程度比如自爆的伞盘缺了几片。这就从检测问题变成了细粒度分类问题标注要求更高但对运维决策的价值也更大。这条路不好走但值得试。