建筑墙壁损伤分割数据集:7820张labelme标注的毕设全流程方案

发布时间:2026/10/25 19:02:56
建筑墙壁损伤分割数据集:7820张labelme标注的毕设全流程方案

简介:面向建筑视觉检测与图像分割方向研究者、算法工程师及高校学生的建筑墙壁损伤缺陷分割数据集,以labelme标注格式提供,可用于训练和评估裂缝、剥落、锈蚀等损伤的自动识别模型,服务于建筑健康监测与智能巡检场景。资源包共1个docx文件,约2.1MB,内含数据集说明与使用指引,7820张640×640像素jpg图片均配有同名json标注文件,采用多边形框精细描绘损伤区域,覆盖涂鸦、支座、潮湿斑迹、锈蚀、剥落、泛碱、空洞区域、风化、残留模板、空腔、外露钢筋、伸缩缝、裂缝、排水、活动裂缝、接头胶带、岩窝、防护设备、水侵蚀混凝土及物体共20个类别,其中锈蚀、剥落、空腔等类别标注量较大。已有103人学习关注。读者可据此了解数据组织方式与类别分布,借助labelme 5.5.0查看编辑标注,并自行转换为mask、YOLO或COCO格式用于语义分割或实例分割实验,为建筑损伤检测研究提供准确合理的图像基础。

1. 建筑墙壁损伤分割数据集:7820 张 labelme 标注到底能撑起什么样的毕设

做建筑缺陷检测方向的同学,十有八九卡在第一步:数据从哪来。自己扛着相机去拍,拍回来还要一张张画多边形,7820 张图光标注就能耗掉一整个学期。这份建筑墙壁损伤缺陷分割数据集正好卡在这个痛点上——7820 张 640×640 的 jpg,配 7820 个 labelme 5.5.0 的 json,20 个损伤类别,全部是多边形 polygon 标注。它适合三类人:做语义分割/实例分割毕设的学生、要给建筑巡检做算法原型的从业者、以及需要一份结构完整的分割数据集方案做课程设计的读者。下面按「需求怎么立、数据怎么摸清、格式怎么转、模型怎么训、最后怎么验收」这条线,把整套方案拆开讲。

2. 需求与数据摸底:20 类损伤的分布决定了你的方案上限

拿到一份数据集,最忌讳上来就train.py一把梭。血泪经验是:先花半天把类别分布、标注质量、长尾情况摸清楚,后面能省掉一周的返工。这一章先把「这份数据能做什么、不能做什么」讲透,再谈模型。

2.1 从建筑巡检场景反推任务定义

建筑外墙和结构面的病害巡检,人工方式是搭脚手架或者无人机拍完人工看图,效率低且漏检率高。自动化方案的核心任务就是像素级分割:给定一张墙面图,输出每个像素属于哪类损伤。这份数据集的 20 个类别恰好覆盖了巡检报告里最常见的病害项——Crack(裂缝)、Spalling(剥落)、Rust(锈蚀)、Efflorescence(泛碱)、ExposedRebars(外露钢筋)、Hollowareas(空洞区域)这些是结构安全相关的重点,Graffiti(涂鸦)、Drainage(排水)、PEquipment(防护设备)则偏外观与附属设施。

任务定义上要先做一个决策:走语义分割还是实例分割。语义分割只关心「这个像素是不是裂缝」,实例分割还要区分「这是第几条裂缝」。从标注形式看,labelme 的 polygon 天然支持实例级,因为每个 shape 是独立对象;但从工程落地看,巡检报告通常只需要「有没有、在哪、多大面积」,语义分割足够,且训练成本低一半。我一般建议毕设走语义分割,把实例分割作为加分项在答辩时提一句「数据支持扩展」。

提示:object类只有 13 个标注,基本是标注过程中的杂项兜底类,训练时建议直接过滤掉,否则会引入噪声梯度。

2.2 类别分布的长尾问题必须先量化

把每个类别的标注数拉出来看,问题立刻暴露:

