资讯详情

资讯详情

TTPLA数据集实战:输电通道检测+分割的COCO标注处理与训练避坑指南

简介输电杆塔与输电线路图像检测与分割数据集面向电力巡检、计算机视觉与深度学习研究者覆盖真实输电场景中杆塔、线路、绝缘子等关键设施的识别与分割任务。压缩包共1244个文件包含1242张JPG图像和2个JSON标注文件整体约275.8MB采用COCO格式并划分了训练集、验证集与测试集方便直接开展模型训练、调参与评估。目前已有531人学习下载图像来自多种环境和天气光照条件可用于输电线路日常巡检、部件状态监测、故障预警等场景。借助该数据集可同时训练目标检测与语义分割模型检测任务可获取杆塔、线路等目标的边界框定位分割任务可输出绝缘子等部件的像素级掩膜实现对电力设施的精细识别。这些标注信息为搭建高效、可靠的智能巡检算法提供了标准化数据基础尤其适合需要工程落地和学术研究的读者。1. TTPLA既有检测框又有分割掩膜的输电通道数据集值得先跑通再谈创新输电杆塔和输电线路TTPLA图像检测分割数据集是我在电力巡检视觉方案里用得比较顺手的一套数据基础。它同时带目标检测的边界框标注和语义分割/实例分割的多边形标注并且整理成了coco标注格式意味着检测模型和分割模型可以直接吃同一份数据不用维护两套标注。对做无人机巡检、通道隐患识别、或者在做输电线路三维重建前的二维感知的人来说这是一个能省掉大量标注成本的起点。这篇文章不是数据集说明书而是按我实际处理这类数据的顺序来写解压后先看什么、标注字段怎么读、训练前要做什么校验和转换、训练参数怎么设以及最容易翻车的几个细节。新手照着做能跑通检测分割的最小闭环熟手可以只看第五章的避坑和第六章的迭代思路。先说明一点我对这个数据集的判断基于通行的coco组织方式和输电场景图像特点具体到你手上的压缩包解压后以实际文件结构为准。2. 数据集结构与coco标注格式解压后先做三件事拿到这个压缩包我一般不会急着写训练脚本。先做三件事看目录结构、确认标注文件里的字段是否完整、用一个统计脚本把标注的底细摸清楚。这三步做好了后面训练阶段能少踩一半的坑。2.1 目录与文件构成一份能直接喂给训练框架的coco标注常见做法是压缩包解压后出现一个根目录里面分成 images 和 annotations 两个子目录。images 下放着原始巡检图像可能是无人机可见光拍摄的杆塔和线路照片分辨率通常比较高annotations 下则是一个或几个 JSON 文件coco标注格式的核心就是这些 JSON。coco格式里最关键的三个顶层字段是 images、annotations 和 categories。images 数组里每个元素记录一张图的 file_name、height、width 和 idannotations 数组里每个元素是一条标注记录通过 image_id 关联到具体图像检测用的是 bbox 字段分割用的是 segmentation 字段categories 则定义类别 id 到类别名的映射。这个数据集的类别按标题看至少包含输电杆塔和输电线路两类实际训练时以 JSON 里的 categories 内容为准。拿到文件后第一步是用命令行确认 JSON 能正常解析别让训练跑到一半才发现标注文件是坏的。python -c import json; djson.load(open(annotations/instances_train.json)); print(len(d[images]), len(d[annotations]), d[categories])这段命令的作用是快速加载标注文件并输出图像数量、标注数量、类别列表。正常情况下三行数据都能打印出来如果某个值明显离谱比如图像数量为 0 或者 categories 为空说明文件结构不对或者解压不完整。我习惯在训练前先跑这一步因为在电力巡检项目里我遇到过好几次标注 JSON 被别人二次编辑后少了逗号解析报错直到深夜调训练才发现。2.2 检测与分割共用一套cocobbox和segmentation字段怎么协同coco标注格式对于检测和分割来说区别主要体现在 annotation 对象内部的字段上。检测任务只需要 bbox 字段格式是 [x, y, width, height]其中 (x, y) 是边界框左上角坐标width 和 height 是框的宽高分割任务则需要 segmentation 字段在该数据集中通常是多边形格式即一个二维数组每个内层数组是一组坐标点 [x1, y1, x2, y2, ...]。一条完整的标注记录通常会同时携带 bbox、segmentation、area、iscrowd 这些字段。area 是多边形围成的面积用于计算分割指标的权重iscrowd 表示该目标是否是一群密集对象为 1 时一般训练时会被跳过。这个数据集里存在一种情况值得注意某些标注对象是多边形和边界框共存的但两者并非总是严格对应因为多边形是人工勾的而边界框可能是从多边形取外接矩形生成的细微偏差属于正常现象。我处理这类数据时的做法是不去强行修正 bbox 和 segmentation 的微小不一致而是让检测和分割各自用自己的标注做训练和评估。强行统一反而会把原本标注准确的 segmentation 坐标改歪。如果发现某个对象的 bbox 明显没有包住多边形那才需要标记出来并做人工修正。2.3 用一个统计脚本摸清标注底细解压后先写个一次性脚本统计标注质量这能帮你判断这个数据集需不需要清洗。我一般会统计每张图的标注数量、标注框的宽高分布、segmentation 多边形的点数量以及是否存在空标注图像。import json import numpy as np with open(annotations/instances_train.json) as f: data json.load(f) img_id_to_info {img[id]: img for img in data[images]} anno_per_img {} bbox_sizes [] poly_points [] empty_images [] for ann in data[annotations]: img_id ann[image_id] anno_per_img[img_id] anno_per_img.get(img_id, 0) 1 x, y, w, h ann[bbox] bbox_sizes.append((w, h)) seg ann[segmentation] if seg: poly_points.append(len(seg[0]) // 2) for img_id, img in img_id_to_info.items(): if anno_per_img.get(img_id, 0) 0: empty_images.append(img[file_name]) bbox_sizes np.array(bbox_sizes) print(图像总数:, len(img_id_to_info)) print(标注总数:, len(data[annotations])) print(bbox宽度中位数:, np.median(bbox_sizes[:, 0])) print(bbox高度中位数:, np.median(bbox_sizes[:, 1])) print(多边形点数中位数:, np.median(poly_points) if poly_points else 0) print(空标注图像数:, len(empty_images))这段脚本统计了几个关键指标。bbox 宽高分布能直接反映目标尺度如果中位宽高都只有几十像素说明图像里目标整体偏小训练时要考虑高分辨率输入。多边形点数中位数如果非常高比如几百甚至上千说明标注勾得很细致转换 mask 时耗时更长。空标注图像如果数量可观要么是背景图要么是漏标需要决定是保留用于负样本还是筛掉。参数上需要注意脚本里 seg[0] 取了第一个多边形如果一个对象被拆成多个多边形其他部分没统计进来。对绝大多数巡检图像这个简化够用但要意识到它低估了点总数。统计完这些数值你对这个数据的脾气基本就有数了。3. 把coco标注变成可训练样本从校验到mask生成coco标注格式虽然在检测和分割框架里都能直接读但真正拿来训练前还有两步必须做一是图像和标注对账二是生成语义分割需要的 mask 图。很多框架内部做了这些事但你要知道它做了什么、在什么情况下会静默失败。3.1 图像-标注对账缺图、坏图、重复标注一次查清图像和标注对账看起来简单实际踩坑最多。常见情况是压缩包经过多次拷贝后images 目录里少了部分文件但标注 JSON 仍然引用它们另一种情况是图像改名后没有同步改 JSON 里的 file_name导致训练时报错找不着图。我一般会写个遍历脚本做一次硬核对。import json import os from PIL import Image with open(annotations/instances_train.json) as f: data json.load(f) img_dir images/train missing [] corrupted [] for img_info in data[images]: path os.path.join(img_dir, img_info[file_name]) if not os.path.exists(path): missing.append(img_info[file_name]) continue try: with Image.open(path) as im: im.load() except Exception: corrupted.append(img_info[file_name]) print(缺失图像:, len(missing)) print(损坏图像:, len(corrupted))这段脚本做了两件事文件存在性检查和图像可解码性检查。Image.load() 会真实读取像素数据能抓出那些扩展名是 jpg 但内容已经损坏的文件。在电力巡检数据里有一种常见情况是图像从相机导出时中断文件大小非零但无法解码训练框架在读图时表现为随机报错很难定位。对账脚本输出缺失和损坏列表后我的处理方式是直接生成过滤后的新标注 JSON而不是去补图。因为这个数据集的标注本身是完整的删除坏样本比补样本成本低得多。过滤时注意同步更新 images 数组和 annotations 数组中关联到这些图的标注否则会出现新的悬空引用。3.2 语义分割必须的mask从polygon到PNG的转换实现语义分割训练需要的是像素级标签图每张图对应一张单通道 PNG像素值等于类别 id。coco 里的 segmentation 是坐标点列需要先把多边形填充成 mask。这里有个关键选择用 pycocotools 还是直接用 OpenCV 填充。pycocotools 的 mask 转换对规则多边形没问题但在这个数据集上我遇到过坐标点边界轻微超出图像宽高的情况pycocotools 会直接报错而 OpenCV 的 fillPoly 会自动裁剪边界更抗造。import json import numpy as np import cv2 import os with open(annotations/instances_train.json) as f: data json.load(f) cat_ids {cat[id] for cat in data[categories]} out_dir masks/train os.makedirs(out_dir, exist_okTrue) anns_by_img {} for ann in data[annotations]: anns_by_img.setdefault(ann[image_id], []).append(ann) for img_info in data[images]: img_id img_info[id] h, w img_info[height], img_info[width] mask np.zeros((h, w), dtypenp.uint8) for ann in anns_by_img.get(img_id, []): cat_id ann[category_id] for poly in ann[segmentation]: pts np.array(poly, dtypenp.int32).reshape(-1, 2) cv2.fillPoly(mask, [pts], cat_id) cv2.imwrite(os.path.join(out_dir, img_info[file_name].replace(.jpg, .png)), mask)这段代码的核心逻辑是逐图建立空白 mask然后把该图的所有多边形标注用 fillPoly 填充成对应的类别 id。参数上需要注意三点dtype 必须是无符号整型类别 id 如果超过 255 需要改用 uint16但该数据集类别数量少uint8 够用reshape(-1, 2) 要求 polygon 坐标是合法的偶数长度数组如果某个 polygon 只有一个坐标点这里会直接报错需要在前面加上长度校验输出文件名我保留了和原图相同的命名前缀只是扩展名换了这样后续数据集类可以通过字符串替换找到对应 mask。这里有个容易翻车的细节同一张图内如果有两个同类目标按上述代码 fillPoly 会直接覆盖像素值不变看起来没问题。但如果两个目标类别不同且多边形有重叠后画的会覆盖先画的最终 mask 只保留后者的类别。输电线场景中杆塔和线路天然存在遮挡关系我的建议是标注越靠后的对象层级越高填充顺序按 annotation 在 JSON 中的顺序即可不用刻意排序因为这类数据集的多边形重叠区域通常很小。3.3 检测与分割的加载配置给训练框架的data path与transform生成 mask 之后检测和分割的数据加载可以分为两条路线检测模型继续吃 coco JSON分割模型吃图像加 mask。我在实际项目中常用现成框架但配置加载器时有个共通点检测和分割对数据增强的要求不完全一样。检测任务中随机翻转和尺度抖动是标配因为 bbox 会随图像一起变换coco 格式的加载器会自动处理分割任务中翻转同样安全但像随机裁剪就需要格外小心裁剪区域必须保证 mask 和图像对齐否则切出来的训练样本会出现标签错位。# 检测分支加载配置伪配置适配常见检测框架 dataset_det dict( typeCocoDataset, data_rootdata/ttp/, ann_fileannotations/instances_train.json, img_prefiximages/train, pipeline[ dict(typeLoadImageFromFile), dict(typeLoadAnnotations, with_bboxTrue), dict(typeResize, scale(1333, 800), keep_ratioTrue), dict(typeRandomFlip, prob0.5), dict(typePackDetInputs) ] ) # 分割分支加载配置 dataset_seg dict( typeCustomSegDataset, img_dirimages/train, mask_dirmasks/train, pipeline[ dict(typeLoadImageFromFile), dict(typeLoadMask), dict(typeResize, scale(512, 512)), dict(typeRandomFlip, prob0.5), dict(typePackSegInputs) ] )配置里值得关注的是 Resize 的不同策略。检测分支我保持 1333x800 的长边缩放因为检测框对分辨率相对钝感但电力杆塔这类大目标和小线路这类小目标尺度差异大输入太小会导致小目标直接丢失分割分支用 512x512 的方形输入这是为了控制显存因为分割的 loss 是逐像素计算的分辨率翻倍显存占用接近四倍。实际训练时这两个配置可以独立调不必强求一致。还需要注意 transform 之间的顺序。RandomFlip 必须放在 Resize 之后因为翻转操作依赖图像和标注的坐标系统一致先 resize 后 flip 不会产生坐标扭曲。这个顺序在框架里通常是固定的但如果你自己搭 pipeline很容易写成先翻转后缩放导致 bbox 坐标和 mask 位置错位。这类问题训练时不报错只在验证时 mAP 偏低定位起来非常耗时间。4. 检测分割联合训练参数怎么设才不翻车数据集准备妥当后进入训练阶段。同一份 TTPLA 标注同时输出检测和分割结果有两种做法一种是把检测和分割分别训练两个模型部署时串行推理另一种是训练一个带检测头和分割头的模型。从工程效率看我更推荐后者但需要处理好损失权重和类别不平衡问题。4.1 最小训练管线检测头与分割头共享backbone把检测和分割放在同一个模型里常见做法是基于一个分割模型加检测分支或者基于检测模型加 mask 分支。backbone 共享意味着图像只需要过一遍特征提取网络检测头在特征图上预测 bbox分割头预测像素类别推理时多任务的额外开销主要来自分割头的上采样。# 多任务模型配置骨架伪配置 model dict( typeMultiTaskModel, backbonedict(typeResNet, depth50, out_indices(1, 2, 3)), neckdict(typeFPN, in_channels[256, 512, 1024], out_channels256), det_headdict(typeRetinaHead, num_classes2), seg_headdict(typeFPNHead, num_classes2, loss_decodedict(typeCrossEntropyLoss, use_maskTrue)), train_cfgdict(), test_cfgdict() )这里检测头的 num_classes 和分割头的 num_classes 都设成 2对应杆塔和线路两类。如果你的标注里还有背景类需要单独处理检测头一般用 num_classes 加 1 的方式表示背景而分割头直接用 0 表示背景这两个类别的计数规则容易混淆是配置里最常见的错误点。实际训练时损失是检测损失加分割损失。我一般把分割损失权重设为 1.0检测损失权重设在 0.5 到 1.0 之间。原因很实际分割损失收敛慢但更稳定检测损失收敛快但容易出现波动权重太大会让训练早期梯度被检测任务主导导致分割头学不到位。这条管线跑通后建议先用少量图像做一次单步验证确认正反向传播没有问题再上全量数据。我在某次项目里跳过这步直接全量训练跑了 6 个小时发现分割头的 loss 一直是 0原因是 mask 目录路径配错模型一直在学全零标签白白浪费了算力。4.2 类别不平衡与权重设置以及评估指标的正确打开方式输电场景的类别天然不平衡。杆塔数量少、面积大线路数量多但很多是细长条。如果直接拿原始标注训练模型会偏向学习线路而忽略杆塔。常用的处理手段是给损失函数加类别权重让数量少的类别有更大的梯度贡献。# 类别权重示例假设类别0为杆塔类别1为线路 class_weight [2.0, 1.0] criterion torch.nn.CrossEntropyLoss(weighttorch.tensor(class_weight))这里的关键是确定权重值而我一般不会拍脑袋定。先用统计脚本算出两个类别的像素占比比如杆塔占 15%、线路占 85%那么权重可以设为占比的反比即杆塔权重高、线路权重低。但我实际用下来会发现反比会导致杆塔过度拟合靠近但不等于反比的取值更靠谱比如 1.5 到 2.0 之间。这个区间需要小规模实验验证没有通用最优值。评估指标上检测任务看 bbox mAP分割任务看 mIoU 或 mask AP。这两个指标的分布很能说明问题如果检测 mAP 高而分割 mIoU 低说明模型找准了位置但对边界不敏感反之则说明边界分割好但定位漂移。我会在训练日志里同时记录这两个指标观察它们的差距是否收敛而不是只看单个指标。另一个值得注意的点是coco 格式的评估默认使用 0.5 到 0.95 的多个 IoU 阈值取平均如果你发现 mAP 数值低得离谱先确认是不是评估配置默认值太高而不是模型完全没学会。5. 用TTPLA做检测和分割的几个避坑点现象、原因、对策这类数据集在使用过程中有几个高频问题我按「现象→原因→对策」写出来每条都是实际踩过的教训。5.1 多边形自相交导致mask面积异常现象从 coco 标注转换出的 mask 里某些杆塔区域像素值出现明显错误的填充有的甚至把背景也填成了目标类别。原因标注人员在勾画复杂钢结构时多边形顶点顺序出现交叉形成自相交多边形。OpenCV 的 fillPoly 在遇到自相交时会按非零环绕规则填充结果可能跟标注意图相反。解决转换 mask 前对每个 polygon 做面积校验比较 polygon 的面积和其最小外接矩形面积。如果两者比例异常高比如超过 0.9就认为多边形接近矩形可能是简化过度如果 proportion 低于 0.05说明多边形极细长多半是画歪了。这类样本我直接丢弃而不修复因为输电通道中杆塔标注数量足够多丢几个不影响整体训练。5.2 线路被标注成断断续续的短线现象分割训练后 mIoU 不低但目视检查 mask 时发现线路标签不连续明明一整条线被标成了三四段。原因coco 格式里一个目标只允许一个 annotation id但有些标注人员为了省事把一条完整的线路拆成多段分别标注导致同一根导线有多个多边形每个多边形的首尾都停留在中间某处。解决检测任务不受影响因为 bbox 会把整条线路包住分割任务就需要做标注合并。我的做法是在转换 mask 时按 category 做形态学连接先填充所有线段多边形再用闭运算把距离较近的线连接起来。注意闭运算的核大小要跟图像分辨率挂钩比如 1333x800 的图像核取 5x5 到 7x7 即可过大的核会把相邻但不同的线路也连成一根。这个方法虽然粗暴在巡检数据里效果比逐段人工合并靠谱。5.3 杆塔尺度大、线路尺度小同一个模型两难现象训练完的模型对杆塔的检测 mAP 超过 80对线路只有不到 50分割结果也是线路的边界明显粗糙。原因杆塔占据图像大部分面积线路往往只有几个像素宽。模型的下采样倍数决定了线路特征很容易在深层特征图中消失检测头根本看不到小目标。解决我做了两件事。第一把训练输入分辨率从 800 提到 1200 到 1333 之间线路的像素宽度随之增加第二给 FPN 增加一个更高分辨率的输出层级让检测头能拿到 stride 为 4 的特征。这相当于让模型在特征金字塔底层多做一次预测对小目标更友好。显存不够时优先保分辨率其次再保 batch size因为线路目标的存在感对分辨率更敏感。5.4 输出是coco但某些框架要segmentation是RLE格式现象调用某个框架的 mask 解码接口时报错说 segmentation 既不是 list 也不是 dict无法解析。原因coco 标注的 segmentation 有两种合法格式多边形列表适用于非密集目标和 RLE 编码适用于 iscrowd1 的目标。该数据集的标注以多边形为主但某些第三方转换工具读入后会尝试把多边形转成 RLE 存储导致下游代码误判。解决在编写数据加载脚本时对 segmentation 字段做类型判断如果是 list 就按多边形处理如果是 dict 就用 pycocotools 的 maskUtils.decode 解码成 mask。判断逻辑一句话就能写完但要写在最前面否则容易在个别样本上突然崩溃。seg ann[segmentation] if isinstance(seg, dict): mask maskUtils.decode(seg) else: mask np.zeros((h, w), dtypenp.uint8) for poly in seg: cv2.fillPoly(mask, [np.array(poly).reshape(-1, 2)], 1)5.5 图像分辨率过高训练时显存不够现象单张图像 4000x3000直接送进模型后显存直接爆炸batch size 只能设 1 甚至报错。原因输电巡检相机的原图分辨率普遍很高而分割头的逐像素 loss 在 4000x3000 输入下显存占用是检测任务的几十倍。解决我的标准做法是离线切图把原图切成 1024x1024 的 patch相邻 patch 之间保留 100 像素的重叠。切图时同步把标注 JSON 里的 bbox 和 segmentation 坐标做平移超出 patch 边界的部分裁剪掉或丢弃。注意如果一个目标横跨多个 patch它会在多个 patch 中重复出现评估时需要根据原图坐标做去重后计算指标直接用 patch 的 mAP 会虚高。6. 进阶做法用小样本验证、切图推理与难例回灌迭代数据集当你把上述流程跑通后这个数据集带来的价值才开始真正释放。我建议的进阶路线分三步小样本端到端验证、切图推理部署、难例回灌持续迭代。小样本验证是指从全量数据里随机抽出 5% 到 10% 的图像加上对应标注跑一个最小训练和评估闭环。目的不是追求指标而是确认加载、转换、训练、推理、评估整条链路没有隐藏 bug。我通常会用这个子集测试不同的 backbone 和输入分辨率得到一个粗粒度结论后再上全量训练能省下好几轮全量实验的时间和算力。切图推理是部署环节的必然选择。因为训练时用了离线切图推理时也要把整张大图切成 patch 分别预测然后把结果拼回原图坐标。这里有个经验参数patch 的重叠区域取 200 像素而不是训练时的 100 像素。原因是推理时没有训练阶段的随机增强来补偿边界信息损失更大的重叠能让边缘处的目标被至少一个 patch 完整包含减少跨 patch 漏检。难例回灌是我目前坚持在做的一步。训练出的模型在验证集上挑出预测置信度低或分割边界差的样本人工复核后把修正过的标注补充进训练集。这类数据集的原始标注质量总体不错但输电场景中逆光、雾天、杆塔被植被遮挡的情况很难在一版数据里覆盖全。每轮回灌只补 200 到 300 张难例模型的泛化能力提升比单纯加大训练轮次明显得多。说句实在话这类数据集的价值不仅在于它能直接训练模型更在于它提供了一个稳定的基准你的数据增强方案、模型选型、训练策略都可以先在这里验证再迁移到自己的业务数据上。我在某次项目里就是先用它验证了分割头比检测头更适合细长目标识别这直接影响了后续业务模型的结构选型。每一条踩过的坑最后都变成了迭代流程里的一道检查工序。希望这篇笔记能帮你少走弯路把 TTPLA 数据集的检测和分割能力尽早落到你自己的巡检方案里。本文还有配套的精品资源点击获取
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →