☰
Python+YOLOv5路面桥梁裂缝检测:源码解析与工程实践
2026/10/7 6:32:05 网站建设 项目流程

简介:基于Python与YOLOv5的路面桥梁裂缝检测识别资源,面向计算机视觉课程设计、毕业设计及道路桥梁巡检等场景,源码与模型配置齐备,可直接编译运行。项目评审分达98分,代码结构清晰,难度适中,围绕YOLOv5目标检测框架展开。压缩包共85个文件,以23个Python脚本、23个YAML配置文件为主,辅以pyc缓存、Shell权重下载脚本、Dockerfile及jpg/png示例图;目录按utils、models、data、runs组织,内置detect_photo.py单图检测与detect_camera.py摄像头实时检测入口,weights目录附下载脚本,便于快速搭建环境。资源整体约1.58MB,轻量紧凑,已有180人浏览学习。适合需参考完整工程结构、快速复现裂缝识别效果,以及完成课程设计、毕业设计或期末大作业的开发者。

1. 这包 Python+YOLOv5 路面桥梁裂缝检测源码能解决什么:一条直接可跑的检测链路

我拆过不少打着“高分项目”旗号的压缩包,大多数是半成品:模型文件有,入口脚本缺,跑起来报一堆模块错误。这套基于 Python+YOLOv5 的路面桥梁裂缝检测识别源码+模型,算是里面比较实诚的一个。它把 YOLOv5 工程需要的几个关键目录都带齐了:weights 对应权重下载脚本,data 里有 coco128/voc 的数据配置和 hyp.finetune/hyp.scratch 两套超参,models 里是 yolov5s/m/l/x 四种结构 yaml,runs 放输出结果,入口除了通用组件还单独给了 detect_photo.py 和 detect_camera.py 两个推理脚本。对做毕业设计、期末大作业或者课程设计的人来说,最省时间的用法是把它当基线工程:先跑通静态图检测,再换成自己的桥梁裂缝数据集微调。适合的人群很明确——刚把 YOLOv5 环境配好、想少踩编译坑的初学者,以及需要快速出一个 demo 给导师看结果的从业者。

2. 裂缝检测的算法选型:为什么这类任务我第一反应是 YOLOv5 而不是传统 CV

2.1 裂缝图像里的真实难点:细长目标、低对比度、背景噪声

桥梁裂缝检测和通用物体检测最大的区别在于目标形态。裂缝是细长条,宽度在原图里经常只有几个像素,对比度又低,和阴影、伸缩缝、水渍长得非常像。传统方案里 Canny 边缘检测加形态学闭运算,在干净的室内路面上还能用,到了桥梁现场基本翻车——模板接缝、钢筋阴影都会输出一堆边缘,你根本分不清哪条是裂缝、哪条是噪声。滑窗加 HOG 特征加 SVM 也能做,但效率很低,一个 4K 的巡检图要滑几千个窗口,而且对旋转和尺度变化不鲁棒。

YOLOv5 这类单阶段检测器把整个问题变成一个端到端的回归任务,一次前向传播同时输出目标框、类别和置信度,不需要单独设计特征。对裂缝这种“有局部纹理特征但全局上下文很重要”的目标,它比传统 CV 方案的泛化能力强很多。另外,YOLOv5 的工程化做得非常好,训练、推理、导出、可视化都有配套脚本,这也是我推荐它作为毕设基线的第二原因——你不用从零开始搭 ResNet 加 RPN 那一套。

2.2 拆开目录看懂工程边界:weights、data、models、runs 各自管什么

先说 weights 目录。里面那个 download_weights.sh 脚本是用来拉官方预训练权重的,常见做法是执行后拿到 yolov5s.pt、yolov5m.pt 这类文件。带 s/m/l/x 后缀分别对应 smallest、medium、large、xlarge,网络深度和宽度逐级增加。对裂缝检测这个任务,我一般先用 s 模型跑通流程,验证数据和标注没问题,再换 l 或 x 模型追求精度,因为裂缝本身是小目标,太浅的网络容易丢细节。

data 目录里要重点看的是 coco128.yaml、coco.yaml、voc.yaml 三个数据配置,以及 hyp.finetune.yaml、hyp.scratch.yaml 两个超参数文件。前者是数据集路径和类别数 nc 的定义,后者是训练时的学习率、动量、数据增强参数。这个项目的 data 目录保留的是 YOLOv5 官方的多数据集兼容层,意味着你可以按 VOC 格式还是 COCO 格式组织数据,脚本都能接住。

models 目录下的 yolov5s.yaml 这类文件是模型结构定义,它只是描述“有多少层、每层用什么模块”,真正的模块实现在 common.py 和 experimental.py 里。特别注意:如果你要训练自己的数据集,必须改 yaml 里的 nc 参数,让它等于你的类别数。runs 目录则是训练和推理的输出目录,默认所有实验结果都会按 exp、exp2、exp3 递增地写在这里。

2.3 网络结构图里的三个尺度:为什么小目标 head 对裂缝这么重要

很多人在搜索 yolov5 网络结构图,其实理解 YOLOv5 只需要抓住一个关键:它有三个检测 head,分别对应 80×80、40×40、20×20 的特征图,分管小目标、中目标、大目标。20×20 那个 head 感受野最大,负责大物体;80×80 那个 head 保留的空间细节最多,专门负责小物体。

head: - [-1, 1, Conv, [512, 1, 1]] - [-1, 1, nn.Upsample, [None, 2, 'nearest']] - [[-1, 6], 1, Concat, [1]] - [-1, 3, C3, [512, False]] - [-1, 1, Conv, [256, 1, 1]] - [-1, 1, nn.Upsample, [None, 2, 'nearest']] - [[-1, 4], 1, Concat, [1]] - [-1, 3, C3, [256, False]] - [-1, 1, Conv, [256, 1, 1]] - [-2, 1, Concat, [1]] - [-1, 3, C3, [256, False]] - [-1, 1, Conv, [256, 1, 1]] - [-1, 3, C3, [256, False]]

这段 yaml 是 YOLOv5 的 PANet 颈部结构,上面用 Upsample 把高层语义特征不断上采样,下面用 Concat 把浅层细节特征融合进来。裂缝这种细线目标,一旦在网络里被下采样太狠,特征就消失了,所以小目标 head 的空间分辨率直接决定召回率上限。

后处理那一步同样关键,YOLOv5 后处理主要就两件事:先用 confidence 阈值过滤低质量框,再用 NMS 非极大值抑制把重叠框合并。同一道裂缝在三个 head 上可能各自出一个框,NMS 按 iou 判断重叠度,保留置信度最高的那个,去掉冗余。

3. 准备自己的桥梁裂缝数据:标注格式、数据 YAML 与超参选择

3.1 YOLO 格式的标注到底长什么样

如果你用过 LabelImg 或 labelme,导出的可能是 VOC 的 XML 或 COCO 的 JSON,但 YOLOv5 训练要的是纯文本的 YOLO 格式:每张图对应一个同名 txt 文件,每一行代表一个目标,格式是 class x_center y_center width height,四个坐标值全部归一化到 0 到 1 之间。

0 0.521875 0.634259 0.028125 0.001852 0 0.448958 0.542130 0.013542 0.002315

上面两行表示这张图里有两个裂缝目标,第一个目标的中心点在图像的 52.2% 宽度、63.4% 高度处,框的宽度占整张图 2.8%,高度占 0.18%。看这个数字就能理解裂缝检测的难点——目标高度占比经常不到千分之几,比普通物体检测里的“小目标”还要极端。

我一般建议标注时把裂缝的一段段断裂区域分别框出来,而不是试图用一个大框把整条裂缝全包住。长裂缝跨度大,一个大框里会混入大量背景,反而干扰训练。分段标注之后,模型学到的是“裂缝局部纹理特征”,泛化能力更好。

3.2 把 VOC/COCO 标注转成 YOLO 格式:一段常用脚本

这个包里 data 目录同时给了 coco.yaml 和 voc.yaml,说明作者考虑了不同来源的数据导入。但真正做自定义训练时,我一般直接写个几十行的小脚本把 XML 标注转成 txt,顺手还能做数据清洗。常见做法是下面这样:

import os import xml.etree.ElementTree as ET def convert_voc_to_yolo(xml_path, out_dir, classes): tree = ET.parse(xml_path) root = tree.getroot() size = root.find('size') img_w = int(size.find('width').text) img_h = int(size.find('height').text) lines = [] for obj in root.iter('object'): cls_name = obj.find('name').text if cls_name not in classes: continue cls_id = classes.index(cls_name) bndbox = obj.find('bndbox') x1 = float(bndbox.find('xmin').text) y1 = float(bndbox.find('ymin').text) x2 = float(bndbox.find('xmax').text) y2 = float(bndbox.find('ymax').text) x_center = (x1 + x2) / 2.0 / img_w y_center = (y1 + y2) / 2.0 / img_h w = (x2 - x1) / img_w h = (y2 - y1) / img_h w = max(w, 1e-6) h = max(h, 1e-6) lines.append(f"{cls_id} {x_center:.6f} {y_center:.6f} {w:.6f} {h:.6f}") if lines: txt_name = os.path.splitext(os.path.basename(xml_path))[0] + '.txt' with open(os.path.join(out_dir, txt_name), 'w') as f: f.write('\n'.join(lines)) voc_classes = ['crack'] for xml_file in ['data/annotations/001.xml', 'data/annotations/002.xml']: convert_voc_to_yolo(xml_file, 'data/labels', voc_classes)

