拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

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

墙面缺陷检测数据集:5737张VOC/YOLO双格式样本与YOLO训练避坑指南

墙面缺陷检测数据集:5737张VOC/YOLO双格式样本与YOLO训练避坑指南

简介:本资源为墙面缺陷检测数据集,面向从事建筑外墙病害识别、结构健康监测及计算机视觉目标检测的开发者与研究人员,可用于训练YOLO系列或VOC格式的目标检测模型,解决墙面裂缝、剥落、锈迹等缺陷自动识别问题。压缩包共含2000个文件,以1999个xml标注文件和1个说明txt为主,整体约143.08MB,图片、xml与txt分别对应JPEGImages、Annotations、labels三个文件夹,方便直接接入主流检测框架。数据集共5737张清晰图片,已做约60%增强,标注7类缺陷:外露钢筋、分层、裂缝、剥落、脱落、锈迹与潮湿,总框数达15733个,其中裂缝4513框、外露钢筋3673框、锈迹2550框,类别分布较均衡。目前已有666人学习下载,适合需要现成标注数据快速验证模型、开展缺陷检测实验或进行数据增强对比的读者参考使用。

1. 墙面缺陷检测数据集:5737 张已增强样本,VOC 与 YOLO 双格式直接落地

做建筑外立面巡检的团队多半遇到过这种局面:无人机或爬壁机器人拍回来几千张墙面图,人眼筛一遍要一周,漏检的裂缝和锈迹还得返工。这份墙面缺陷检测数据集就是冲着这个场景来的——5737 张 jpg 图片,配套 5737 个 VOC 格式 xml 和 5737 个 YOLO 格式 txt,7 类缺陷共 15733 个矩形框,约 60% 的样本做过增强。它解决的不是"有没有数据"的问题,而是"拿到就能训、训完能对齐业务标签"的问题。适合做 YOLO 目标检测落地、需要快速验证外立面病害识别方案的从业者,也适合拿它当缺陷检测练手项目的数据底座。下面从格式结构、标签分布、训练配置一路拆到踩坑记录。

2. 双格式目录结构与 7 类标签分布:先看清数据再动手

2.1 三个文件夹怎么对应,xml 和 txt 谁是谁

拿到压缩包解压后是三个平级文件夹:JPEGImages 放 jpg 原图,Annotations 放 VOC 的 xml,labels 放 YOLO 的 txt。三者的文件名主干一一对应,比如sl_images880.jpg对应sl_images880.xml和sl_images880.txt。这种"同名不同后缀"的设计是刻意为之,方便你写脚本做交叉校验,也方便在两种格式之间来回切换。

VOC 的 xml 里存的是绝对像素坐标,结构是<bndbox>下的xmin/ymin/xmax/ymax,外加<name>标签名。YOLO 的 txt 每行是class_id x_center y_center width height,五个值全部归一化到 0~1。同一张图两种格式描述的是同一批框,只是坐标系和存储方式不同。常见做法是训练用 txt,做数据审查或转 COCO 时用 xml,因为 xml 可读性更好,能直接看到像素级坐标。

提示:解压后先别急着改文件名。三个文件夹的命名主干必须严格一致,任何一张图缺了对应的 xml 或 txt,训练时都会在数据加载阶段报错或静默丢样本。

2.2 7 类标签的框数分布与类别不平衡

标签是意大利语,先对照一遍业务含义,别训完了才发现标签对不上:

class_id标签名中文含义框数
0Armatura in vista钢筋外露3673
1Delaminazione分层剥落1005
2Fessura裂缝4513
3Scaling起皮剥落240
4Spalling剥落掉块2004
5Tracce di ruggine锈迹2550
6Umidita潮湿渗水1748

总框数 15733。一眼能看出问题:Fessura(裂缝)4513 个框,Scaling(起皮)只有 240 个,差了将近 19 倍。这是典型的类别不平衡,直接拿去训 YOLO,Scaling 这一类大概率召回率极低,甚至被模型忽略。我一般会先跑一遍统计脚本确认每类框数,再决定要不要对少数类做重采样或调 loss 权重。

2.3 用脚本核对图片、xml、txt 三方一致性

动手训练前,先写个校验脚本把三个文件夹对齐一遍。这一步能省掉后面几小时的排查时间:

import os img_dir = "JPEGImages" xml_dir = "Annotations" txt_dir = "labels" # 取文件名主干(不含扩展名) imgs = {os.path.splitext(f)[0] for f in os.listdir(img_dir) if f.endswith(".jpg")} xmls = {os.path.splitext(f)[0] for f in os.listdir(xml_dir) if f.endswith(".xml")} txts = {os.path.splitext(f)[0] for f in os.listdir(txt_dir) if f.endswith(".txt")} print("图片数:", len(imgs)) print("xml数:", len(xmls)) print("txt数:", len(txts)) # 找出三方不一致的主干 only_img = imgs - xmls - txts only_xml = xmls - imgs - txts only_txt = txts - imgs - xmls print("只有图片:", len(only_img), list(only_img)[:5]) print("只有xml:", len(only_xml), list(only_xml)[:5]) print("只有txt:", len(only_txt), list(only_txt)[:5])

