尧图精选

Python+YOLOv5路面桥梁裂缝检测实战:从数据标注到模型部署

🕒 发布时间:2026/9/16 21:04:14 📁 来源:尧图网络
简介一套基于PythonYolov5的路面桥梁裂缝检测识别项目面向计算机相关专业本科生完成毕业设计、课程设计或期末大作业也适合希望上手目标检测实战的初学者。项目源码结构清晰覆盖数据配置、模型定义、训练/检测脚本、工具模块及Docker环境部署等环节并配有示例图片与权重获取脚本可在本地复现裂纹识别流程。压缩包共85个文件以Python脚本26个py/pyc、YAML配置文件23个为主辅以Shell脚本、Dockerfile、图片样例总大小仅1.6MB轻量易下载。该方案曾获导师认可并取得99分评审成绩完整度较高已有77人学习下载。1. PythonYOLOv5 做路面与桥梁裂缝检测真正的难点在数据与源码路面和桥梁裂缝检测放在毕业设计里最容易出现的偏差是把“会跑通 YOLOv5”当成“完成了题目”。实际这类项目的难点不在模型本身而在三个地方细长目标的标注、训练策略的调整、以及怎么把检测结果接进演示界面。一套能用的 Python YOLOv5 路面桥梁裂缝检测识别项目源代码部分通常包含数据清洗、标注转换、训练脚本、推理封装模型部分则需要针对裂缝的形态重新选择网络结构和超参数。这篇博客按我平时做目标检测工程的顺序把从网络结构选型到模型落地验证的路径完整讲一遍新手能照着复现老手也能看到锚框分析和部署封装这些容易忽略的边界。2. YOLOv5 网络结构的取舍CSPDarknet、PANet 与 anchor 怎么配合裂缝目标2.1 裂缝目标在 YOLOv5 网络结构里为什么容易丢YOLOv5 的输出层有三个尺度分别来自 stride 8、16、32 的特征图对应输入 640 时的 80x80、40x40、20x20。COCO 默认的 anchor 是按 person、car 这类目标统计出来的用来框裂缝这种细长目标并不合适。裂缝在图像里的特点有两个一是绝对面积小二是一张图里会出现很多条。面积小意味着经过 CSPDarknet 多次下采样后目标在高层特征图上的响应可能不足一个像素数量多则意味着 NMS 后处理里大量低置信度框会互相抑制如果置信度阈值没调好一条裂缝可能被拆成多段或者直接被滤掉。理解这一点后就不会急着换 EfficientNet 或 Transformer。YOLOv5 的 CSPDarknet 在轻量级主干里保留细粒度信息的能力足够真正需要改的是输入分辨率、anchor 配置和数据增强策略。很多开源项目直接把yolov5s.yaml拿过来训练发现 P 值很高但 R 值很低就是因为 recall 阶段 anchor 和真实框的匹配率太低。先修网络结构相关的配置比换模型收益更大。2.2 depth_multiple、width_multiple 和 anchorsYOLOv5 配置文件里需要认识的三个字段先看一个最常用的yolov5s.yaml骨架下面是要修改的片段# yolov5s_crack.yaml 片段 nc: 1 # 类别数只有 crack depth_multiple: 0.33 # 控制 CSP 模块的重复次数 width_multiple: 0.50 # 控制卷积通道数的缩放系数 anchors: - [10, 13, 16, 30, 33, 23] # stride 8 对应的小目标 anchor - [30, 61, 62, 45, 59, 119] # stride 16 对应的中目标 anchor - [116, 90, 156, 198, 373, 326] # stride 32 对应的大目标 anchordepth_multiple和width_multiple决定的是模型容量。裂缝背景比 COCO 复杂场景简单但裂缝边缘的细节纹理比普通目标更难表达。如果选yolov5nwidth_multiple只有 0.25训练速度快但对细长边缘的拟合能力会明显下降。我一般不会为了毕设演示的 FPS 去选 nyolov5s是最平衡的起点显存不够再降 batch而不是降通道数。yolov5m也可以但训练时间会成倍增加对于几百到一千张的裂缝数据集收益未必明显。anchor 字段是真正按数据决定的参数。训练脚本默认会在训练前自动聚类 anchor我们也可以手动干预。注意 anchor 的单位不是类别数而是在输入分辨率下的像素宽高[10, 13]表示宽 10 像素、高 13 像素的框。裂缝的真实框在很多情况下宽高比极大比如一段横向裂缝的框可能是 400x8 像素聚类结果会把 anchor 的高压得很小这是正常现象不要觉得它们“不符合直觉”就去改回 COCO 默认值。配置项yolov5s 默认值裂缝任务建议说明depth_multiple0.33保持 0.33再加深对边缘特征的提升有限不如调整数据width_multiple0.500.50 或 0.55低于 0.25 会明显丢失裂缝边缘细节anchorsCOCO 聚类值用 autoanchor 重新聚类覆盖实际数据的宽高比和尺度分布2.3 用一段 Python 统计标注框宽高比预判 anchor 要往哪个方向调在训练之前我习惯先写一个独立脚本分析标签而不是直接跑训练。因为训练脚本里的 autoanchor 输出只给了推荐 anchor不会告诉你数据本身的分布。import os import glob import numpy as np def load_bbox_wh(label_dir): wh_list [] for txt in glob.glob(os.path.join(label_dir, *.txt)): with open(txt, r) as f: for line in f: parts line.strip().split() if len(parts) 5: w float(parts[3]) h float(parts[4]) wh_list.append([w, h]) return np.array(wh_list) wh load_bbox_wh(data/crack/labels/train) print(bbox 数量:, len(wh)) print(宽高比 5% 分位数:, np.percentile(wh[:, 0] / wh[:, 1], 5)) print(宽高比 50% 分位数:, np.percentile(wh[:, 0] / wh[:, 1], 50)) print(宽高比 95% 分位数:, np.percentile(wh[:, 0] / wh[:, 1], 95))这段脚本从 YOLO 格式的 txt 标注里读出归一化宽高再计算所有标注框宽高比的百分位数。YOLO 格式里parts[3]和parts[4]分别是 bbox 的宽和高归一化到 0 到 1所以宽高比不受输入分辨率影响。如果 95% 分位数超过 15说明数据里存在大量细长框默认 anchor 根本覆盖不到。同样的脚本也可以输出框面积分布比如统计w*h的中位数若中位数低于 0.01表示目标偏小训练时应该考虑把--img从 640 提高到 1280。还要注意anchor 聚类是沿特征图尺度分组的utils/autoanchor.py会在训练前对每个输出尺度分别做 k-means。如果你在配置里手动写了 anchor且没有去掉--autoanchor训练脚本仍然会用聚类结果覆盖你写的值。想完全固定 anchor要在train.py参数里关闭--autoanchor但大多数情况下没必要。3. 裂缝检测数据集的采集、标注和增强毕设源代码里最值得提前做的工作3.1 路面与桥梁裂缝数据从哪里来自己采集和公开数据集的取舍裂缝检测的公开数据集并不少常见的有 Crack500、DeepCrack 这类以像素分割为主的数据集也有带边界框标注的桥梁裂缝数据。但毕业设计直接拿公开数据集训练容易遇到两个问题第一很多公开数据是路面特写视角单一和你要演示的桥梁场景差异大第二分割标注转检测框后一个长裂缝会被切成一整块大框正负样本比例失衡训练出来的模型只会找裂缝密集区域不会定位单条裂缝。更稳的做法是“公开数据做大头自己补 20% 场景图”。用手机拍学校周边的人行天桥、校园路面注意包括阴影下的裂缝、伸缩缝、排水管边缘、水渍这些在视觉上和裂缝接近如果不拍进去模型在演示时会把伸缩缝和水渍全部误检。数量要求上目标检测项目里裂缝框的多寡比图像张数更重要。我一般保证标注框总数在 3000 个以上图像张数不要低于 600 张其中负样本图像占比控制在 10% 到 20%。负样本太多会让模型整体偏向保守演示时漏检变多完全不加负样本又会在真实场景里疯狂误报。3.2 标注工具与 YOLO 格式生成LabelImg 和分割标注的转换标注阶段可以直接用 LabelImg界面简单导出 YOLO 格式是一个 txt 里面一行一个目标。启动命令conda activate yolov5 pip install labelimg labelimg data/crack/images/train data/crack/labels/train第一个路径是图片目录第二个是标注输出目录。LabelImg 会记住上次打开路径所以在项目根目录启动更容易管理相对路径。标注裂缝有三个容易踩的细节。第一对斜向裂缝不要用一个巨大的倾斜框包住整条裂缝YOLOv5 的框是 axis-aligned巨大框里包含大量背景模型学不到裂缝边界正确做法是用多个小框沿着裂缝方向覆盖框不要重叠太多。第二类别建议只设crack一类。把裂缝拆成横向、纵向、网状三类类间特征高度重叠会让 loss 在相近类别上来回震荡对毕设演示没有价值。第三阴影和伸缩缝不标也不要放进背景图里完全不加标注让它们出现在训练集的负样本里模型才能学到“这些不是裂缝”。如果你拿到的是 LabelMe 的分割标注需要手动转成 YOLO 检测框。下面的代码读取 LabelMe 的 JSON 格式把多边形点集转换成最小外接水平框并写入 YOLO txtimport os import json import glob def labelme_to_yolo(json_path, out_dir, class_map): with open(json_path, r, encodingutf-8) as f: data json.load(f) image_w data[imageWidth] image_h data[imageHeight] lines [] for shape in data[shapes]: label shape[label] if label not in class_map: continue points shape[points] xs [p[0] for p in points] ys [p[1] for p in points] xmin, xmax min(xs), max(xs) ymin, ymax min(ys), max(ys) x_center (xmin xmax) / 2.0 / image_w y_center (ymin ymax) / 2.0 / image_h box_w (xmax - xmin) / image_w box_h (ymax - ymin) / image_h lines.append(f{class_map[label]} {x_center:.6f} {y_center:.6f} {box_w:.6f} {box_h:.6f}) out_name os.path.splitext(os.path.basename(json_path))[0] .txt with open(os.path.join(out_dir, out_name), w) as f: f.write(\n.join(lines)) class_map {crack: 0} for jpath in glob.glob(data/crack/labelme/*.json): labelme_to_yolo(jpath, data/crack/labels/train, class_map)代码中class_map是类别名到 ID 的映射points是多边形的顶点列表最后一行把归一化后的中心坐标和宽高写入 txt。要注意分割标注里一条裂缝可能被画成多个多边形转换后会生成多个独立的框这在后续训练时等同于多个目标没有语义问题但 NMS 可能会把同一条裂缝的不同段合并成一个框需要根据实际情况决定是否合并标注。3.3 数据增强配置Mosaic、Copy-Paste 和亮度扰动要一起改YOLOv5 内置的增强在data/hyps/hyp.scratch-low.yaml里训练时通过--hyp指定。裂缝数据推荐修改这几个值# hyp.crack.yaml 片段 mosaic: 1.0 # 拼接四张图训练 copy_paste: 0.2 # 把裂缝区域复制粘贴到无裂缝区域 hsv_h: 0.02 # 色调扰动保持很小 hsv_s: 0.4 # 饱和度中等 hsv_v: 0.2 # 亮度扰动减小防止裂缝融进阴影 degrees: 30.0 # 旋转范围 translate: 0.1 scale: 0.5 shear: 2.0mosaic对裂缝目标尤其有用因为裂缝数据集里目标和背景的分布很不均匀拼接四张图可以让一个 batch 内出现更多种类的路面纹理。copy_paste在目标稀疏的场景效果很好但它依赖实例分割标注如果你只有检测框YOLOv5 新版也会用检测框内的像素做粘贴边界可能带出明显拼接痕迹所以系数别超过 0.3。hsv_v默认是 0.4裂缝本身是暗色直线亮度扰动太强时它和阴影的灰度差会被抹掉所以我会在裂缝任务里把它调低。degrees和shear用来模拟桥梁上不同角度的裂缝注意旋转超过 45 度会让水平框变大太多背景被包进来推荐 30 度左右。4. 用 YOLOv5 训练裂缝模型环境配置、超参数与收敛判断的完整命令4.1 环境配置Python 版本、PyTorch 和 YOLOv5 依赖安装YOLOv5 对 Python 版本没有特别苛刻的要求但 3.8 到 3.10 之间最稳太新的 Python 版本反而容易遇到opencv-python和numpy的预编译包不兼容。常见做法是先建一个独立的 conda 环境conda create -n yolov5 python3.9 -y conda activate yolov5 conda install pytorch torchvision pytorch-cuda11.7 -c pytorch -c nvidia pip install -r requirements.txtpytorch-cuda11.7是 conda 里的 CUDA 运行时版本如果机器没有 NVIDIA GPU可以换成 CPU 版 torch但训练速度会慢一个量级以上。执行pip install -r requirements.txt前确认当前目录是 YOLOv5 项目目录requirements 里包含 opencv-python、matplotlib、pyyaml、tqdm、scipy、tensorboard 这些常用库。如果之前的项目里已经装过老版本 Pillow启动训练时可能报cannot import name PILLOW_VERSION通常需要pip install pillow9.0升级。很多人在这一步卡住不是因为命令不对而是 conda 和 pip 混用导致 torch 被覆盖。我一般只用 conda 装 torch 和 torchvision其余依赖全部用 pip装完不要随手pip install torch去覆盖 conda 里的版本。训练前跑一个最小验证python -c import torch; print(torch.__version__, torch.cuda.is_available())输出里torch.cuda.is_available()必须是True否则后面--device 0会直接报错。如果是笔记本集显加 NVIDIA 独显还要确认驱动版本别太老conda 装的 CUDA 运行时是自包含的驱动能识别 GPU 就能用。4.2 数据目录与数据集配置文件代码与数据分离的写法训练脚本通过--data参数读取一个 yaml 文件这个文件里不需要写类别名称以外的多余信息。推荐把数据集放到项目外的独立目录避免git status被大量图片干扰。目录结构如下data/crack/ ├── images/ │ ├── train/ │ └── val/ └── labels/ ├── train/ └── val/对应的crack.yaml# crack.yaml train: data/crack/images/train val: data/crack/images/val nc: 1 names: [crack]train和val写的是相对路径相对的是你执行train.py时所在目录。YOLOv5 在启动时会检查images目录下每个图片对应的 txt 是否存在于labels目录如果图片没有标注它会直接跳过并提示 found X images and Y labels。这一点格外重要因为裂缝数据集里负样本图没有 txt 文件会被当成无目标样本参与训练但如果负样本比例过高训练脚本会在一开始提示 labels 数量远小于 images 数量这不算错误。还要注意验证集不要从训练集里随机抽要按拍摄场景抽。比如同一个桥面不同角度的照片如果既在训练集又在验证集验证结果会虚高答辩时换一段新的桥面视频就露馅。实践里常按图片文件名的前缀或拍摄点位划分比如把同一座桥的图片放到同一个子目录再整体划分。4.3 训练命令与超参数设置img、batch、epochs 和 patience 的参考表数据准备好之后训练命令可以直接从 YOLOv5 官方示例改出来。以下是一个适合裂缝数据的起始命令python train.py \ --data crack.yaml \ --cfg yolov5s_crack.yaml \ --weights yolov5s.pt \ --img 640 \ --batch 16 \ --epochs 100 \ --hyp hyp.crack.yaml \ --project runs/crack \ --name exp1各参数的含义和调整方向如下参数裂缝任务建议值说明--img640显存够就 1280裂缝细节依赖分辨率640 是底线1280 能明显提升细裂缝召回--batch16 或 8不要低于 8batch 太小会让 BN 统计不稳定--epochs100用预训练权重时不需要从头训练 300 轮--patience20连续 20 轮验证集没提升就早停--workers4 或 8Windows 下 workers 不要大于 4容易报 DataLoader worker 错误--device0多卡时才写0,1毕设单卡足够--cacheram数据量不大时缓存到内存能省一半训练时间cfg参数对应模型结构文件如果使用的是yolov5s.pt预训练权重cfg会自动从权重里读取不写也可以。但把结构文件单独传一遍能保证你改过的 anchor 或nc生效。epochs100不是固定的观察第 30 轮之后的验证集 loss如果还在明显下降就继续跑如果已经平坦可以直接 CtrlCYOLOv5 会保存last.pt和当前best.pt后处理不受中断影响。4.4 训练日志与 loss 曲线裂缝模型收敛的判断标准训练过程中终端会打印实时的 loss类似下面这行Epoch gpu_mem box obj cls labels img_size 49/99 2.4G 0.06512 0.03120 0.00391 2 640对单类裂缝任务clsloss 会很小可能低于 0.01这不代表模型没学到裂缝而是因为只有一个类别分类分支几乎不需要区分。更该看的是box和obj。box是定位回归 loss持续下降说明 anchor 和预测框在往标注框靠近obj是目标置信度 loss如果它到后期上下波动不再下降说明模型对裂缝和背景的区分已经接近当前数据容量的极限。训练结束后YOLOv5 会生成runs/crack/exp1/results.png这是一张包含训练集和验证集各类指标曲线的图。用 tensorboard 也能看tensorboard --logdir runs/crack打开浏览器后关注验证集那一组曲线也就是带val/前缀的曲线。对于裂缝检测val/box_loss在 0.06 到 0.09 之间通常已经可接受mAP0.5 达到 0.8 以上就足够做演示。如果 mAP 一直上不去先看混淆矩阵confusion_matrix.png大多数情况是验证集里真实裂缝被预测成背景也就是 recall 低这时优先调低conf_thres而不是盲目加训练轮次。5. 裂缝检测模型源码接入演示系统的两个实战技巧5.1 把 detect.py 的输出封装成可调用的检测类不少毕设源码会在 GUI 或者网页里通过 subprocess 调detect.py每检测一张图就重新加载一次模型演示时延迟高得不像话。标准做法是直接用 YOLOv5 的 Python API把模型加载和推理封装成一个类。以下是我在离线环境下常用的一段import torch import cv2 from models.common import DetectMultiBackend from utils.augmentations import letterbox from utils.general import non_max_suppression class CrackDetector: def __init__(self, weightsbest.pt, devicecpu, conf0.25): self.device torch.device(device) self.model DetectMultiBackend(weights, deviceself.device, fuseTrue) self.conf conf def predict(self, img0, size640): img letterbox(img0, (size, size), stride32, autoTrue)[0] img cv2.cvtColor(img, cv2.COLOR_BGR2RGB) img torch.from_numpy(img).to(self.device).float() / 255.0 img img.permute(2, 0, 1).unsqueeze(0) pred self.model(img) pred non_max_suppression(pred, conf_thresself.conf, iou_thres0.45) return pred这里letterbox负责保持原始宽高比并填充到 640避免直接 resize 导致裂缝被压缩成非真实宽高比non_max_suppression的返回值是[x1,y1,x2,y2,conf,cls]的张量坐标仍是填充后图像上的坐标。要得到原图坐标需要用scale_boxes把坐标映射回去这段逻辑和detect.py里完全一致直接复用即可。封装成类后GUI 只需要初始化一次CrackDetector后续每帧调用predict在 CPU 上也能达到每张几百毫秒。5.2 用 warmup 测 FPS用 P/R 曲线找验证集问题演示系统里如果写 FPS 指标不要用首次推理的时间。第一次调用 CUDA 会做 context 初始化时间会被拉长两三倍。测帧率前先跑 10 次空推理预热再统计 50 次的平均耗时import time import torch detector CrackDetector(weightsbest.pt, devicecuda) dummy torch.rand(1, 3, 640, 640).to(cuda) for _ in range(10): detector.model(dummy) t0 time.time() n 50 for _ in range(n): detector.model(dummy) print(f平均耗时: {(time.time() - t0) / n * 1000:.1f} ms)这个技巧同时适合做模型选型对比比如比较yolov5s和yolov5m在演示机器上的速度warmup 后得到的数据才是答辩时能写进 PPT 的数值。验证模型质量时相比 mAP我更建议看results.png里的 Precision 和 Recall 曲线。裂缝场景里 P 高 R 低说明当前conf_thres定高了演示时把阈值降到 0.15漏检会明显减少R 高 P 低则说明模型把伸缩缝或阴影误判成裂缝优先去补负样本而不是调阈值。本文还有配套的精品资源点击获取
上一篇/下一篇内容由系统自动关联 返回资讯列表 →