类别标注数占比定位
Rust 锈蚀14665头部
Spalling 剥落9849头部
Cavity 空腔9222头部
Weathering 风化4621中段
Efflorescence 泛碱3956中段
Crack 裂缝3612中段
Graffiti 涂鸦2197中段
ExposedRebars 外露钢筋1993中段
PEquipment 防护设备1892中段
Wetspot 潮湿点1657中段
Drainage 排水1649尾部
Hollowareas 空洞区域1538尾部
Bearing 支座1201尾部
JTape 接头胶带1076尾部
Restformwork 残留模板1019尾部
Spalling 之外的 Cavity 已列——
Rockpocket 岩窝616长尾
EJoint 伸缩缝506长尾
ACrack 活动裂缝423长尾
WConccor 水侵蚀混凝土407长尾
object 物体13噪声

头部 Rust 是尾部 ACrack 的 34 倍,这个比例意味着:如果直接用交叉熵训练,模型会倾向于把所有像素预测成 Rust 和背景,尾部类别的 IoU 会趋近于 0。方案上必须提前设计应对——要么在损失函数里加权,要么在采样时做类别平衡,要么在评估时单独报告尾部类别。这一步不做,后面调参全是玄学。

2.3 标注质量抽检与清洗规则

labelme 5.5.0 的 json 结构里,每个 shape 有label、points、shape_type。抽检时重点看三件事:一是shape_type是否全是polygon(如果有rectangle混入,转 mask 时会出问题);二是 points 是否闭合合理(少于 3 个点的多边形要丢弃);三是同一区域是否被重复标注成两个类别(比如裂缝和活动裂缝 ACrack 容易混)。

清洗脚本可以这样写,跑一遍全量 json,输出问题清单:

import json, os, glob def audit_labelme(json_dir): issues = [] for fp in glob.glob(os.path.join(json_dir, "*.json")): with open(fp, "r", encoding="utf-8") as f: data = json.load(f) for i, shape in enumerate(data.get("shapes", [])): pts = shape.get("points", []) # 规则1:多边形至少3个点 if shape.get("shape_type") != "polygon" or len(pts) < 3: issues.append((fp, i, "bad_shape", shape.get("label"))) # 规则2:坐标越界检查(图片640x640) for x, y in pts: if not (0 <= x <= 640 and 0 <= y <= 640): issues.append((fp, i, "out_of_bound", shape.get("label"))) break return issues if __name__ == "__main__": problems = audit_labelme("./jsons") print(f"问题标注数: {len(problems)}") for p in problems[:20]: print(p)

这段脚本做的是「体检」而不是「治疗」:bad_shape说明该 shape 无法转成有效 mask,训练前要剔除;out_of_bound说明坐标超出 640×640,可能是标注时图片被缩放导致的,需要人工复核。跑完拿到问题清单,再决定是批量修复还是直接丢弃对应图片。通常 7820 张里问题样本在几十张量级,丢弃对总量影响可忽略。

3. 格式转换方案:从 labelme json 到 mask / YOLO / COCO 的三条路径

数据集只给 jpg + json,不含 mask,这是最容易被低估的工作量。转换做错,后面训练全白搭。这一章把三条主流路径讲清楚,并给出可直接复用的转换代码。

3.1 语义分割路径:json 转单通道 mask

语义分割需要的是每张图对应一张 mask,像素值等于类别 id。转换核心是用PIL.ImageDraw.polygon把每个 shape 填充到对应类别 id 上。注意两个坑:一是填充顺序,后填的会覆盖先填的,重叠区域要以「更具体的类别优先」为原则排序;二是背景设为 0,类别 id 从 1 开始,避免和背景混淆。

import json, os import numpy as np from PIL import Image, ImageDraw # 20类映射,背景0,object类不参与训练可跳过 CLASS_MAP = { "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 不映射,视为背景 } def json_to_mask(json_path, out_path, size=(640, 640)): with open(json_path, "r", encoding="utf-8") as f: data = json.load(f) mask = Image.new("L", size, 0) # 0 = 背景 draw = ImageDraw.Draw(mask) # 按类别id升序填充,保证重叠时高id覆盖低id(可按业务调整) shapes = sorted(data["shapes"], key=lambda s: CLASS_MAP.get(s["label"], 0)) for shape in shapes: cid = CLASS_MAP.get(shape["label"]) if cid is None: continue pts = [tuple(p) for p in shape["points"]] if len(pts) >= 3: draw.polygon(pts, fill=cid) mask.save(out_path) if __name__ == "__main__": os.makedirs("./masks", exist_ok=True) for fp in os.listdir("./jsons"): if fp.endswith(".json"): json_to_mask(f"./jsons/{fp}", f"./masks/{fp.replace('.json', '.png')}")