这段代码的逻辑很简单:读 XML 里的原始像素坐标,除以图片宽高完成归一化。唯一要注意的是 w 和 h 加了一个下界 1e-6,因为有极端细长的裂缝框,归一化后宽度可能接近 0,如果不兜底,后面训练时 loss 计算会出 NaN。

3.3 改写 data/crack.yaml:路径、nc、names 的坑

训练自己的数据集,核心是写一个数据配置 yaml。很多人直接在官方 coco128.yaml 上改,我建议新建一个 crack.yaml,避免污染原来的文件:

train: ./data/crack/images/train val: ./data/crack/images/val nc: 1 names: 0: crack

这里有两个最常见的坑。第一个是路径,老版本 YOLOv5 的 train/val 路径是相对于项目根目录的,不是相对于 yaml 文件所在目录的。如果你把 crack.yaml 放在 data 下,而图片实际在项目外的某个绝对路径,就会报 dataset not found。我一般统一用相对项目根目录的路径,换机器时整个工程带着走,不用改。第二个是 nc 和 names 的顺序,names 的索引必须和标注 txt 里的 class id 一一对应,如果标注时把 crack 编成 1,这里却写 0,训练出来的模型会在推理时把类别标签全部错位。

3.4 hyp.finetune.yaml 和 hyp.scratch.yaml:什么时候用哪一套

这个包同时带了两套超参文件,这是 YOLOv5 官方的一个设计:hyp.scratch.yaml 是从零开始训练时用的,初始学习率较高,适合大数据集和大训练轮数;hyp.finetune.yaml 是迁移学习微调用的,初始学习率大约是 scratch 的三分之一,warmup 时间也短,适合在预训练权重基础上接自己的小数据集。桥梁裂缝数据集通常只有几百到几千张,我强烈建议用 finetune 这套。

python train.py --data data/crack.yaml --weights weights/yolov5s.pt \ --cfg models/yolov5s.yaml --hyp data/hyp.finetune.yaml \ --epochs 200 --batch-size 16 --imgsz 640

注意命令里的 --cfg 参数,这个项目带的是较早版本的 YOLOv5 工程,老版本训练时如果只给了 --weights 不给 --cfg,模型结构会和你选的权重对不上。参数含义逐个说:--data 指向刚才建的 crack.yaml;--weights 用官方预训练权重,这里取 yolov5s.pt;--epochs 200 是训练轮数,裂缝数据集小,200 轮足够收敛,再多容易过拟合;--batch-size 16 取决于显存,8GB 显卡跑 yolov5s 用 16 比较稳,16GB 可以试 32;--imgsz 640 是输入分辨率,裂缝检测我建议不低于 640,因为分辨率越低,细裂缝越容易在下采样中消失。

这里要诚实说明一个边界:这个压缩包里没有单独抽出 train.py,它更偏推理工程,但目录里的 models、data、utils 都是标准 YOLOv5 结构,你从对应的 YOLOv5 官方版本拿一个 train.py 放进来就能直接用,数据配置和超参文件是兼容的。

4. 跑通推理入口:detect_photo.py 与 detect_camera.py 的用法和参数

4.1 单图推理:从加载权重到画框完整走一遍

detect_photo.py 是这个包里对新手最友好的入口。它本质上是把 YOLOv5 官方 detect.py 里的推理核心抽出来,只处理静态图片。如果我拿到这个工程,第一件事不是训练,而是让这个脚本跑通一张图,验证环境没问题。我一般会把它的核心逻辑维护成下面这样:

import cv2 import torch from models.experimental import attempt_load from utils.general import non_max_suppression, scale_coords from utils.datasets import letterbox weights = 'weights/yolov5s.pt' device = 'cuda:0' if torch.cuda.is_available() else 'cpu' model = attempt_load(weights, map_location=device) model.eval() img0 = cv2.imread('data/images/test_crack.jpg') img = letterbox(img0, new_shape=640)[0] # 老版本返回 (img, ratio, pad) img = img[:, :, ::-1].transpose(2, 0, 1) # BGR 转 RGB,HWC 转 CHW img = torch.from_numpy(img.copy()).float() / 255.0 img = img.unsqueeze(0).to(device) with torch.no_grad(): pred = model(img)[0] pred = non_max_suppression(pred, conf_thres=0.25, iou_thres=0.45)[0] if pred is not None: pred[:, :4] = scale_coords(img.shape[2:], pred[:, :4], img0.shape) for *xyxy, conf, cls in pred.tolist(): x1, y1, x2, y2 = map(int, xyxy) cv2.rectangle(img0, (x1, y1), (x2, y2), (0, 0, 255), 2) cv2.putText(img0, f'crack {conf:.2f}', (x1, y1 - 5), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 0, 255), 2) cv2.imwrite('runs/detect/test_result.jpg', img0)

几个关键点必须在注释里说明。letterbox 这个函数在不同版本的 YOLOv5 里返回值不一样,老版本返回三个值 (img, ratio, pad),新版本返回四个值,多一个 padding 信息。如果你的环境是新版,直接解包三个值会报 too many values to unpack,改成 img, ratio, pad, _ = letterbox(...) 就行。这在工程上是最容易翻车的兼容性问题。

non_max_suppression 里第一个参数是模型的原始输出,第二个是置信度阈值,第三个是 NMS 的 IoU 阈值。置信度阈值管的是“多可信才算目标”,调低会召回更多裂缝但误报也变多;IoU 阈值管的是“两个框重叠多少算同一个目标”,一般保持 0.45 到 0.5 就行。

4.2 摄像头和视频流接入

demo 需求里经常要求摄像头实时检测,这就是 detect_camera.py 的职责。它的核心其实只有两段代码:一段打开视频源,一段把单图推理塞进循环。

import cv2 import torch cap = cv2.VideoCapture(0) # 0 是默认摄像头,传文件路径则读视频文件 assert cap.isOpened(), 'camera open failed' while True: ret, frame = cap.read() if not ret: break # 把上一节的单图推理封装成 detect(frame) 函数 result = detect(frame) cv2.imshow('crack_detect', result) if cv2.waitKey(1) & 0xFF == ord('q'): break cap.release() cv2.destroyAllWindows()

这里有个容易被忽略的性能问题。用 yolov5s 加 640 输入,在 RTX 3060 级别显卡上能跑到 30 帧以上,但在纯 CPU 机器上会很吃力,可能掉到 10 帧以下。如果要做离线视频分析,我一般先把视频抽帧成图片,再批量推理,最后合成视频,而不是实时逐帧处理,这样哪怕一秒钟只跑两帧也不影响最终结果。

4.3 置信度和 NMS 参数怎么定:给一组实测参考

很多用户直接套默认参数,然后抱怨检测结果不对。实际上 conf-thres 和 iou-thres 需要按场景调,下面这组参考值我在裂缝检测上验证过多次:

参数推荐值效果表现适用场景
conf-thres0.25召回高,误报略多先看模型上限,快速摸底
conf-thres0.40召回和误报较均衡默认巡检,优先保证不漏检
conf-thres0.60误报很少,但会漏小裂缝人工复核前的初筛
iou-thres0.45标准 NMS 强度常规使用
iou-thres0.60更激进地去重同一裂缝出多个框时

我的习惯是:第一次跑全用默认值,先看结果哪些是误报、哪些是漏检;如果误报集中在低置信度区间,就把 conf-thres 从 0.25 往上调到 0.35 到 0.45,而不是无脑拉高到 0.7,因为裂缝目标本身置信度普遍不高,调太狠会漏掉真正需要关注的裂缝。

5. 避坑排查:这个 YOLOv5 工程最容易翻车的五个问题

5.1 运行 detect_photo.py 报 No module named 'utils'

现象:在项目目录下执行 python detect_photo.py,立刻报 ModuleNotFoundError: No module named 'utils'。

原因:这个工程里的 utils 和 models 都是本地目录,不是 pip 包。Python 运行时只会把当前工作目录加入模块搜索路径,如果你在别的目录下执行脚本,或者用 IDE 打开了整个硬盘目录,就找不到本地模块。

解决:先 cd 到项目根目录再运行,绝对不要直接双击脚本或从任意目录用绝对路径调用。如果用的是 PyCharm,把项目根目录设为 Sources Root,顺手就把 import 路径问题解决了。