逻辑说明:用集合差集找出任何一方缺失的样本。参数上,os.path.splitext去掉扩展名拿到主干,三个集合两两做差就能定位孤儿文件。如果输出三个"只有"都是 0,说明数据完整;如果有非零,先补齐或删掉再进下一步,别带着脏数据训。

2.4 从 xml 反推 YOLO 归一化坐标的换算

有时候你需要自己从 xml 重新生成 txt,比如改了标签映射或做了框过滤。换算公式不复杂,但边界容易翻车:

import xml.etree.ElementTree as ET def voc_to_yolo(xml_path, class_map): tree = ET.parse(xml_path) root = tree.getroot() size = root.find("size") w = int(size.find("width").text) h = int(size.find("height").text) lines = [] for obj in root.iter("object"): name = obj.find("name").text if name not in class_map: continue bnd = obj.find("bndbox") xmin = float(bnd.find("xmin").text) ymin = float(bnd.find("ymin").text) xmax = float(bnd.find("xmax").text) ymax = float(bnd.find("ymax").text) # 归一化中心点与宽高 xc = (xmin + xmax) / 2.0 / w yc = (ymin + ymax) / 2.0 / h bw = (xmax - xmin) / w bh = (ymax - ymin) / h lines.append(f"{class_map[name]} {xc:.6f} {yc:.6f} {bw:.6f} {bh:.6f}") return lines

逻辑说明:size节点给出图片宽高,用它做归一化分母。class_map是标签名到 class_id 的字典,必须和训练时的 data.yaml 顺序一致,否则类别全错位。参数上,坐标保留 6 位小数足够,YOLO 读取时对精度不敏感。注意 xmax 可能等于图片宽度,归一化后是 1.0,这是合法的,别手抖去 clamp 成 0.99。

3. YOLO 训练配置:从 data.yaml 到增强策略对齐

3.1 data.yaml 怎么写,路径和类别顺序别搞反

YOLO 系列训练第一步是配 data.yaml。这份数据集是 7 类,顺序必须和 txt 里的 class_id 严格对应:

path: ./wall_defect train: images/train val: images/val nc: 7 names: 0: Armatura in vista 1: Delaminazione 2: Fessura 3: Scaling 4: Spalling 5: Tracce di ruggine 6: Umidita

逻辑说明:path是数据集根目录,train和val是相对路径。nc是类别数,必须等于 names 的条目数。names 的索引就是 txt 第一列的 class_id,顺序错一位,模型学到的就是错的映射。常见做法是先把 5737 张按 8:2 切成 train/val,切分时保证每类都有样本进验证集,别让 Scaling 这种少数类在 val 里一个都没有。

3.2 类别不平衡下的 loss 权重与采样

前面看到 Scaling 只有 240 个框,Fessura 有 4513 个。直接训,模型会偏向多数类。两个常用手段:一是给少数类更高的 loss 权重,二是对含少数类的图片做重复采样。YOLOv8 里没有直接的 class weight 参数,但可以在数据加载层做 oversampling,或者用 focal loss 的变体。我一般先跑一版 baseline,看混淆矩阵里 Scaling 的召回,如果低于 0.3 再动手调。

# 统计每类框数,决定是否重采样 from collections import Counter import os txt_dir = "labels" counter = Counter() for f in os.listdir(txt_dir): if not f.endswith(".txt"): continue with open(os.path.join(txt_dir, f)) as fp: for line in fp: cid = int(line.split()[0]) counter[cid] += 1 for cid in sorted(counter): print(f"class {cid}: {counter[cid]} boxes")

逻辑说明:遍历所有 txt,统计每个 class_id 出现的行数,就是该类框数。参数上没什么可调的,纯粹是摸底。跑完对照第 2 章的表格,确认和简介里的数字一致,不一致说明数据被改过或读取有误。

3.3 增强数据的使用边界:约 60% 已增强意味着什么

简介里写"已增强(约60%)",意思是这批图里大约六成做过几何或色彩变换。这对训练是好事,能提升泛化,但有两个边界要注意。第一,增强后的图可能和原图高度相似,如果切分时把增强图和它的原图分别放进 train 和 val,会造成验证集泄漏,指标虚高。第二,增强可能改变了缺陷的尺度分布,比如旋转后细长裂缝变得更难检。

注意:切分前先确认有没有"同源图"——同一张原图的不同增强版本。如果文件名有规律(比如带 aug 后缀或编号连续),按原图分组切分,别按单张随机切。

3.4 训练命令与关键超参

以 YOLOv8 为例,一条命令能跑起来:

yolo detect train \ data=wall_defect.yaml \ model=yolov8s.pt \ epochs=100 \ imgsz=640 \ batch=16 \ lr0=0.01 \ patience=20 \ project=runs/wall \ name=exp1

逻辑说明:model用预训练权重起步,比从头训收敛快。imgsz=640是常见输入尺寸,如果原图分辨率远大于 640,小目标(比如细裂缝)可能被缩没,可以试 960 或 1280。batch=16看显存,V100 上可以拉到 32 甚至 64。patience=20是早停,连续 20 轮没提升就停,省时间。lr0初始学习率,0.01 是 YOLO 的常规起点,数据量小可以降到 0.001。

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

4.1 标签名是意大利语,映射错一位全盘皆输

现象:训练 loss 正常下降,但验证时混淆矩阵里类别全乱,Fessura 被预测成 Umidita。 原因:data.yaml 里 names 的顺序和 txt 里的 class_id 没对齐,或者你中途改过标签映射但没重新生成 txt。 解决:写脚本抽查若干 txt,把 class_id 反查 names,和 xml 里的<name>对照,确认一一对应。改过映射就重新跑一遍转换,别手动改 txt。

4.2 增强图与原图跨集泄漏,验证指标虚高

现象:验证集 mAP 很高,一上真实新图就崩。 原因:同一张原图的增强版本被分到了 train 和 val 两边,模型在验证集上见到的是"见过的图"。 解决:按原图分组切分。如果文件名无法区分原图和增强图,用图像哈希或感知哈希做去重分组,同组只进一个集合。

4.3 Scaling 类召回极低甚至为 0

现象:训完发现 Scaling 这一类几乎检不出,混淆矩阵里全被吞进其他类。 原因:240 个框对比 4513 个框,类别严重不平衡,模型倾向于忽略少数类。 解决:先确认 val 里有没有 Scaling 样本;再考虑对含 Scaling 的图做 oversampling,或换用带类别权重的 loss。实在不行,把 Scaling 和 Spalling 合并成一类"剥落",看业务能不能接受。

4.4 坐标越界导致训练报错或框画歪

现象:训练时提示坐标超出 [0,1],或可视化时框跑到图外。 原因:xml 里 xmax 等于图片宽度时归一化得 1.0,某些版本的加载器会判越界;或者手工改过坐标没重新归一化。 解决:转换时对坐标做一次 clamp 到 [0,1],但要注意 clamp 会轻微改变框,只在越界时用。更稳妥的是检查原始 xml 的 size 是否和实际图片尺寸一致。

4.5 图片与标签文件名主干不一致,静默丢样本

现象:训练日志里显示的样本数比 5737 少,但没报错。 原因:YOLO 加载时按图片找同名 txt,找不到就跳过,不报错。 解决:跑第 2 章的校验脚本,确保三个文件夹主干完全一致。发现孤儿文件要么补齐,要么删掉,别留着。

5. 进阶技巧:用混淆矩阵和框分布反推数据质量

训完一版模型,别只看 mAP 就收工。混淆矩阵和每类框的尺寸分布能告诉你数据本身的问题。我习惯先导出混淆矩阵,看哪些类互相混淆——如果 Fessura 和 Delaminazione 经常互判,说明这两类在视觉上边界模糊,可能需要合并或补充区分性样本。再看框的宽高散点图,如果某一类的框全是极端细长或极小,说明标注尺度单一,增强没覆盖到。

import numpy as np import os # 统计每类框的宽高分布(归一化后) txt_dir = "labels" stats = {} for f in os.listdir(txt_dir): if not f.endswith(".txt"): continue with open(os.path.join(txt_dir, f)) as fp: for line in fp: parts = line.split() cid = int(parts[0]) bw, bh = float(parts[3]), float(parts[4]) stats.setdefault(cid, []).append((bw, bh)) for cid in sorted(stats): arr = np.array(stats[cid]) print(f"class {cid}: n={len(arr)}, " f"w_mean={arr[:,0].mean():.4f}, h_mean={arr[:,1].mean():.4f}, " f"w_min={arr[:,0].min():.4f}, h_min={arr[:,1].min():.4f}")

逻辑说明:把每类的归一化宽高收集起来算均值和最小值。如果某类 w_min 或 h_min 特别小(比如小于 0.01),说明存在极小目标,训练时输入尺寸不能太小,否则这些框在特征图上只剩不到一个像素。参数上,imgsz至少要保证最小框在特征图上占 2~3 个像素,按这个反推输入尺寸。

另一个技巧是用验证集的预测结果反查漏标。把模型置信度中等(比如 0.3~0.5)但没匹配到真值的预测框导出来,人工看一眼,很可能是数据集漏标的缺陷。这份数据集有 15733 个框,漏标几个很正常,用模型帮你找比人眼快得多。

从那以后我每次拿到新数据集,都强制先跑一遍三方一致性校验和类别分布统计,再动手配训练。这两步花不了十分钟,但能挡掉后面大半的玄学问题。希望帮到你。

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

返回列表