news 2026/10/6 5:57:42

7820张Labelme建筑缺陷图片:语义分割数据集转换与类别合并实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
7820张Labelme建筑缺陷图片:语义分割数据集转换与类别合并实战

简介:建筑墙壁损伤缺陷分割数据集介绍文档,面向计算机视觉与机器学习研究人员、建筑安全检测从业者,用于解决建筑墙面损伤自动识别与分割问题。该文档对应一个包含7820张jpg图片及同名json标注文件的labelme格式数据集,覆盖涂鸦、支座、锈蚀、剥落、裂缝、泛碱、风化、外露钢筋等20类损伤缺陷,每张图片均为640x640分辨率并采用多边形框精确标注。资源压缩包共1个docx文件,大小2.1MB,重点介绍了数据集的目录结构、标注类别名称与各类别标注框数量统计、labelme(5.5.0)使用说明,以及转换为mask、YOLO或COCO格式用于语义/实例分割的注意事项。文档还附有图片预览与标注示例,可帮助用户快速掌握数据组织方式与标注规则,为后续模型训练和数据预处理提供清晰指引。已有102人查看学习,适合需要基于真实建筑损伤图像开展分割算法研究与实验的开发者使用。

1. 建筑墙壁损伤缺陷分割数据集:7820 张 labelme 图片到底能不能直接开工

做建筑外观巡检的都知道,无人机绕楼飞一圈能拍回几千张墙照片,真正耗时间的反而是后面的人工筛查。这份数据集解决的就是「有没有带标注的墙损伤图片」这个卡脖子问题:7820 张 640×640 的 jpg,配上 7820 个 labelme 格式 json,20 类损伤从锈蚀、剥落、泛碱、空腔到外露钢筋、裂缝全覆盖,全部用多边形 polygon 精细描边。适合想训语义分割或实例分割模型的人拿去做 baseline,也适合研究标注规范和类别合并的人。注意一点:数据集只含图片和 json,不含 mask,训练精度不保证,格式转换要自己动手——这就是后面几章要展开的事。

2. 认识这份数据集:20 类缺陷的语义边界与 labelme 5.5.0 文件结构

2.1 二十个类别拆开看:缺陷、干扰项与易混对

拿到数据先别急着转换,第一件事是把 20 个类别摊开看一遍。这里面的类别性质差别很大,有的是真正的结构损伤(Rust、Spalling、Cavity),有的是环境干扰(Graffiti、PEquipment),有的是施工残留(Restformwork、JTape),还有一个是纯粹的兜底类(object)。如果不做区分直接拿去训练,模型会把大量精力浪费在错误的目标上。

类别名中文含义标注框数性质
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施工后未拆模板
Rockpocket岩窝616骨料集中形成的蜂窝麻面
EJoint伸缩缝506结构伸缩缝
ACrack活动裂缝423动态裂缝
WConccor水侵蚀混凝土407长期水蚀破坏的混凝土
object物体13兜底类

有几对类别需要重点区分。Crack 和 ACrack 在视觉上几乎一致,区别在于裂缝是否还在发展,静态图片里人工都未必分得清;Cavity 和 Hollowareas 都表现为凹陷或空洞,前者偏结构性空腔,后者偏表层空鼓;Spalling、Weathering、Efflorescence 三者经常伴生出现在同一块墙面上,标注时边界很容易打架。这些易混对决定了后面做类别合并时的基本思路。

2.2 labelme 5.5.0 的 json 结构:shapes、points 与 imageData

labelme 的 json 结构从 4.x 到 5.x 有变化,这份数据集标注工具锁在 5.5.0,所以解析时也建议以这个版本为准。打开任意一个 json 看结构:

import json with open("000001.json", "r", encoding="utf-8") as f: data = json.load(f) print("imagePath:", data["imagePath"]) print("imageWidth:", data["imageWidth"]) print("imageHeight:", data["imageHeight"]) print("shapes 数量:", len(data["shapes"])) print("第一个 shape 内容:") print(data["shapes"][0])