CLASS_MAP是整条流水线的「字典」,训练、评估、可视化都必须引用同一份,建议单独存成classes.json避免多处硬编码不一致。size固定 640×640 与原始图片一致,不要在这里做缩放,缩放留给训练时的 data augmentation。填充顺序按类别 id 排序是一种简化策略,如果业务上「裂缝」应该覆盖「风化」,就把 Crack 的 id 调大或者单独写优先级字段。

3.2 实例分割路径:转 COCO 格式

如果要做实例分割,COCO 是事实标准。转换要点:每个 shape 变成一个 annotation,segmentation存多边形点列表,bbox由多边形外接矩形算出,category_id与 CLASS_MAP 对齐。COCO 的iscrowd设为 0,area用多边形面积。

import json, os from shapely.geometry import Polygon def build_coco(json_dir, out_file): coco = {"images": [], "annotations": [], "categories": []} for name, cid in CLASS_MAP.items(): coco["categories"].append({"id": cid, "name": name, "supercategory": "defect"}) ann_id = 1 for img_id, fp in enumerate(sorted(os.listdir(json_dir)), start=1): if not fp.endswith(".json"): continue with open(os.path.join(json_dir, fp), "r", encoding="utf-8") as f: data = json.load(f) coco["images"].append({ "id": img_id, "file_name": fp.replace(".json", ".jpg"), "width": 640, "height": 640 }) for shape in data["shapes"]: cid = CLASS_MAP.get(shape["label"]) pts = shape["points"] if cid is None or len(pts) < 3: continue poly = Polygon(pts) if not poly.is_valid: poly = poly.buffer(0) # 自相交多边形修复 xs = [p[0] for p in pts]; ys = [p[1] for p in pts] coco["annotations"].append({ "id": ann_id, "image_id": img_id, "category_id": cid, "segmentation": [[c for p in pts for c in p]], "bbox": [min(xs), min(ys), max(xs)-min(xs), max(ys)-min(ys)], "area": poly.area, "iscrowd": 0 }) ann_id += 1 with open(out_file, "w", encoding="utf-8") as f: json.dump(coco, f, ensure_ascii=False)

poly.buffer(0)是处理自相交多边形的常用手段,labelme 手画的多边形偶尔会出现「8 字形」,直接算面积会报错或得到负值。segmentation的扁平化写法[x1,y1,x2,y2,...]是 COCO 对单多边形的标准格式,别写成嵌套列表。

3.3 YOLO 路径与三条路径的取舍

YOLO 分割格式(YOLOv8-seg)要求每张图一个 txt,每行是class_id x1 y1 x2 y2 ...,坐标归一化到 0~1。转换逻辑与 COCO 类似,只是输出形态不同。三条路径怎么选,给一张对照表:

路径适用任务训练框架转换成本毕设推荐度
mask 单通道语义分割U-Net / DeepLab / SegFormer低高,最稳
COCO实例分割Mask R-CNN / YOLOv8-seg中中,加分项
YOLO txt实例分割YOLOv8-seg / YOLOv11-seg中中,部署友好

我的建议是:主线走 mask + 语义分割,保证能出结果;如果时间充裕,再补一条 COCO 做实例分割对比。两条线共用同一份 CLASS_MAP,评估指标口径一致,答辩时能讲出「同一数据两种任务范式」的深度。

4. 训练方案设计:类别不平衡下的分割模型落地

数据准备好了,接下来是模型。这一章不讲模型原理,讲的是「在这份 20 类、长尾严重、640×640 的数据上,怎么配出一套能跑出合理结果的训练方案」。

4.1 基线模型选型与输入尺寸策略

640×640 的输入对分割模型是友好的——不算大,单卡 8G 显存能跑 batch size 8 左右。基线我一般选两个:U-Net(ResNet34 或 EfficientNet-B0 编码器)作为「能跑通、好解释」的保底方案,SegFormer-B0 作为「指标更好看」的进阶方案。两者都在 640 输入下工作良好,不需要额外 resize。

