资讯动态

建筑墙壁缺陷分割数据集:7820张labelme标注的转换与训练指南

发布时间:2026/10/25 12:59:12 来源:尧图企业网站定制
简介面向建筑视觉检测与图像分割方向研究者、算法工程师及高校学生的标注数据集聚焦墙壁损伤缺陷的自动识别与分类任务可用于训练和评估语义分割或实例分割模型帮助减少人工巡检依赖。资源包共1个docx文件约2.1MB以文档形式汇总数据集说明与类别信息配套的7820张640×640像素jpg图片及同名json标注文件按labelme格式组织覆盖涂鸦、锈蚀、剥落、裂缝、外露钢筋、排水等20个类别其中锈蚀与剥落标注框数量尤为丰富。已有103人学习关注。读者可据此了解多边形标注规范与各类别分布借助labelme 5.5.0查看编辑json并自行转换为mask、YOLO或COCO格式开展分割实验为建筑健康监测与缺陷识别研究提供准确合理的数据基础。1. 建筑墙壁缺陷分割数据集7820 张 labelme 标注到底能干什么拿到一份标注好的建筑缺陷数据集第一反应往往不是“太好了”而是“这 20 个类别长什么样、标注质量能不能撑起训练”。这份资源给的是 7820 张 640×640 的 jpg 图片配 7820 个 labelme 5.5.0 生成的 json 文件全部是多边形 polygon 标注覆盖涂鸦、锈蚀、剥落、泛碱、空腔、外露钢筋、裂缝、活动裂缝、伸缩缝等 20 类建筑墙壁损伤。它不包含 mask 文件也不附带任何训练好的权重价值在于把“从原图到分割掩码”这条链路的前半段替你走完了。适合做建筑病害检测、结构健康监测、语义分割或实例分割的从业者尤其是想快速验证一个分割方案、又不想从零标数据的人。类别分布很不均衡锈蚀 14665 个标注、剥落 9849 个、空腔 9222 个而 object 只有 13 个、活动裂缝 423 个这个长尾结构会直接决定你后面怎么切分和采样。2. 先看清标注结构labelme json 里到底存了什么2.1 polygon 标注的字段与坐标系labelme 的 json 不是“一张图一个框”而是把每个多边形实例的顶点坐标、标签名、图像尺寸、图像路径都塞进一个 JSON 对象里。理解这几个字段后面写转换脚本才不会翻车。核心结构大致是这样{ version: 5.5.0, flags: {}, shapes: [ { label: Crack, points: [[120.5, 88.3], [180.2, 90.1], [176.4, 132.7]], group_id: null, shape_type: polygon, flags: {} } ], imagePath: 000123.jpg, imageData: null, imageHeight: 640, imageWidth: 640 }shapes是标注实例列表每个元素里的label对应 20 个类别名之一points是像素坐标下的多边形顶点shape_type为polygon表示这是多边形而非矩形框。imagePath是相对路径imageData通常为 nulllabelme 默认不内嵌 base64所以转换时必须靠imagePath去同目录找 jpg。这里有个容易忽略的点points的坐标是浮点数直接取整会带来亚像素误差做语义分割时影响不大但做实例分割的 mask 边界时建议保留浮点再栅格化。2.2 20 个类别的长尾分布与训练影响把每个类别的标注数拉出来看分布极不均衡类别标注数类别标注数Rust 锈蚀14665Crack 裂缝3612Spalling 剥落9849Graffiti 涂鸦2197Cavity 空腔9222ExposedRebars 外露钢筋1993Weathering 风化4621PEquipment 防护设备1892Efflorescence 泛碱3956Wetspot 潮湿斑迹1657Drainage 排水1649Hollowareas 空洞区域1538Bearing 支座1201JTape 接头胶带1076Restformwork 残留模板1019Cavity 空腔9222Rockpocket 岩窝616EJoint 伸缩缝506ACrack 活动裂缝423WConccor 水侵蚀混凝土407object 物体13锈蚀、剥落、空腔三类占了标注总量的六成以上而 object 只有 13 个标注基本不具备单独训练一个类别的条件。常见做法是把 object 合并进“其他”或直接忽略活动裂缝、水侵蚀混凝土、伸缩缝这类少于 500 的类别要么在采样时做类别平衡要么在损失函数里给稀有类更高权重。如果直接按原始分布训模型会强烈偏向锈蚀和剥落稀有类的 IoU 会低得很难看这是血泪经验。2.3 为什么选 labelme 而不是现成 masklabelme 的 polygon 标注保留了原始顶点转 mask、转 COCO、转 YOLO 都只是后处理问题而如果资源直接给 mask你就被锁死在某个分辨率、某个类别映射上想改类别或做实例分割就得重新标。这份数据集给 json 而不是 mask本质是把“标注到训练格式”的转换权交给你。代价是你要自己写转换脚本但换来的是灵活性同一份 json 可以生成语义分割的单通道 mask也可以生成实例分割的 COCO json还能裁成 YOLO 的检测框。对于要做多任务对比的实验这个选择是合理的。3. 把 json 转成可训练格式三条转换路线与脚本3.1 转语义分割 mask逐类别栅格化语义分割要的是每张图一个单通道 mask像素值等于类别索引。用 labelme 自带的labelme_json_to_dataset能转但它输出的是彩色 mask 加 label 映射批量处理和多类别合并比较麻烦。我一般直接写脚本用PIL.ImageDraw.polygon栅格化控制力更强import json, os import numpy as np from PIL import Image, ImageDraw # 20 类映射顺序固定训练和推理必须一致 CLASSES [Graffiti,Bearing,Wetspot,Rust,Spalling,Efflorescence, Hollowareas,Weathering,Restformwork,Cavity,ExposedRebars, EJoint,Crack,Drainage,ACrack,JTape,Rockpocket, PEquipment,WConccor,object] CLS2ID {c: i 1 for i, c in enumerate(CLASSES)} # 0 留给背景 def json_to_mask(json_path, out_path): with open(json_path, r, encodingutf-8) as f: data json.load(f) h, w data[imageHeight], data[imageWidth] mask Image.new(L, (w, h), 0) # 背景 0 draw ImageDraw.Draw(mask) for shape in data[shapes]: label shape[label] if label not in CLS2ID: continue # 未知类别跳过避免脏数据污染 pts [tuple(p) for p in shape[points]] if len(pts) 3: continue # 少于三个点无法构成多边形 draw.polygon(pts, fillCLS2ID[label]) mask.save(out_path) # 批量处理 src_dir, dst_dir labels_json, masks os.makedirs(dst_dir, exist_okTrue) for fn in os.listdir(src_dir): if fn.endswith(.json): json_to_mask(os.path.join(src_dir, fn), os.path.join(dst_dir, fn.replace(.json, .png)))这段脚本的关键在CLS2ID类别索引从 1 开始0 固定为背景训练时num_classes要设成 21 而不是 20。draw.polygon按 shapes 顺序绘制后画的会覆盖先画的如果两个类别区域重叠最终 mask 取的是后出现的那个这是语义分割的固有取舍需要实例级区分就得走 COCO 路线。len(pts) 3的过滤是必要的labelme 里偶尔会有误操作留下的两点线段不滤掉会抛异常。3.2 转 COCO 格式实例分割的边界处理要做实例分割COCO 的segmentation字段需要多边形点集列表且坐标要按[x1,y1,x2,y2,...]展平。转换时有两个坑一是 COCO 要求category_id从 1 开始连续二是多边形不能自相交labelme 手画的多边形偶尔会有自交直接喂给 pycocotools 会报错。常见做法是用 shapely 做一次buffer(0)修复import json, os from shapely.geometry import Polygon def json_to_coco(json_path, image_id, ann_id_start): with open(json_path, r, encodingutf-8) as f: data json.load(f) anns [] ann_id ann_id_start for shape in data[shapes]: if shape[label] not in CLS2ID: continue pts [tuple(p) for p in shape[points]] if len(pts) 3: continue poly Polygon(pts) if not poly.is_valid: poly poly.buffer(0) # 修复自相交 if poly.is_empty: continue pts list(poly.exterior.coords)[:-1] # 去掉闭合重复点 flat [coord for pt in pts for coord in pt] xs flat[0::2]; ys flat[1::2] x1, y1, x2, y2 min(xs), min(ys), max(xs), max(ys) anns.append({ id: ann_id, image_id: image_id, category_id: CLS2ID[shape[label]], segmentation: [flat], bbox: [x1, y1, x2 - x1, y2 - y1], area: poly.area, iscrowd: 0 }) ann_id 1 return anns, ann_idbuffer(0)是修复自交多边形的常用手段它会把自交部分重新组织成合法几何但可能产生 MultiPolygon这里只取exterior会丢掉内环对建筑缺陷这种实心区域影响不大。bbox由多边形顶点极值算出area用 shapely 的面积而不是 bbox 面积COCO 评估时area影响小目标的分档统计别写错。3.3 转 YOLO 检测框从 polygon 到 bbox 的取舍如果只想做缺陷检测而不是分割可以把 polygon 退化成外接矩形生成 YOLO 的class cx cy w h归一化格式。这一步的取舍是polygon 的精细边界信息丢了但训练速度快、对小目标更鲁棒。转换时注意归一化除以的是图像宽高不是 640 硬编码def json_to_yolo(json_path, out_txt): with open(json_path, r, encodingutf-8) as f: data json.load(f) w, h data[imageWidth], data[imageHeight] lines [] for shape in data[shapes]: if shape[label] not in CLS2ID: continue pts shape[points] if len(pts) 3: continue xs [p[0] for p in pts]; ys [p[1] for p in pts] x1, y1, x2, y2 min(xs), min(ys), max(xs), max(ys) cx (x1 x2) / 2 / w cy (y1 y2) / 2 / h bw (x2 - x1) / w bh (y2 - y1) / h cls_id CLS2ID[shape[label]] - 1 # YOLO 从 0 开始 lines.append(f{cls_id} {cx:.6f} {cy:.6f} {bw:.6f} {bh:.6f}) with open(out_txt, w) as f: f.write(\n.join(lines))YOLO 的类别索引从 0 开始而前面语义分割和 COCO 都从 1 开始这个偏移量是最容易翻车的地方训练时类别对不上、mAP 全零八成是这里错了。另外bw、bh要检查是否超过 1polygon 顶点偶尔会超出图像边界超界框会被 YOLO 过滤掉建议加个 clamp。4. 训练前的数据切分与类别平衡别让长尾拖垮指标4.1 按图像切分而不是按标注切分7820 张图要切成 train/val/test必须按图像切不能按标注切。同一张图里的多个缺陷实例高度相关如果按标注随机切同一张图的锈蚀进了训练集、裂缝进了验证集验证指标会虚高。常见比例是 8:1:1 或 7:2:1建筑缺陷场景样本量够8:1:1 就够用。切分时用固定随机种子把文件名列表存成 txt保证多次实验可复现import os, random files sorted([f for f in os.listdir(images) if f.endswith(.jpg)]) random.seed(42) random.shuffle(files) n len(files) train, val, test files[:int(n*0.8)], files[int(n*0.8):int(n*0.9)], files[int(n*0.9):] for name, split in [(train, train), (val, val), (test, test)]: with open(f{name}.txt, w) as f: f.write(\n.join(split))种子固定后任何人拿到这份 txt 都能复现同一套切分这在多人协作或论文复现里是基本要求。4.2 稀有类的过采样与损失加权object 只有 13 个标注、活动裂缝 423 个直接训这两个类几乎学不到东西。两条路一是过采样把含稀有类的图像在训练时重复采样 23 倍二是损失加权用类别频率的倒数做权重或者用 focal loss 让模型关注难样本。我一般两者结合过采样控制在 2 倍以内避免过拟合损失权重用1/log(1.02 freq)这种平滑形式比纯倒数稳定。验证集不要过采样保持原始分布才能反映真实泛化能力。4.3 640×640 分辨率下的增强策略原图就是 640×640增强时要注意别把缺陷裁没了。随机裁剪建议保留至少 70% 面积翻转和旋转 90 度对建筑缺陷是安全的颜色抖动幅度要小因为锈蚀、泛碱这类类别的判别很依赖颜色HSV 的 H 通道抖动超过 10 就可能把泛碱和潮湿斑迹搞混。Mosaic 增强对小目标有帮助但会把 640 的图拼成 1280训练时再缩回 640缺陷会变得更小稀有类本来样本就少Mosaic 反而可能帮倒忙建议对稀有类关闭 Mosaic。5. 避坑与排查这份数据集最容易翻车的五个地方5.1 转换后 mask 全黑或类别错位现象转出来的 mask 全是 0或者训练时 loss 不降。原因通常是imagePath和实际 jpg 文件名对不上或者CLS2ID的类别顺序和训练配置里的classes.txt不一致。labelme 的imagePath可能带子目录前缀批量转换时要os.path.basename取文件名再去匹配。类别顺序务必以数据集给出的 20 类列表为准自己重排一次就会导致标签错位这种错误在指标上表现为“所有类 IoU 都接近随机”。5.2 多边形自交导致 COCO 转换报错现象pycocotools 加载时报polygon is invalid或面积算出来是负数。原因是手画多边形存在自交或顶点顺序混乱。解决是用 shapely 的buffer(0)修复修复后检查is_valid和area 0仍不合法的直接丢弃并在日志里记下文件名别让一条脏数据卡住整个转换流程。5.3 类别索引 0/1 起点混用现象YOLO 训练时类别全预测成背景或者 COCO 评估时 category_id 对不上。语义分割背景是 0、类别从 1 开始COCO 的 category_id 从 1 开始YOLO 的 class id 从 0 开始。三套体系混在一个项目里偏移量错一次就全盘皆输。我的习惯是在转换脚本顶部统一定义CLS2ID各格式转换时显式做-1或1并在生成后抽三张图可视化核对。5.4 稀有类在验证集里消失现象验证时某些类别 IoU 为 0 或 nan。原因是按图像随机切分时object 只有 13 个标注、可能集中在少数几张图随机切分后验证集里一个都没有。解决是切分前先统计每个类别的图像分布对稀有类做分层抽样保证每个 split 里稀有类至少出现几次实在不够就把稀有类图像优先放进训练集验证集用整体指标加稀有类召回单独看。5.5 把 json 当 mask 直接喂给分割框架现象框架报通道数不对或标签越界。这份数据集只有 jpg 和 json没有 mask任何分割框架都需要你先转出 mask 或 COCO。直接拿 json 路径去配 dataset 是行不通的转换是必经步骤别指望框架自动读 labelme 格式。转完后用np.unique(mask)检查像素值是否落在[0, num_classes]范围内超出就是类别映射写错了。6. 验证转换正确性的三个硬指标与一个习惯转换脚本写完别急着开训先用三个硬指标把数据链路验一遍。第一个是 mask 像素值分布随机抽 20 张转出的 masknp.unique的结果必须全部落在[0, 20]内且背景占比不能是 100%否则说明多边形没画上去。第二个是可视化叠加把 mask 以半透明方式叠回原图用固定调色板着色人眼扫一遍锈蚀区域是不是真的盖在锈迹上、裂缝是不是沿着裂缝走这一步能抓出坐标翻转、宽高颠倒这类脚本 bug。第三个是类别计数对账转换后统计每个类别的实例数和数据集给出的标注数逐个比对锈蚀应该是 14665、剥落 9849差几个可能是过滤了非法多边形差几百就是类别映射或文件匹配出了问题。import numpy as np from PIL import Image # 对账统计转换后 mask 里每个类别的像素占比与实例覆盖 for fn in sample_files[:20]: mask np.array(Image.open(fmasks/{fn.replace(.jpg, .png)})) uniq np.unique(mask) assert uniq.max() 20, f{fn} 类别越界: {uniq} assert (mask 0).mean() 0.001, f{fn} 疑似空 mask这段断言脚本建议挂到转换流程末尾任何一张图不满足就中断比训到一半发现标签错了再回头查要省事得多。参数上(mask 0).mean() 0.001是个经验阈值640×640 里至少 400 个前景像素才算有效标注低于这个值大概率是空 json 或坐标写错。从那以后我每次拿到 labelme 格式的数据集都强制先跑一遍“像素值范围 可视化叠加 类别计数对账”这三步再动训练脚本。这份 7820 张、20 类的建筑墙壁缺陷数据价值不在它有多大而在于它把建筑病害的长尾分布真实地摆在你面前——锈蚀和剥落管够object 和活动裂缝逼着你处理不平衡。把转换链路和切分策略做扎实它就能撑起一个可复现的分割基线跳过验证直接开训大概率是在给脏数据调参。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