顶层字段一般是 version、flags、shapes、imagePath、imageData、imageHeight、imageWidth。其中 shapes 是核心,它是一个列表,每个元素对应一个多边形标注,包含 label(类别名)、points(顶点坐标列表,每个点形如 [x, y])、group_id(通常为 null)、shape_type(这里固定是 polygon)。imageData 是内嵌的 base64 图片,如果带了这个字段,json 文件体积会膨胀好几倍,后面避坑章节会专门说它。

提示:解析时以 json 里的 imageHeight、imageWidth 为准,不要硬编码 640。

2.3 类别数量与训练策略:不均衡比你想的更严重

看数量分布就知道,这份数据的类别极不均衡。Rust 有 14665 个框,object 只有 13 个,差距超过一千倍。Crack 3612 个还算能用,但 ACrack 只有 423 个,WConccor 407 个,EJoint 506 个,这几个类在训练时很容易被模型直接忽略。常见做法是聚合成小类族,或者对小样本类别加大权重。如果项目目标是裂缝检测,建议把 Crack 和 ACrack 合并处理,后面第 6 章会展开。总之,先把数量分布记在脑子里,再决定损失函数怎么写、需不需要类权重、要不要做下采样。

3. labelme 5.5.0 实操:装环境、核验标注、批量统计三步走

3.1 conda 安装 labelme 5.5.0:版本锁死是第一课

labelme 的安装本身不难,难点在于版本。这份数据集的 json 是用 labelme 5.5.0 生成的,所以我强烈建议把版本锁死在这个数,不要图省事直接 pip install labelme 装最新版,因为新版可能改 json 字段结构,旧版又打不开某些 polygon 属性。

conda create -n labelme python=3.9 -y conda activate labelme pip install labelme==5.5.0 labelme --version

指定 python 3.9 是因为 labelme 5.5.0 在 3.9 环境下最稳,3.11、3.12 有些依赖轮子要现编。安装完确认识别到 5.5.0 再往下走。打开数据集的命令是这样的:

labelme --labels class_names.txt --nodata /path/to/images

--labels 参数指定一个类别名单文件,编辑器里下拉列表就只显示这 20 类,能避免手抖打错类名;--nodata 表示保存时把图片以路径引用,不内嵌 base64,这样 json 文件体积小、打开快。最后那个路径是图片目录而不是 json 目录,labelme 会自动配对同名 json。

3.2 人工核验的四个关注点:边界、错类、漏标、重叠

打开之后逐张翻,重点看四件事。第一是 polygon 顶点是否贴住缺陷边界,标注松散的框在转 mask 时会引入大量噪声像素;第二是错类,特别是 Weathering 与 Spalling、Cavity 与 Hollowareas 这种易混对,发现标错的直接用 Edit 里选中 shape 改 label;第三是漏标,640×640 图上的细小裂缝很容易被跳过,漏标比错标还难发现,只能靠翻图时多留个心眼;第四是重叠,同一区域同时标了 Crack 和 Spalling 的情况不少,转语义分割 mask 时会有覆盖问题,第 5 章会讲。人工核验只建议做抽样,7820 张全查不现实,我一般按每类挑 10 张看边界质量。

3.3 批量统计脚本:验证 json 数量与框数是否和声明一致

在动手转换之前,先跑一遍全量的类别统计。这步作用是双重的:一是验证数据完整度,看看 7820 这个数字是否属实;二是确认类名没有空格、大小写不一致之类的坑,这类问题一旦混进转换脚本,后面排查非常费劲。

import json import glob from collections import Counter stats = Counter() json_files = glob.glob("/path/to/jsons/*.json") for jf in json_files: with open(jf, encoding="utf-8") as f: data = json.load(f) for shape in data["shapes"]: stats[shape["label"]] += 1 print("json 文件数:", len(json_files)) print("标注总框数:", sum(stats.values())) for label, cnt in stats.most_common(): print(f"{label:20s} {cnt}")

跑出来的数字应当和声明一致:json 7820 个,总框数约 6.6 万,各类别数量与 2.1 节表格吻合。如果对不上,先查是不是有 json 没配对成功,再查是不是混入了别的库文件。这一步相当于给数据做体检,体检通过再进入转换。

4. 格式转换实战:json 转 mask、YOLO seg、COCO 的三条路线与核心参数

4.1 转语义分割 mask:fillPoly 画多边形的细节与顺序问题