5.2 加载权重时报 size mismatch

现象:torch.load 成功,但 model.load_state_dict 时抛出一长串 size mismatch,提示某个 conv 层的输出通道对不上。

原因:最常见的是拿 yolov5s.yaml 的结构去加载 yolov5x.pt 的权重,或者修改了 models 里 nc 参数之后,没有同步修改已经下载的权重。检测头的输出通道数等于 (nc + 5) × 3,类别数一变,最后一层卷积的形状必然对不上。

解决:要么保持 nc 和权重训练时一致,要么用默认权重先跑通,改完 nc 之后重新训练一个对应类别的模型,不要指望官方权重能直接适配你自己的类别数。

5.3 把伸缩缝和水渍识别成裂缝

现象:在桥梁现场图里,模型把模板伸缩缝、雨后水渍、阴影边界都画框标成 crack,置信度还不低。

原因:裂缝训练集里负样本不够。模型只见过“像裂缝的正样本”,没见过“长得像裂缝但不是裂缝”的难负样本,决策边界自然偏松。另外 conf-thres 太低也会放大这个问题。

解决:收集一批明显是伸缩缝、水渍、施工缝的负样本图片,标注为空或单独加一个 background 类放进训练集,让模型看到难负样本。同时把推理时的 conf-thres 提到 0.4,先看高分误报还有多少,再决定要不要补样本。

5.4 长裂缝只检测出一段,另外半段漏检

现象:一条贯穿整张图的裂缝,模型只框住中间一小段,两头断开,或者完全漏掉细的那半条。

原因:裂缝是极端细长目标,大幅下采样之后,细的那段特征平均进了背景,响应值过低。另一个常见原因是训练时数据增强把长图随机缩得太狠,模型没见过完整的、通道数足够的裂缝。

解决:训练和推理时把 imgsz 提到 1280,保留更多细部纹理;标注时保证断裂处的短段也单独框出来,不要只框最明显的中段;推理时用 tiled 切图,把大图切成 640×640 的 patch 分别检测,再合并结果。切图时注意相邻 patch 至少留 50 像素重叠,避免裂缝正好跨在切缝上。

5.5 推理到一半显存不够,报 CUDA out of memory

现象:单图推理没问题,但把 imgsz 调成 1280 后,batch 稍微大一点就报 torch.cuda.OutOfMemoryError。

原因:显存占用和分辨率是平方关系,1280 输入比 640 输入显存占用大约翻四倍。摄像头实时推理加显示窗口又占一部分显存,叠加在一起就爆了。

解决:显存 6GB 以下的卡,推理 imgsz 控制在 640 到 960 之间;先用 half 精度推理,model.half() 加输入 .half(),显存占用直接减半;都不行就退回 CPU 推理,把 imgsz 调到 416,牺牲一点召回换流程跑通。

6. 进阶技巧:批量推理后自动生成人工复核清单

6.1 按置信度给一批现场图排序,而不是只看单张结果

当项目从 demo 走向实际巡检,你手里往往不是一张图,而是一个路段几百张现场照片。这时候一张张看检测结果效率太低,我一般会在跑完批量推理后,把每张图的最高置信度和检测框数量汇总成一个 CSV,按置信度升序排列,先看“模型自己都不确定”的图,再决定哪些归档、哪些人工复核。

import csv import glob results = [] for img_path in glob.glob('data/field_images/*.jpg'): dets = detect(img_path) # 复用 4.1 节的检测函数 if not dets: results.append((img_path, 0.0, 0)) else: top_conf = max(d['conf'] for d in dets) results.append((img_path, round(top_conf, 4), len(dets))) results.sort(key=lambda x: x[1]) with open('review_list.csv', 'w', newline='') as f: writer = csv.writer(f) writer.writerow(['image', 'top_conf', 'box_count']) writer.writerows(results)

这段脚本的价值不是统计本身,而是改变了人工复核的次序。巡检场景最怕的是漏检,而不是误报——误报顶多多看一张图,漏检一条裂缝可能直接导致结构安全隐患。按置信度升序排列后,你自然先看模型最不确定的那批图,也就是漏检风险最高的样本。每张图只看两秒,几百张图半小时内能完成复核。

从那以后我每次拿到一批现场图,都强制走一遍这个置信度排序流程,再累也会先看后 20% 的低分样本。高分检测结果反而可以放一放,因为模型已经确认过了。希望这个习惯和上面这套调试方法能帮到你,少走几段我当年踩过的泥坑。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询