首页 / 资讯中心 / 文章详情

桥梁表观病害实例分割数据集:labelme格式详解与训练落地

桥梁表观病害实例分割数据集:labelme格式详解与训练落地 ★ FEATURED ARTICLE
干过结构检测项目的人应该都有同感桥梁外观病害识别这件事最大的瓶颈从来不是模型结构选哪个、loss怎么调而是手里没有一批“能打”的标注数据。裂缝、剥落、露筋、锈蚀这些病害长得像、伴生多、边界又模糊指望随便拿个公开数据集训练完就上桥检测基本都会在真实场景里翻车。最近整理一套桥梁表观缺陷实例分割数据集规模是5341张图、5个类别采用labelme格式存储标注正好解决了这个阶段最头疼的问题——数据从哪来、标注怎么组织、模型怎么训。这篇文章就把这套数据的构成逻辑、labelme格式的工程细节、以及从标注到训练落地的完整链路拆开讲清楚给正在做桥梁健康监测、结构缺陷识别或者无人机巡检视觉方案的朋友做个参考。1. 数据集的核心价值它到底解决了什么问题1.1 桥梁表观病害检测的真实痛点先说说我为什么觉得这种数据集稀缺。传统的桥梁检测主要靠人工巡检工程师带着相机、望远镜、甚至爬到桥底搭脚手架靠肉眼一张一张看。这个过程有几个绕不开的麻烦一是作业风险高桥墩、箱梁内部、悬索锚固区这些位置人很难接近二是效率低一座常规跨径的梁桥整体外观检查可能要花掉好几天三是结果主观性太强同一个裂缝不同人量出来的长度和宽度可以差很多。这几年机器视觉方案越来越多地往这个领域渗透无人机挂载高分辨率相机绕桥飞行、爬壁机器人贴面扫描、病害检测车边走边拍采集图像已经不是问题。问题在于采集回来的图像怎么变成结构工程师能直接用的病害信息——这就要靠分割模型把裂缝、剥落、露筋、锈蚀这些区域一个个“抠”出来。但视觉模型是数据喂出来的桥梁病害数据本身就有天然门槛不可能像互联网图片那样随便爬需要去真实桥梁现场拍还要有专业人员做像素级标注。所以一套整理规范、类别清楚、数量足够的桥梁缺陷分割数据集对做落地项目的人来说就是最实在的资产。1.2 五类缺陷的业务拆解不只是“找出问题”这套数据集总共覆盖了桥梁表观检测中最常见的几类缺陷裂缝、混凝土剥落、钢筋外露、钢筋锈蚀以及与之经常伴生的渗水泛碱或表层劣化区域。很多人会把“露筋”和“锈蚀”当成一回事实际业务里它们是要分开处理的。露筋指的是混凝土保护层脱落之后钢筋直接暴露在空气中它的危害在于钢筋失去了混凝土的保护后续锈蚀速度会加快而锈蚀本身在混凝土表面就能看出来比如大面积的锈迹、锈水流痕它是化学作用的结果。这两类病害在工程修复方案上完全不同——一个是补护层、一个是做除锈防锈处理所以模型必须能区分它们。裂缝是桥梁病害里最典型也最细长的目标标注难度最大。混凝土剥落则是成片出现的边缘不规则、面积相对较大往往和露筋伴生——剥落区域里可能藏着露筋露筋表面又覆盖着锈蚀。这种伴生关系给数据集标注规范提出了一个关键问题同一块区域到底标哪一个类我的建议是给病害定义一个优先级露筋优先级最高其次是锈蚀再次是剥落最后才是裂缝。这样多类别之间不重叠训练出来的模型输出也干净不会在同一个像素上给出矛盾预测。1.3 为什么选用实例分割而非目标检测有朋友问过目标检测给个框不也能定位病害吗对桥梁病害来说目标检测的定位方式远远不够。裂缝是细长条状且连续变化的目标一个bounding box里可能同时框住裂缝、背景、甚至相邻的另一条裂缝所谓检测结果完全无法回答“这条裂缝有多长、走向如何、是否贯穿”。而分割掩膜可以直接给出病害区域的像素集合下游可以精确计算长度、面积、宽度分布、或者和上一次检测结果做同点位时序差异对比。实例分割和语义分割之间的取舍也要说清楚。如果只是想把裂缝像素和背景分开语义分割就够了但桥梁上是多目标多病害共存的需要知道“这个剥落区域是第几个、面积多大”实例分割更能满足业务统计需求。这个数据集用labelme的polygon标注本身就支持实例级标注——同一类别不同实例用不同多边形表示转成COCO格式就是标准的实例分割数据集训练Mask R-CNN、YOLOv8-seg这类模型可以直接用。如果你更偏好语义分割把同类别掩膜合并即可留有余地。2. labelme格式深度拆解一个json文件里到底有什么2.1 标注文件字段逐项解析labelme是这个领域最常用的开源标注工具它产出的标注文件是JSON格式一个图像对应一个同名json。打包好的数据集里每张图像旁边都跟着一个.json文件打开之后核心字段就是这个样子{ version: 5.2.0.post1, flags: {}, shapes: [ { label: crack, points: [ [124.5, 365.2], [130.8, 367.1], [138.2, 369.0], [150.1, 372.3] ], group_id: null, shape_type: polygon, flags: {} }, { label: spalling, points: [ [512.3, 214.0], [520.1, 230.5], [508.7, 248.2], [499.0, 228.4] ], group_id: 1, shape_type: polygon, flags: {} } ], imagePath: IMG_20240118_103200.jpg, imageData: null, imageHeight: 3000, imageWidth: 4000 }逐项看shapes是标注区域列表每个元素包含label类别名、points多边形顶点坐标、shape_type当前标注的形状类型这里是polygon、group_id分组ID可用于区分实例处理重叠区域非常有用。imagePath指向原图文件名imageData是图片的base64编码字符串imageHeight和imageWidth记录图像尺寸方便程序在加载时校验图片是否被误改过。这里有个和后续工程化密切相关的细节imageData字段在标注时通常是空的不会把所有图片先base64编码塞进json这样文件体积小、便于git版本管理或在服务器间拷贝。但如果你直接把json丢给有些训练框架读取框架默认从json里读imageData而忽略了imagePath就会报“图片为空”的错。正确做法是训练前把json里的imagePath和对应文件名对齐让框架从磁盘加载原图。我见过太多人卡在这一步其实不是模型问题是数据加载逻辑没理顺。2.2 标注坐标体系与多边形规范labelme的坐标系是图像像素坐标原点在左上角x向右为正y向下为正单位就是像素。points里存的多边形顶点全部遵循这个体系。很多人拿到的图像可能是手机拍的竖图或者无人机飞控系统输出的带水印大图如果标注前做过旋转、裁剪、重采样那么标注坐标和实际图像内容就对不上了。所以数据集的原始图像应当保持“标注时的那张图”不被二次修改任何预处理都放到训练管线里做而不是在数据存储层做。多边形的闭合问题也容易踩坑。labelme标注的多边形在界面上看是闭合的但json的points不会重复存储第一个顶点。转掩膜或转COCO格式时程序一般会自动闭合首尾但如果你自己写转换代码时忽略了这一步就会画出开口多边形面积计算和掩膜渲染都会出错。最稳妥的做法是转换脚本里显式把points首尾闭合mask绘制时把最后一个点和第一个点连起来填充。另外同一个病害区域被标了两个重叠的多边形、或者一个复杂病害区被拆成多个多边形这类情况在手工标注中非常常见。工程上建议为多边形的group_id做约定同一个病害实例的多个多边形共用同一个group_id不同实例用不同id。这样后续做实例分割时可以把同组的多边形合并成一个实例避免同一个裂缝被当成两个目标。2.3 两种主流转换路线COCO还是YOLO-seg拿到labelme格式数据之后紧接着的问题就是怎么喂给模型目前主流分割模型基本分成两派一派以MMDetection、Detectron2为基础需要COCO格式的JSON一派以Ultralytics的YOLOv8-seg/YOLO11-seg为代表需要归一化坐标的txt格式。这里提供一个简化的labelme转COCO思路import json import glob from pycocotools import mask as mask_utils import numpy as np def labelme_to_coco(annotation_dir, output_json): images, annotations [], [] ann_id, image_id 1, 1 category_map {crack: 1, spalling: 2, rebar_exposed: 3, corrosion: 4, efflorescence: 5} jsons sorted(glob.glob(f{annotation_dir}/*.json)) for json_path in jsons: with open(json_path, r, encodingutf-8) as f: data json.load(f) images.append({ id: image_id, file_name: data[imagePath], width: data[imageWidth], height: data[imageHeight] }) for shape in data[shapes]: # 多边形转COCO分割格式 polygon [coord for point in shape[points] for coord in point] # 这里省略了多边形转二值掩膜再转RLE的细节 annotations.append({ id: ann_id, image_id: image_id, category_id: category_map[shape[label]], segmentation: [polygon], area: 0, # 用mask_utils计算 bbox: [], # 用mask_utils计算 iscrowd: 0 }) ann_id 1 image_id 1 with open(output_json, w, encodingutf-8) as f: json.dump({images: images, annotations: annotations, categories: [{id: v, name: k} for k, v in category_map.items()]}, f)转YOLO-seg格式时要注意两点一是多边形顶点坐标必须归一化到0~1之间二是同一行的类别ID必须从0开始和COCO的从1开始不同。很多转换脚本漏了减一操作训练时模型输出通道数和标签值对不上直接表现为loss剧烈震荡或acc恒为0。这类问题不复杂但在实际项目中反复出现过值得留意。3. 用这套数据训练分割模型步骤、参数与踩坑记录3.1 数据集划分千万别随机打乱训练前最关键的一步不是调参而是切数据。桥梁病害数据有一个特性同一座桥、同一个桥墩、同一个拍摄视角得到的连续图像之间高度相似。如果直接随机洗牌划分训练集和验证集会出现训练集里已经有某一段桥面的病害图像、验证集又抽到同一段的几乎重复图像验证分数虚高得离谱。等模型部署到一座新桥上精度立刻被打回原形。正确做法是按“桥梁”或者按“桥跨/墩位”划分尽量保证同一座桥的连续画面全部进训练集验证集和测试集来自模型没见过的新桥段。这个特性和遥感图像分割、医学影像切片的数据划分逻辑一致——数据单元之间不能有重叠的信息泄露。以这套数据集为例我建议按采集项目批次分第一批次某桥A的500张、第二批次某桥B的300张不要混到同一集合里测试集单独留出至少一整个采集批次的图。3.2 类别不平衡裂缝是“细线”困境桥梁缺陷数据天然存在严重的类别不平衡。裂缝通常是细线状在整张图里占比可能只有百分之零点几到几而剥落、渗水这类区域动辄成片占比能到10%以上。如果直接拿交叉熵损失训练模型会把所有像素都预测成背景或者占比大的类别裂缝检测能力基本为零。两个实操经验分享一是用加权损失比如给裂缝类别的权重设到背景的10倍以上权重基本按像素数量倒过来算再乘个系数二是用focal loss这类困难样本挖掘损失让模型聚焦在难分的边界像素上。我自己在裂缝分割上比较常用Tversky Loss或者它的变体这类损失对细长小目标更友好。但注意权重不能拍脑袋定要先统计数据集里各类别的像素占比再决定权重倍率否则会把背景误分严重。3.3 模型选型与起步训练参数这套数据集的标注是实例级多边形灵活性很大基于它训练可以按需选模型。以我实际测试经验3.3表格可以辅助决策模型任务类型推理速度精度特点适用场景Mask R-CNN实例分割较慢稳定边界稍糙离线批量病害统计YOLOv8-seg / YOLO11-seg实例分割很快细小目标需调参无人机实时巡检U-Net / DeepLabV3语义分割中对裂缝形态提取好后续要做裂缝宽度计算SAM微调交互分割慢泛化好但依赖提示半自动标注辅助起步训练建议先用YOLOv8-seg这类工程性强的框架跑一遍输入分辨率设置到1280以上因为裂缝宽度往往只有几个像素缩到640会直接丢细节。预训练权重建议用COCO的虽然领域差异大但底层的边缘特征和纹理特征迁移仍有收益。Batch size根据显存来训练轮次不用贪多80到120轮足够看到收敛趋势。关键是观察验证集上细小裂缝类别的recall而不是只看mAP。3.4 数据增强策略光线和尺度是两大敌人桥梁外观图像受天气和拍摄环境影响极大——晴天和阴天的光照条件不同顺光和逆光下裂缝对比度差好几倍无人机在不同距离拍摄的病害尺度也不同。数据增强策略里色彩抖动、亮度对比度调整、随机灰度化这几项一定要加模拟不同光照下的成像差异。几何增强方面水平翻转和轻微旋转可以放心用但随机缩放要收敛一点不要过度把裂缝缩到几乎看不见那反而教模型把细裂缝忽略掉。另一个容易被忽略的是图像增强与标注的联动。labelme数据转换到训练格式时如果用albumentations库做增强必须确保同样的随机种子同步作用在图像和掩膜上。分割任务里图像做过旋转或翻转、掩膜没跟着动模型训练过程中等于在看错误的标签损伤比不加增强还严重。4. 从标注数据到产线落地部署思路与后续扩展4.1 评估口径对齐算法指标不等于业务指标模型训练完了mAP也挺好看但结构工程师未必认可。这里有个关键经验算法团队和检测报告之间必须建立一套业务指标翻译层。对桥梁病害任务来说业务上关心的是“检出率”和“漏检率”而不是“mIoU提升3%”。需要做两件事第一把分割掩膜投影回原图坐标系按连通域统计病害实例每个实例计算面积、长宽、中心点坐标第二设定一个面积阈值——小于阈值的病害实例不计入正式结果因为工程上几平方毫米的剥落不在修复范围内。如果要做裂缝宽度这种更精细的指标用像素面积除以骨架化后的中轴长度得到平均宽度再用标定板换算成物理毫米。标定信息可以来自无人机机载的激光测距数据也可以来自图像里已知尺寸的结构构件——比如栏杆立柱间距是固定2米用它估出每像素对应毫米数。有了这些检测结果才能真正用于损伤评级和维修方案制定。4.2 边缘部署架构与增量迭代桥梁检测现场的网络环境往往不稳定模型最终要跑在边缘设备上。常见做法是把训练好的分割模型导出为TensorRT引擎部署在带GPU的工控机或Jetson系列设备上无人机采集的图像通过板载Wi-Fi或SD卡回传到边缘盒子实时推理出掩膜并叠加渲染到原图上同步生成病害记录表。推理速度方面YOLOv8-seg在Jetson Orin级别设备上处理1280分辨率图像能做到实时足够配合无人机巡检节奏。部署之后的迭代闭环更重要。每次外业采集回一批新图先让模型做自动预标注人工只需要修正错误和补标漏掉的病害再把新数据增量加入训练集。这个“模型辅助标注”的流程能把标注成本压到原来纯手工的30%左右。人机回圈迭代到三轮以上模型对新光线、新桥型的适应力会明显上一个台阶。这就是为什么有了一套起步数据集后后续扩展一定不要停——三五百张新场景图远比在算法层面死磕提升更明显。5. 常见问题与排查技巧实录5.1 json里的imageData为空训练时读不到图这是最基础也最高频的问题。labelme保存json时默认不嵌入图片imageData字段是null。很多训练脚本从json读图字符串是空的直接报错。排查方法很简单如果数据代码是按imagePath去读原图的确认原图路径和json里的imagePath一致且文件确实存在于磁盘。如果某个json是从别处拷来的imagePath可能写的是绝对路径需要改成相对路径或重新拼接。批量检查时写个脚本遍历所有json逐个验证对应图片是否存在、尺寸是否和json里的height/width一致。5.2 转换后掩膜全黑或所有标签变成背景这个问题基本集中在“类别ID从1开始还是从0开始”的约定上。COCO格式mask取值从0到N0是背景YOLO-seg的类别从0开始就是第一类。如果你的自定义数据集里“背景”没占一个mask通道而转换代码里用0占了背景就会把第一个类别顶到1再往后类别全都顺延一位。训练时模型输出的通道和mask对不上结果就是全部预测成背景。建议转换脚本里显式打印每一个文件的类别分布只花几秒就能发现这种错位。5.3 细小裂缝被过滤或者被模型直接忽略排除标注质量问题后最常见原因有两个。一是输入尺寸下采样太狠原始图像4000x3000为了提速缩到640裂缝从几个像素宽变成亚像素宽度标注掩膜也基本消失模型自然学不到。二是后处理里用的掩膜置信度阈值太高细长裂缝的置信度平均偏低阈值一高就只剩零零碎碎的线段。处理办法推理时用1280或原始尺寸切块推理后处理阈值单独为裂缝类别调低甚至可以给裂缝类别单独走一条分割分支。5.4 标签命名不一致导致类别错乱多人协作标注时很容易出现一个人叫“rebar_exposed”、另一个人叫“rebarExposed”或者有人标“spalling”、有人标“spal”。这类命名不一致如果不统一转换脚本按名称映射类别ID时就会把同一类病害拆成两个类别模型分错不说统计结果也直接乱了。建议数据集交付前做一个唯一化检查统计所有json里的label值集合确认和类别清单完全一致不存在的命名一律按映射表修正。五类病害、5341张、labelme格式这套数据真正串联起来的是“采集图像→像素级标注→实例分割模型→病害报表”这一段完整链路。我在实际使用中最深的感觉是数据工程做扎实了后面模型和部署都顺很多反过来如果只在模型结构上反复折腾数据接口到处都是坑项目大概率在集成阶段卡壳。最后再分享一个小经验拿到这类数据集后别急着直接训练完整模型先花一点时间做数据质量巡检用可视化脚本把标注掩膜叠加在原图上随机抽查几十张确认多边形贴合度、类别分配、以及伴生病害的分割边界。这一步看似多花了时间实际上能把后面调试模型的时间省下一大块。数据是土模型是种子土先整好了种子才能长好。
阅读完成 · 觉得有帮助?
咨询建站