简介:面向墙面与水泥路面裂缝检测的目标检测数据集,包含8678张图像样本,标注类别为单一crack,共11741个矩形标注框,适用于学习Pascal VOC/YOLO标注规范,也可作为混凝土表面缺陷识别、道路巡检等视觉任务的训练与验证样本。压缩包共2000个文件,以VOC格式xml标注文件为主体(1999个),另含说明txt,整体约622.83MB;xml与同名图像配套,可直接接入YOLO、Faster R-CNN等常见检测框架。该数据集由labelImg手工画框标注,类别统一、框体准确,适合需要标准数据格式的初学者快速上手,也便于有经验者做数据增强或迁移学习。目前已有388人学习下载,是获取墙面/路面裂缝样本与标准标注的实用资源。
1. 拿到墙面水泥路面裂缝检测数据集,先别急着解压训练
拿到一份“墙面水泥路面裂缝检测数据集8678张VOC+YOLO格式.zip”,多数人第一反应是解压、看一眼、直接丢进 YOLO 开训。这做法不是不行,但十个项目里有六七个会在格式对齐和类别统计上翻车——VOC 的 xml 和 YOLO 的 txt 对不上、图片里有黑边有损图、裂缝目标只占几个像素,最后训练出来的 mAP 惨不忍睹。这篇文章会按“解压 → 格式对齐 → 数据体检与拆分 → YOLO 训练 → 踩坑排查 → 验证部署”的顺序,把这条裂缝检测数据集的完整使用路径讲清楚。适合第一次用 VOC/YOLO 格式数据集的入门工程师,也适合想评估这份数据值不值得投入的团队。
2. 解压 zip 后先做格式对齐:VOC 与 YOLO 两套标注怎么衔接
2.1 解压命令与目录结构检查
先把 zip 包解开,我习惯在 Linux 下用命令行,Windows 下建议用 7-Zip 或者直接双击解压,这一步不用纠结。
unzip 墙面水泥路面裂缝检测数据集8678张VOC+YOLO格式.zip -d crack_dataset cd crack_dataset tree -L 2 --dirsfirst如果tree命令没装,用find . -maxdepth 2 -type d也能看到目录层级。重点确认三样东西:图片目录、VOC 标注目录、YOLO 标注目录。常见做法是JPEGImages/、Annotations/(放 xml)、labels/(放 txt),也可能叫images/和labels/,看清实际结构再往下走。
命令参数说明:-d crack_dataset指定解压目标目录,避免把几百个文件直接撒在当前目录里;先--dirsfirst看目录再管文件,目的是最快识别是哪套布局。
2.2 VOC 格式:XML 标注里的字段逐个拆
VOC 格式的核心是每个图片对应一个同名 xml 文件,存放在Annotations/下。XML 里最关键的结构如下:
<annotation> <filename>crack_000001.jpg</filename> <size> <width>1920</width> <height>1080</height> <depth>3</depth> </size> <object> <name>crack</name> <bndbox> <xmin>320</xmin> <ymin>410</ymin> <xmax>455</xmax> <ymax>470</ymax> </bndbox> </object> </annotation>filename要与JPEGImages/里实际图片名严格一致,size里的宽高决定了框坐标的绝对上限。bndbox的四个值是像素坐标,x 方向范围 0 到 width,y 方向范围 0 到 height,这个坐标系和 YOLO 的归一化坐标是两套体系。
判断这套 VOC 是否规范,看两点:size是否与真实图片分辨率一致,xmax/ymax是否小于图片宽高。很多数据集这里就埋了雷,标注框超出边界的并不罕见。
2.3 YOLO 格式:TXT 标注的归一化坐标规则
YOLO 格式每个图片对应一个同名 txt,放在labels/下,每行代表一个目标:
0 0.2018 0.4074 0.0703 0.0278 0 0.5123 0.6481 0.0412 0.0185五个数字依次是类别ID x_center y_center width height,全部归一化到 0~1。换算公式:
- x_center = (xmin + xmax) / 2 / 图片宽度
- y_center = (ymin + ymax) / 2 / 图片高度
- width = (xmax - xmin) / 图片宽度
- height = (ymax - ymin) / 图片高度
所以 VOC 转 YOLO 不是简单改后缀,而是要做坐标换算。反过来 YOLO 转 VOC 就是乘回图片宽高。这个换算关系是整个数据集格式对齐的核心,后面我给的校验脚本也是基于这套公式。
2.4 两套格式不一致时,用 Python 做交叉校验
拿到双格式数据集,第一步不是训练而是校验。我一般直接写一个小脚本,把图片、xml、txt 三者的文件名做集合比对,再抽查坐标换算是否一致:
import os import glob import xml.etree.ElementTree as ET img_dir = 'JPEGImages' voc_dir = 'Annotations' yolo_dir = 'labels' imgs = sorted(glob.glob(os.path.join(img_dir, '*.jpg'))) voc_xmls = sorted(glob.glob(os.path.join(voc_dir, '*.xml'))) yolo_txts = sorted(glob.glob(os.path.join(yolo_dir, '*.txt'))) print(f'图片 {len(imgs)} 张, VOC {len(voc_xmls)} 个, YOLO {len(yolo_txts)} 个') # 提取不带后缀的文件名,做集合差 img_names = {os.path.splitext(os.path.basename(p))[0] for p in imgs} voc_names = {os.path.splitext(os.path.basename(p))[0] for p in voc_xmls} yolo_names = {os.path.splitext(os.path.basename(p))[0] for p in yolo_txts} print('缺VOC标注:', len(img_names - voc_names)) print('缺YOLO标注:', len(img_names - yolo_names)) # 抽查第一个样本,验证坐标换算 tree = ET.parse(voc_xmls[0]) root = tree.getroot() w = int(root.find('size/width').text) h = int(root.find('size/height').text) obj = root.find('object') 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) with open(os.path.join(yolo_dir, os.path.basename(voc_xmls[0]).replace('.xml', '.txt')), 'r') as f: line = f.readline().strip().split() x_c, y_c, bw, bh = float(line[1]), float(line[2]), float(line[3]), float(line[4]) print(f'VOC : ({xmin},{ymin})-({xmax},{ymax})') print(f'YOLO: cx={x_c:.4f} cy={y_c:.4f} w={bw:.4f} h={bh:.4f}') print(f'换算: cx={(xmin+xmax)/2/w:.4f} cy={(ymin+ymax)/2/h:.4f} ' f'w={(xmax-xmin)/w:.4f} h={(ymax-ymin)/h:.4f}')脚本逻辑分三段:第一段统计三个目录的文件数,判断是否有缺漏;第二段取文件名做集合差,找出只有图片没有标注的样本;第三段抽第一个样本做反向换算,手工比对 VOC 像素坐标转成归一化坐标后与 YOLO txt 里写的是否一致。
参数说明:glob.glob匹配文件名时,如果你的图片是.png或.jpeg后缀,把*.jpg改成对应后缀;replace('.xml', '.txt')依赖文件名一一对应,如果文件名本身有杂质,比如crack_001(2).jpg,这里就会出问题,需要先做一次文件名清洗。
3. 训练前的数据体检与数据集拆分,先把 8678 张理顺
3.1 图片完整性、尺寸与通道数检查
裂缝检测数据集里,最容易被忽略的是图片本身的质量问题。破损文件、尺寸不一致、灰度图混在 RGB 图里,这些在训练时会让数据加载器直接报错,或者让 batch 内的图片尺寸对不上。
from PIL import Image import glob imgs = glob.glob('JPEGImages/*.jpg') bad_files = [] size_set = set() mode_set = set() for p in imgs: try: img = Image.open(p) img.verify() # 检查文件是否损坏 img = Image.open(p) size_set.add(img.size) # 统计分辨率集合 mode_set.add(img.mode) # 统计颜色模式 except Exception as e: bad_files.append((p, str(e))) print('分辨率类型数:', len(size_set), size_set) print('颜色模式:', mode_set) print('损坏文件:', bad_files)verify()只校验文件完整性,不加载像素数据,速度快但对截断文件敏感。如果出现大量OSError: image file is truncated,说明图片在采集或打包时没写完整,常见做法是用ImageFile.LOAD_TRUNCATED_IMAGES = True强行加载,但我建议直接把这类文件剔除,训练集里少几十张图影响不大,混入坏图才影响收敛。
分辨率集合超过三种就要警惕:模型输入固定为 640 或 1280 时,极端细长比图片会被强制 resize,裂缝这种细长目标会被拉变形,后续标注框也跟着失真。遇到这种情况,我一般把图片统一 resize 到长边 1280、短边等比缩放,再做训练。
3.2 类别统计:裂缝分了几个类、各类多少框
裂缝数据集的类别设计差异很大:有的只有一个crack类,有的按位置拆成wall_crack、cement_crack、pavement_crack,还有的会把网状裂缝单独做一类。类别数直接影响 data.yaml 里的nc参数,这里必须统计清楚。
from collections import Counter import glob yolo_txts = glob.glob('labels/*.txt') cls_counter = Counter() total_boxes = 0 box_area_sum = 0.0 class_names = { 0: 'crack', 1: 'wall_crack', 2: 'cement_crack', 3: 'pavement_crack', # 按数据集的实际情况补全 } for txt in yolo_txts: with open(txt, 'r') as f: for line in f: parts = line.strip().split() if len(parts) < 5: continue cls_id = int(parts[0]) w = float(parts[3]) h = float(parts[4]) cls_counter[cls_id] += 1 total_boxes += 1 box_area_sum += w * h print('各类别框数:', dict(cls_counter)) print('总框数:', total_boxes) print('平均归一化面积:', box_area_sum / total_boxes)parts[0]到parts[4]对应 YOLO 格式五字段,parts[3]和parts[4]是归一化宽高,两者相乘就是目标占整图的比例。平均面积如果小于 0.01,说明数据集以中小目标为主,训练时imgsz不能设太小,后面验证阶段也要多关注小目标召回率。
统计结果如果发现某个类别只有几十个框,比如wall_crack只出现 20 次,那这个类基本练不出来,要不要合并成一个大类,得提前决策。
3.3 训练集/验证集/测试集划分,随机种子与目录分配
划分数据集的常见坑是:直接按文件名排序切前 80%,结果前 800 张全是白天拍的柏油路裂缝,后面全是墙面裂缝,训练和验证分布对不上。正确做法是打乱后按比例切,并且固定随机种子让结果可复现。
mkdir -p images/train images/val images/test mkdir -p labels/train labels/val labels/testimport random from pathlib import Path import shutil random.seed(42) img_dir = Path('JPEGImages') label_dir = Path('labels') imgs = sorted(img_dir.glob('*.jpg')) random.shuffle(imgs) train_cnt = int(len(imgs) * 0.8) val_cnt = int(len(imgs) * 0.1) train_imgs = imgs[:train_cnt] val_imgs = imgs[train_cnt:train_cnt + val_cnt] test_imgs = imgs[train_cnt + val_cnt:] def move_samples(img_list, sub): for img in img_list: lbl = label_dir / (img.stem + '.txt') shutil.copy(img, f'images/{sub}/') if lbl.exists(): shutil.copy(lbl, f'labels/{sub}/') move_samples(train_imgs, 'train') move_samples(val_imgs, 'val') move_samples(test_imgs, 'test') print(f'train {len(train_imgs)} val {len(val_imgs)} test {len(test_imgs)}')random.seed(42)决定打乱顺序,换一个种子划分结果就不同;shutil.copy而不是move,避免源文件丢失后想重做划分还要重新解压。验证集和测试集的差异要说清楚:验证集用于调整超参数和模型选择,测试集只在最终评估时用一次,不能拿测试集反复试错,否则就是数据泄漏。
划分比例上,8000 多张的数据量按 8:1:1 合理,如果只有两三千张,建议改成 9:0.5:0.5,尽量保训练量。
3.4 针对细长裂缝的数据增强,哪些操作会翻车
YOLO 训练默认会做 Mosaic、随机翻转、色域变换。这些增强对普通目标没问题,但裂缝是典型的细长目标,几个增强操作要特别小心。
左右翻转对裂缝是安全的,因为裂缝方向语义不变;上下翻转要谨慎,路面裂缝和墙面裂缝在光照方向上有隐含的物理信息,垂直翻转会让模型学到错误的阴影模式。旋转增强如果角度超过 30 度,横平竖直的墙面裂缝会变成斜线,反而增加了学习难度。
我一般建议在 YOLO 的增强参数里做如下控制:
fliplr: 0.5 flipud: 0.0 degrees: 10 shear: 5 scale: 0.3 mosaic: 0.8fliplr保持 0.5,flipud直接关掉,degrees限制在 ±10 度。mosaic的 0.8 意思是 80% 概率使用四张图拼成一张,对小目标检测有明显收益,但 mosaic 拼接会把裂缝在拼接缝切断,模型被迫学习断头裂缝的特征,所以训练后期建议把 mosaic 关掉做精调。
4. 用 ultralytics 跑通裂缝检测训练:从 data.yaml 到训练曲线
4.1 ultralytics 环境配置与版本锁定
YOLO 的开箱即用方案,现在基本都围绕ultralytics这个包展开。配置环境时最容易出问题的是版本漂移,所以我直接给出版本锁定做法:
python -m venv yolenv source yolenv/bin/activate pip install ultralytics==8.3.40 python -c "from ultralytics import YOLO; print(YOLO.__name__)"python -m venv建虚拟环境是第一步,日常项目里见过太多人把 ultralytics 直接装进系统 Python,然后跟其他框架的依赖打架。pip install ultralytics==8.3.40锁死版本,避免下次安装时自动拉一个新大版本,训练超参数和 API 行为都变了。
为什么不装最新版本?YOLO 的迭代太快,新版可能改了数据增强默认值或者推理接口,对数据集复现不友好。固定一个常用稳定版本比追新更省心,这是做落地项目的基本习惯。
4.2 data.yaml 的写法:路径、类别数、类别名
data.yaml 是 ul 训练时的唯一数据入口,路径写错是最常见的启动失败原因。我给一个可直接套用的模板:
path: /home/user/crack_dataset train: images/train val: images/val test: images/test nc: 1 names: 0: crackpath是项目根目录的绝对路径,不要用相对路径,因为yolo命令行的工作目录和path的解析起点可能不一致。train、val、test是相对path的子目录。nc必须和你统计出来的类别数一致,如果第 3 章统计出 4 个类,这里就写 4,并列出完整的 names 映射。
常见翻车点:names列表顺序不对应 txt 里的class_id。如果训练时发现 loss 正常但预测框类别张冠李戴,先检查这里。
4.3 训练命令与关键参数:epochs、imgsz、batch
yolo detect train \ data=data.yaml \ model=yolov8s.pt \ epochs=100 \ imgsz=640 \ batch=16 \ lr0=0.01 \ patience=10model=yolov8s.pt是用 COCO 预训练权重做迁移学习,比自己从零训练收敛快得多。imgsz=640是最常用输入分辨率,如果你的显卡显存余量充裕,且第 3 章统计出小目标偏多,可以升到1280,但显存占用会翻 4 倍,实测 8G 显存下 batch 只能拉到 8 左右。batch=16是单卡常见值,调大能加速但受显存限制。lr0=0.01是初始学习率,用默认值起步就行,不用一开始就动。
提示:
patience=10表示验证集指标连续 10 个 epoch 不提升就提前停止。别小看这个参数,没有它很多模型会过拟合,最后 20 个 epoch 的 val_loss 一直在涨,但训练没停,白白浪费时间。
4.4 训练曲线怎么看:box_loss、cls_loss、mAP
训练结束后,去runs/detect/train/目录看results.png和vis.png。四个指标最值得关注:train/box_loss、train/cls_loss、metrics/mAP50(B)、metrics/mAP50-95(B)。
box_loss持续下降是模型在学框的位置和大小,如果曲线在 30 epoch 后变成水平震荡,说明收敛了,再训练也不会明显变好。cls_loss只在多类别时有意义。单裂缝类的情况下,它是二分类损失,快速降到接近 0 是正常的。mAP50是 IoU 阈值 0.5 下的平均精度,裂缝检测任务看这个指标最直观。常规良好结果在 0.7 以上,0.4 以下说明数据或训练有问题。mAP50-95更严苛,对框的精准度敏感。裂缝这种细长目标天然难拿高分,能到 0.35 就算不错,别用这个指标否定整个模型。
如果发现mAP50停在 0.5 左右上不去,优先怀疑第 5 章里提到的小目标和标注噪声问题,不要急着换大模型。
5. 裂缝检测训练避坑笔记:四个最容易翻车的环节
5.1 图片与标注文件编号错位
现象:训练能启动,验证 loss 不降,可视化预测时发现预测框出现在完全没有裂缝的图片区域,或者裂缝明显的图片里一个框都没有。
原因:数据集打包时图片和标注文件按不同顺序打乱,文件名编号并不连续对应。举例:crack_1024.jpg的标注内容其实是crack_1023.jpg的,而那两处裂缝外观高度相似,人眼分不出来,模型被错误监督信号带偏。
解决:训练前必须用第 2 章给的交叉校验脚本跑一遍,抽 20 个样本手工对照xml里的filename字段和实际图片内容。更稳妥的做法是重新生成一套统一的文件名,而不是依赖原数据集命名:
import os, shutil from pathlib import Path img_files = sorted(Path('JPEGImages').glob('*.jpg')) for idx, img in enumerate(img_files): new_name = f'{idx:06d}.jpg' shutil.copy(img, f'images/{new_name}') txt = Path('labels') / (img.stem + '.txt') if txt.exists(): shutil.copy(txt, f'labels/{idx:06d}.txt')这个脚本把全部图片和标注重新命名为000001.jpg的连续格式,一次性消除原命名的错位隐患。注意拷贝而不是移动,源目录保留,出问题有后悔药。
5.2 细长裂缝的小目标漏检
现象:模型推理在明显裂缝图片上只框出一小段,中间断开,或者完全漏检;mAP50不高,尤其在大图片上的表现明显退化。
原因:裂缝是长条状目标,一个完整裂缝可能横跨 800 像素,但宽度只有 15 像素。训练时imgsz=640将原图缩放一半,裂缝宽度缩到 7 个像素,特征在多层下采样后几乎消失,只剩断裂的局部纹理信息。
解决:把imgsz升到1024或1280,同时用mosaic增强给小目标更多采样机会。如果显存不够,可以训练时用 640,推理时用 1280,模型对输入分辨率有一定容忍度。实测裂缝检测项目里,推理分辨率提升到 1280 后mAP50通常能涨 5 到 8 个百分点,代价是单帧推理时间变长。
5.3 类别不平衡导致收敛后偏科
现象:多类别训练结束后,crack类mAP50有 0.75,pavement_crack类只有 0.12,查看confusion_matrix.png发现少数类大量被判成多数类。
原因:第 3 章统计时会看到少数类只有几十个框,多数类上千个框。YOLO 的损失是逐目标计算的,类别样本数差距过大时,少数类的梯度被多数类淹没,模型学会直接输出多数类。
解决:三个手段按顺序用。第一,给少数类做过采样,把包含该类别的图片复制一份加入训练集;第二,类别权重让损失函数对少数类更敏感,ultralytics 里可以在dataset.yaml中指定weight字段,但更直接的是先过采样;第三,如果少数类框数少于 100,果断删掉它,把标注合并到主类里。数据量都不足时强行分类,是性价比最低的做法。
5.4 loss 变 NaN 或训练直接发散
现象:训练跑到第 10 个 epoch 左右,box_loss数值突然变成nan,训练日志里出现RuntimeError: CUDA error: device-side assert triggered,甚至进程直接退出。
原因:最常见不是 GPU 坏了,而是标注里有非法值。YOLO 格式的归一化坐标要求 x_center、width 都在 0~1 之间,如果某个 txt 里出现负数或大于 1 的值,损失计算时就出 NaN。另一种可能是提前学习率过大,但 0.01 对 YOLOv8 是安全的,优先排查数据。
解决:
import glob bad_files = [] for txt in glob.glob('labels/**/*.txt', recursive=True): with open(txt, 'r') as f: for line_no, line in enumerate(f, 1): parts = line.strip().split() if len(parts) != 5: bad_files.append((txt, line_no, '列数不对')) continue try: vals = [float(x) for x in parts] except ValueError: bad_files.append((txt, line_no, '非数值')) continue if vals[0] < 0 or vals[1] < 0 or vals[1] > 1 or vals[2] < 0 or vals[2] > 1: bad_files.append((txt, line_no, f'越界: {vals}')) for item in bad_files[:20]: print(item) print('非法标注总数:', len(bad_files))脚本遍历所有 txt,检查三点:列数是否为 5、数值是否可解析、归一化坐标是否在 [0,1]。凡是越界的第一时间把对应图片拉到 yolov8 训练时用dataloader强制校验。找到非法标注后可以直接删除这一行,或者在原数据集里找到对应图片重新标注。
6. 验证模型与部署:从 val 指标到落地可用的最后一步
模型训练完,第一件事不是急着部署,而是跑一次官方验证流程,把测试集上的指标固定下来。用best.pt而不是last.pt,best.pt是验证集表现最好的权重,last.pt只是最后一个 epoch 的产物,两者差别在训练后期可能很明显:
from ultralytics import YOLO model = YOLO('runs/detect/train/weights/best.pt') metrics = model.val(data='data.yaml', split='test') print('mAP50:', round(metrics.box.map50, 4)) print('mAP50-95:', round(metrics.box.map, 4)) model.predict(source='test_images/real_01.jpg', conf=0.3, save=True, imgsz=1280)split='test'用测试集,这个数字是你对外汇报的唯一依据,不要拿验证集结果冒充。conf=0.3对裂缝场景偏低,推理时会出现大量低置信度碎片框,实际部署时可以调到 0.5 以上过滤噪声。
部署环节,如果是服务端推流检测,做法是导出 ONNX 后用 TensorRT 加速;如果是边缘盒子,直接转成 NCNN 或 RKNN。导出命令:
yolo export model=best.pt format=onnx imgsz=1280 opset=17导入 ONNX 时注意imgsz必须和导出时一致,输入尺寸变了要重新导出或做动态维度。实测在 T4 上用 TensorRT 推理一张 1280 分辨率的裂缝图片,大概 6 到 8 毫秒,这已经足够支撑实时视频流。
最后说一个我自己反复踩过的教训:这套流程跑完,模型的mAP50可能不错,但在真实场景表现打折,原因多半不是模型,而是训练图像和现场光照差距大。我现在的习惯是部署后前两周每天收集 20 张现场误检和漏检图,人工修正后增量训练,两周后模型在真实环境的表现会有可见提升。落地从来不是训练完那天完成的,是把数据回路闭合之后才真正完成。希望这套从解压到部署的路径,能帮你少走一些我走过的弯路。
本文还有配套的精品资源,点击获取