为什么不建议一上来就上大模型?7820 张图对分割任务属于中等偏小规模,大模型(如 SegFormer-B5)容易过拟合,且训练时间长,毕设周期内调参空间被压缩。先用 B0 跑出 baseline IoU,再决定要不要升级,这是更稳的节奏。

数据划分建议 8:1:1,即训练 6256、验证 782、测试 782。划分时按图片随机分,但要检查每个类别的标注在三个子集里的分布是否大致一致——尾部类别(ACrack 423 个标注)如果测试集里一个都没有,测试指标就没意义。可以写个脚本统计划分后各类别标注数,偏差超过 20% 就重新分。

4.2 损失函数与类别加权的具体配置

针对长尾,方案是「交叉熵 + Dice」组合损失,并对交叉熵做类别加权。权重按1 / sqrt(freq)计算,比1/freq温和,避免尾部类别权重爆炸导致训练不稳定。

import numpy as np import torch import torch.nn as nn # 各类别标注数(来自数据集统计,object已排除) counts = np.array([14665, 9849, 9222, 4621, 3956, 3612, 2197, 1993, 1892, 1657, 1649, 1538, 1201, 1076, 1019, 616, 506, 423, 407], dtype=np.float32) # 1/sqrt 加权,再归一化到均值1 weights = 1.0 / np.sqrt(counts) weights = weights / weights.mean() class_weights = torch.tensor(weights, dtype=torch.float32) ce_loss = nn.CrossEntropyLoss(weight=class_weights, ignore_index=255) def dice_loss(pred, target, smooth=1.0): # pred: [B, C, H, W] logits; target: [B, H, W] long pred = torch.softmax(pred, dim=1) target_onehot = torch.nn.functional.one_hot(target, pred.shape[1]).permute(0, 3, 1, 2).float() inter = (pred * target_onehot).sum(dim=(2, 3)) union = pred.sum(dim=(2, 3)) + target_onehot.sum(dim=(2, 3)) dice = (2 * inter + smooth) / (union + smooth) return 1 - dice.mean() def total_loss(pred, target): return ce_loss(pred, target) + dice_loss(pred, target)

ignore_index=255是为边界像素预留的——如果后续做边界忽略训练,把边界像素标成 255 即可。weights归一化到均值 1 是为了让学习率不用跟着权重整体缩放重新调。Dice 损失负责拉高重叠度,CE 负责像素级分类,两者相加是分割任务里最稳的组合之一。

4.3 训练配置与显存控制

优化器用 AdamW,lr 初始 1e-4,weight decay 1e-4,cosine 退火到 1e-6,训练 60~80 epoch。batch size 8,配合 AMP 混合精度,8G 显存能稳住。数据增强用水平翻转、随机裁剪(裁到 512 再 resize 回 640)、亮度对比度扰动、轻微旋转(±10°)。注意:建筑病害有方向语义(比如裂缝走向),旋转幅度不要太大,否则会破坏「竖向裂缝」这类先验。

验证指标用 mIoU 和每类 IoU,每 5 个 epoch 存一次 checkpoint,保留验证 mIoU 最高的那个。训练日志里单独记录尾部 5 类(Rockpocket、EJoint、ACrack、WConccor、Restformwork)的 IoU,这是判断加权策略是否生效的关键信号。

5. 避坑与排查:这份数据集最容易翻车的五个点

这一章是踩坑记录,每条按「现象 → 原因 → 解决」写,都是这类 labelme 数据集转分割任务时高频出现的问题。

5.1 转出来的 mask 全是背景

现象:训练 loss 不降,预测图全黑,mIoU 接近 0。原因:json 里shapes的label字段与 CLASS_MAP 的 key 大小写或空格不一致,get全部返回 None,所有 shape 被跳过。解决:转换前先跑一遍set(shape["label"] for ...)打印实际标签集合,与 CLASS_MAP 做差集比对。这份数据集的标签是驼峰命名(如ExposedRebars、WConccor),手敲映射表极易写错,建议直接从 json 里程序化提取标签生成映射表初稿。

5.2 训练到一半 loss 变 NaN

