
简介这是一份面向路面与桥梁裂缝自动检测场景、基于 Python 与 YOLOv5 构建的完整可运行项目适合作毕业设计、课程设计或期末大作业参考也适合具备一定深度学习基础的学习者快速上手。压缩包共49个文件约1.98MB其中yaml文件用于模型结构、训练与数据配置py/pyc为检测和推理代码sh脚本负责权重下载jpg/png为示例裂缝图像Dockerfile则方便快速部署环境。当前已有98人学习下载。项目包含可直接运行的源码、预训练模型与必要文档配合示例图片可完成对裂缝形态、大小和位置的识别同时提供图片检测和摄像头实时检测脚本便于在此基础上继续训练、调优或移植到实际检测场景。1. PythonYolov5路面桥梁裂缝检测识别项目源代码模型它到底要交付什么这套 PythonYolov5 路面桥梁裂缝检测识别项目源代码模型拆开看是三样东西一份标注好的裂缝数据集、一份用于训练的 Yolov5 源码工程、一份训练完成的 best.pt 权重。它不是拿来跑个 demo 就能交差的而是要回答“裂缝为什么被检测出来、模型怎么调、换一组图还能不能用”这三个问题。土木、交通、计算机方向拿它做毕业设计的人很常见你要在答辩前把环境搭建、数据转换、训练调参、推理验证这条链路完整走通。它的上手门槛不在某个单独的步骤而在于所有环节互相依赖——数据管不好训练就白费环境配不对代码根本跑不起来。好消息是这条路已经高度模板化只要把数据集换成你自己的道路病害图训练命令和推理脚本几乎可以原样复用。2. 环境与源码准备从Python安装到跑通第一次推理拿到源码和模型之后千万别急着pip install一把梭。Yolov5 这个工程迭代非常快它依赖的 PyTorch、Torchvision、Python 版本只要有一个对不上训练时就会冒出各种看不懂的 AttributeError。我一般会把环境搭建当作项目的第一个里程碑这个里程碑的标志不是“装好了”而是“用标题里提供的模型对一张真实图片跑通了 detect.py”。2.1 conda环境与torch版本匹配先别急着pip install第一步是确认机器上有可用的 NVIDIA 显卡和正确的驱动。在终端里执行nvidia-smi看右上角的 CUDA Version。这一步决定了后面安装哪个版本的 PyTorch也决定了训练时能不能用得上 GPU。# 查看GPU型号、显存占用和驱动能支持的CUDA版本 nvidia-smi # 创建独立虚拟环境避免污染系统Python conda create -n crack_yolo python3.9 -y conda activate crack_yolo # 安装PyTorch这里以CUDA 11.8对应的wheel为例 # 如果你的驱动显示CUDA 12.x把 cu118 换成 cu121 pip install torch torchvision --index-url https://download.pytorch.org/whl/cu118这段命令背后的逻辑是Yolov5 对 PyTorch 版本有最低要求装了太老的 Torchmodels/yolo.py里的部分算子会直接报错装了和驱动不匹配的 Torchtorch.cuda.is_available()会返回 False最后所有训练都落到 CPU 上慢到让人怀疑人生。用 conda 建一个独立环境还有一个好处赖毁了直接删环境重来不用重装系统 Python。装完后必须做一次 GPU 可用性验证。很多人忽略这一步直接往下跑训练结果跑了两小时才发现用的是 CPU。验证命令很短但信息量很大python -c import torch; print(torch.__version__, torch.cuda.is_available(), torch.cuda.device_count(), torch.cuda.get_device_name(0))输出里True和1是最关键的——is_available为 True 说明驱动和 PyTorch 匹配device_count不少于 1 说明 GPU 真能被检测到。如果输出 False先检查驱动再检查 PyTorch 版本不要急着去改代码。日常用 VSCode 写代码的话装好 Python 扩展后按CtrlShiftP选择解释器指向刚才创建的 crack_yolo 环境不然终端里 import torch 没问题编辑器里却报 ModuleNotFoundError这种环境错位问题几乎每个项目周期都能遇到一次。2.2 源码结构与最小推理验证跑通 detect.py 才算环境合格环境装好后先花十分钟把源码目录结构过一遍。Yolov5 源码里models/存放网络结构定义train.py、detect.py、export.py三个文件是毕设里改动频率最高的data/目录下放数据集定义 yaml 和超参数文件utils/是通用工具。很多人一上来就改 train.py实际上毕设项目里 train.py 根本不用大改真正要动手的是数据 yaml、超参数文件和数据标注。依赖安装和模型验证可以一起做# 进入源码根目录安装requirements里锁定的依赖 cd yolov5-master pip install -r requirements.txt # 用标题里提供的best.pt权重对一张裂缝图片做推理 # 如果这一步成功说明源码和权重是配套的 python detect.py --weights best.pt --source test.jpg --conf 0.25命令里的--conf 0.25意思是置信度阈值低于 25% 的检测框会被滤掉。第一次跑通后去runs/detect/exp目录看输出图如果裂缝位置被框出来了环境这一关就算过了。这一步的真正价值是提前暴露权重和源码不匹配的问题——Yolov5 各版本的权重文件和代码结构并不完全通用旧权重配新代码经常在模型加载阶段就抛 KeyError。与其等到训练一百轮再看结果不如先拿现成模型做一次端到端验证。3. 裂缝数据集制作标注格式、转换脚本与划分策略环境跑通后下一个决定成败的环节是数据。路面裂缝和桥梁裂缝的样貌差异很大沥青路面裂缝是暗色细条带水泥路面裂缝对比度更低桥梁上还有伸缩缝、设备线管这类和裂缝长得很像的干扰物。常见做法是从公开路面裂缝数据集找一批图再自己补拍一部分现场照片凑成几百到一千张的量级这个规模对 Yolov5s 来说已经能训练出可用模型。3.1 标注格式与LabelImg操作要点Yolov5 使用 YOLO 格式的 txt 标注每张图片对应一个同名 txt 文件文件里每一行代表一个目标class_id cx cy w h其中cx cy是目标的中心点坐标w h是宽和高四个数字都归一化到 0~1 之间。标注工具我用 LabelImg 居多它可以在 YOLO 模式和 PascalVOC 模式之间切换但要注意LabelImg 的 YOLO 模式依赖同目录下的classes.txt里面每一行是一个类别名顺序就是类别编号的映射这个文件和之后 data.yaml 里的 names 必须保持一致。标注裂缝时有一个关键心法不要为了“框得准”而把框压到和裂缝一样细。YOLO 的矩形框回归对小而细的目标非常不友好宽度只有三五个像素的裂缝框在训练时容易被当成噪声。更可靠的做法是沿裂缝走向把整条裂缝包含进一个略宽的矩形框里让模型学习的是“裂缝条带所在的区域”而不是勉强框住裂缝的细线。矩形贴近裂缝反而会在标注阶段引入大量像素级不一致。标完一批图后建议写几行代码检查标注文件的合法性而不是肉眼翻图# 检查labels目录下所有txt标注是否合法 # YOLO格式要求一行5个数值cx/cy/w/h都在(0,1]范围内且w/h不能为0 from pathlib import Path label_dir Path(labels) for txt_file in label_dir.glob(*.txt): for i, line in enumerate(txt_file.read_text().strip().splitlines()): parts line.strip().split() if len(parts) ! 5: print(f{txt_file.name} 第{i1}行格式异常: {line}) continue cls, cx, cy, w, h [float(x) for x in parts] if not (0 w 1 and 0 h 1): print(f{txt_file.name} 第{i1}行宽/高越界: {line})这个脚本的逻辑很简单逐行解析、检查字段数量、检查归一化坐标是否在合法区间。很多新手标注完后直接开训练结果 Yolov5 在dataset.py加载数据时报“label shape mismatch”玄学半天才发现是某个 xml 转 txt 时写出了一个空标注文件或非法坐标。提前跑一遍这个小脚本能把这类问题从训练阶段前移到数据准备阶段。3.2 从VOC到YOLO转换脚本与类别名配置很多开源的裂缝数据集提供的是 PascalVOC 格式的 xml 标注我们需要批量转成 YOLO 格式。转换逻辑不复杂解析 xml 里的 bndbox 四个角点坐标换算成中心点加宽高的归一化表示再写进同名 txt。# voc_to_yolo.py 批量将PascalVOC的xml标注转换为YOLOv5所需的txt标注 import xml.etree.ElementTree as ET from pathlib import Path xml_dir Path(Annotations) out_dir Path(labels) out_dir.mkdir(exist_okTrue) classes [crack] # 类别列表顺序决定class_id必须固定 for xml_file in xml_dir.glob(*.xml): tree ET.parse(xml_file) root tree.getroot() img_w int(root.find(size/width).text) img_h int(root.find(size/height).text) lines [] for obj in root.iter(object): name obj.find(name).text if name not in classes: continue box obj.find(bndbox) x1 float(box.find(xmin).text) y1 float(box.find(ymin).text) x2 float(box.find(xmax).text) y2 float(box.find(ymax).text) # 角点坐标转中心点宽高再归一化 cx ((x1 x2) / 2) / img_w cy ((y1 y2) / 2) / img_h w (x2 - x1) / img_w h (y2 - y1) / img_h lines.append(f{classes.index(name)} {cx:.6f} {cy:.6f} {w:.6f} {h:.6f}) (out_dir / (xml_file.stem .txt)).write_text(\n.join(lines))这段脚本注意三点第一classes列表的顺序就是类别编号训练时的data.yaml必须和它完全一致第二x2-x1和y2-y1如果出现负数或零说明原始 xml 里有脏数据转换后宽高会是负数训练直接报错建议在转换前对四个坐标做一次max(0, ...)过滤第三如果你的原始标注是分割多边形而不是矩形框需要先用cv2.boundingRect求最小外接矩形再走同样的归一化流程。转换完再看一下生成的 txt 文件确认不是空文件——空标注文件在训练时会导致对应图片被跳过数量多了模型根本学不到东西。图片和标注都准备好后做一次固定的数据集划分。用一个随机种子大的脚本按比例拆成训练集、验证集和测试集并把划分结果写进 train.txt / val.txt 供 data.yaml 引用# 按 7:2:1 划分图片路径固定随机种子保证结果可复现 python - EOF import random from pathlib import Path random.seed(42) imgs sorted(Path(images).glob(*.jpg)) random.shuffle(imgs) n len(imgs) cut1, cut2 int(n * 0.7), int(n * 0.9) for split, paths in [(train, imgs[:cut1]), (val, imgs[cut1:cut2]), (test, imgs[cut2:])]: with open(f{split}.txt, w) as f: f.write(\n.join(str(p.resolve()) for p in paths)) EOF划分的细节决定训练时的数据分布验证集不能太小不然 val 曲线波动剧烈你没法判断模型是否真的在收敛测试集要有代表性最好单独放一些另拍的、光照条件差的图。之后在源码根目录新建crack.yaml# crack.yaml 裂缝检测数据配置 path: /your/project/root # 数据根目录建议写绝对路径 train: train.txt val: val.txt nc: 1 names: [crack]这里最容易犯的错是path写成相对路径。Yolov5 在训练时会基于path拼接train.txt里的路径相对路径一旦目录切换就会报“No such file or directory”所以我一直建议直接写绝对路径。nc和names必须与标注文件的 class_id 一一对应如果只标了 crack 这一个类别nc写成 2 会让类别编号错位损失曲线看起来像在飞但训练逻辑上根本是错的。4. 训练与超参数调整让裂缝检测模型真正收敛数据准备好了训练就是一个“配参数 盯曲线”的过程。Yolov5 训练自己的数据集时大多数人犯的错不是参数太少而是参数太多——一次改五六个地方出问题根本定位不到源头。我的习惯是第一次训练只调必调参数跑通之后再用小步长优化。4.1 train.py训练命令与显存边界训练命令本身不复杂复杂的是如何根据你的显卡选择参数。一个 6GB 显存显卡的典型配置如下python train.py --data crack.yaml --weights yolov5s.pt \ --img 640 --batch 8 --epochs 100 --cache ram \ --name crack_run --patience 20逐项说参数含义--img是输入图片分辨率640 是产能和精度的折中点--batch是每个批次的图片张数它和显存强相关--cache ram把图片预加载到内存减少每轮 epoch 的磁盘 IO数据量不大时收益非常明显但会占用大量内存8GB 物理内存的机器慎用--name是本次训练的输出目录名所有 ckpt 和日志都会落在runs/train/crack_run/下--patience 20表示验证集指标连续 20 轮不提升就早停。低显存跑 Yolov5 的核心思路是三个减法降 batch、降 img、开 AMP。如果一张 6GB 卡连 batch 8 都撑不住就把--img 512 --batch 4再配上--amp混合精度训练。注意这里有个矛盾点裂缝本身就是小目标--img降太多裂缝在特征图上的像素数不够检测能力会明显退化。所以我的底线是--img不低于 512如果还想再省显存就换更小的 yolov5n 模型而不是继续降分辨率。不同规模的预训练权重对显存和精度的要求差距很大毕设场景里建议直接看这张表权重文件参数量显存压力适用场景yolov5n约1.9M最低显存4GB以下或追求速度yolov5s约7.2M适中毕设首选精度和速度平衡yolov5m约21M较大数据量大、对精度要求高训练启动后终端会先输出一堆超参数表然后开始跑。前几十个 batch 的输出可以不用太在意但到了第一个 epoch 结束一定要去runs/train/crack_run/目录看results.png里 train/val 的 loss 曲线。正常情况是 box_loss 和 obj_loss 整体下行val 曲线有波动但趋势向下。如果第一条 loss 曲线直接横着走后面避坑章里的排查思路会用得上。4.2 超参数调整学习率、锚框与数据增强Yolov5 把可调超参数集中在data/hyps/hyp.scratch-low.yaml里。常见做法是复制一份改名为hyp.crack.yaml单独修改不碰原文件。毕设项目里最值得调的三个位置是第一个是学习率相关参数。lr0是从0.01开始的初始学习率lrf是最终学习率系数。数据量小的时候lr0默认的 0.01 起步很快发散我一般会降到0.005或更低保证训练前二十轮不会因为梯度爆炸直接把权重冲坏。如果 loss 曲线像一个锯齿沿一个方向抖动厉害通常就是学习率偏大的信号。第二个是锚框配置。Yolov5 在训练开始时会在一定轮次内自动重新聚类锚框这一步对标准目标很好用但裂缝是细长条目标自动聚类出来的锚框宽高比可能覆盖不到极端长条。如果你发现检测结果里框总是“截断”裂缝可以在模型配置文件里预设一组宽高比大的锚框或者干脆关闭 autoanchor 用默认值先训练。多数情况下 autoanchor 已经够用改动锚框属于进阶调优不要一开始就动。第三个是数据增强开关。mosaic和mixup对裂缝检测是把双刃剑它们能把小目标的多样性撑起来但裂缝数据集通常只有几百到一千张图增强过猛会让模型学到“拼接痕迹”。一个可靠的调参路径是前三十轮把mosaic调到0.5左右让模型先见到相对正常的图片分布后三十轮再提升到1.0去磨小目标泛化能力。一次只改一个参数改完看三四十轮的曲线再决定下一步这是最省时间的调参方式。5. 裂缝检测训练避坑5条血泪经验与排查思路标题给的源代码和模型能用但你自己的数据进去后一定会出现新问题。下面这 5 个坑按“现象 → 原因 → 解决”的方式记录都是我实际处理过的高频问题按优先级排列。5.1 AttributeError与算子崩溃PyTorch版本不匹配现象训练代码跑起来不到一分钟就抛AttributeError: module torch has no attribute overrides或者Upsample object has no attribute recompute_scale_factor。原因Yolov5 源码迭代快对 PyTorch 版本有隐性下限源码里的某些算子在新旧版本之间改过 API。你以为代码没问题实际上是环境这个黑匣子里某个底层库的版本对不上。解决不要试图去改源码适配旧 torch。直接重建一个干净的 conda 环境按自己显卡驱动支持的 CUDA 版本重新安装 torch 和 torchvision然后用python -c import torch, torchvision; print(torch.__version__, torchvision.__version__)确认版本号再重跑 detect.py。这套流程跑通了再进训练。5.2 小裂缝检测不到小目标过滤与标注粒度现象训练和 evaluate 都很顺利但把模型跑在新图上时宽度几十像素的粗裂缝能框出来断断续续的细裂缝几乎全部漏检precision 挺高recall 惨不忍睹。原因Yolov5 数据加载阶段对尺寸过小的标注框有过滤逻辑三五个像素宽的裂缝框被视为噪声丢弃另外如果标注框大多紧贴裂缝而裂缝又很细模型学到的特征本身就弱。解决从标注端改把同一条连续裂缝合并成一个较大的矩形框不要切成几十个小框同时把训练输入分辨率从 640 提到 960让裂缝在输入图里占更多像素。两个手段同时用recall 通常会有明显改善。标注端处理永远比调参端处理更有效。5.3 CUDA OOM训练中断显存不足的三种降级方案现象训练跑了二三十个 batch突然报RuntimeError: CUDA out of memory显卡能力看起来够但还是炸了。原因--img 640 --batch 8在 6GB 显卡上本来就是临界状态训练过程中模型会缓存中间激活值实际峰值显存比启动时看到的占用高出一截。后台如果再有一个开着 GPU 硬件加速的浏览器更凑不够显存。解决按顺序做三次降级。第一次降 batch 到 4还炸就把--img降到 512再不行就加--amp开混合精度。这三板斧下来4GB 的卡也能把 yolov5s 跑起来。降级后对比一下 loss 曲线确认精度损失可接受再继续往下跑。5.4 误检桥梁伸缩缝/路面阴影负样本策略与阈值调整现象单张图上裂缝检测很准但一跑整段路伸缩缝、油渍、阴影甚至白色标线都会被框出来框的数量比真裂缝还多。原因模型学到的是“暗色细长条带”这个视觉模式而不是“裂缝”这个概念。训练集里这类干扰物如果一张都没出现过模型天然会把它们当正例。解决最有效的手段是收集一批不含裂缝但长得很像裂缝的干扰图放入数据集中作为无标注样本让模型把它们当作背景学习。配合调整--conf阈值把默认的 0.25 提到 0.35 甚至 0.5能压掉一批低置信度误检。两个手段要一起用只调阈值会牺牲对小裂缝的召回。5.5 Loss不降或震荡学习率与类别配置问题现象训练到二十轮以后box_loss 卡在 0.1 附近不动obj_loss 震荡得像一条锯齿线val 曲线完全没有下行趋势。原因学习率偏高导致权重更新步长太大另一种常见原因是 data.yaml 里nc和实际标注类别数量不一致或者标注文件里有大量空文件模型在反复学背景。解决先把lr0降到 0.005 以下重跑一轮然后在数据集目录跑一遍统计脚本确认有标注文件的图片数量占总图片数量的比例低于 70% 就需要补标或删除空标注文件。检查顺序永远是数据在前、参数在后数据和标注的问题不清理调任何超参都是白费。6. 部署验证与进阶从best.pt到业务可用的裂缝识别训练结束后runs/train/crack_run/weights/下有两个文件best.pt是验证集指标最好的权重last.pt是最后一轮的权重。毕设演示和后续部署一律用best.pt这个没有悬念。但一个模型能不能交付不是看训练集上框得多漂亮而是它在没见过的照片上表现如何。6.1 批量推理与置信度阈值选择用 detect.py 对测试集做批量推理是验证模型泛化能力的第一步python detect.py --weights runs/train/crack_run/weights/best.pt \ --source test_images/ --imgsz 960 --conf 0.30 --iou 0.45--source可以指向文件夹detect.py 会遍历目录下所有图片--imgsz 960用训练时的同档分辨率--conf 0.30是置信度阈值这个值值得不断调整。我的验证技巧是取二十张现场光线最差的照片把 conf 从 0.25 往上逐档提高每档跑一次记录漏检数和误检数。理想阈值通常出现在“误检数量开始下降、漏检数量还没急剧上升”的交界处。这个经验值一旦确定后面所有照片都固定用它。6.2 导出ONNX与轻量推理脚本如果毕业设计需要部署到没有 PyTorch 的环境或者想在 Web 端提供接口第一步是把权重导出为 ONNXpython export.py --weights runs/train/crack_run/weights/best.pt \ --include onnx --opset 11 --simplify导出成功后可以用 onnxruntime 写一个不依赖 PyTorch 的推理脚本这对部署环境的兼容性提升非常明显# onnx_infer.py 使用onnxruntime加载ONNX模型进行裂缝检测 import onnxruntime as ort import cv2 import numpy as np session ort.InferenceSession(best.onnx) input_name session.get_inputs()[0].name # 读取并预处理图片为960x960的RGB张量 img0 cv2.imread(bridge_crack.jpg) img cv2.cvtColor(img0, cv2.COLOR_BGR2RGB) img cv2.resize(img, (960, 960)).astype(np.float32) / 255.0 img img.transpose(2, 0, 1)[None] # [1, 3, 960, 960] output session.run(None, {input_name: img}) # output[0] shape为[1, 25200, 6]nc1时: 5个坐标属性1个类别 # 后处理就是对每一行做置信度筛选 NMS再映射回原图坐标这段脚本拿来做后端服务时只需要把图片读取部分换成接口上传的二进制流再把后处理的框转换逻辑抽成独立函数即可。导出 ONNX 后建议对比一下 PyTorch 原模型和 ONNX 在同一张图上的检测结果两者差异应该在几个置信度小数位以内如果差异明显检查--opset是否设得太低。整个项目走到这一步才算把“训练代码”变成了“可用系统”。我习惯在每个毕设交付前做一次“冷酷验证”把二十张阴天、逆光、雨后路面的照片丢进模型算一次漏检和误检比例能接受这项目就算立住了不能接受回头补数据而不是继续调参数。这个习惯帮我躲过很多答辩时的尴尬希望帮到你。本文还有配套的精品资源点击获取