语义分割训练需要的是每张图对应的单通道 mask,像素值就是类别 id。最有代表性的转换是遍历 json 的 shapes,对每个 polygon 执行 cv2.fillPoly 填色:

import json import numpy as np import cv2 label2id = { "Graffiti": 0, "Bearing": 1, "Wetspot": 2, "Rust": 3, "Spalling": 4, "Efflorescence": 5, "Hollowareas": 6, "Weathering": 7, "Restformwork": 8, "Cavity": 9, "ExposedRebars": 10, "EJoint": 11, "Crack": 12, "Drainage": 13, "ACrack": 14, "JTape": 15, "Rockpocket": 16, "PEquipment": 17, "WConccor": 18, "object": 19, } def json_to_mask(json_path, label2id, img_h, img_w): with open(json_path, encoding="utf-8") as f: data = json.load(f) mask = np.zeros((img_h, img_w), dtype=np.uint8) for shape in data["shapes"]: cls_id = label2id.get(shape["label"], 0) pts = np.array(shape["points"], dtype=np.int32) pts = pts.reshape((-1, 1, 2)) cv2.fillPoly(mask, [pts], cls_id) return mask mask = json_to_mask("000001.json", label2id, 640, 640) cv2.imwrite("000001.png", mask)

几个关键点:dtype 用 uint8 是为了支持最多 255 类,如果类数少于 256 就用它,省内存;points 必须 reshape 成 (-1, 1, 2) 才能喂给 fillPoly;fillPoly 是按顺序填充的,同一像素被多个 polygon 覆盖时,后画的会覆盖先画的,所以 shapes 列表的顺序会影响结果。顺序问题没有完美解,稳妥做法是事先按类别优先级排好序,比如结构性损伤优先于表层损伤。

4.2 转 YOLO 分割 txt:归一化坐标与类别 id 的对应关系

YOLO 系列做实例分割训练(比如 yolov8-seg)要求每张图对应一个 txt 文件,每行格式是「类别 id + 归一化后的多边形顶点」,顶点按 x y 交替排列,全部归一化到 0~1:

def json_to_yolo_seg(json_path, label2id, img_w, img_h): with open(json_path, encoding="utf-8") as f: data = json.load(f) lines = [] for shape in data["shapes"]: cls_id = label2id[shape["label"]] norm_pts = [] for x, y in shape["points"]: nx = min(max(x / img_w, 0.0), 1.0) ny = min(max(y / img_h, 0.0), 1.0) norm_pts.append(f"{nx:.6f} {ny:.6f}") lines.append(f"{cls_id} " + " ".join(norm_pts)) return "\n".join(lines) txt = json_to_yolo_seg("000001.json", label2id, 640, 640) with open("000001.txt", "w") as f: f.write(txt)

归一化时做 clamp 是必要的,因为有些 polygon 顶点会超出图像边界几个像素,不约束的话 YOLO 会报错。保留 6 位小数对 640×640 的图像足够,1/640 约等于 0.00156,6 位小数精度到 1e-6,不会引入可见偏差。转换完的 txt 需要和 jpg 同名同目录存放,dataset.yaml 里 names 的顺序必须和 label2id 一一对应,这里是整个流程里最容易翻车的地方。

4.3 转 COCO 实例分割:segmentation、bbox、area 怎么算

COCO 格式适合喂给 mmdetection、detectron2 这一系框架,annotations 里每个对象要同时给出 segmentation、bbox、area、iscrowd。polygon 型 segmentation 可以直接用顶点扁平列表,bbox 和 area 交给 OpenCV 算即可:

import json import numpy as np import cv2 def json_to_coco_anns(data, img_id, ann_id, label2id): anns = [] for shape in data["shapes"]: pts = np.array(shape["points"], dtype=np.float32) x, y, w, h = cv2.boundingRect(pts) area = cv2.contourArea(pts) anns.append({ "id": ann_id, "image_id": img_id, "category_id": label2id[shape["label"]], "segmentation": [pts.flatten().tolist()], "area": float(area), "bbox": [int(x), int(y), int(w), int(h)], "iscrowd": 0, }) ann_id += 1 return anns, ann_id

