建筑墙壁缺陷分割数据集:7820张labelme标注的20类缺陷分割实战 简介这份建筑墙壁损伤缺陷分割数据集面向计算机视觉与机器学习方向的研究者、算法工程师及建筑安全检测从业者用于训练和评估图像分割模型实现对剥落、锈蚀、裂缝、排水等墙体病害的自动识别与分类。资源包共1个docx文件约2.1MB文档内整理了数据集说明与标注信息核心数据为7820张640×640像素的jpg图片及一一对应的json标注文件采用labelme多边形框标注覆盖涂鸦、支座、潮湿斑迹、锈蚀、剥落、泛碱、空洞区域、风化、残留模板、空腔、外露钢筋、伸缩缝、裂缝、排水、活动裂缝、接头胶带、岩窝、防护设备、水侵蚀混凝土和物体共20个类别其中锈蚀、剥落、空腔等类别标注框数较多。目前已有102人学习。读者可据此构建语义或实例分割流程自行将json转为mask、YOLO或COCO格式用于建筑健康监测与自动化巡检研究。1. 建筑墙壁缺陷分割数据集7820 张 labelme 标注到底能跑出什么手里拿到一份标注好的建筑墙壁损伤数据集第一反应往往不是“太好了”而是“这 20 类到底能不能训起来”。这份资源给的是 7820 张 640×640 的 jpg 图片配 7820 个 labelme 格式的 json标注方式是 polygon 多边形覆盖 Graffiti、Rust、Spalling、Crack、ExposedRebars 等 20 个类别。它解决的是建筑外立面巡检里最费人力的那一步——把“哪里有缺陷、缺陷边界在哪”变成可训练的像素级监督信号。适合做语义分割、实例分割的算法工程师也适合做建筑健康监测、无人机巡检、边缘端缺陷识别的团队。但要注意它只给图片和 json不给 mask也不保证任何模型精度转换和清洗得自己来。2. labelme 格式拆解json 里到底存了什么为什么不能直接喂给分割网络2.1 一个 json 文件的内部结构labelme 的 json 不是图片也不是 mask它是一份“矢量标注记录”。每张图对应一个同名 json里面最关键的字段是shapes和imageData。shapes是一个列表每个元素代表一个多边形包含label、points、shape_type、flags。points是像素坐标点对shape_type通常是polygon。imageData在多数导出设置下是空的因为图片单独存成 jpg。真正决定类别的是label字符串比如Crack、Rust、Spalling。{ version: 5.5.0, flags: {}, shapes: [ { label: Crack, points: [[120.5, 88.2], [180.3, 95.7], [176.1, 130.4]], group_id: null, shape_type: polygon, flags: {} } ], imagePath: wall_0001.jpg, imageData: null, imageHeight: 640, imageWidth: 640 }逻辑说明points的顺序决定多边形闭合方向labelme 默认按点序连成闭合区域。imageHeight和imageWidth必须和 jpg 实际尺寸一致否则转 mask 时会整体偏移。参数说明version是 5.5.0和资源说明一致shape_type若出现rectangle或circle转换脚本要单独处理但这份数据集标注规则写的是 polygon理论上全是多边形。2.2 20 类标注的分布意味着什么类别数量不是均匀的。Rust 有 14665 个多边形Spalling 有 9849 个Cavity 有 9222 个而 object 只有 13 个ACrack 只有 423 个EJoint 只有 506 个。这种长尾分布直接决定训练策略如果按普通交叉熵训 20 类object 和 ACrack 大概率被淹没。常见做法是先把类别按频次分三档——高频Rust、Spalling、Cavity、中频Crack、Weathering、Efflorescence、Graffiti、ExposedRebars、PEquipment、Wetspot、Drainage、Hollowareas、Bearing、JTape、Restformwork、低频Rockpocket、EJoint、ACrack、WConccor、object。低频类要么过采样要么在 loss 里给更高权重要么先合并成“其他缺陷”再逐步细分。import json, os from collections import Counter label_counter Counter() json_dir path/to/jsons for fname in os.listdir(json_dir): if not fname.endswith(.json): continue with open(os.path.join(json_dir, fname), r, encodingutf-8) as f: data json.load(f) for shape in data[shapes]: label_counter[shape[label]] 1 for label, count in label_counter.most_common(): print(f{label}: {count})逻辑说明这段脚本用来复核资源说明里的类别框数是否和实际 json 一致。参数说明json_dir换成你解压后的 json 目录如果某些 json 的shapes为空说明该图可能只有背景训练时要决定是否保留。跑完如果发现某个类别数量对不上优先检查是否有 json 损坏或 label 拼写不一致比如ExposedRebars和Exposedrebar会被当成两类。2.3 为什么必须自己转 mask 或 yolo 格式labelme 的 json 是矢量分割网络要的是栅格 mask 或检测框。语义分割需要每张图一个单通道 png像素值对应类别 id实例分割需要 mask 加类别yolo 格式需要归一化后的多边形点或框。资源说明里明确写了“json 数据集需自己转成 mask 或者 yolo 格式或者 coco 格式”这不是偷懒是因为不同框架对输入格式要求不同。常见做法是用 labelme 自带的labelme2voc.py或labelme2coco.py但这两个脚本对中文路径和空 shapes 处理不够稳我一般会自己写转换。# 用 labelme 自带脚本转 VOC 格式生成 mask png python labelme2voc.py data_annotated data_dataset_voc --labels labels.txt --noviz逻辑说明data_annotated是放 jpg 和 json 的目录data_dataset_voc是输出目录labels.txt每行一个类别名顺序决定 mask 像素值。参数说明--noviz跳过可视化省时间如果报KeyError多半是某个 json 的 label 不在 labels.txt 里需要先补全类别列表。转完后SegmentationClass里是 pngSegmentationObject里是实例 mask但这份数据集只有 polygon 没有 group_id实例区分要靠连通域自己拆。3. 从 json 到训练集转换脚本、类别映射与 640×640 的边界处理3.1 写一个可控的 json 转 mask 脚本自带脚本能用但遇到 20 类长尾和空标注时容易翻车。我一般用下面这个脚本显式控制类别顺序、忽略空 shapes、并检查图片尺寸。import json, os import numpy as np from PIL import Image, ImageDraw # 类别顺序必须固定训练和推理要一致 CLASSES [ Graffiti, Bearing, Wetspot, Rust, Spalling, Efflorescence, Hollowareas, Weathering, Restformwork, Cavity, ExposedRebars, EJoint, Crack, Drainage, ACrack, JTape, Rockpocket, PEquipment, WConccor, object ] CLASS_TO_ID {name: idx 1 for idx, name in enumerate(CLASSES)} # 0 留给背景 def json_to_mask(json_path, jpg_path, out_path): with open(json_path, r, encodingutf-8) as f: data json.load(f) img Image.open(jpg_path) w, h img.size mask Image.new(L, (w, h), 0) draw ImageDraw.Draw(mask) for shape in data[shapes]: label shape[label] if label not in CLASS_TO_ID: continue points [tuple(p) for p in shape[points]] if len(points) 3: continue draw.polygon(points, fillCLASS_TO_ID[label]) mask.save(out_path) # 批量处理 src_dir path/to/data out_dir path/to/masks os.makedirs(out_dir, exist_okTrue) for fname in os.listdir(src_dir): if not fname.endswith(.json): continue base fname[:-5] json_path os.path.join(src_dir, fname) jpg_path os.path.join(src_dir, base .jpg) out_path os.path.join(out_dir, base .png) if os.path.exists(jpg_path): json_to_mask(json_path, jpg_path, out_path)逻辑说明CLASS_TO_ID从 1 开始0 是背景这是语义分割的通用约定。draw.polygon按点序填充重叠区域后画的会覆盖先画的如果两个缺陷重叠需要决定优先级。参数说明src_dir是 jpg 和 json 混放目录out_dir输出 png mask如果图片尺寸不是 640×640mask 会按实际尺寸生成训练时再统一 resize。3.2 类别映射表与 id 对照类别名中文含义建议 id频次档Graffiti涂鸦1中Bearing支座2中Wetspot潮湿斑迹3中Rust锈蚀4高Spalling剥落5高Efflorescence泛碱6中Hollowareas空洞区域7中Weathering风化8中Restformwork残留模板9中Cavity空腔10高ExposedRebars外露钢筋11中EJoint伸缩缝12低Crack裂缝13中Drainage排水14中ACrack活动裂缝15低JTape接头胶带16中Rockpocket岩窝17低PEquipment防护设备18中WConccor水侵蚀混凝土19低object物体20低这张表建议直接存成labels.txt每行一个类别名顺序和 id 一致。训练时读这个文件生成类别权重推理时按 id 映射回中文。注意object只有 13 个标注如果做 20 类分割它几乎学不到东西常见做法是训练时先把它归到背景或“其他”等模型稳定后再单独微调。3.3 640×640 分辨率下的多边形边界坑640×640 不算大裂缝这种细长目标在 polygon 里可能只有几个像素宽。转 mask 时如果直接用draw.polygon细裂缝可能因为点太少而断裂。我一般会在转换前做一次点密度检查如果某个多边形的外接框面积小于 16 像素或者点数少于 4就标记为“可疑标注”单独输出列表人工复核。另一个坑是坐标越界labelme 里手动标注时可能把点拖到图片外draw.polygon不会报错但会裁掉导致 mask 边缘缺失。转换前加一句points [(min(max(x,0),w-1), min(max(y,0),h-1)) for x,y in points]能挡掉大部分。# 坐标裁剪与可疑标注记录 suspect [] for shape in data[shapes]: points shape[points] xs [p[0] for p in points] ys [p[1] for p in points] if max(xs) 0 or min(xs) w or max(ys) 0 or min(ys) h: suspect.append((fname, shape[label], out_of_bound)) continue if (max(xs)-min(xs)) * (max(ys)-min(ys)) 16: suspect.append((fname, shape[label], too_small))逻辑说明这段代码不修改原 json只做质检。参数说明too_small阈值 16 是经验值裂缝类可以放宽到 9但再小就建议直接忽略否则训练时噪声大于信号。4. 避坑与排查20 类长尾、空标注和格式转换的 5 个血泪经验4.1 现象训练 loss 不降Rust 和 Spalling 的 IoU 很高但 ACrack 和 object 几乎为 0原因类别频次差距太大Rust 有 14665 个多边形object 只有 13 个普通交叉熵会被高频类主导。解决在 loss 里用类别权重权重按频次倒数开方或者用 focal loss。更稳的做法是先训 10 类高频中频把 backbone 训稳再解冻最后几层微调低频类。我一般会保留一个“其他缺陷”类把 ACrack、EJoint、Rockpocket、WConccor、object 先合并等模型能稳定分割裂缝和剥落后再拆开。4.2 现象转 mask 后某些图全黑但 json 里明明有 shapes原因json 里的 label 拼写和labels.txt不一致比如ExposedRebars写成Exposedrebar或者WConccor写成WConccor带空格。解决转换前先跑一遍全量 label 统计和标准 20 类做差集。差集里的 label 要么修正 json要么在脚本里做映射。注意 labelme 保存时不会自动 trim 空格手动改过 json 的尤其容易出这个问题。4.3 现象mask 和原图对不上整体偏移几个像素原因json 里的imageWidth、imageHeight和 jpg 实际尺寸不一致或者 jpg 被外部工具重新保存过。解决转换时不要信 json 里的尺寸直接用Image.open(jpg).size。如果发现大量尺寸不一致说明图片被批量 resize 过但 json 没同步这种图建议直接剔除否则训练时 resize 会引入额外偏移。4.4 现象用 labelme2coco.py 转 coco 格式时报polygon相关错误原因labelme 5.5.0 的 json 里shape_type可能是polygon但某些版本导出时会出现rectangle或circlecoco 转换脚本对非 polygon 支持不完整。解决先统一 shape_type。用脚本遍历所有 json把rectangle的四个角转成 polygon 点序circle按外接多边形近似。这份数据集标注规则写的是 polygon但人工标注时可能误用矩形工具跑一遍检查能省很多事。4.5 现象训练时显存爆了但图片只有 640×640原因20 类语义分割如果直接输出 20 通道加上 batch size 和数据增强显存占用比想象中大。解决先确认模型输出通道是 2120 类背景不是 20。如果显存不够优先降 batch size 到 4 或 2用梯度累积补回来。另外mask 用uint8存不要用int64否则 dataloader 会多吃 8 倍内存。我一般会在 Dataset 里把 mask 转成torch.long之前先确认它是uint8。5. 进阶用法用 640×640 小图做滑窗推理与类别权重验证这份数据集图片是 640×640但实际建筑巡检图往往更大。一个实用技巧是用这 7820 张图训一个 640×640 的分割模型推理时对高分辨率图做滑窗窗口 640×640重叠 128 像素每个窗口单独推理后拼回全图。这样既能利用现有数据又能覆盖大图。滑窗推理的关键是重叠区融合常见做法是取最大概率或加权平均我一般用高斯权重中心高、边缘低减少拼接缝。import torch import torch.nn.functional as F def sliding_window_inference(model, image, window640, stride512, num_classes21): # image: 1x3xHxW, 已归一化 _, _, H, W image.shape output torch.zeros(1, num_classes, H, W, deviceimage.device) count torch.zeros(1, 1, H, W, deviceimage.device) weight torch.ones(1, 1, window, window, deviceimage.device) # 高斯权重边缘衰减 for i in range(window): for j in range(window): weight[0,0,i,j] torch.exp(-((i-window/2)**2 (j-window/2)**2) / (2*(window/4)**2)) for y in range(0, H, stride): for x in range(0, W, stride): y1, y2 y, min(ywindow, H) x1, x2 x, min(xwindow, W) patch image[:, :, y1:y2, x1:x2] if patch.shape[2] window or patch.shape[3] window: patch F.pad(patch, (0, window-patch.shape[3], 0, window-patch.shape[2])) with torch.no_grad(): pred model(patch) pred F.softmax(pred, dim1) output[:, :, y1:y2, x1:x2] pred[:, :, :y2-y1, :x2-x1] * weight[:, :, :y2-y1, :x2-x1] count[:, :, y1:y2, x1:x2] weight[:, :, :y2-y1, :x2-x1] return output / count.clamp(min1e-6)逻辑说明stride512表示重叠 128 像素weight是高斯核中心权重高边缘低拼回时不会出现明显接缝。参数说明window固定 640和训练分辨率一致num_classes21对应 20 类加背景如果显存允许可以一次跑多个窗口但要注意output和count的显存占用是原图的num_classes倍大图建议分块处理。另一个进阶点是类别权重验证。训练前先用上面统计的频次算一组权重训练后再用混淆矩阵看哪些类被误判。我一般会固定一个验证集每训 5 个 epoch 跑一次 per-class IoU如果某个低频类连续 3 次为 0就把它合并到相近类比如 ACrack 合并到 CrackRockpocket 合并到 Cavity。这样比硬训 20 类更实际。从那以后我每次拿到 labelme 格式的数据集都先跑一遍 label 统计、尺寸检查和空 shapes 检查再开始写转换脚本。这三步花不了十分钟但能挡掉后面几天的玄学调参。希望帮到你。本文还有配套的精品资源点击获取