资讯动态

墙面裂缝检测YOLO实战:数据集格式、训练脚本与避坑指南

发布时间:2026/10/2 3:23:17 来源:尧图企业网站定制
简介面向墙面缺陷检测的 YOLO 格式数据集覆盖裂缝、剥落、渗水等 5 类常见墙面破损适合计算机视觉开发者、研究人员及工程落地人员直接用于目标检测模型的训练与验证能有效解决墙面缺陷样本不易采集、人工标注成本高的问题。数据按 YOLOv5 标准目录结构组织训练集 2554 张、验证集 44 张均为 416×416 的 RGB 图片每张图像至少有一个完整边界框标注采用类别加相对中心坐标、宽高的 YOLO 相对坐标格式。压缩包共 2000 个文件含 1999 个 txt 标签文件与 1 个 Python 可视化脚本整体约 45.92MB以 7z 格式压缩txt 文件对应逐张图像的标注信息并额外提供 5 类别文本文件数据构成清晰。随包可视化脚本无需修改传入任意一张图片即可自动绘制边界框并保存到当前目录可在训练前快速预览和质检数据。目前已有 332 人学习下载这份数据能显著节省墙面破损检测任务的数据准备时间适合作为起步阶段的可靠数据基础。1. 墙面裂缝检测为什么值得用 YOLO 做墙面裂缝检测听起来不像个新话题但真正上手做过的人都知道它和常规目标检测的体验完全不一样。裂缝是典型的细长小目标在整张墙面照片里可能只占几十个像素光照一变、阴影一压肉眼能看到的东西模型就是学不会。这个标题给的东西能直接解决最耗时间的部分不用自己满世界找图、清洗、标注、划分数据集拿到的是一套已经划分好的 YOLO 格式数据集配好类别 class 文件和可视化脚本可以直接开始训练和验证。适合谁用想快速跑通墙面裂缝检测 demo 的开发者、做房屋检测或建筑巡检的算法工程师、还有刚学 YOLO 但不知道数据怎么组织的新手。模型的选型、训练的参数、标签格式的细节这篇文章按实操顺序全部讲透。2. 拆解这套裂缝缺陷数据集目录结构、class 文件与标签格式拿到一个 YOLO 数据集第一件事不是打开图片看裂缝长什么样而是先把目录结构摸清楚。YOLO 训练时对数据的组织方式有严格要求目录不规整后续每一步都会出问题。2.1 标准 YOLO 数据目录长什么样常见的 YOLO 数据集有两种组织方式。一种是纯 Imagenet 风格的images和labels两个大目录下面再按train、val、test分子文件夹另一种是扁平化目录所有图片在一个文件夹、所有标签在另一个文件夹用train.txt、val.txt、test.txt三个文本文件记录划分关系。标题里明确写了“包含划分好的数据集”所以这里更可能是第二种或二者混合。不管哪种核心结构都长这样crack_dataset/ ├── images/ │ ├── train/ # 训练图片 │ ├── val/ # 验证图片 │ └── test/ # 测试图片 ├── labels/ │ ├── train/ # 训练标签 txt │ ├── val/ # 验证标签 txt │ └── test/ # 测试标签 txt ├── classes.txt # 类别文件 ├── train.txt # 训练图片路径清单 ├── val.txt # 验证图片路径清单 ├── test.txt # 测试图片路径清单 └── visualization/ └── 脚本文件.py这段目录结构是 YOLO 系列通用的标准布局。labels 里每个 txt 文件的文件名和对应的图片文件同名比如图片是IMG_001.jpg标签就是IMG_001.txt。如果一张图片里没有任何目标对应的标签文件为空文件这种情况在裂缝数据里很常见——很多墙面图确实干干净净没有裂缝这类负样本必须保留不能删。train.txt、val.txt、test.txt 里每一行是图片的绝对路径或相对路径。训练脚本启动时会先读这三个文件再按行去拼接图片路径。这里有个经验路径中尽量不要出现中文和空格YOLO 对路径的处理不够健壮出现过不少因为路径带空格导致图片加载失败的例子。2.2 读懂 class 文件与归一化坐标class 文件也就是 classes.txt决定了模型输出的类别数量和顺序这个顺序必须和标签文件里的第一个数字一一对应。墙面裂缝检测里常见的最小化方案是只设一个类wall_crack复杂一点的工程会把裂缝拆成crack_line线性裂缝、crack_net网状裂缝、crack_wide宽裂缝三个类。类别拆得越细标注成本越高但模型能学到裂缝的形态差异后续维护时更有价值。class 文件内容很简单就是每行一个类名wall_crack如果是多个类就按顺序每行一个。有了这个文件可视化脚本才能把标签里的数字 0、1、2 翻译成可读的类名。写代码时千万别硬编码类名直接从 classes.txt 读取——这是很多人踩过的坑后面避坑章节会专门说。每个标签 txt 里面每行代表一个标注框格式是五个数字class_id x_center y_center width height。注意所有坐标都是归一化的取值范围在 0 到 1 之间分母是图片的宽和高。举个例子一张 1920x1080 的墙面上裂缝框在像素坐标是x_min960, y_min540, box_w192, box_h54归一化之后就是x_center0.55, y_center0.525, width0.1, height0.05。这是 YOLO 与 COCO 格式最大的区别——COCO 用绝对像素坐标YOLO 必须用相对坐标。后面可视化脚本要把框画回图片上本质上就是做一次归一化坐标的逆运算。2.3 数据划分为什么比训练还容易翻车很多人拿到数据的第一反应是直接把图片随机分成 8:1:1。这个做法裂缝检测场景下很容易翻车原因在于裂缝数据存在严重的“场景关联性”——同一个墙角拍的一组照片随机划分后可能训练集里有两张、验证集里有一张、测试集里还有一张相当于模型在训练时已经见过验证集的背景纹理验证指标虚高模型泛化能力根本没有被真实评估。我一般拿到划分好的数据集会先检查划分文件是基于图片名随机划分还是基于场景分组划分。判断方法很简单看 val.txt 和 train.txt 里有没有来自同一场景、同一批次拍摄的图片。如果有训练时模型会把背景纹理当成裂缝的共现特征。这个问题的解决办法是在划分时按目录、按拍摄批次、按墙面编号做分组而不是按单张图片随机切。标题里说这套数据集已经划分好了具体划分逻辑没有写在标题里我建议拿到手先做一次可视化抽样确保验证集和训练集的场景不重叠。3. 把裂缝标注喂进 YOLO可视化脚本与标签格式转换数据集的标签格式和可视化脚本是连接原始数据与训练框架之间的桥梁。数据可视化这一步很多人觉得多余可省。实际上裂缝数据集的标注质量参差不齐如果不先可视化一批图片训练出来的模型大概率会学到错误的位置信息。3.1 可视化脚本的核心逻辑txt 坐标如何映射回像素坐标YOLO 标签的可视化本质上就是把归一化坐标乘以图片宽高得到像素框然后调用 OpenCV 的rectangle函数绘制矩形框。过程看起来简单但有个细节容易被忽略归一化坐标用的是比例所以必须先拿到图片的真实宽高。读取图片时我习惯用cv2.imread后直接通过shape获取高和宽不需要再单独解析图片信息文件。类名的读取也要从 classes.txt 读按行解析成列表标签里 class_id 作为列表索引。裂缝场景下最常出现的问题是标签坐标越界——归一化坐标偶尔会给出大于 1 或小于 0 的值标注软件抽风或者转换脚本 bug画框前做一次 clamp 可以避免绘制异常。这个细节能省下不少排查时间。3.2 一个可落地的可视化脚本从 datasets 读标注到画框保存import os import cv2 def load_class_names(class_file): 读取 classes.txt返回按行索引的类名列表 with open(class_file, r, encodingutf-8) as f: return [line.strip() for line in f.readlines() if line.strip()] def visualize_label(img_path, label_path, class_names, out_dir): 将一张图片及其 YOLO 标签绘制成带框图片并保存。 返回绘制后的图像方便批量调用时统计。 img cv2.imread(img_path) if img is None: print(f[WARN] 图片读取失败: {img_path}) return None h, w img.shape[:2] with open(label_path, r, encodingutf-8) as f: lines [line.strip() for line in f.readlines() if line.strip()] for line in lines: parts line.split() if len(parts) 5: continue cls_id int(parts[0]) x_center, y_center, box_w, box_h map(float, parts[1:5]) # 归一化坐标 - 像素坐标 x1 int((x_center - box_w / 2) * w) y1 int((y_center - box_h / 2) * h) x2 int((x_center box_w / 2) * w) y2 int((y_center box_h / 2) * h) # 越界保护裂缝框经常贴在图片边缘 x1, y1 max(0, x1), max(0, y1) x2, y2 min(w - 1, x2), min(h - 1, y2) class_name class_names[cls_id] if cls_id len(class_names) else fID_{cls_id} cv2.rectangle(img, (x1, y1), (x2, y2), (0, 0, 255), 2) cv2.putText(img, class_name, (x1, max(20, y1 - 5)), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 0, 255), 2) os.makedirs(out_dir, exist_okTrue) out_path os.path.join(out_dir, os.path.basename(img_path).replace(.jpg, _vis.jpg)) cv2.imwrite(out_path, img) return img if __name__ __main__: img_root images/val label_root labels/val class_names load_class_names(classes.txt) for lb in os.listdir(label_root): if not lb.endswith(.txt): continue img_name lb.replace(.txt, .jpg) img_path os.path.join(img_root, img_name) label_path os.path.join(label_root, lb) visualize_label(img_path, label_path, class_names, visualization/val_vis)这段脚本的逻辑不复杂但很实用先读 classes.txt再遍历 validation 标签目录把每个 txt 对应的图片读进来按照 YOLO 的五个字段解析出类别和归一化坐标乘以图片宽高后画框。参数上矩形框颜色用了红色(0, 0, 255)线宽设为 2这样在墙面的浅色背景上最醒目。类别文字放在了框左上角外侧如果 y1 小于 20 像素就放到框内避免文字溢出图片边界。输出目录统一是visualization/val_vis方便集中查看。跑完之后一定要逐张检查重点看框的位置是否贴合裂缝边缘框有没有偏大或偏小这直接反映标注质量。3.3 如果拿到的是 VOC 格式 XML转 YOLO 的备用脚本有些数据集标注文件不是 YOLO 的 txt而是 Pascal VOC 的 XML。YOLO 官方训练工具不直接吃 XML得先转换成 YOLO 格式。转换原理很简单XML 里存的是绝对像素坐标xmin, ymin, xmax, ymax需要算成中心点坐标和宽高再分别除以图片宽高做归一化。需要注意的一点是 XML 里的width和height是图片原始尺寸有些标注工具导出的 XML 会带一个depth字段不要混淆。转换脚本里最关键的运算就是这段x_center (xmin xmax) / 2 / width y_center (ymin ymax) / 2 / height box_w (xmax - xmin) / width box_h (ymax - ymin) / height有个容易被忽略的坑是 class 索引的映射。VOC XML 里namewall_crack/name存的是类名字符串转 YOLO 时要转换成整数 ID。这个 ID 的排序必须和类名在 classes.txt 里的顺序一致否则会出现“名称叫 A、标签编号指 B”的错位。我在实际项目中遇到过因为类名排序不一致导致的训练结果混乱训练进度正常但预测时类别张冠李戴排查了一整天。建议转换完成后立即用上一节的可视化脚本抽查 50 张图确认类名和框位置都正确再进训练流程。4. 用 YOLOv8 训练裂缝检测模型参数设置与权重准备数据集准备好、可视化验证通过接下来就是正式训练。当前 YOLO 生态里YOLOv8 是训练自己数据集最顺手的版本ultralytics 框架把数据加载、训练、验证打包得很好适合裂缝检测这种样本量不大、需要快速迭代的项目。YOLO 系列对比下来v8 的模块化设计让后续做改进实验也更方便比如把 C2f 模块换成其他结构时不用动整体框架。4.1 裂缝检测的 yaml 配置文件怎么写训练前要先写一个 yaml 描述数据集的路径和类别信息。注意这个 yaml 和前面的 class 文件是两个东西yaml 是给训练框架读的class 文件是给标注和可视化用的两者类别顺序必须一致。# crack.yaml path: /home/user/crack_dataset # 数据集的绝对路径 train: images/train # 训练图片相对路径 val: images/val # 验证图片相对路径 test: images/test # 测试图片相对路径 # 类别数和类名顺序必须与 classes.txt 完全一致 nc: 1 names: 0: wall_crack这里最值得注意的参数是path。yaml 里写了绝对路径换了机器就要改很麻烦。更好的做法是让训练脚本动态生成这个 yaml从相对路径取地址。我一般在项目里直接用os.path.abspath()拼出当前机器的绝对路径写进 yaml这样换机器训练只需要把数据集目录拷贝走重新跑一次生成脚本就行。另外nc是类别总数字符串形式的类名映射在 v8 里也支持但旧版本 v5 只认数字索引所以写成数字索引兼容性更好。4.2 最小可跑的裂缝训练命令与关键参数yolo detect train \ datacrack.yaml \ modelyolov8s.pt \ epochs200 \ imgsz1280 \ batch16 \ patience40 \ lr00.005 \ device0这条命令就是训练一个墙面裂缝检测模型的最小配置。逐项说清楚参数含义modelyolov8s.pt是预训练权重路径第一次运行会自动去官方服务器下载预训练模型文件如果网络环境不方便也可以先手动下载后放到项目目录再把这里改成yolov8s.pt的本地路径这一步解决的就是“yolo 预训练模型下载”最常遇到的断线问题。epochs200是训练轮数裂缝数据集通常几百到几千张图200 轮足够模型收敛。imgsz1280是训练输入分辨率这个参数对裂缝检测至关重要——裂缝是细长小目标YOLO 默认的 640 分辨率会把裂缝下采样成几个像素甚至一个像素特征基本消失。把分辨率提到 1280 后裂缝边缘信息保留得多检测效果提升明显。代价是显存占用增加如果显卡只有 8G 显存imgsz保持 1280 时batch需要降到 8 或 4。patience40是早停参数连续 40 轮验证集指标没有提升就停止训练防止过拟合。lr00.005是初始学习率YOLOv8 默认是 0.01但对裂缝这种背景复杂、目标小的数据集学习率太大会导致损失值震荡甚至梯度过冲。device0指定第一块 GPU 训练。显存不够时典型的报错就是CUDA out of memory解决办法是把batch减半或者把imgsz降到 960。训练脚本跑起来之后终端会打印每一轮的损失、精度、召回率和 mAP。很多人只看最后一行 mAP忽略中间过程。实际上要重点观察前 20 轮的box_loss和cls_loss曲线是否平稳下降。如果 loss 曲线出现突然的尖峰或直接变成nan需要停下来处理这个问题的细节放在下一章的避坑里讲。4.3 训练产物与第一次评估训练结束后runs/detect/train目录下会生成weights/best.pt和weights/last.pt两个权重文件。best.pt是验证集上 mAP 最高的模型last.pt是最后一轮的模型。裂缝检测场景我强烈建议用best.pt因为last.pt在训练后期往往已经过拟合训练集验证集的 mAP 可能在最后十几轮开始下滑best.pt才是泛化能力最好的快照。第一次评估不要只看 mAP50 这个数字要看验证集图片上的实际检测效果。yolo 框架提供了现成命令yolo detect predict \ modelruns/detect/train/weights/best.pt \ sourceimages/test/ \ saveTrue \ conf0.25这条命令会用训练好的模型预测测试集图片saveTrue会把画了检测框的结果图保存到runs/detect/predict目录。跑完之后打开结果图重点看真实裂缝密集的区域框是否稳定、是否有重复框、有没有把墙上的暗纹和电线阴影误检成裂缝。这些都判断模型能不能实际部署的关键依据比单纯看指标重要得多。5. 裂缝数据集训练避坑五条能救场的排查记录墙面裂缝检测的数据集有它的特殊性训练过程中踩过的坑和通用目标检测不完全一样。以下五条都是实操中反复遇到的问题按“现象、原因、解决”的方式记录照着排查能省下大量试错时间。5.1 训练时 Loss 变成 NaN 或 BN 层崩溃现象训练到第 10~30 轮终端输出的box_loss突然变成nan模型权重也随之失效后续所有指标全部变为 0。原因这个现象在 YOLO 社区被讨论最多的就是“bn 崩溃”。裂缝数据集图片亮度差异大墙面区域经常有高光、阴影、反光输入分布波动剧烈。BN 层在统计均值方差时如果遇到极端激活值累积统计量会跑飞。根因常见有两个一是学习率太高二是 batch size 太小导致 BN 统计量不稳定。解决先把lr0从 0.01 降到 0.005 或 0.003如果还在崩溃把batch从 16 降到 8。两个手段同时使用能解决绝大多数情况。另外一个容易忽视的点是不要用ampTrue混合精度训练老显卡某些 GPU 架构在混合精度下容易出现数值溢出裂缝检测这种小目标任务对数值精度更敏感直接关掉混合精度反而更稳定。5.2 可视化脚本画出类别名错乱的框现象可视化出来的图片上框的位置是正确的但框上显示的类名和图里实际裂缝类型对不上训练时也出现类别概率混乱。原因脚本里没读 classes.txt而是自己写死了[wall_crack, crack_line]之类的列表。一旦 class 文件里的顺序变了索引就错位了。还有一种情况是从 VOC XML 转 YOLO 时XML 里的类名顺序和 classes.txt 不一致导致标签文件里的 class_id 语义错乱。解决可视化脚本统一从 classes.txt 读取类名禁止硬编码。转换 VOC 时先读取目标 classes.txt 生成一个类名到 id 的映射字典再逐文件转换。转完以后用可视化脚本抽检在图上确认类名和外观是否匹配。5.3 小裂缝漏检严重mAP 却很高现象验证集 mAP50 到了 0.85看似不错但打开预测结果发现细小裂缝基本检不到检出来的全是宽裂缝和明显裂缝。原因mAP 是被所有目标平均的指标样本里宽裂缝数量多模型倾向于学容易学的大目标。裂缝本身的像素占比太小模型的能力上限摆在那里640 分辨率下一条宽度 4 像素的裂缝在特征图上只剩 1 个像素。这不是参数问题是数据处理问题。解决把imgsz提升到 1280 甚至 1536如果显存不够把原图切成 640 的 patch 来训练和推理保证每条裂缝在输入图上至少有 10 个像素的宽度。还有一种做法是对训练集做过采样让细小裂缝在每一轮 epoch 里出现的次数增加缓解正负样本比例失衡。5.4 验证集指标抖动量极大现象验证集的 precision 和 recall 在相邻两个 epoch 之间波动超过 10 个百分点曲线像锯齿。原因一种场景是验证集图片太少每张图里的目标数量差异大单张图检测结果就足以改变整体指标。另一种场景是数据集划分没做好验证集里混入了和训练集来自同一批墙面拍摄的图片模型对这些图片的检测效果时好时坏。解决验证集至少保留 100 张图且裂缝目标总数在 300 个以上。划分数据时按拍摄批次分组不要按单张图片随机分。如果目标总数仍然不够就把训练轮数加上去并且配合早停让模型在验证集最稳定的阶段被保存下来。5.5 模型把墙上的暗影、插座、污渍误检成裂缝现象训练效果不错但预测时出现大量误检墙面反光、电线、插座边缘都被画上了红框。原因裂缝标注框过宽把裂缝周边的暗区域也包了进去。标注员在拉框时习惯性贴近整个可见裂缝区域但裂缝旁边的阴影也被包含进去。模型学到的是“暗色长条形区域就是裂缝”而不是“裂缝就是裂缝”。解决标注时收紧框边界让标注框严格贴合裂缝本体不要包含周围阴影。如果标签已经写好不想返工可以在预处理阶段对训练图片做增强加大亮度对比度扰动削弱模型对阴影纹理的依赖。另一个效果显著的办法是在训练时开启mosaic1.0让不同图片拼接在一起破坏原始场景的连续纹理模型被迫关注局部特征但这个方案在裂缝目标非常细小时会漏检使用时需要权衡。6. 验证模型阶段比 mAP 更值的三个技巧混淆矩阵、错检图片定位、阈值选择训练结束进入部署前的验证阶段我发现很多人的验证流程就是看一眼 mAP 然后直接打包模型。这个流程在裂缝检测场景里远远不够。裂缝误检和漏检的代价不同错检可以在后处理里用业务规则过滤漏检则直接导致安全事故所以验证的目标应该是搞清楚模型在哪些图上犯了什么错而不是只看一个平均值。第一个技巧是学会看混淆矩阵。ultralytics 在runs/detect/train下会自动生成confusion_matrix.png很多人对着这张图问“为什么行和列的总和不唯一”。这是因为 YOLO 的混淆矩阵做了两个方向的归一化每一行代表真实类别中的目标被预测到了哪些类别所以按行求和是 100%每一列代表预测为某个类别的目标实际来自哪些类别所以按列求和也是 100%。两个方向的百分比的基数不同横排竖排的总和自然对不上。看这个矩阵时重点不是检验总和而是观察对角线之外的响应集中在哪——如果是background列有大量响应说明模型在用纹理特征做判断误检会集中出现。第二个技巧是写一个小脚本把预测错误的图片单独挑出来保存。不要只看汇总结果要看图模型到底把什么东西当成了裂缝漏掉的裂缝长什么样。我自己的做法是这段逻辑思路简单作用很大from ultralytics import YOLO import os, cv2, numpy as np model YOLO(runs/detect/train/weights/best.pt) img_dir images/test label_dir labels/test out_dir visualization/fp_fn os.makedirs(out_dir, exist_okTrue) for lb in os.listdir(label_dir): if not lb.endswith(.txt): continue img_path os.path.join(img_dir, lb.replace(.txt, .jpg)) img cv2.imread(img_path) h, w img.shape[:2] gt [] for line in open(os.path.join(label_dir, lb)): parts line.strip().split() if len(parts) 5: x1 (float(parts[1]) - float(parts[3]) / 2) * w y1 (float(parts[2]) - float(parts[4]) / 2) * h x2 (float(parts[1]) float(parts[3]) / 2) * w y2 (float(parts[2]) float(parts[4]) / 2) * h gt.append([x1, y1, x2, y2]) res model.predict(img_path, conf0.25, verboseFalse)[0] pred_boxes res.boxes.xyxy.cpu().numpy() if res.boxes is not None else np.empty((0, 4)) # 以 IoU0.1 作为粗匹配裂缝检测的框重合度要求不宜太高 matched set() for p_i, pb in enumerate(pred_boxes): best_iou 0 best_g -1 for g_i, gb in enumerate(gt): xx1, yy1 max(pb[0], gb[0]), max(pb[1], gb[1]) xx2, yy2 min(pb[2], gb[2]), min(pb[3], gb[3]) inter max(0, xx2 - xx1) * max(0, yy2 - yy1) union (pb[2] - pb[0]) * (pb[3] - pb[1]) (gb[2] - gb[0]) * (gb[3] - gb[1]) - inter iou inter / union if union 0 else 0 if iou best_iou: best_iou, best_g iou, g_i if best_iou 0.1: matched.add(p_i) matched.add(best_g) # 存在错检或漏检就保存图片 if len(matched) ! len(gt) len(pred_boxes): dst os.path.join(out_dir, lb.replace(.txt, _debug.jpg)) for b in pred_boxes: cv2.rectangle(img, (int(b[0]), int(b[1])), (int(b[2]), int(b[3])), (0, 0, 255), 2) for b in gt: cv2.rectangle(img, (int(b[0]), int(b[1])), (int(b[2]), int(b[3])), (0, 255, 0), 2) cv2.imwrite(dst, img)这段脚本用了 IoU 为 0.1 的匹配阈值而不是默认的 0.5原因还是裂缝框细长GT 框和预测框只要在位置上重合就能算命中只有这样才能把真正漏检的图捞出来而不是被过高的 IoU 阈值淹没。红框是模型预测绿框是真实标注保存下来的图就是模型犯错的具体位置一张一张翻问题会非常直观。第三个技巧是部署前做一遍置信度阈值扫描。YOLO 默认conf0.25但在裂缝检测场景里这个阈值不是处处合适。我习惯用验证集跑三组阈值0.1、0.25、0.5分别记录误检数和漏检数。如果是漏检代价更高的室内结构检测场景就降阈值如果误检会被客户投诉就升阈值。选好之后把这个阈值固化到部署脚本里不要每次推理都临时决定。这个简单的习惯能让模型交付后的表现稳定很多也让我在每次训练结束后的验证环节有了一个固定的收尾动作。希望帮到你。本文还有配套的精品资源点击获取

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

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

免费获取报价 →
↑