cv2.boundingRect 返回的是外接矩形的 x、y、w、h,注意它是整数坐标;cv2.contourArea 用的是格林公式,也就是鞋带公式,对四边形以内的多边形都够准。segmentation 里的坐标是原始像素坐标,不需要归一化,这和 YOLO 完全不同,别搞混。最后把 images 列表、categories 列表和 annotations 拼成一个大字典写盘,就是标准的 COCO json 了。

5. 避坑:这套 labelme 数据转训练格式时的五个翻车现场

5.1 现象:转换后 mask 整体偏移对不上原图

生成的第一张 mask 叠到原图上,发现多边形整体往右下偏了几个像素,越靠图像边缘偏得越厉害。原因多半是硬编码了 640 作为宽高,而实际 json 里 imageHeight、imageWidth 与 jpg 的真实尺寸不一致,或者个别图片是 639、641 这种非标尺寸。解决方法是转换前统一读取 jpg 的 shape,再用 min 校验 json 里的宽高和实际一致,不一致的单独拉出来人工确认。从那之后我所有转换脚本第一行永远是读图尺寸,而不是用常量。

5.2 现象:object 类 13 个框把训练 loss 带偏

20 类全量训练,模型在验证集上疯狂误检,检出的框大量落在 object 上。原因是 object 是标注兜底类,它不承载任何语义,13 个框在 6.6 万框里占比约 0.02%,属于纯噪声。解决方法是转换时直接过滤掉 label 为 object 的 shape,让类别数变成 19,而不是留着它凑数。顺手在统计脚本里确认过滤后的总数,再进入下一步。

5.3 现象:YOLO 训练能跑但预测类别名全错位

训练流程没报任何错,但预测出来的框类别名对不上,Rust 显示成 Graffiti,Crack 显示成 Drainage。原因基本可以锁定在 label2id 和 dataset.yaml 里 names 的顺序不一致,两边只要错一位,整个类别体系就平移了。解决方法是把类别顺序抽成一个固定的 class_names.txt,转换脚本和 dataset.yaml 的 names 都从同一个文件读取生成,禁止两边各写一份。换环境、换机器时也不要重排名单,文件文字排序和语义排序是两码事。

5.4 现象:多个 polygon 重叠,转 mask 后低序类别消失

一张墙上既有剥落又有裂缝,两层多边形叠在一起,转成单通道 mask 后裂缝区域被剥落完全盖住,裂缝类像素占比直接归零。原因是 fillPoly 按 shapes 列表顺序填充,后面的覆盖前面的。解决思路分两种:语义分割任务只能接受单值,那就接受覆盖并按类别重要性排序,结构损伤优先;如果要保留重叠关系,就别用语义分割 mask,改走 COCO 实例分割路线,每个 polygon 独立成一个 annotation,重叠不丢失。

5.5 现象:json 文件几百 MB,labelme 打开卡到怀疑人生

某些 json 文件体积异常大,labelme 打开要等十几秒,内存占用飙高。原因是 json 里带了 imageData 字段,里面内嵌整张图的 base64 字符串,一张 640×640 的 jpg 转成 base64 能膨胀三分之一以上。解决方法是打开时用 --nodata 参数,或者写个批量脚本把 json 里的 imageData 字段删掉重新保存。这个字段对标注内容没有影响,图片路径才是真正的引用来源。

注意:删除 imageData 前先确认 imagePath 字段还在,否则图片会失联。

6. 把 20 类合并成能用的标签体系:合并策略与转换后三查验证

6.1 按病害语义合并:一张表说清哪些类能合、哪些别合

20 类直接跑不是不行,但 6.6 万框摊到 20 个类上,小样本类基本是废的。更务实的做法是先按病害语义合并成大族。我的合并建议如下:Crack 与 ACrack 合并为「裂缝」,因为静态图像上两者视觉特征几乎一致,活动与否需要时序信息才能判断;Spalling、Weathering、Efflorescence 合并为「表层劣化」,三者常在同一墙面伴生,边界本来就模糊;Cavity、Hollowareas、Rockpocket 合并为「空洞/蜂窝」,都表现为凹陷或空腔;Wetspot 与 WConccor 都是水相关的损伤表征,可以并成「水蚀」。Bearing、EJoint、Drainage、PEquipment 属于结构部件或环境设施,尽量保留独立类;Graffiti、JTape、Restformwork 是干扰项,如果任务只关心损伤就剔掉。

