ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

桥梁裂缝COCO数据集解析与Mask R-CNN分割训练实战

桥梁裂缝COCO数据集解析与Mask R-CNN分割训练实战 简介面向桥梁结构健康监测、土木工程智能检测及计算机视觉分割方向的研究者与开发者该数据集围绕桥梁裂缝缺陷识别任务构建共包含约4500张训练图像与200张验证图像所有标签均采用COCO格式的json文件标注便于直接接入主流实例分割或语义分割框架。压缩包内共2000个文件以jpg图像为主1998个并附2个json标注文件包体总大小约为557.74MB数据按训练验证划分清晰可快速完成数据加载与模型训练。目前已有317人学习下载适用于高校实验室、科研项目以及相关竞赛实战。尤其对于缺乏标注样本的团队这套现成的裂缝分割数据能大幅降低数据采集和人工标注成本帮助学习者将精力集中于网络结构调优、后处理优化与工程部署验证是开展桥梁病害智能化检测研究的高质量起步数据集。1. 桥梁裂缝缺陷数据集COCO格式分割任务的第一手资源第一次拿到这个包的时候我差点被文件名劝退——1654.rf.d39d5748db4496baa8557d6f8b7564fb.jpg这种命名一眼就是 Roboflow 导出的产物再加上一个 COCO 标注文件乍看像个“半成品”。但把它跑起来之后我才发现这正是实际项目里最常遇到的形态4500 张出头的训练图、200 张左右的验证图标注是完整的多边形分割而不是简单的框裂缝这种细长目标用框根本框不干净。这篇笔记就是把我拆这个数据集的完整过程记录下来从 COCO JSON 的结构到怎么用 pycocotools 把标注变成能直接训练的 mask再到训练参数怎么定、哪些地方最容易翻车。适合正在做桥梁检测、混凝土裂缝识别或者想拿一份真实缺陷数据练分割模型的人。2. COCO 格式拆解从 jpg 文件名到 JSON 标签的对应关系很多人在第一步就卡住了手上是图片和 JSON但不知道两者怎么对应。COCO 格式的核心思路是“所有标注都通过 ID 关联”图片是图片、标注是标注二者靠image_id串起来而不是像 VOC 那样按文件夹同名对应。2.1 文件结构图像命名与标注文件的关系数据集里图片文件名形如1654.rf.d39d5748db4496baa8557d6f8b7564fb.jpg中间那段.rf.是 Roboflow 导出时留下的痕迹rf后面的哈希串是图片的唯一标识。文件名本身不会参与训练逻辑COCO 的 JSON 里用file_name字段指向实际图片路径。也就是说你完全可以把图片改名只要同步改 JSON 里的file_name就不会出问题。我一般会把图片单独放到train2017/、val2017/这样的目录里再写一段小脚本按 JSON 索引复制而不是手工整理。数据集目录结构我通常整理成下面这样bridge_crack/ ├── train/ │ ├── images/ │ └── annotations/ │ └── train.json └── val/ ├── images/ └── annotations/ └── val.json逻辑上训练阶段只读 JSON 里的file_name去拼接图片路径所以目录名和 JSON 里的路径一定要对齐。常见做法是直接改 JSON 里的路径字段然后把图片按类别或按 train/val 分开。整理完目录就可以开始看 JSON 内容了用 Python 读一下确认顶层结构import json with open(train.json, r, encodingutf-8) as f: coco json.load(f) print(顶层字段:, list(coco.keys())) print(图片数量:, len(coco[images])) print(标注数量:, len(coco[annotations])) print(类别数量:, len(coco[categories]))这段代码用来快速确认标注文件是不是完整的 COCO 格式。输出里应该包含images、annotations、categories三个核心字段。如果缺少annotations或者categories为空说明标注文件损坏或者导出不完整后面对不上号就不奇怪了。2.2 COCO 的三段式结构images、annotations、categoriesCOCO 标注文件本质上是一个大字典拆开来看就是三张表。images里每一条记录一张图片的基本信息包括id、file_name、width、height。annotations里每一条记录一个目标实例关键字段是image_id指向某张图、category_id指向某个类别、segmentation多边形坐标或 RLE、bbox矩形框、area像素面积。categories则定义类别 ID 到类别名的映射。拿这个桥梁裂缝数据集来说categories大概率只有一类比如[{id: 1, name: crack}]。要注意的是类别 ID 可能从 1 开始而不是 0这在后面训练时特别容易踩坑。segmentation字段常见的存储形式是[[x1, y1, x2, y2, ...]]每个点对是一个顶点坐标一条裂缝可能由多个多边形拼成。area字段是 Mask 的像素面积而不是框的面积这个值在算指标时有用。我每次拿到新数据集都会先打印一条 annotation 看看 segmentation 的长相ann coco[annotations][0] print(image_id:, ann[image_id]) print(category_id:, ann[category_id]) print(bbox:, ann[bbox]) print(segmentation 顶点个数:, len(ann[segmentation][0]) // 2) print(area:, ann[area])为什么要先看这步因为segmentation有两种形态多边形坐标列表或者是 RLE 编码。Roboflow 导出时一般给的是多边形但如果原图标注方式不同也可能是 RLE这两种形态在后续处理上差异很大。如果你拿到的是 RLE转 mask 的方式跟多边形就不一样提前确认形态能省不少排查时间。2.3 图片尺寸一致性问题与面积字段很多 COCO 数据集的图片尺寸是统一的但这个桥梁裂缝数据集不一样。裂缝检测往往来自不同设备、不同拍摄距离图片宽高可能从几百到几千不等。COCO 格式每张图都单独记录width和height就是为这种情况设计的。我在拆这个数据集时发现一个关键信息标注里的area是多边形实际像素面积而bbox是多边形的最小外接矩形两者差距可能非常大。裂缝细长外接矩形覆盖了很多背景像素如果按 bbox 面积做样本均衡会严重失真按area才算准。另一个常见问题是标注多边形坐标是否超出图像边界。Roboflow 导出偶尔会把边缘目标的坐标微调出边界导致后续转 mask 时数组越界。我一般会加一段校验逻辑把超出边界的点拉回图像范围内。这个操作放在解析阶段做掉比等训练报错再处理要高效得多。3. 用 pycocotools 把标注读出来加载、可视化与训练集划分拿到 COCO JSON 只是第一步真正要用起来得把标注转成模型能吃的 mask。这中间最顺手的工具就是官方提供的pycocotools库它同时负责解析 JSON、画标注、算 mIoU 这些脏活累活。3.1 安装环境与 COCO 类加载pycocotools在 Windows 上安装有已知的坑Linux 上用 pip 直接装就行。建议在虚拟环境里装避免污染别的项目pip install pycocotools opencv-python matplotlib装好之后加载 COCO 标注的核心代码很固定。先实例化 COCO 类然后遍历所有图片from pycocotools.coco import COCO import os ann_file train.json coco COCO(ann_file) img_ids coco.getImgIds() print(训练图片总数:, len(img_ids)) img_info coco.loadImgs(img_ids[:3])[0] print(样例图片信息:, img_info)COCO(ann_file)在构造时就会把 JSON 解析完毕内部建好索引表。getImgIds()返回所有图片 ID 列表loadImgs()根据 ID 加载图片信息。参数ann_file是标注 JSON 的路径路径错了会直接抛异常。3.2 从多边形到裂缝 Mask可视化标注训练分割模型需要的是每张图对应的二值 Mask而 COCO 里存的是多边形坐标所以必须先做一次转换。这里我用coco.annToMask()把单个标注转成 mask然后把同一张图的所有标注按裂缝合并成一张图。合并时用累积相加再加阈值避免不同裂缝区域重叠导致的数值叠加问题import numpy as np import cv2 from pycocotools import mask as mask_util def load_image_with_masks(coco, img_id): img_info coco.loadImgs(img_id)[0] image cv2.imread(os.path.join(train, img_info[file_name])) image cv2.cvtColor(image, cv2.COLOR_BGR2RGB) ann_ids coco.getAnnIds(imgIdsimg_id) anns coco.loadAnns(ann_ids) h, w img_info[height], img_info[width] mask np.zeros((h, w), dtypenp.uint8) for ann in anns: ann_mask coco.annToMask(ann) mask np.maximum(mask, ann_mask) # 合并所有裂缝区域 return image, mask, anns img_id img_ids[0] image, mask, anns load_image_with_masks(coco, img_id) print(mask 唯一值:, np.unique(mask)) print(裂缝像素占比:, round(mask.mean(), 4))这段代码的关键点在np.maximum它能安全合并多个标注的 mask。annToMask是这个 API 里最常用的方法它内部会自动判断 segmentation 是多边形还是 RLE多边形的直接填充RLE 的解码后直接转数组。如果mask 唯一值输出只有[0]说明这张图的标注为空或者annToMask没有被正确调用。打印裂缝像素占比很有用能很快发现哪些样本裂缝极小、哪些样本几乎全黑。可视化这一步我会习惯性地做不然后面模型训练跑出一个错乱的结果你可能都分不清是标注错了还是模型错了import matplotlib.pyplot as plt fig, axes plt.subplots(1, 3, figsize(15, 5)) axes[0].imshow(image) axes[0].set_title(Original) axes[1].imshow(mask, cmapgray) axes[1].set_title(Crack Mask) axes[2].imshow(image) axes[2].imshow(mask, alpha0.4, cmapReds) axes[2].set_title(Overlay) plt.show()可视化时我习惯把原图、mask、叠加图三张并排放。这一步不是为了好看而是为了快速排查标注偏移。如果你看到叠加图里红色区域明显跑到了裂缝以外的位置那基本可以断定是标注和图像没有对齐而不是模型问题。3.3 按 9:1 重新切分训练集官方 split 不一定够用这个数据集原始划分是约 4500 张训练图加 200 张验证图比例接近 22:1验证集只占了大约 4%。这个比例在训练中后期评估时会吃亏因为验证集太小mIoU 抖动会很大。我自己通常会重新按 9:1 切分一次把验证集扩到 500 张左右训练集相应减少到约 4200 张。切分可以直接对 JSON 做不改动图片文件import random, json, copy with open(train.json, r, encodingutf-8) as f: coco json.load(f) img_ids [item[id] for item in coco[images]] random.seed(42) random.shuffle(img_ids) val_ratio 0.1 val_ids set(img_ids[: int(len(img_ids) * val_ratio)]) train_json copy.deepcopy(coco) val_json copy.deepcopy(coco) train_json[images] [img for img in coco[images] if img[id] not in val_ids] val_json[images] [img for img in coco[images] if img[id] in val_ids] train_json[annotations] [ann for ann in coco[annotations] if ann[image_id] in train_ids] val_json[annotations] [ann for ann in coco[annotations] if ann[image_id] in val_ids] with open(train_new.json, w) as f: json.dump(train_json, f) with open(val_new.json, w) as f: json.dump(val_json, f)代码逻辑很直接读原 JSON打乱图片 ID按 9:1 分 ID再分别过滤 images 和 annotations。deepcopy是防止修改原始对象因为后面还要继续用原始数据做对比实验。重新切分的意义在于你后面调学习率、做数据增强、判断是否过拟合都需要一个足够大的验证集来反馈规律。200 张验证集很容易被一两张极端裂缝图带偏指标每次 epoch 的分数忽高忽低根本没法定位问题。4. 分割模型训练参数怎么定以 Mask R-CNN 为例的完整流程数据集就位之后接下来就是模型选型和训练参数。裂缝分割这个任务核心矛盾在于裂缝很细、背景占比极大普通的语义分割模型容易把裂缝直接吞掉。这里我以目标检测/分割两用的 Mask R-CNN 为例讲整个参数设定逻辑因为它是用 COCO 格式最顺手的模型之一。4.1 选模型Mask R-CNN 还是 UNet裂缝分割有两个技术路线实例分割和语义分割。实例分割对应 Mask R-CNN输出的是每个裂缝实例独立的 mask语义分割对应 UNet 这类编码器解码器结构输出的是整张图的像素类别。部分工程师会默认选 UNet因为裂缝是一类目标语义分割逻辑上更简单。但在这个数据集上我实测发现Mask R-CNN 反而更容易收敛原因是裂缝的连通区域往往被噪声或阴影断开实例分割可以通过检测框先锁定裂缝区域再在框内做精细分割天然屏蔽了大量背景干扰。如果用语义分割建议把模型输出的类别数设为 2背景 裂缝并把损失函数改为带类别权重的交叉熵。因为裂缝像素占比通常不到 1%不加权重的话模型会稳坐输出全背景。用 Mask R-CNN 则不用太担心这个问题因为检测框已经筛掉了大部分背景。两种路线的取舍我放到后面踩坑部分细说。这里我用 Detectron2 来跑 Mask R-CNN因为它对 COCO 格式支持最好注册数据集只需写一个函数from detectron2.data import DatasetCatalog, MetadataCatalog from detectron2.data.datasets import register_coco_instances register_coco_instances(bridge_crack_train, {}, train_new.json, train/images) register_coco_instances(bridge_crack_val, {}, val_new.json, val/images) MetadataCatalog.get(bridge_crack_train).set(thing_classes[crack])这段代码的作用是注册 COCO 格式数据集register_coco_instances接收三个核心参数数据集名称、可选元信息、标注 JSON 路径、图片根目录。注册完成后Detectron2 的默认数据加载器就能直接读取。thing_classes一定要和 JSON 里的categories对应否则类别显示会错位。4.2 训练配置学习率、batch size、迭代数怎么设训练分割模型参数崩不崩基本看学习率和迭代数。裂缝分割这个场景我给的配置是基础学习率1e-4batch size 2显存不够就梯度累积迭代数 15000 步左右开始看验证集趋势。第一次跑先把输入尺寸缩到short edge 800, long edge 1333这个尺寸是 Detectron2 在 COCO 上的默认值在这个数据集上足够撑起裂缝细节同时显存还能压得住。from detectron2.config import get_cfg from detectron2.engine import DefaultTrainer cfg get_cfg() cfg.merge_from_file(configs/COCO-InstanceSegmentation/mask_rcnn_R_50_FPN_3x.yaml) cfg.DATASETS.TRAIN (bridge_crack_train,) cfg.DATASETS.TEST (bridge_crack_val,) cfg.DATALOADER.NUM_WORKERS 4 cfg.SOLVER.IMS_PER_BATCH 2 cfg.SOLVER.BASE_LR 1e-4 cfg.SOLVER.MAX_ITER 15000 cfg.SOLVER.STEPS (10000, 13000) cfg.MODEL.ROI_HEADS.NUM_CLASSES 1 cfg.MODEL.WEIGHTS detectron2://COCO-InstanceSegmentation/mask_rcnn_R_50_FPN_3x/137849600/model_final_f10217.pkl cfg.OUTPUT_DIR output/bridge_crack os.makedirs(cfg.OUTPUT_DIR, exist_okTrue) trainer DefaultTrainer(cfg) trainer.resume_or_load(resumeFalse) trainer.train()这里重点注释几个参数SOLVER.STEPS是指学习率在第 10000 步和第 13000 步分别衰减一次MODEL.ROI_HEADS.NUM_CLASSES必须等于 1因为数据集只有一个裂缝类MODEL.WEIGHTS是预训练权重路径用 COCO 预训练权重能显著加速收敛因为检测骨干网络已经学会了通用特征。训练时 Loss 曲线如果一直是「分类 loss 很小、分割 loss 降不动」常见原因是 mask 标注太碎或者裂缝区域过小需要调大MASK_CROP相关参数来放大感兴趣区域。4.3 数据加载流水线避免内存瓶颈4500 张训练图如果每次训练都现读全图磁盘 IO 会直接卡死训练进程。我一般会先用脚本把所有图缩放到统一长边 1333 并缓存为内存对象或者落地成 LMDB 格式。Detectron2 自带的DatasetMapper已经包含随机翻转、缩放、crop 这些增强足够应付这个数据集。如果用的是自定义的训练脚本建议至少做三个标准增强水平翻转、随机亮度扰动、随机对比度扰动。桥梁裂缝图像的光照环境变化很大室内外温差、阴影、反光都会影响灰度分布不做光度扰动的话模型容易在光照上过拟合。数据加载的多进程数NUM_WORKERS也值得调一下。4 个 worker 在 CPU 上够用但如果你的图像分辨率很大worker 太多会导致内存直接翻倍。我习惯用 4 到 8 之间同时把 torch 的num_threads设到 4避免数据加载和训练抢 CPU 资源。5. 训练中的避坑记录标注错位、类别失衡与验证集漂移这部分是我实际跑这个数据集时踩过的坑。每一条都按「现象 → 原因 → 解决」排列适合训练前先扫一遍直接省掉几天的调试时间。5.1 背景被当成裂缝category_id 从 0 开始导致全图变红现象训练好的模型跑验证图预测 mask 几乎覆盖整个桥面除了背景全被判断成裂缝。原因COCO JSON 里的categoriesid 用的是0表示裂缝而大多数模型框架默认0是背景类别。Detectron2 和 MMDetection 内部都假设类别 ID 从 1 开始0 留给背景。如果不做映射模型就把背景学成了裂缝。解决在注册数据集时统一把类别迁移到1开头或者写一段脚本把 JSON 里的category_id全部重映射再保存。我推荐重映射 JSON因为这样后续所有框架都能直接用。for ann in coco[annotations]: ann[category_id] ann[category_id] 15.2 图像尺寸不一致导致 mask 偏移现象验证集上预测 mask 和裂缝在空间上错位裂缝实际在中部但预测区域偏到边缘。原因加载图片时用了 OpenCV 默认的 BGR 读取宽高没有按 JSON 里的width/height对齐或者多进程数据加载时做了 resize 忘了同步修改标注坐标。COCO 的 mask 是按原图尺寸计算的一旦图片被 resize 而 mask 没有跟着变形错位就不可避免。解决在数据加载阶段统一走cv2.resize同时把标注映射回原尺寸。建议写一个简单的检查函数分别统计 mask 和图片的宽高比一旦超过阈值就报警。5.3 裂缝像素占比太低loss 几乎不下降现象训练 5000 步后训练 loss 停在某个值不动验证集 mIoU 接近 0预测结果全黑。原因裂缝属于极端前景稀疏目标平均像素占比可能不到 1%。如果不做类别平衡模型的最优策略就是全部预测为背景。解决对语义分割方案把损失函数换成 Focal Loss 或者带类权重的 CrossEntropy。对 Mask R-CNN 方案调小模型输入尺寸以减少下采样倍数同时把ROI_HEADS.POSITIVE_FRACTION提高确保 RoI 采样时裂缝区域不至于被漏掉。5.4 验证集出现训练集中的图片现象训练完跑验证集指标比预想的高很多但换一批真实现场图效果立刻掉下来。原因原始数据集的 train 和 val 划分不够严格部分图片来自同一段视频帧或同一面桥的连续拍摄画面高度相似验证机有效信息被污染了。解决严格按图片哈希去重后重新切分验证集。我用imagehash库做过一轮感知哈希去重把重复或近景再裁为相同内容帧剔除效果立竿见影。5.5 随机种子不固定导致复现不了现象同一套配置训练两次mIoU 差 5 个点以上。原因数据加载顺序随机、多进程采样顺序随机、GPU 卷积操作本身也有随机性。不固定seed的话小数据集上的波动会被放大。解决在训练脚本入口固定torch、numpy、random三个库的种子并关闭 CUDA 的 benchmark 模式。训练结束时记录使用的seed到日志里方便后面严格复现或排查。6. COCO 转 Mask 再算 mIoU裂缝分割的验证与进阶用法模型训练到什么程度才算过关不能只盯着 loss。我自己习惯的做法是每训练 2000 步就在验证集上算一次 mIoU记录到一个 CSV 里训练结束后再画 mIoU 随迭代数的曲线看有没有过拟合拐点。计算 mIoU 可以用 pycocotools 自带的cocoEval接口它同时输出 AP、AR 这些 COCO 标准指标。但对于裂缝分割我更倾向于算mIoU因为 AP 偏重于检测框质量而裂缝这种细长目标对像素级重合度更敏感。from pycocotools.coco import COCO from pycocotools.cocoeval import COCOeval gt COCO(val_new.json) pred gt.loadRes(predictions.json) evaler COCOeval(gt, pred, segm) evaler.evaluate() evaler.accumulate() evaler.summarize() print(mIoU 约等于:, evaler.stats[0])这段代码是在验证集上做标准 COCO 评估。predictions.json是模型输出的检测与分割结果格式和 ground truth 类似。COCOeval的第二个参数segm指定评估 mask 而非 bbox。我第一次用这个接口时没注意evaler.stats[0]是APIoU0.5:0.95不是 0.5 阈值下的 IoU。如果要和经典论文对标建议同时打印stats[1]那个是APIoU0.50和裂缝分割常见报告口径一致。关于进阶用法我个人有两点心得。第一裂缝分割在预测阶段可以做一个后处理——用形态学闭运算把小裂缝空隙连接起来再用面积阈值过滤掉零星噪声点。这一步能显著提升主观视觉效果但对指标影响不大适合工地现场看图的场景。第二如果模型在验证集上表现不错、到真实场景掉点多拍一些现场图补进训练集做一次增量训练而不是重新训整个模型。从那以后我每次训练分割模型都会强制做一遍这些步骤先画三张可视化确认标注没问题再跑一次 200 步短训练看 loss 是否会下降最后才上完整训练和 mIoU 评估。这套流程帮我筛掉过好几个看似能用、实则标注错位的项目希望你也能避开这些坑一次就跑通。本文还有配套的精品资源点击获取
返回列表