ARTICLE DETAIL

资讯详情

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

YOLOv8基建裂缝检测实战:从数据标注到部署的全流程指南

YOLOv8基建裂缝检测实战:从数据标注到部署的全流程指南 简介面向高校毕业设计、课程设计与项目开发的基建裂缝目标检测系统基于Python与YOLOv8实现将数据准备、模型训练、评估与结果可视化整合为完整闭环适合土木工程或计算机视觉方向的本科生、研究生及开发者直接参考。包体共850个文件约666MB其中329张jpg图像配合299个txt及158个xml标注构成训练数据23个pt权重与4个yaml配置用于模型加载与推理7个py脚本和开发文档md文件可帮助理解代码结构与快速改造csv结果记录便于核对各项训练指标。系统源码已经严格测试可直接运行并在原框架基础上延伸使用对想要快速搭建裂缝检测基准方案、完成课程报告或毕设模块的读者很有价值。已有206人在线学习说明其真实可用程度较高是一份能落地的实践型资源。1. 基建裂缝目标检测为什么从yolov8起步先解决“能不能用”基建裂缝目标检测在公路、桥隧和房屋巡检里被反复提起不是因为模型本身多难而是人工目检的真实成本摆在那一堵三百米的挡墙两个人拿裂缝测宽仪走一上午回来还要对着手写记录重新录入。用目标检测先在现场照片里把可疑裂缝区域自动框出来巡检人员只需要复核框内位置这是目前最成熟的自动化路线而 yolov8 是这个方向上性价比很高的切入点。它训练生态完整、部署链路短本科毕设或课程设计拿一张普通显卡哪怕只有 CPU也能从零跑通一个带标注、带训练、带评估、带展示的完整项目。这篇文章适合两类人一是正在做毕业设计或课程设计需要一个“从数据集到结果展示”都说得通的项目二是刚接触目标检测的从业者想用基建场景练手同时搞清楚细长目标检测和常规目标检测的差别。下面按数据制备、训练调参、结果评估、部署交付四个环节拆开讲最后给一份我常用的训练日志可视化技巧。2. 数据制备现场照片、labelme标注与YOLO格式转换这步决定模型上限2.1 现场照片怎么拍才够用光源、距离和背景多样性裂缝检测的目标是细长线状物和检测行人、车辆完全不同。行人是紧凑目标一个框大致包住就好裂缝可能横跨半张图也可能只有十几个像素宽。数据采集阶段就要围绕这个特点设计。我一般会按三个维度控制采集光源条件至少覆盖顺光、逆光、阴天三种拍摄距离近景 0.5 米到 1 米拍细节中景 2 米到 3 米拍断裂趋势背景种类混凝土墙面、沥青路面、砖石砌体、水泥抹面都要有因为模型在训练时会把背景纹理一起学进去。如果只拍一种表面换到现场就容易翻车。数量上一个能支撑毕设演示的项目500 到 1500 张原图比较合理。每张图上的裂缝目标尽量控制在 1 到 5 个目标太密会让标签互相覆盖太疏则训练时正样本不足。清洗规则要狠虚焦的、过曝的、被树叶或阴影大面积遮挡的直接删除不要在清洗阶段心疼数据量。模糊图混进训练集后果是模型学到“边缘模糊就是裂缝”。2.2 labelme标注规则一个框还是两个框框边留多少标注工具用 labelme 最省事因为它的 json 格式里直接用像素坐标记录 shape转成 YOLO 格式时逻辑透明。标矩形框时有两条规则我建议坚持。第一条框要贴着裂缝外接矩形四个边离裂缝边缘不要超过 10 个像素。很多人习惯性留大片空白结果 YOLO 的 anchor 匹配时中心点落在背景上正样本质量被稀释小裂缝直接学不到。第二条一条裂缝断成几段时如果段间距小于裂缝宽度的 3 倍合进一个框如果间距明显大于裂缝宽度拆成多个框。这样模型学到的是“裂缝趋势是连续的”而不是把每一小段当成独立物体。类别命名上如果只想完成毕设展示单类 crack 就够如果想做得更有区分度可以拆成 crack_h 和 crack_v 两个类别横向和纵向分开统计。建议初期只标 crack 一类先把链路跑通再考虑细分类。2.3 把labelme的json转成YOLO格式坐标换算与异常过滤labelme 的 json 里存的是原始像素坐标而 YOLO 训练需要的是归一化的中心点坐标和宽高。转换脚本的核心是读取 json 的 shapes取每个标注的外接矩形再除以图片宽高得到 0 到 1 之间的数值。import json import os import glob from pathlib import Path def convert_labelme_json(json_path, out_dir, class_map): # class_map 形如 {crack: 0}类别名映射到类别编号 with open(json_path, r, encodingutf-8) as f: data json.load(f) img_w data[imageWidth] img_h data[imageHeight] img_name Path(json_path).stem # json文件名与图片同名 out_line [] for shape in data[shapes]: label shape[label] if label not in class_map: continue points shape[points] # labelme 的矩形用两个对角点表示这里兼容多边形取外接矩形 xs [p[0] for p in points] ys [p[1] for p in points] x_min, x_max min(xs), max(xs) y_min, y_max min(ys), max(ys) # 转成 YOLO 的 cx, cy, w, h 并归一化 cx (x_min x_max) / 2.0 / img_w cy (y_min y_max) / 2.0 / img_h w (x_max - x_min) / img_w h (y_max - y_min) / img_h # 过滤异常宽高为0的框会让训练时loss变成nan if w 0 or h 0: print(fskip invalid box: {json_path}, label{label}) continue out_line.append(f{class_map[label]} {cx:.6f} {cy:.6f} {w:.6f} {h:.6f}) if out_line: out_path Path(out_dir) / f{img_name}.txt with open(out_path, w, encodingutf-8) as f: f.write(\n.join(out_line))遍历标注目录时这样调用labelme_dir ./labelme_jsons out_dir ./labels/train class_map {crack: 0} os.makedirs(out_dir, exist_okTrue) for json_path in glob.glob(str(Path(labelme_dir) / *.json)): convert_labelme_json(json_path, out_dir, class_map)这段脚本里最容易踩的坑是imageWidth和imageHeight字段与图片实际尺寸不一致。labelme 有时会在编辑后没有刷新这两个字段尤其是你用旧版本标注、新版本看图时。转换完最好随机抽几张图把标注框画回去肉眼复核。只看数据不看框的转换等于埋雷。2.4 数据划分与data.yaml同一个裂缝不能横跨训练集和验证集目录结构按 YOLO 惯例组织datasets/crack/ ├── images/ │ ├── train/ │ ├── val/ │ └── test/ ├── labels/ │ ├── train/ │ ├── val/ │ └── test/ └── data.yaml划分脚本用随机种子保证可复现8:1:1 是毕设常见比例import random import shutil import pathlib src_img pathlib.Path(images) # 未划分的原始图片 labels_src pathlib.Path(labels_all) # 未划分的标签txt目录 train_dst pathlib.Path(images/train) val_dst pathlib.Path(images/val) test_dst pathlib.Path(images/test) imgs list(src_img.glob(*.jpg)) random.Random(42).shuffle(imgs) n len(imgs) n_train int(n * 0.8) n_val int(n * 0.9) for i, img in enumerate(imgs): if i n_train: dst train_dst elif i n_val: dst val_dst else: dst test_dst dst.mkdir(parentsTrue, exist_okTrue) shutil.copy(img, dst / img.name) label_txt labels_src / f{img.stem}.txt if label_txt.exists(): label_dst dst.parent.parent / labels / dst.name label_dst.mkdir(parentsTrue, exist_okTrue) shutil.copy(label_txt, label_dst / label_txt.name)固定随机种子 42 是故意的保证你第二天重跑还是同样的划分不会因为 shuffle 变了导致对比实验失真。一个容易忽略的分组原则同一个墙面的连续拍摄照片要整体进同一侧。如果 1 到 10 张是同一面墙的连拍其中几张进 train、几张进 val验证集的“简单”会超出实际训练出的 mAP 虚高换到真实场景立刻打回原形。最后写data.yamlpath: datasets/crack train: images/train val: images/val test: images/test nc: 1 names: 0: crackpath建议写相对路径避免换机器后还要改绝对路径。如果只做毕设test目录可以先不填val就够评估用。3. 训练环境与参数CPU也能跑通但imgsz和增强不能照抄默认值3.1 Ubuntu20.04 CPU环境搭建能跑但要有耐心很多同学的训练机器没有独显看到 yolov8 第一反应是“先装 CUDA”。实际不需要CPU 版本完全可以跑通只是慢。训练 yolov8s、单张 1024 尺寸、CPU 跑一个 epoch 可能十几分钟但数据量在 500 张上下时几十个 epoch 也够出一个能演示的模型。环境搭建放在 Ubuntu 20.04 上最省心conda create -n crack python3.9 -y conda activate crack pip install ultralyticsPyTorch 的 CPU 版本按官网生成的 CPU 指令安装即可不要在 GPU 版本上卡太久。装完后跑一句python -c from ultralytics import YOLO; print(YOLO.__name__)验证 import 正常。预测单张图的耗时在我常用的设备上是 0.5 到 1.5 秒训练一个 epoch 在 500 张图、1024 分辨率下大约十几分钟到半小时。这个速度说明什么说明 CPU 能用于验证链路和数据但大规模调参还是建议找云 GPU 或实验室机器。3.2 模型选择yolov8n还是yolov8s看参数对比对比维度yolov8nyolov8s说人话参数量约 3.2M约 11.2Ms 的容量更大能学到更多裂缝纹理特征CPU 推理速度较快中等n 适合边缘盒子s 适合实验室演示mAP50 上限基线通常高 3 到 5 个点数据量越足s 优势越明显训练显存占用低中等8G 显存可跑CPU 也能跑我的习惯是先用 yolov8n 跑一次完整训练确认数据流和代码链路没有问题再换 yolov8s 出正式结果。两个模型共用同一份数据只是改model参数不折腾。如果你只需要快速验证标注质量n 就是最好的调试工具。3.3 训练命令先照着跑再解读参数命令行版本yolo train modelyolov8s.pt datadata.yaml epochs100 imgsz1024 \ batch8 patience20 devicecpu mosaic0.0等价的 Python 脚本from ultralytics import YOLO model YOLO(yolov8s.pt) model.train( datadata.yaml, epochs100, imgsz1024, batch8, patience20, devicecpu, mosaic0.0, )参数说明按优先级排imgsz1024是裂缝检测里最值得改的参数。默认 640 对行人、车辆没问题但裂缝宽度可能只有 10 到 20 像素缩到 640 后特征被压缩细裂缝直接消失。1024 是通用性较好的折中显存吃紧时用 800 也行但不建议低于 640。mosaic0.0是第二个值得改的。mosaic 会把四张图拼接成一张对紧凑目标有效但裂缝是长条状切片拼接会把一条裂缝切成几十段模型学到的是“断裂的短线才是裂缝”推理时反而对完整裂缝漏检。关闭 mosaic 是常见做法代价是训练收敛稍慢但最终 mAP 更稳。patience20表示验证集 mAP50 连续 20 个 epoch 没提升就提前停。裂缝数据集偏小训练到 50 到 70 个 epoch 往往就到平台期没必要硬跑满 100。batch8在 CPU 上偏大内存 16G 以下建议改 4。GPU 上如果显存 8G1024 尺寸配 batch8 是安全的再大容易 OOM。3.4 训练过程监控和续跑训练时每隔一段时间看终端输出的 P、R、mAP50 和 loss。loss 下降慢不要慌裂缝目标比行人检测收敛慢是常态。真正要警惕的是 val 指标涨到某一个点后开始掉头这是过拟合信号配合patience会自动停下停的位置就是最好的 checkpoint。如果训练中途断了不要从头再来model YOLO(runs/detect/trainXX/weights/last.pt) model.train(resumeTrue)last.pt是断电后悔药best.pt是最终交付物。注意 resume 时不要再传data.yaml和数据增强参数直接续跑上次的配置。4. 看懂结果并做出“结果展示”从best.pt到一份能答辩的验证报告4.1 runs目录里的文件都是什么别只盯着best.pt训练结束后进入runs/detect/trainXX/里面有几十个文件。毕设答辩时评委大概率会指着results.png问“这是怎么画的”所以每个文件都要知道用途文件作用答辩价值weights/best.pt验证集指标最好的权重最终交付物weights/last.pt最后一个 epoch 的权重续跑用results.png训练过程 loss 和指标曲线说明调参过程confusion_matrix.png混淆矩阵说明误检率PR_curve.pngPrecision-Recall 曲线说明置信度阈值选择val_batch*.jpg每轮验证集预测样例展示检测效果best.pt和last.pt的区别要讲清楚val 指标可能在第 63 个 epoch 到达峰值后面 37 个 epoch 在过拟合边缘挣扎best.pt保存的是第 63 轮的状态last.pt是最后一轮。交付永远用 best。4.2 验证集指标解读mAP50能到多少算合格用 best.pt 做验证命令要和训练时的 imgsz 保持一致yolo val modelruns/detect/trainXX/weights/best.pt datadata.yaml imgsz1024 splitvalPython 版from ultralytics import YOLO model YOLO(runs/detect/trainXX/weights/best.pt) metrics model.val(datadata.yaml, imgsz1024, splitval) print(mAP50:, metrics.box.map50) print(mAP50-95:, metrics.box.map) print(precision:, metrics.box.mp) print(recall:, metrics.box.mr)解读这几个数字时要有个心理预期裂缝的 mAP50-95 通常比 mAP50 低 0.2 到 0.4这是线状目标的天然缺陷。IoU 计算是框和真值框的面积交并比裂缝框只要错位几个像素IoU 就从 0.8 掉到 0.4所以 mAP50-95 会很难看。答辩时主动解释这一点反而显得你理解任务本质。如果 mAP50 能到 0.6 以上recall 在 0.7 附近这个模型就有演示价值了。注意别只报 best 指标要报“最后一轮验证”的指标才是真实水平。4.3 把预测结果拼成一页网格图直接放进毕设文档验证集指标是数字答辩还需要可视化。把每张验证图的预测结果拼接成 4x4 大图能直观展示裂缝框的位置质量。from PIL import Image import glob from ultralytics import YOLO model YOLO(runs/detect/trainXX/weights/best.pt) results model.predict( sourcedatasets/crack/images/val, conf0.3, iou0.45, imgsz1024, line_width2, saveFalse, ) # 收集预测结果图BGR ndarray转成RGB后拼接 thumbs [] for r in results: img_bgr r.plot() # 返回带框的BGR图 img_rgb img_bgr[:, :, ::-1] thumbs.append(Image.fromarray(img_rgb)) # 4行4列拼接 grid_w, grid_h 4, 4 cell_w, cell_h 480, 360 grid Image.new(RGB, (grid_w * cell_w, grid_h * cell_h), (255, 255, 255)) for i, img in enumerate(thumbs[: grid_w * grid_h]): img img.resize((cell_w, cell_h)) x (i % grid_w) * cell_w y (i // grid_w) * cell_h grid.paste(img, (x, y)) grid.save(result_grid.jpg)r.plot()是 YOLO 自带的绘图方法返回带目标框的 BGR 数组比手动cv2.rectangle省事得多。conf0.3对裂缝场景是合理起点巡检优先别漏检阈值太高会漏掉细裂缝。如果误检太严重再调高到 0.4 或 0.5。拼接完看一眼如果网格里大多数框能贴合裂缝轮廓、框边没有大面积背景这套结果展示可以直接进论文第 4 章。5. 部署与常见问题避坑把模型封装成检测服务再谈边界5.1 把yolov8封装成检测服务加载一次复用多次毕设答辩现场很可能要当场演示“传入一张新照片输出带框图”。这时不能每次调用都重新加载权重要封装成一个类初始化时加载一次之后每一张图只走推理。import cv2 from ultralytics import YOLO class CrackDetector: def __init__(self, weightsbest.pt): self.model YOLO(weights) def detect(self, img): results self.model.predict( sourceimg, conf0.35, iou0.5, imgsz1024, verboseFalse, ) r results[0] boxes r.boxes.xyxy.cpu().numpy() # 左上右下坐标 confs r.boxes.conf.cpu().numpy() # 每个框的置信度 plot_img r.plot() # 带框的BGR图 return boxes, confs, plot_img detector CrackDetector(runs/detect/trainXX/weights/best.pt) boxes, confs, out_img detector.detect(demo.jpg) print(fdetected {len(boxes)} cracks) cv2.imwrite(demo_result.jpg, out_img)这个类就是你的“交付物”配合一个demo.jpg写进开发文档的接口说明里评委能直接理解你的工程结构。conf0.35是巡检场景的默认值优先级是别漏检如果是做量化评估可以提高到 0.5 减少误报。5.2 导出onnx给边缘设备先导出再检查预处理毕设做到部署演示时常见需求是把模型导成 onnx跑在带 NPU 的边缘盒子上比如 RK3588 这类设备。导出命令yolo export modelruns/detect/trainXX/weights/best.pt formatonnx dynamicTrue imgsz1024 opset12导出后使用 onnxruntime 推理import onnxruntime as ort import numpy as np session ort.InferenceSession(best.onnx) input_name session.get_inputs()[0].name # 输入需要和训练时一样的letterbox预处理输出是 (1, 4nc, 8400) 的原始预测 outputs session.run(None, {input_name: input_blob})三个重要注意点第一onnx 输出去掉了 NMS框的坐标和置信度要自己在后处理里算不能直接用 ultralytics 的predict想当然第二letterbox 预处理必须和训练时一致否则框的位置整体偏移第三1000 尺寸的输入在 onnx 里张量更大如果设备内存有限就导出imgsz800。边缘设备部署这件事适合在毕业设计的“系统展望”章节写一句“已完成 onnx 导出验证”真要跑通 RKNN 转换需要额外处理检测头工作量会明显超出课程设计范围。5.3 高频问题避坑清单现象、原因、解决坑 1细裂缝一个都检不出来只检到粗裂缝现象验证集里宽度 2 毫米以下的裂缝全部漏检粗裂缝能框住。原因imgsz太小细裂缝在 feature map 上只剩下 1 到 2 个像素同时标签框留了太多背景导致 anchor 正样本质量差。解决训练和预测统一用imgsz1024标注时框边紧贴裂缝外接矩形。如果还不行对高分辨率原图做切块推理把 4K 原图切成 1024 的 tiles 分别检测再合并坐标。坑 2训练loss居高不下甚至出现nan现象train box loss 前 30 个 epoch 不降偶尔跳出 nan。原因标签 txt 文件里出现img_w或img_h为 0 的归一化坐标或者转换脚本读到的 json 里有两个 label 名其中一个不在 class_map 里被忽略后同一张图的正样本数量骤减。解决写完转换脚本立刻做回画检查把生成的 txt 画到原图上。如果批量操作不方便就随机抽 20 张检查出现一张坐标错位说明转换逻辑有 bug不要直接训练。坑 3训练集mAP很高验证集mAP很低现象train 的 mAP50 有 0.9val 只有 0.4 出头。原因数据量太小或者增强过强。裂缝是结构性很强的纹理数据增强里的hsv_h、hsv_s调得过大时颜色改变会让模型学到错误的关联数据量不足时模型把训练集的背景“背”下来了。解决先降低增强强度hsv_h0.01、hsv_s0.5起步同时增加无裂缝的背景负样本图比例控制在训练集的 20% 到 30%让模型学会“没有裂缝就是没有”。坑 4墙面污渍、水渍被当成裂缝误报现象precision 很低验证集里墙面污渍、伸缩缝、工具阴影都被框了出来。原因负样本太单一模型没有见过足够多的“像裂缝但不是裂缝”的区域。解决把典型的误报图片收集起来标注成空背景放进训练集。注意空背景集也要标注但不产生任何框YOLO 会把这些图作为背景学习。同时把推理阈值提到 0.45 以上牺牲一点 recall 换 precision。坑 5换一个光源条件效果下降一大截现象训练用的照片是阴天拍的换到正午强光下漏检明显。原因训练集没有覆盖光照变化颜色和阴影把模型带偏。解决补拍逆光和强光场景或者在训练时用hsv_v做强光照扰动。这个坑在桥梁和隧道项目里特别常见因为现场灯源角度和白天自然光完全不同。6. 训练日志可视化一张曲线图说清调参全过程6.1 从results.csv里把训练曲线捞出来训练结束后的runs/detect/trainXX/results.csv是官方记录的全量训练日志包含每个 epoch 的 loss、precision、recall、mAP。答辩时把它画成曲线图比贴一张截图专业得多。import pandas as pd import matplotlib.pyplot as plt df pd.read_csv(runs/detect/trainXX/results.csv) # 不同版本列名略有差异先打印列名确认 print(df.columns.tolist()) fig, axes plt.subplots(1, 3, figsize(15, 4)) axes[0].plot(df[epoch], df[train/box_loss], labeltrain box) axes[0].plot(df[epoch], df[val/box_loss], labelval box) axes[0].set_title(box loss) axes[0].legend() axes[1].plot(df[epoch], df[metrics/precision(B)], labelprecision) axes[1].plot(df[epoch], df[metrics/recall(B)], labelrecall) axes[1].set_title(precision recall) axes[1].legend() axes[2].plot(df[epoch], df[metrics/mAP50(B)], labelmAP50) axes[2].plot(df[epoch], df[metrics/mAP50-95(B)], labelmAP50-95) axes[2].set_title(mAP) axes[2].legend() plt.tight_layout() plt.savefig(training_curves.png, dpi200)如果 matplotlib 画出的图出现中文方块是系统缺少中文字体标题全部用英文即可中文解释留给答辩 PPT。6.2 再把验证集图片合成动图把每轮验证集预测图合成 GIF可以一眼看出“模型从乱框到收敛”的过程import glob import imageio.v2 as imageio files sorted(glob.glob(runs/detect/trainXX/val_batch*.jpg)) if files: imageio.mimsave(training_progress.gif, [imageio.imread(f) for f in files], fps5)这招在课程设计答辩时特别好用当评委问“你怎么证明模型在学习”放一张动图比列十个数字更有说服力。最后说一个我的习惯模型训完先画曲线再看混淆矩阵最后跑一次 val 看每类指标。这三步都能通过才开始部署和封装接口。身边见过太多同学第一步就跳过直接把 best.pt 丢进部署脚本结果调了两天预处理才发现模型根本没有收敛。先验数据、再验模型、最后验部署这个顺序能救回大量无效时间希望帮到你。本文还有配套的精品资源点击获取
返回列表