合并要服务于最终用途。做结构安全检测时,Rust 和 WConccor 最好分开,因为前者指金属锈蚀,后者是混凝土水蚀,维修方案完全不同;做外观缺陷筛查时,合并能显著提高小样本类的训练稳定性。我在实际项目中是把 20 类压成 8 类用的,batch size 和训练轮数都能往下降,收敛速度明显更快。

6.2 转换后的三查验证习惯:每批数据走一次

合并方案定下来、转换脚本跑完之后,别急着开训练。我给自己定了一条强制流程,每次转换完必须过三查。第一查数量:转换生成的文件数必须等于 json 数,少一个都不能放过;第二查映射:随机抽 5 张图,把 mask 或 txt 叠回原图,肉眼确认类别 id 和区域边界是对的;第三查分布:统计每个类别的 mask 像素占比或 txt 行数,确认合并后的小样本类不是零像素。这三查全部通过,数据才进入训练管线。

顺手可以写一行检查代码,把像素占比打出来看:

import numpy as np import cv2 mask = cv2.imread("000001.png", cv2.IMREAD_GRAYSCALE) for cls_id in range(8): ratio = (mask == cls_id).sum() / mask.size print(f"class {cls_id}: {ratio:.4f}")

连续训练几个模型之后我养成了固定习惯:每拿到一批新标注数据,先跑统计脚本看分布,再跑三查,最后才进训练,这三步加起来不到十分钟,却能省下后面几小时的排错时间。从那以后我每次转换完都强制走一遍这个流程,成了不做不舒服的习惯,希望帮到你。

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

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/6 5:57:41

NI 488.2完全指南:GPIB通信驱动、调试与Python应用

简介:NI-488.2用户手册为2018年6月官方版本,面向自动化测试(ATE)平台开发与维护工程师,尤其适合需要借助NI MAX完成GPIB仪器控制与系统集成的场景。手册系统介绍NI-488.2规范、GPIB接口工作原理及基于NI MAX的设备配置…

作者头像 李华
网站建设 2026/10/6 5:57:24

工业软件AI化实战:从画图纸到会思考的智能设计系统

不用从“项目概述”这种泛泛的话开始,咱们直接说点实在的。我在工业软件这个圈子里干了十几年,从早期的AutoCAD二次开发做到后来的PLM实施,再到这两年开始系统性地把AI能力往工业软件里塞,最大的感受是:工业软件不缺功…

作者头像 李华
网站建设 2026/10/6 5:57:08

谢希仁计算机网络课件高效利用指南:从五层模型到备课避坑

简介:谢希仁《计算机网络》课件完整版以单份PPT收录共1173页,压缩包大小20.32MB,是配套经典教材的极完整教学课件。内容从第1章概述起步,系统讲解计算机网络在信息时代中的作用,包括因特网作为“网络的网络”的组成、结…

作者头像 李华
网站建设 2026/10/6 5:57:07

RAG进阶实战:从检索瓶颈到智能体化的完整路径

1. 为什么我决定策划一个「RAG进阶实战」专栏这两年RAG(检索增强生成)几乎成了AI应用落地的默认起手式。不管是企业知识库、智能客服,还是个人文档问答,团队一上来就问“你接没接RAG”。但实际情况是:demo阶段的RAG非常…

作者头像 李华
网站建设 2026/10/6 5:56:39

NMOS与PMOS开关电路详解:电流方向、体二极管与选型实战

做了这么多年硬件,见得太多了。很多人一看到MOS管的符号就开始背口诀:“箭头朝里是NMOS,箭头朝外是PMOS”,结果一到实际画电路、调板子的时候,还是搞不清电流到底从哪进哪出,Source和Drain到底怎么接&#…

作者头像 李华
网站建设 2026/10/6 5:56:13

零依赖网页小游戏框架:WebRTC P2P联机与Shadow DOM隔离实战

1. 为什么我要折腾一个“零依赖”的网页小游戏框架先说结论:OmniGame 是我在过去几个月里反复推倒重来三次之后,才勉强敢拿出来讲的一个网页小游戏工程方案。它的核心目标很朴素——让一个网页小游戏在不装任何第三方运行时依赖的前提下,既能…

作者头像 李华