简介:基于Python与YOLOv5的路面桥梁裂缝检测识别项目,面向计算机相关专业学生及需要项目实战的学习者,适用于毕业设计、课程设计、期末大作业等场景,提供一套可运行、可扩展的高分参考实现。压缩包共85个文件,含23个Python源码(检测、训练、常用工具)、23个YAML配置(模型结构、数据集参数)、5个Shell脚本(权重下载与环境准备)、2个Dockerfile及若干示例图片,整体约1.6MB,轻量精简。目前已有78人学习下载。项目结构清晰,包含detect_photo.py、detect_camera.py等入口,models目录内置YOLOv5s/m/l/x多种网络配置,data目录提供数据配置文件,还可通过Dockerfile快速搭建环境;配套模型权重下载脚本,能直接完成裂缝目标检测任务。该毕业设计经导师指导并认可,评审99分,代码完整保证可运行,对初次接触YOLO的开发者尤为友好,可作为二次开发与学习的基础。
1. 路面桥梁裂缝检测,为什么毕业设计都在做 Python+Yolov5
土木和计算机交叉的毕业设计里,路面和桥梁裂缝检测识别是出现频率最高的方向之一,核心原因很直接:传统人工巡检靠目测和打分,效率低、主观性强,而“Python+Yolov5”这条组合能在一个月内跑完数据标注、模型训练、界面演示的全流程,低成本交出一套带检测框和置信度的成品。它的本质不是一个“图像分类”任务,而是“目标检测”——不仅要判断图片里有没有裂缝,还要把裂缝在哪、有多长、面积多大用矩形框标出来。对于做毕设、横向课题或小团队试点的人来说,Yolov5是唯一一个同时满足代码成熟、显存要求低、中文教程多、能部署到嵌入式设备这几个条件的模型。
这篇笔记按一条完整的落地路径展开:先讲为什么选 Yolov5、数据怎么准备;再讲训练命令和超参数怎么调;然后讲模型导出和用 Python 写推理脚本;最后集中写 5 个训练部署中容易翻车的典型问题,以及验证指标和进阶技巧。读者如果是第一次做裂缝检测,跟着章节顺序走完就能出图;如果已经在跑 Yolov5,可以直接跳到第 5 章和第 6 章看排查思路和参数边界。
2. 选型与数据准备:裂缝检测到底难在哪、YOLO 格式怎么转
2.1 为什么路面裂缝检测普遍选 Yolov5,而不是分类网络或其它检测框架
很多人第一次接触这个题目,会先想到用 ResNet 之类的分类网络,把图片判断成“有裂缝/无裂缝”。这个方案的缺陷在第 1 章已经提到了:分类网络只能告诉你“有没有”,无法给出裂缝的位置和几何信息,而桥梁定期检测的规范里恰恰需要记录裂缝的宽度、长度和分布区域。所以这个题目必须落到目标检测或语义分割这两类模型上,Yolov5 属于前者,工程复杂度比分割模型低一个量级。
在目标检测模型里,Faster R-CNN 在裂缝小目标上有不错的召回率,但训练慢、部署体积大、写代码时依赖版本容易冲突;YOLOv8 和 v11 的新版本虽然官方维护活跃,但毕业设计要查的很多资料、预设权重和部署方案都还是以 v5 为主力,加上 v5 代码对新手更友好,网络结构图、超参数说明、训练自己的数据集这类教程一搜一大把。综合时间成本和风险,我一般会建议第一次做裂缝识别的人直接选 Yolov5s 或 Yolov5m,而不是一上来就用最大的 Yolov5x。
从检测对象本身来看,路面和桥梁裂缝有三个让模型容易“翻车”的特点。第一是尺度极端:裂缝宽度可能只有 3~5 个像素,而整张巡检图往往有 4000×3000 分辨率,属于典型的小目标检测。第二是背景干扰强:沥青路面的纹理、桥梁伸缩缝、水渍、阴影,在灰度特征上和裂缝很接近。第三是样本不平衡:正常的“无裂缝”区域数量远大于裂缝区域,模型很容易学成“偏向预测背景”。这些特点会直接影响后续数据增强策略和推理时的切图方式,不是换一个更大的模型就能解决的。
2.2 裂缝数据集从哪来、怎么整理成 YOLO 格式
做裂缝检测,数据比模型重要得多。公开渠道上能找到的路面裂缝数据集主要有 CRACK500、CrackForest、DeepCrack 这类学术数据集,Roboflow 和 Kaggle 上也能搜到不少桥梁裂缝图片集,搜“bridge crack detection dataset”或“concrete crack”能找到可下载的资源。需要注意两点:一是学术数据集里的图片多为大学实验室拍摄,光照和角度比较单一,拿到现场不一定好用;二是下载后格式各不相同,有的是原始图片加 JSON 标注,有的是 VOC 的 XML 格式,有的只有像素级二值掩码。
我处理数据的第一步,总是先把所有标注统一成 YOLO 的 txt 格式,再开始训练。这里给一个把 VOC 的 XML 转成 YOLO txt 的 Python 脚本,这是做目标检测最常用的格式转换操作,因为早年的公开数据集大量使用 VOC 标注方式。
import os import xml.etree.ElementTree as ET def voc_to_yolo(xml_path, out_dir, class_list): """把VOC格式的XML标注转为YOLO格式的txt文件 class_list: 类别列表,例如 ['crack'],顺序对应类别id """ tree = ET.parse(xml_path) root = tree.getroot() img_w = int(root.find('size/width').text) img_h = int(root.find('size/height').text) txt_name = os.path.basename(xml_path).replace('.xml', '.txt') out_lines = [] for obj in root.findall('object'): cls_name = obj.find('name').text if cls_name not in class_list: continue # 跳过未在类别列表中定义的标注 cls_id = class_list.index(cls_name) 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) # 计算归一化后的中心点坐标和宽高,越界值需要clip到[0,1] cx = ((x1 + x2) / 2) / img_w cy = ((y1 + y2) / 2) / img_h bw = (x2 - x1) / img_w bh = (y2 - y1) / img_h out_lines.append(f"{cls_id} {cx:.6f} {cy:.6f} {bw:.6f} {bh:.6f}") out_path = os.path.join(out_dir, txt_name) with open(out_path, 'w') as f: f.write('\n'.join(out_lines)) # 用法示例:class_list 按你的实际情况写,比如桥梁裂缝只有一类时写 ['crack'] voc_to_yolo('001.xml', './labels', ['crack'])这段代码的核心逻辑是读取 XML 里的目标框左上角和右下角坐标,换算成归一化的中心点加宽高,最后写入 txt 文件。YOLO 格式对坐标的归一化要求非常严格,如果你的原图被 resize 过,但标注没同步缩放,后面训练时 loss 会出现剧烈的抖动甚至变成 nan。转换完成后要随机抽 20 张图,把标注框画回图片上检查一遍,这一步能拦下八成以上的标注错位问题。
整理好标注后,文件结构要严格对齐 Yolov5 的 data 配置要求。常见做法是按下面的目录摆放图片和标签:
datasets/ ├── images/ │ ├── train/ # 训练图片 │ └── val/ # 验证图片 ├── labels/ │ ├── train/ # 对应训练图片的txt标注 │ └── val/ # 对应验证图片的txt标注 └── crack.yaml # 数据集配置文件2.3 裂缝数据增强策略:小目标与背景干扰怎么解决
裂缝检测里最有效的增强手段,第一是 Mosaic,第二是亮度扰动,第三是随机裁剪放大。Mosaic 是 Yolov5 自带的数据增强,把四张图拼成一张再参与训练,打乱了裂缝在不同图片里的上下文,缓解模型对某一种背景的过拟合。亮度扰动对应的是现场工况:桥梁墩柱下的裂缝往往在阴影里,路面裂缝在晴天正午拍出来曝光偏高,如果训练集里没有低亮度样本,模型在桥底就会疯狂漏检。
小目标处理上,Yolov5 有一个内置参数叫 copy_paste 或 scale,配合 mosaic 能在训练时把小目标成倍复制到其它图片上。实际操作中,我通常关闭 paste 操作,但把 mosaic 概率提到 1.0,同时设置 hsv_h、hsv_s、hsv_v 的扰动范围。下面这段是训练时修改的数据增强配置片段,可以直接对应到你的 Yolov5 项目里的 hyp.scratch.yaml 文件中:
# hyp.scratch.yaml 中与裂缝检测相关的增强参数 mosaic: 1.0 # 始终启用mosaic增强,提高小裂缝的上下文多样性 mixup: 0.2 # 轻微mixup,比例太高会让裂缝纹理变糊 hsv_h: 0.015 # 色相偏移调小,裂缝颜色变化不大 hsv_s: 0.5 # 饱和度可以适当放大,覆盖不同光照 hsv_v: 0.4 # 亮度扰动加大,模拟桥底阴影和逆光场景 fliplr: 0.5 # 水平翻转 scale: 0.5 # 随机缩放范围,模拟不同拍摄距离参数设置的逻辑是:裂缝的形状和颜色都比较规整,太大的色相偏移会让沥青纹理也变得花哨,所以 hsv_h 要保守;而 hsv_v 的亮度抖动是必需的,因为现场光照差异大。训练时如果你想验证增强是否生效,可以在 train.py 里临时把 max_det 和画图逻辑调出来看增强后的 batch 图,Yolov5 会保存train_batch*.jpg到 runs/train 目录,每次训练完翻一翻这些图,比看曲线更能直观发现数据问题。
3. 环境搭建与训练:把 Yolov5 在本地跑通并训练裂缝模型
3.1 Python 环境和依赖安装:版本搭配是第一个拦路虎
Yolov5 官方推荐的 Python 版本是 3.8 到 3.10,配套 PyTorch 用 1.10 到 2.0 左右都能跑。第一步是创建独立的虚拟环境,不要直接装在系统 Python 里,这是血泪经验:很多人同时做多个课题,系统环境里 torch 和 tensorflow 打架是家常便饭。用 conda 创建环境并安装依赖:
conda create -n yolov5_crack python=3.9 conda activate yolov5_crack # 先装PyTorch,根据自己的显卡驱动版本选择CUDA版本 # 以CUDA 11.8为例,NVIDIA驱动版本需在520以上 pip install torch==2.0.1 torchvision==0.15.2 --index-url https://download.pytorch.org/whl/cu118 # 拉取Yolov5源码,这里以官方Ultralytics的v5仓库为目标 git clone https://github.com/ultralytics/yolov5.git cd yolov5 pip install -r requirements.txtrequirements.txt 里会安装 opencv、matplotlib、pandas、seaborn 这些依赖,如果你在纯服务器环境跑,seaborn 和 matplotlib 这类画图库不是必需的,但装了也不吃亏。需要注意的是 PyTorch 版本不要直接选最新版,因为 CUDA 工具链太新的话,Yolov5 里某些算子可能没有对应的预编译轮子。
安装完成后验证环境是否正常,可以跑一次官方自带的检测命令,用一张花朵图片测试前向推理:
python detect.py --weights yolov5s.pt --source data/images/bus.jpg --conf-thres 0.25如果这条命令能跑出检测框,说明环境没问题。新手最容易在这里卡住,症状通常是报错ImportError: libGL.so.1,这是因为 opencv-python 缺少系统图形库,在服务器上经常出现。解决办法是安装 libgl 相关系统包,在 Ubuntu 上对应执行apt install -y libgl1 libglib2.0-0,再把 opencv-python-headless 装一遍替代原来的 opencv-python。
3.2 训练自己的裂缝数据:train.py 参数与数据集配置文件
在训练前要先写好数据配置文件 crack.yaml,告诉 Yolov5 去哪找图片和标签。内容如下:
# crack.yaml 数据集配置文件 train: datasets/images/train val: datasets/images/val # 类别数:如果只分“裂缝”这一类,nc就是1 # 如果想区分“横向裂缝/纵向裂缝/网状裂缝”,这里的nc和names要对应调整 nc: 1 names: ['crack']这里最容易埋坑的是 nc 和 names 不匹配,或者标注文件里出现了 names 之外的类别名,会导致训练时 category id 越界。检查方法很简单:读一下任意一个 txt 标注文件的第一列数字,保证它的最大值小于 nc。如果标注文件里出现除了第一个类别之外的 id,就要回到转换脚本的 class_list 修正。
开始训练时,我会用 Yolov5s 预训练权重做迁移学习。因为裂缝的底层特征(边缘、纹理、灰度梯度)和 COCO 数据集里的很多物体是共享的,初始化权重比从零训练收敛快得多。常见训练命令如下:
python train.py \ --data crack.yaml \ --weights yolov5s.pt \ --img 640 \ --batch 16 \ --epochs 100 \ --device 0 \ --cache images几个关键参数的意义:--img 640表示输入图片会被缩放到 640×640 训练。由于裂缝是小目标,我建议显存允许时至少用 640,条件好的直接上 1280,但代价是训练时间翻倍。--batch 16是批大小,不能设太大超过显存,报 CUDA out of memory 时把它减半就行。--cache images会把图片提前加载进内存,减少每个 epoch 的磁盘读取时间,如果你硬盘是机械盘,这个参数能让训练快一倍。
训练结果会保存在runs/train/exp*目录下,里面有 weights 文件夹,best.pt 是按验证集 mAP 保存的最好权重,last.pt 是最后一轮权重。做毕设演示或部署时用 best.pt,不要用 last.pt,因为 last.pt 可能已经在过拟合阶段了。
3.3 超参数调整:学习率、anchor 与收敛判断
Yolov5 的学习率默认写在 hyp.scratch.yaml 里,初始学习率 lr0 从 0.01 开始,配合余弦退火或线性衰减。裂缝检测如果发现 loss 前期下降很慢,可以把 lr0 调到 0.02,但超过 0.03 后容易发散。另一个比较玄学的参数是 warmup_epochs,默认是 3.0,意思是前 3 个 epoch 用小学习率热身,避免前期梯度方向不稳定。
anchor(锚框)是检测裂缝时最需要关注的超参数之一。Yolov5 默认的 anchor 是针对 COCO 数据集统计出来的,偏向中等大小的物体;而裂缝通常是长条形的,宽高比很大。常见做法是在训练前用 k-means 算法重新聚类自己数据集的 anchor 尺寸,把结果写进模型配置文件。Yolov5 官方提供了一条命令生成自适应 anchor:
python train.py --data crack.yaml --weights yolov5s.pt --img 640 --noautoanchor不过更省事的方法是不加--noautoanchor,让 Yolov5 在训练开始时自动计算一次 anchor。需要注意的是,如果数据集标注框的尺寸非常极端,例如大多是 200×3 这样的长条框,自动聚类可能不稳定,这时需要人工检查生成的 anchor 表,确认宽高比能覆盖到训练数据中真实框的分布。
训练过程中关注三个信号:Box 损失曲线是否持续下降、验证集 mAP@0.5 是否在上升、训练集和验证集 loss 的差距是否快速拉大。当验证集 mAP@0.5 连续 15 个 epoch 不再上升时,就可以早停了,继续训练大概率是过拟合。Yolov5 默认每轮都会输出类似下面的日志:
Epoch gpu_mem box obj cls labels img_size 49/99 4.17G 0.0234 0.0136 0.0000 17 640 Class Images Instances P R mAP50 mAP50-95 all 200 500 0.872 0.815 0.865 0.412对裂缝检测这个单类别任务,mAP@0.5 到 0.85 左右就算能用的模型了,mAP@0.5-95 偏低是正常的,因为裂缝长条形的形状对 IoU 计算极不友好,稍微偏移一点 IoU 就会掉很多。不要在 mAP@0.5-95 上死磕。
4. 模型导出与推理落地:从 pt 权重到可用的检测脚本
4.1 用 export.py 把 Yolov5 模型导出为 ONNX
训练完的 best.pt 还要转换成适合部署的格式,最常见的中间格式就是 ONNX。ONNX 的好处是通用性强,可以再转成 TensorRT、OpenVINO 或瑞芯微 RKNN 在边缘设备上跑,而且用 ONNX 做推理时不需要依赖完整的 Yolov5 代码库,只需要 onnxruntime 就够了。导出命令如下:
python export.py \ --weights runs/train/exp/weights/best.pt \ --include onnx \ --opset 12 \ --img 640参数说明:--include onnx指定导出格式,--opset 12是算子集版本,onnxruntime 对 opset 12 支持非常稳定,不需要追新。--img 640与训练时的输入尺寸保持一致,如果这里设置成训练时不同的尺寸,模型输出的感受野和 anchor 匹配关系会被破坏,导致检测结果偏移。导出的文件是best.onnx,放在runs/train/exp/weights/目录下。
不懂的细节是,暴力导出的 ONNX 里会带上 NMS 吗?Yolov5 的 export.py 里有一个--nms参数,加上后导出的 ONNX 会包含非极大值抑制算子,好处是部署时少写后处理代码,坏是一旦在边缘设备上,这个 NMS 算子很多硬件加速库不支持。我建议不加--nms,在后处理阶段用 Python 自己写 NMS,可控性高得多。这里提前说一下这个思路,具体代码在下一节给出。
4.2 用 Python + onnxruntime 写裂缝检测推理脚本
没有 Yolov5 框架的依赖,直接用 onnxruntime 加载 ONNX 模型并写推理脚本,是实际工作中最多的做法。下面这段是我的标准裂缝检测脚本骨架,包含预处理、推理、NMS 后处理和画框:
import cv2 import numpy as np import onnxruntime as ort CLASS_NAMES = ['crack'] INPUT_SIZE = 640 def preprocess(img): """缩放到640x640,转RGB并归一化到0~1""" img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) img_resized = cv2.resize(img_rgb, (INPUT_SIZE, INPUT_SIZE)) img_float = img_resized.astype(np.float32) / 255.0 # Yolov5训练时输入格式为 NCHW,需要增加batch维度 blob = np.transpose(img_float, (2, 0, 1))[None] return blob def nms(boxes, scores, iou_thres=0.45): """简单的NMS实现,按分数从高到低去掉重叠框""" x1, y1, x2, y2 = boxes[:, 0], boxes[:, 1], boxes[:, 2], boxes[:, 3] areas = (x2 - x1) * (y2 - y1) order = scores.argsort()[::-1] keep = [] while order.size > 0: i = order[0] keep.append(i) xx1 = np.maximum(x1[i], x1[order[1:]]) yy1 = np.maximum(y1[i], y1[order[1:]]) xx2 = np.minimum(x2[i], x2[order[1:]]) yy2 = np.minimum(y2[i], y2[order[1:]]) inter = np.maximum(0.0, xx2 - xx1) * np.maximum(0.0, yy2 - yy1) iou = inter / (areas[i] + areas[order[1:]] - inter + 1e-6) order = order[np.where(iou <= iou_thres)[0] + 1] return keep def postprocess(output, img_shape, conf_thres=0.25): """解析ONNX输出,坐标缩放回原图并执行NMS""" # output形状为 [1, 25200, 5+nc],25200是三个尺度下anchor总数 preds = output[0] scores = preds[:, 4] mask = scores > conf_thres boxes = preds[mask][:, :4] scores = scores[mask] if len(boxes) == 0: return np.array([]), np.array([]) # Yolov5输出的是中心点坐标加宽高,转成左上右下角形式 cx, cy, w, h = boxes[:, 0], boxes[:, 1], boxes[:, 2], boxes[:, 3] x1, y1 = cx - w / 2, cy - h / 2 x2, y2 = cx + w / 2, cy + h / 2 boxes_xyxy = np.stack([x1, y1, x2, y2], axis=1) # 坐标乘以缩放比例,从640x640映射回原图 scale_h = img_shape[0] / INPUT_SIZE scale_w = img_shape[1] / INPUT_SIZE boxes_xyxy[:, [0, 2]] *= scale_w boxes_xyxy[:, [1, 3]] *= scale_h keep = nms(boxes_xyxy, scores, iou_thres=0.45) return boxes_xyxy[keep], scores[keep] # 加载模型并推理 sess = ort.InferenceSession('best.onnx', providers=['CUDAExecutionProvider', 'CPUExecutionProvider']) img = cv2.imread('test_bridge.jpg') input_blob = preprocess(img) outputs = sess.run(None, {sess.get_inputs()[0].name: input_blob}) boxes, scores = postprocess(outputs[0], img.shape)代码里的两个关键处理点要说明:一是缩放映射,模型在 640×640 下的输出坐标必须乘回原图尺寸比例,否则画框会整体偏移;二是 NMS 的 IoU 阈值,裂缝因为长条形的特点,两个标注框可能高度重叠但覆盖的是裂缝的不同分段,阈值设到 0.45 以上会误删相邻框,建议保持在 0.4~0.5 之间。
4.3 大图切块推理:让模型处理高分辨率的桥梁巡检图
实际巡检拍出的图片往往远大于 640×640,如果直接整图缩放,裂缝可能缩成一条线就完全丢失了。所以我做这个项目时,切块推理几乎是一个必须步骤——把大图切成 640×640 的小块,每个小块独立推理,最后把检测框映射回原图坐标。这一步也是让毕设看起来“能落地”的关键,因为评委一定会问“那么大的原图你直接传进去?”。
切块的常见实现方案是给 overlap(重叠率)留 64~128 像素,防止裂缝恰好被切在块边界上导致中间断裂。切块代码比较简单,核心逻辑如下:
def sliding_window_detect(img, window_size=640, step=512): """滑动窗口切图推理,避免裂缝被切碎在边界上""" h, w = img.shape[:2] all_boxes = [] for y in range(0, h, step): for x in range(0, w, step): # 确保窗口不超出图像边界 y2 = min(y + window_size, h) x2 = min(x + window_size, w) crop = img[y:y2, x:x2] # 推理窗口 blob = preprocess(crop) outputs = sess.run(None, {sess.get_inputs()[0].name: blob}) boxes, scores = postprocess(outputs[0], crop.shape) # 将框坐标平移到原图坐标系 boxes[:, [0, 2]] += x boxes[:, [1, 3]] += y all_boxes.append(np.column_stack([boxes, scores])) return np.vstack(all_boxes)切块的步长如果等于窗口尺寸,会漏掉边界处的裂缝;步长小于窗口尺寸虽然增加了计算量,但能保证裂缝跨块时被完整检出。我一般把 step 设成窗口的 80%,比如 640 窗口配 512 步长,重叠区 128 像素,算是精度和速度之间的一个平衡点。如果现场机器性能允许,也可以把 step 继续降到窗口的 50%,召回率会有明显提升。
5. 裂缝检测训练与部署的 5 个常见问题排查
5.1 训练时 loss 变成 nan 或直接崩掉
现象:train.py 跑了几十个 step 后,loss 输出变成nan,或者程序直接报错中断。
原因:最常见的是标注文件里出现负数坐标、坐标超出图片边界,或者类别 id 大于模型的 nc 值。数据增强阶段如果对损坏图片做了 resize,也会产生非法坐标导致梯度爆炸。另一个原因是学习率设置过高,某些机器上混合精度训练时一个异常的梯度就能把权重冲飞。
解决:先跑数据校验脚本,遍历所有 txt 标注,检查每一行的类别 id 是否小于 nc、归一化坐标是否在 0~1 之间;再用 OpenCV 打开所有训练图片,过滤掉无法解码的损坏文件。代码层面可以在 train.py 里把 AMP 开关关闭(添加参数--no-amp或用旧版本 Yolov5 的不启用 AMP),排除混合精度带来的问题。
5.2 训练集 mAP 很高,但现场测试图片漏检严重
现象:验证集 mAP@0.5 能达到 0.9 以上,但拿到自己拍的手机照片或现场相机图,裂缝基本检测不出来。
原因:数据集域差异造成的。公开数据集或自己标注的照片往往光照均匀、背景简单;现场图可能包含大量阴影、树叶遮挡、水渍,模型学到的“裂缝特征”和现场样本分布完全不同。另一个原因是训练图里没有包含足够的负样本,比如沥青路面纹理本身在视觉上很像小裂缝。
解决:从现场采集图并补充进训练集,建议现场图至少占总量 30% 以上。每次收集现场数据后,用训练好的模型自动检测一遍并人工修正标注,这样模型的鲁棒性会稳步上升。同时把负样本图中容易误检的局部区域裁出来作为背景样本,放到 images 目录里配一个空的 txt 标注文件,让模型学会判断“这是背景”。
5.3 横向裂缝能检出,但细小的纵向裂缝漏掉
现象:模型对宽度在 5 像素以上的裂缝检测效果好,但宽度只有 1~2 像素的细微裂缝基本检不出,尤其在干旱天气下裂缝对比度低时。
原因:小目标问题。640×640 输入下,细微裂缝的像素面积可能不足 20 个像素,特征在图内的响应非常弱;加上长条形目标经过多次下采样后,高层特征图上的响应被严重稀释。
解决:首先提高训练分辨率,把--img从 640 改成 1280,代价是显存占用约增大 4 倍,batch 需要相应缩小。其次在推理阶段对大图先做对比度增强或伽马校正,再输入模型。如果项目允许,换用 Yolov5-seg 把检测问题改为分割问题,对细裂缝的召回往往有质的提升——分割模型对每个像素做预测,本质上是另一个维度的解决方案。
5.4 同一张图多次推理,检测框位置不稳定
现象:同一张图片用同一权重、同一推理脚本跑十次,每次的框位置、置信度有细微浮动,或者标注工具里看起来很准的框,在自己的脚本里偏移了几个像素。
原因:如果推理脚本里开启了随机增强或测试时数据增强,输出会浮动;更常见的是坐标映射写错。很多复制来的代码直接用 640×640 的输出坐标画框,而没把它缩放回原图尺寸。还有一种是模型训练时用了矩形推理(rect=True),推理脚本没用相同的矩形尺度策略。
解决:推理过程中关闭 TTA(Yolov5 的 detect.py 默认不开,但自定义脚本里要注意),保证每张图的输入尺寸固定。检查坐标缩放代码,务必用原始图的宽高除以输入尺寸来计算比例,不要用模型输出的固定 640 假设。再多跑一个环节验证:用标注工具画个框,把 txt 坐标画回图片,再用模型推理同一张图,确认两者误差在几个像素以内。
5.5 部署到 RK3588 或 ARM 设备后推理速度很慢
现象:在 PC 上用 GPU 推理单张图 20ms,换到 RK3588 等边缘设备上用 ONNX Runtime CPU 推理,单张需要 1~2 秒,无法满足巡检车的实时处理要求。
原因:没有针对目标硬件做算子和精度优化。直接跑 fp32 ONNX 在 ARM CPU 上本身就是低效玩法;端侧推理至少要做到两个优化:输入尺寸控制在 640 以内,模型用 INT8 量化或使用厂商提供的硬件加速框架转换。
解决:常见做法是先用瑞芯微的 RKNN-Toolkit2 把 ONNX 转成 RKNN 格式并做 INT8 量化,量化前需要准备 200~500 张典型样本做校准集。量化后检测精度会掉 2~4 个百分点,这个代价对比推理速度 3~5 倍提升是值得的。对桥梁检测这种偏静态场景,如果不需要实时,也可以直接放宽性能要求,在 PC 上离线批量处理。
6. 验证指标、模型对比与一个进阶技巧
先验证再交付,是这个项目最后一道关。在毕设答辩或项目验收时,单纯把 mAP@0.5 报出来是不够的,因为裂缝检测的最终用户是工程师,他们关心的是“每 100 张现场照片能漏掉几张、误报几张”。这里我常用的验证方式是做一次“人工可读的抽样评估”:在验证集里随机抽 100 张图,其中包含裂缝的正样本和纯背景的负样本各 50 张,让模型输出检测结果,然后按一般检测任务的标准统计:查准率 = 正确检出的裂缝框数 / 模型输出的总框数;查全率 = 正确检出的裂缝框数 / 标注的真实裂缝框数。对裂缝这类长条形目标,IoU 阈值不要用 0.5,我习惯用 0.3 作为“检出”标准,因为长条框很难达到高 IoU。
再提一个对裂缝检测特别实用的进阶技巧:难例挖掘(Hard Negative Mining)。第一次做这个项目时,我拿到一批桥墩表面图像,模型总是把竖向排水管边缘误判为裂缝。原因是标注数据里没有这类“像裂缝但不是裂缝”的样本。解决方法是跑一次推理,把所有置信度高于 0.3 的误检区域裁剪出来,人工确认是无裂缝的背景后,把这些图片作为背景样本混入训练集,让模型在下一轮训练时有机会学习这些负样本的纹理差异。经过两轮难例挖掘,模型在桥墩场景的误检率能下降超过一半。这个技巧不需要改动任何代码,但实际效果比调任何超参数都明显。
以我的经验,做路面桥梁裂缝检测项目时,判断一个模型方案值不值得投入,首先要看它能不能稳定处理现场采集的高分辨率原图,而不仅是标准测试集。前面提到的切块推理、难例挖掘和 ONNX 导出,这些步骤比追求跑分更重要。最后的习惯性操作是把 best.onnx 连同推理脚本、README 和环境安装说明一起打包交付,因为对用的人来说,能一键跑起来的黑匣子不如能二次开发的 Python 文件。希望这条落地路径对你做毕业设计或实际项目有帮助。
本文还有配套的精品资源,点击获取