现象:前 10 个 epoch 正常,之后 loss 突然 NaN。原因:Dice 损失在某个 batch 里某类完全没出现,union为 0,虽然加了 smooth 但混合精度下仍可能数值溢出;或者学习率偏高导致梯度爆炸。解决:Dice 的 smooth 从 1.0 提到 1e-5 量级不够,建议保持 1.0 但把 AMP 的 GradScaler 初始 scale 调低,同时把 lr 降到 5e-5 重跑。另一个排查点是检查 mask 里是否有超出类别数的像素值(比如转换脚本 bug 写入了 20 以上的 id),用np.unique(mask)抽查几张。

5.3 验证集指标虚高但测试集崩盘

现象:验证 mIoU 0.6+,换测试集掉到 0.3。原因:划分时没做分层,验证集恰好集中了头部类别(Rust、Spalling),尾部类别几乎没进验证集。解决:按类别做分层划分,保证每个类别在验证/测试集里至少有 10% 的标注量;尾部类别样本太少时,用「按图片分组、组内含尾部类别则整组进验证」的策略。划分完必须打印三个子集的类别分布表核对。

5.4 可视化时颜色对不上类别

现象:预测图和 mask 叠在一起,颜色和类别图例完全错位。原因:可视化脚本用的调色板顺序与 CLASS_MAP 的 id 顺序不一致,或者 mask 存的是 0~19 而训练用的是 1~20。解决:把「类别名 → id → RGB」做成唯一一份配置,训练、转换、可视化全部 import 同一份,禁止各写各的。这类问题不报错,但会让人在答辩现场被问住。

5.5 显存够但训练速度异常慢

现象:batch size 8 能跑,但每个 epoch 要两三个小时。原因:数据加载是瓶颈——每张图实时读 json 转 mask,IO 和 CPU 解码占满。解决:转换阶段就把 mask 落盘成 png,训练时直接读 png;DataLoader 的num_workers设到 CPU 核数的 70% 左右,pin_memory=True,persistent_workers=True。这一步做完,同样配置下 epoch 时间通常能砍掉一半以上。

6. 验收与答辩:用一张指标表把方案讲圆

最后落到验收。毕设或交付方案的验收,不是「模型跑起来了」就算完,而是要有一套可复现、可解释的评估口径。我一般会准备一张「总表 + 分表」:总表给 mIoU、像素准确率、各类平均 Dice;分表按头部/中段/尾部三档分别报告 IoU,并附上混淆矩阵里最容易混的三对类别。

评估脚本的核心是逐类累计混淆矩阵,最后一次性算 IoU,避免逐 batch 平均带来的偏差:

import numpy as np def compute_iou(conf_matrix): # conf_matrix: [C, C],行是真值,列是预测 ious = {} for c in range(conf_matrix.shape[0]): tp = conf_matrix[c, c] fp = conf_matrix[:, c].sum() - tp fn = conf_matrix[c, :].sum() - tp ious[c] = tp / (tp + fp + fn + 1e-6) miou = np.mean(list(ious.values())) return miou, ious

答辩时怎么讲这份方案,给几个具体建议。第一,开场不要讲模型,先讲「建筑巡检人工成本高、漏检率高」这个需求,再用类别分布表说明「这份数据的难点在长尾」,让评委先认可问题定义。第二,讲转换流水线时,把「json → mask → 训练 → 评估」画成一条线,强调 CLASS_MAP 单一来源这个工程细节,这是区分「跑通」和「做对」的地方。第三,指标汇报一定要带尾部类别,主动说「ACrack 只有 423 个标注,IoU 0.2 是数据量限制而非模型问题」,比被问出来强。第四,准备一个失败案例的可视化——比如某张图把 Wetspot 误判成 Rust,分析原因是两者颜色接近、标注边界模糊,这体现的是分析能力。

验收标准可以量化成这样:整体 mIoU ≥ 0.45(20 类长尾数据下这是合理线),头部 5 类 IoU ≥ 0.6,尾部 5 类 IoU ≥ 0.15,推理单张 640×640 在单卡上 ≤ 50ms。达不到就回到第 4 章调损失权重和增强策略,而不是换模型。

从那以后我每次接分割数据集的项目,都强制先跑一遍类别分布统计和标注抽检,再动一行训练代码——这份 7820 张、20 类的数据集尤其如此,长尾和格式转换这两关过了,后面的模型调参才有意义。希望帮到你。

本文还有配套的精品资源,点击获取