ARTICLE DETAIL

资讯详情

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

YOLO马路裂缝检测实战:3258张数据集训练与调优指南

YOLO马路裂缝检测实战:3258张数据集训练与调优指南 简介这份资源面向从事道路病害检测、计算机视觉目标检测方向的学习者与工程人员提供一套可直接用于YOLO系列算法训练的马路裂缝数据集帮助解决裂缝识别任务中数据采集与标注成本高的问题。压缩包共2000个文件以xml标注文件为主同时提供YOLO格式的txt标签两种格式分别存放便于按需选用整体约104.55MB并附带data.yaml配置文件适配yolov5、yolov8、yolov9、yolov7、yolov10及yolo11等主流版本。数据集已按训练与验证划分完毕图像与标签一一对应YOLO标签采用类别索引加归一化中心点与宽高的标准格式可直接投入模型训练与测试。目前已有187人学习下载适合希望快速复现裂缝检测实验、验证算法效果或搭建道路巡检原型的读者参考使用。1. 马路裂缝数据集与 YOLO 训练3258 张带标签图像能跑出什么结果手头拿到一个yolo算法-马路裂缝数据集-3258张图像带标签.zip第一反应通常不是急着解压而是先判断这批数据能不能直接喂给 YOLO。3258 张带标签图像在目标检测里属于中小规模数据集做单类裂缝检测够用但想直接刷高 mAP 不现实。马路裂缝本身是细长、低对比度、边缘模糊的目标标注框稍微松一点模型学到的就是一团糊。这个数据集真正适合的人群是做道路巡检、市政养护、无人机路面排查的工程团队以及想用真实场景练 YOLO 微调的学生和算法工程师。它解决的核心问题是——不用自己扛着相机去马路上拍几千张也不用从零标框直接进入训练和调参环节。但前提是你得先搞清楚标签格式、类别定义和图像质量否则后面全是玄学。2. 先验数据3258 张裂缝图像到底长什么样2.1 解压后先看目录结构和标签格式拿到 zip 之后不要直接丢给训练脚本。先解压看目录层级。常见做法是images/和labels/两个文件夹并列或者按train/val/test分好。如果只有一堆图和一个annotations.xml那可能是 VOC 格式需要转 YOLO。下面这段脚本用来快速统计图像数量、标签文件数量和类别分布。import os import glob from collections import Counter root road_crack_dataset img_dir os.path.join(root, images) lbl_dir os.path.join(root, labels) imgs glob.glob(os.path.join(img_dir, *)) lbls glob.glob(os.path.join(lbl_dir, *.txt)) print(f图像总数: {len(imgs)}) print(f标签文件数: {len(lbls)}) # 统计类别分布YOLO 格式第一列为 class_id cls_counter Counter() for lb in lbls: with open(lb, r) as f: for line in f: parts line.strip().split() if parts: cls_counter[parts[0]] 1 print(类别分布:, cls_counter)逻辑说明先确认图像和标签是否一一对应。如果图像 3258 张标签只有 3000 个说明有 258 张漏标训练时要么剔除要么当负样本。类别分布如果只有一个类0那就是单类检测YOLO 的nc设为 1。参数说明root换成你实际解压路径cls_counter统计的是每个类别的框数量不是图像数量一个图可能有多个框。2.2 检查图像分辨率和裂缝框的宽高比马路裂缝的框通常又长又窄宽高比可能到 1:10 甚至更极端。YOLO 默认 anchor 是基于 COCO 的直接拿来训裂缝小目标召回会很难看。先统计一下框的宽高分布。import numpy as np ratios [] areas [] for lb in lbls: with open(lb, r) as f: for line in f: parts line.strip().split() if len(parts) 5: _, x, y, w, h map(float, parts) if h 0: ratios.append(w / h) areas.append(w * h) ratios np.array(ratios) areas np.array(areas) print(f宽高比中位数: {np.median(ratios):.3f}) print(f宽高比 10% 分位: {np.percentile(ratios, 10):.3f}) print(f宽高比 90% 分位: {np.percentile(ratios, 90):.3f}) print(f框面积中位数: {np.median(areas):.5f})逻辑说明YOLO 的 anchor 如果和真实框宽高比差太远正样本匹配会很少。中位数宽高比如果小于 0.3说明裂缝普遍细长需要重新聚类 anchor。参数说明w和h是归一化后的宽高面积也是归一化面积不是像素面积。如果 90% 分位的宽高比都不到 0.5那默认 anchor 基本废了。2.3 图像质量筛查模糊、过曝、重复图3258 张里一定混着废图。模糊图会让模型学不到边缘过曝图裂缝和路面一个颜色。用拉普拉斯方差快速筛模糊。import cv2 blur_threshold 100 blur_imgs [] for img_path in imgs: img cv2.imread(img_path) if img is None: continue gray cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) score cv2.Laplacian(gray, cv2.CV_64F).var() if score blur_threshold: blur_imgs.append((img_path, score)) print(f模糊图像数量: {len(blur_imgs)}) for p, s in blur_imgs[:10]: print(p, round(s, 2))逻辑说明拉普拉斯方差低于 100 通常认为模糊。马路裂缝如果本身对比度低这个阈值可以降到 50。参数说明blur_threshold根据你的图像分辨率调整1080p 用 1004K 用 200。筛出来的模糊图不要直接删先人工看一眼有些是裂缝本身太细导致方差低不是真模糊。3. 把 VOC 或 COCO 标签转成 YOLO 格式脚本与四个边界坑3.1 VOC XML 转 YOLO txt 的完整脚本如果解压出来是annotations.xml或者一堆.xml必须转。下面这个脚本处理单个 XML 文件夹输出 YOLO 格式的.txt。import xml.etree.ElementTree as ET import os def voc_to_yolo(xml_dir, out_dir, classes): os.makedirs(out_dir, exist_okTrue) for xml_file in os.listdir(xml_dir): if not xml_file.endswith(.xml): continue tree ET.parse(os.path.join(xml_dir, xml_file)) root tree.getroot() size root.find(size) w_img int(size.find(width).text) h_img int(size.find(height).text) lines [] for obj in root.findall(object): cls_name obj.find(name).text if cls_name not in classes: continue cls_id classes.index(cls_name) 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) # 边界裁剪防止越界 xmin max(0, min(xmin, w_img - 1)) xmax max(0, min(xmax, w_img - 1)) ymin max(0, min(ymin, h_img - 1)) ymax max(0, min(ymax, h_img - 1)) x_center (xmin xmax) / 2.0 / w_img y_center (ymin ymax) / 2.0 / h_img w (xmax - xmin) / w_img h (ymax - ymin) / h_img if w 0 or h 0: continue lines.append(f{cls_id} {x_center:.6f} {y_center:.6f} {w:.6f} {h:.6f}) out_path os.path.join(out_dir, xml_file.replace(.xml, .txt)) with open(out_path, w) as f: f.write(\n.join(lines)) voc_to_yolo(annotations, labels, classes[crack])逻辑说明VOC 的坐标是绝对像素YOLO 要归一化到 0-1。x_center和y_center是框中心点不是左上角。边界裁剪防止标注框超出图像范围导致归一化后出现负数。参数说明classes列表顺序决定cls_id如果只有裂缝就写[crack]。w和h如果算出来是 0说明标注框退化了直接跳过。3.2 转换后必须验证三个检查点转完不是就完了。第一检查有没有空标签文件空文件意味着这张图没有目标YOLO 会当负样本但如果是漏标就会误导模型。第二检查归一化坐标有没有超过 1。第三随机抽 10 张画框可视化。import matplotlib.pyplot as plt import matplotlib.patches as patches import random def visualize_yolo(img_path, lbl_path): img cv2.imread(img_path) img cv2.cvtColor(img, cv2.COLOR_BGR2RGB) h, w, _ img.shape fig, ax plt.subplots(1) ax.imshow(img) with open(lbl_path, r) as f: for line in f: parts line.strip().split() if len(parts) ! 5: continue _, xc, yc, bw, bh map(float, parts) x1 (xc - bw / 2) * w y1 (yc - bh / 2) * h rect patches.Rectangle((x1, y1), bw * w, bh * h, linewidth2, edgecolorr, facecolornone) ax.add_patch(rect) plt.show() sample random.sample(imgs, 5) for img_path in sample: base os.path.basename(img_path) lbl_path os.path.join(labels, base.replace(.jpg, .txt)) if os.path.exists(lbl_path): visualize_yolo(img_path, lbl_path)逻辑说明可视化是最直接的验证。如果框歪了、框太大、框太小一眼就能看出来。参数说明x1和y1是左上角像素坐标bw * w是框的像素宽度。如果画出来框跑到图像外面说明归一化坐标有问题。3.3 四个边界坑越界、空标签、类别错位、文件名不匹配越界坑VOC 标注时框可能超出图像边界归一化后x_center可能大于 1 或小于 0。解决方法是转换时强制裁剪到[0, 1]。空标签坑有些图没有裂缝XML 里没有object转出来是空 txt。YOLO 默认把空 txt 当负样本但如果是漏标模型会学到“这里没裂缝”的错误知识。解决方法是人工抽查空标签对应的图。类别错位坑如果classes列表顺序和 XML 里的name不一致cls_id会错。比如 XML 里写的是crack你列表里第一个是pothole那所有裂缝都被标成坑洞。解决方法是先打印 XML 里所有name去重。文件名不匹配坑图像是001.jpg标签是001.xml转出来的001.txt但如果图像有.jpeg后缀标签是.txtYOLO 找不到对应文件。解决方法是统一后缀或者用脚本批量重命名。4. 用 YOLOv8 训练裂缝检测配置文件与关键参数4.1 写 dataset.yaml路径、类别数、名称YOLOv8 需要一份dataset.yaml告诉它训练集、验证集在哪有几个类。path: /home/user/road_crack_dataset train: images/train val: images/val test: images/test nc: 1 names: 0: crack逻辑说明path是数据集根目录train和val是相对路径。nc是类别数裂缝只有一类就写 1。names是类别名索引从 0 开始。参数说明如果验证集和训练集在同一个文件夹val可以写images/train但这样验证指标会虚高不建议。4.2 启动训练命令行与 Python 两种方式命令行方式最直接yolo detect train \ datadataset.yaml \ modelyolov8n.pt \ epochs100 \ imgsz640 \ batch16 \ lr00.01 \ patience20 \ device0逻辑说明modelyolov8n.pt是 nano 版本速度快适合先跑通。epochs100对 3258 张图够用但裂缝细长可能需要 200 轮。imgsz640是默认输入尺寸如果裂缝在图中占比很小可以提到 1024。batch16根据显存调8G 显存跑 640 用 16 没问题。lr00.01是初始学习率微调时降到 0.001。patience20是早停20 轮验证指标不升就停。device0用第一块 GPU。Python 方式适合嵌入到自己的流程from ultralytics import YOLO model YOLO(yolov8n.pt) results model.train( datadataset.yaml, epochs100, imgsz640, batch16, lr00.01, patience20, device0, projectcrack_runs, nameexp1 )逻辑说明project和name控制输出目录方便对比不同实验。参数说明lr0如果设太大损失会震荡设太小收敛慢。裂缝检测建议从 0.001 开始试。4.3 裂缝检测的 anchor 重聚类为什么默认 anchor 不够用YOLOv8 虽然是无 anchor 的但它的正样本匹配仍然依赖框的尺度分布。如果裂缝框普遍是细长条默认的匹配策略会偏向正方形框。常见做法是用 k-means 对训练集的宽高重新聚类然后调整anchor或者用autoanchor。from sklearn.cluster import KMeans import numpy as np wh [] for lb in lbls: with open(lb, r) as f: for line in f: parts line.strip().split() if len(parts) 5: _, _, _, w, h map(float, parts) wh.append([w, h]) wh np.array(wh) kmeans KMeans(n_clusters9, random_state0).fit(wh) print(聚类 anchor 宽高:) print(kmeans.cluster_centers_)逻辑说明9 个聚类中心对应 9 个 anchor。如果聚类出来的宽高比普遍小于 0.5说明默认 anchor 确实不匹配。参数说明n_clusters9是 YOLO 默认 anchor 数量可以改成 6 或 12但 9 最稳。5. 训练裂缝检测的避坑与排查从 loss 不降到 mAP 虚高5.1 现象loss 一直不降mAP 在 0.1 附近晃原因学习率太大或者标签有问题。裂缝框如果归一化坐标错了模型学的是随机框loss 自然不降。解决先拿 10 张图过一遍模型看预测框是不是乱飞。如果是回头检查标签。另外把lr0降到 0.001batch降到 8再跑 20 轮看趋势。5.2 现象训练集 mAP 很高验证集 mAP 很低原因过拟合。3258 张图如果训练集占 90%验证集只有 10%模型会把训练集背下来。解决调整划分比例训练集 70%验证集 20%测试集 10%。另外加数据增强YOLOv8 默认有 mosaic 和 mixup但裂缝检测可以关掉 mixup因为裂缝被混合后语义就乱了。5.3 现象小裂缝漏检严重大裂缝框不准原因输入分辨率不够。裂缝在 640 尺寸下可能只有几个像素宽模型看不到。解决把imgsz提到 1024 或 1280但显存会涨。另一个方法是切图推理把大图切成 640 的小块分别检测再合并。参数说明imgsz每翻一倍显存大约涨 4 倍8G 显存跑 1280 基本到极限。5.4 现象验证集 mAP 突然掉到 0原因验证集路径写错或者验证集标签格式不对。YOLO 在验证时如果找不到标签会把所有预测当误报mAP 直接归零。解决检查dataset.yaml里的val路径确保images/val和labels/val都存在且文件名一一对应。5.5 现象训练到一半显存爆了原因batch太大或者imgsz太大。3258 张图如果都是高分辨率640 的 batch16 可能就爆。解决用batch8或者batch4同时开ampTrue混合精度。YOLOv8 默认开 AMP但如果显卡不支持手动关掉。6. 裂缝检测的进阶技巧从单帧检测到视频流推理训练完模型只是第一步。马路巡检的实际场景是视频流或者连续拍照单帧检测不够。下面这段代码把 YOLOv8 模型接到摄像头或者视频文件上做逐帧推理并加一个简单的裂缝计数逻辑。from ultralytics import YOLO import cv2 model YOLO(crack_runs/exp1/weights/best.pt) cap cv2.VideoCapture(road_inspection.mp4) frame_count 0 crack_frames 0 while cap.isOpened(): ret, frame cap.read() if not ret: break frame_count 1 results model(frame, imgsz1024, conf0.25, iou0.45) boxes results[0].boxes if boxes is not None and len(boxes) 0: crack_frames 1 for box in boxes: x1, y1, x2, y2 map(int, box.xyxy[0]) cv2.rectangle(frame, (x1, y1), (x2, y2), (0, 0, 255), 2) cv2.imshow(Crack Detection, frame) if cv2.waitKey(1) 0xFF ord(q): break cap.release() cv2.destroyAllWindows() print(f总帧数: {frame_count}, 有裂缝帧数: {crack_frames})逻辑说明conf0.25是置信度阈值低于 0.25 的框不要。iou0.45是 NMS 的 IoU 阈值裂缝框重叠多的话可以调到 0.5。crack_frames统计有多少帧检测到裂缝用来估算裂缝路段的占比。参数说明imgsz1024比训练时的 640 大推理会慢但小裂缝召回更好。如果视频是 30fps1024 尺寸在 V100 上大概能跑到 20fps实时性够用。进阶技巧里最值得说的是切片推理。如果裂缝在 4K 图里只占很小一块直接缩到 640 会丢。把 4K 图切成 6 块 640 的小图分别推理再按坐标合并。这样小裂缝的召回能提升 20% 以上。代价是推理时间翻 6 倍但巡检场景不需要实时离线跑完全能接受。另一个技巧是用验证集反推标注质量。训练完把验证集里 mAP 最低的 20 张图挑出来人工看标签。如果模型预测对了但标签没标说明漏标如果模型预测错了但标签是对的说明模型没学好。这个习惯我坚持了三年每次都能揪出几十张问题图。裂缝检测的数据集标注质量比模型结构重要得多。3258 张图里如果有 10% 的框是歪的再好的模型也白搭。希望帮到你。本文还有配套的精品资源点击获取
返回列表