Yolov5道路裂缝检测实战:从数据标注到Jetson Nano部署
2026/9/15 21:47:21 网站建设 项目流程

路面养护圈子里有一句话:裂缝是道路病害的“最先信号”,但发现它靠人眼盯着路面一寸一寸扫,效率实在太低了。我当时接这个需求时,第一反应就是直接用目标检测模型来做,最后选型落在Yolov5上,完整跑通了一个道路裂缝检测项目——从数据采集、标注规范、训练调参到Jetson Nano边缘部署,整个过程踩了不少坑,也总结了一套对这个特定场景很有效的处理方式。这篇文章就把整个项目从0到1的完整路径写出来,如果你正打算用Yolov5做道路裂缝检测,或者想训练一个自己的自定义数据集但卡在参数和部署环节,那这篇内容应该能帮你少走很多弯路。

1. 为什么是Yolov5:裂缝检测的选型逻辑与项目定位

1.1 传统图像处理为什么做不好裂缝检测

先说个反直觉的事:很多人以为裂缝检测就是个"边缘检测"的活,拿Canny或者Sobel算子找一找暗色线条就行了。我在项目预研阶段还真的试过这条老路——灰度化、高斯滤波、Canny边缘提取、形态学闭运算把断开的裂缝连起来。在实验室干净、光照均匀的几张测试图上,效果确实像模像样,能描出裂缝的大致轮廓。

但一到真实路面就崩了。路面上有树荫、有油渍、有轮胎印、有水渍反光、有人行道接缝,这些纹理在灰度图上和裂缝长得极其相似。传统CV靠的是人工设计规则,本质上是"猜"路面长什么样,而真实路面的纹理变化比想象中大得多,规则永远写不全,最后的结果就是误检率居高不下,完全没法用。

退一步说,就算用CNN分类网络判断"这张图里有没有裂缝",也只能给个整体结果。巡检工人的真实需求是"裂缝在哪个位置、大概多长多宽",一个框的坐标比一句"可能有裂缝"有用得多。这也是当初把这个项目定位成目标检测而不是分类或分割的核心原因——检测模型能同时给出位置和类别,直接对接后续的派单维修流程。

1.2 Yolo家族怎么选

Yolo家族到了2024年版本已经很多了,为什么选Yolov5而不是更新的Yolov8或者YoloX?我的理由非常实际。

第一是生态成熟度。Yolov5从2020年发布到现在,社区积累的资料、踩坑记录、第三方工具脚本非常完善。做工程最怕的不是模型精度低,而是报错找不到人问。搜"Yolov5 报错",出来一堆解决方案;搜其他新框架的报错,可能只有官方issue里孤零零的几条。

第二是部署链路完整。这个项目最终要上Jetson Nano这种边缘设备,Yolov5官方就提供了TensorRT导出的脚本,FP16推理、engine格式封装都给你写好了,省掉了很多自己造轮子的时间。Yolov8当然也不错,但它的优势更多体现在统一API和新的训练策略上,对于道路裂缝这种对细节纹理敏感、类别极其单一的场景,Yolov5s和Yolov8n的精度差距并不大。

第三是可控性。Yolov5的代码结构是出了名的模块化,从数据增强到NMS后处理都能改。比如裂缝目标长宽比特别夸张,需要关掉自动锚框或者修改anchor大小;再比如mosaic增强在裂缝场景下会导致标签错位,我在第四部分会细说——这些"魔改"在Yolov5里改起来都很顺手,换别的框架未必有这么灵活。

1.3 项目目标与类别设计

我最后只定义了两个类别:横向裂缝和纵向裂缝。有的项目也会加"网状裂缝"和"修补区域",但我的建议是类别不要一上来就搞太细。

原因有两个:一是标注工作量会爆炸,网状裂缝的边界本身就模糊,一个框很难框得准;二是模型学到的区分特征不够,横裂和纵裂在形状上有本质区别,但网状裂缝和横向裂缝掺杂在一起时,框的边界很难定义。

输出就是一个标准的检测结果:每个框带四个坐标、一个类别、一个置信度。后期接业务系统时,可以根据框的宽度估算裂缝长度,按路段编号汇总上报,这就是完整的落地闭环了。

2. 裂缝数据的获取、清洗与标注规范

2.1 数据从哪来

跑通Yolov5自定义数据集,最先卡住你的往往是数据,而不是代码。道路裂缝虽然常见,但网上现成的高质量标注数据集并不多。

我当时主要用了两条腿走路。一是公开数据集,像CrackDataset、DeepCrack这类学术数据集可以下载,它们的好处是标注相对规范、类别已经分好,适合先跑通流程;但缺点是拍摄机位多为固定角度、光照均匀,和实际巡检场景(手持手机、车载相机、不同天气)差距很大,直接拿来做最终模型泛化能力不够。

二是自己采集。这个方法最土但最有效:拿手机贴着路面拍,或者装一个行车记录仪在园区、老旧小区、市政道路转几圈。我建议拍的时候刻意覆盖阴天、逆光、傍晚、雨后天晴这些场景。裂缝检测最大的敌人是光照变化,训练数据里没见过的光照,模型在线上一定翻车。

公开数据加自采数据,我当时凑了大概2600张有效图。如果预算充足或者项目周期紧,还有一条捷径是找道路养护单位要历史巡检照片,但要注意版权和数据合规问题。

2.2 清洗比标注更重要

拿到原始数据之后,第一件事不是打开标注软件干活,而是删图。

删除标准很明确:严重模糊的、虚焦的、过曝到白茫茫一片的、运动模糊到看不出纹理的,一律删。这些图强行标进去,只会让模型学到一堆噪声。视频抽帧得到的图片还要做去重,同一段路面连续几十帧高度相似,全部喂进去会造成数据冗余,训练时模型反复看同一块路面,验证集稍微有点变化就露馅。

还有一个容易忽略的点是类别平衡。横向裂缝和纵向裂缝的数量要尽量接近,如果纵裂是横裂的三倍,模型会偏向预测纵裂,横裂的召回率会明显偏低。我当时专门数了数,把数量少的那一类又补拍了些数据。

划分数据集时也要注意:按场景划分,不要按文件随机划分。比如同一个小区路面连续拍的100张图,如果不加处理随机分到train和val,验证集里就会出现和训练集几乎一样的图,指标虚高,到真实场景马上露出原形。

2.3 标注规范要提前定死

标注工具我用的是LabelImg和AnyLabeling。AnyLabeling支持半自动分割辅助,效率高一些,LabelImg更轻量、启动快,随便哪个都行。

真正重要的是标注规范,团队协作时尤其如此。我踩过最大的坑就是对"裂缝框怎么框"没有提前定义,导致几个标注助手各框各的,有的贴裂缝边缘贴得死死的,有的留了很大边距,模型训练出来很飘。后来统一成一条规则:框要贴合裂缝的主体区域,但四周留出大约裂缝宽度20%~30%的余量(给模型一点上下文信息),锚框回归会更稳。

另外一个争议点是裂缝断开怎么处理。一整条裂缝中间有积水或者杂物,看起来断开了,但人眼能看出它是同一条。我的规范是:断开的段落如果走向一致且间隔小于裂缝长度的一半,就当作同一条裂缝框一个整体框;如果间隔很大或者走向明显变化,就各标各的。标注最忌讳的是随手一点、随性一框,模型会学到混乱的模式。

还要特别警惕一类标注错误:把路面伸缩缝、裂缝阴影、轮胎印标成裂缝。这些目标在灰度图上和裂缝长得太像了,一旦标进去,模型就会把它们当成正样本学,检测时误检率直线上升。我在项目中期检查标注文件时,发现这类噪声占比接近8%,后来专门组织了一次清洗,把错误标注全部挑出来重新标。

导出格式用YOLO标准格式:每张图片对应一个txt文件,每行是class x_center y_center width height,四个坐标都是归一化到0~1之间的浮点数。标注软件一般会自动转换,不熟悉的同学打开生成的txt看一眼就明白了。

2.4 数据量到底要多少

很多人喜欢问"我到底要标多少张图?"这个问题没有标准答案,取决于场景复杂度。裂缝检测有个好消息和一个坏消息。

好消息是裂缝在视觉上的模式相对固定,就是暗色线条状纹理,不需要像通用目标检测那样学几千个类别。坏消息是裂缝在画面里的尺寸跨度极大,有的裂缝宽到占画面三分之一,有的细到只有几十个像素,模型需要同时学会检测"大象"和"蚂蚁"。

从我经验看,单一场景下每类裂缝有500张以上干净的标注图,就能训出一个能用的模型;要做到稳定上线,每类800~1500张合适。如果数据量不够,优先保质量——1000张干净图比3000张糊弄的图训练效果好得多。

3. 训练环境搭建与工程目录设计

3.1 环境准备

Yolov5环境配置是很多人第一道坎,尤其是PyTorch和CUDA版本对应关系搞不清楚,一装就是半天。

我用的组合是Python 3.8 + PyTorch 1.12 + CUDA 11.6,这个组合在Yolov5官方requirements.txt里运行得非常稳。创建环境用conda:

conda create -n yolov5 python=3.8 conda activate yolov5

然后从GitHub官方仓库拉取Yolov5代码:

git clone https://github.com/ultralytics/yolov5.git cd yolov5 pip install -r requirements.txt

如果显卡驱动已经装好,PyTorch用pip直接装即可。建议安装时留意一下PyTorch的版本号,别拿到CPU版本还浑然不知,训练起来能慢到怀疑人生。

我自己踩过的一个坑是:换了新显卡之后,旧环境里的PyTorch CUDA版本不匹配,训练时直接报找不到CUDA设备。后来统一用conda管理环境、把CUDA相关的变量写在环境激活脚本里,再没出过这种问题。

3.2 项目目录结构

Yolov5对数据集的目录结构有硬性要求,官方文档写得很清楚,但新手还是经常搞错。

datasets/ └── crack/ ├── images/ │ ├── train/ │ └── val/ └── labels/ ├── train/ └── val/

images和labels的train/val目录名必须完全一致,否则训练时找不到对应的标签文件报错。文件命名也保持一致,比如IMG_001.jpg对应IMG_001.txt

3.3 数据集配置文件

在Yolov5根目录下新建一个crack.yaml,内容如下:

# 数据集的根路径,相对于yolov5仓库执行命令的位置 path: ../datasets/crack train: images/train val: images/val # 类别信息 nc: 2 names: ['transverse_crack', 'longitudinal_crack']

一个很蠢但常见的报错是path路径写错。如果你把crack.yaml放在yolov5/目录下,path: ../datasets/crack意味着yolov5仓库外层的datasets目录。建议第一遍跑之前用绝对路径,比如path: /home/user/datasets/crack,排除路径问题之后再改成相对路径。

3.4 起步训练命令

环境搭好、目录结构确认无误之后,先跑一个最小的训练命令验证流程:

python train.py \ --data crack.yaml \ --cfg models/yolov5s.yaml \ --weights yolov5s.pt \ --img 640 \ --batch-size 16 \ --epochs 100

--cfg指定模型结构,yolov5s是small版本,显存够用、速度还行,适合第一轮实验;--weights yolov5s.pt是用COCO预训练权重做迁移学习初始化,千万别从头训练,裂缝特征虽然和COCO的日常物体不同,但底层纹理特征(边缘、角点、梯度)是通用的,迁移学习能让收敛速度快很多。

训练起来之后,日志里会打印每个epoch的box_loss、obj_loss、cls_loss、mAP等指标。第一步的目标不是追求最好指标,而是确认数据流、训练流程、验证流程全部正常,模型确实在学习。

4. 裂缝小目标场景的参数调优方法

4.1 分辨率与切图

如果你直接拿Yolov5默认的--img 640去训裂缝,大概率会得到一个漏检严重的模型。原因是裂缝在640分辨率下可能只有二三十个像素宽、几百个像素长,目标太小,特征图上的信息被压缩得很厉害。

最简单的优化是提高输入分辨率。我把--img从640提到1280之后,mAP@0.5直接涨了差不多6个点。但代价也很明显:显存占用翻倍,训练时间变长。

如果你的显卡只有6G或8G显存,1280分辨率下batch size就只能开到4左右,训练速度慢很多。这时候我更推荐"切图"方案:把大图(比如3000x4000的路面照片)按1280x1280的窗口切成有重叠的patch,分别推理,最后再把所有patch的检测结果合并到大图坐标上。这个方案在工程上有一个现成工具叫SAHI,专门为小目标检测设计,实测能把裂缝漏检率再降低不少。

4.2 超参数设置:别急着魔改

Yolov5官方有一堆超参数文件,hyp.scratch-low.yaml是低数据增强配置,hyp.scratch-high.yaml是高增强配置。我第一轮实验用的是hyp.scratch-low.yaml,只在COCO预训练权重基础上微调,默认学习率0.01就能收敛得很好。

如果你做裂缝检测感觉收敛慢,第一件事不是去调学习率,而是检查数据。漏标太多、标注框不贴目标、类别不平衡,这些数据问题会直接体现在loss曲线异常上。我见过太多人在参数里折腾半天,最后发现是标注文件没配对。

几个关键超参数的经验值:

参数推荐值说明
epochs100~150裂缝特征不复杂,更多轮次容易过拟合
batch-size16或尽量大裂缝背景占比极高,过大的batch意义不大
lr00.01使用官方默认即可,迁移学习微调够用
mosaic0.0~0.5见下文详细说明
hsv_h/hsv_s/hsv_v减小裂缝颜色基本是深灰,颜色抖动意义不大

4.3 数据增强的取舍:mosaic是个双刃剑

这是裂缝检测项目里我踩得最深、也最想提醒后来人的一个坑。

Yolov5默认开启mosaic增强,把四张图拼成一张再训练。这个策略在通用目标检测里效果很好,因为物体通常是紧凑的、独立的。但裂缝不一样——裂缝可能横贯整张图,长度远超一个拼图块。mosaic在裁剪拼贴时,极大概率会把一条完整的裂缝从中间截断,标签跟着被切成两半,甚至四张图的裂缝拼在一起变成一条奇怪的折线。

模型大量学习这种"半截裂缝"之后,表现就是:对整条完整的裂缝反而检测不出来了,因为训练时没见过"完整的长裂缝"长什么样。

我的解决办法是自定义一个hyp.custom.yaml,把mosaic关掉或降到0.5以下:

mosaic: 0.0 # 0.0表示关闭,0.5表示50%概率使用 mixup: 0.0 copy_paste: 0.0

同时把hsv_hhsv_s这些颜色增强参数下调,因为裂缝的颜色、饱和度变化很小,过强的颜色抖动反而会让模型学到错误的颜色关联。

这个改动之后,模型的召回率提升非常明显,尤其是长裂缝的检测。做裂缝检测的同学,强烈建议一上来就检查一下mosaic在你自己数据集上的表现,不要被默认参数牵着走。

4.4 锚框要不要自己算

Yolov5默认情况下训练开始前会跑一遍autoanchor,根据你的数据集标签自动计算新的anchor大小。这是一个非常好的功能,但对裂缝这种极端长宽比的目标,自动anchor的效果未必理想。

裂缝的边界框长宽比经常是1:10甚至1:30,而默认COCO预训练模型里的anchor是按通用物体尺寸设计的,长宽比最大也就1:3左右。autoanchor会在训练前计算并提示你"anchors mismatch",有时候它自己算出来的新anchor长宽比还是不够极端。

我当时的做法是:先跑一次python train.py,让它打印出autoanchor的结果,然后打开代码里utils/autoanchor.py生成的anchor,手动调整成更适合裂缝长条形状的值。例如把三组anchor调整为:

anchors: - [10, 6, 24, 8, 16, 16] # 小目标 - [32, 12, 48, 20, 64, 32] # 中等目标 - [96, 48, 192, 96, 384, 192] # 大目标

调整完之后训练,mAP又涨了一截。这块属于锦上添花,但确实是裂缝检测这种极端形状目标最容易见效的优化点。

5. 从评估指标到误检分析:判断模型能不能上线

5.1 先看哪些指标

训练结束后,runs/train/exp/目录下有一整套评估结果,包括PR曲线、混淆矩阵、F1曲线、标注错误的图片。我不建议直接看最后的mAP数字就完事,要有重点地看。

对裂缝检测来说,mAP@0.5是最主要的参考标准,mAP@0.5:0.95在小目标上通常很难看,不用太焦虑。更重要的是召回率。道路巡检业务里,漏检一条裂缝比误检一个非裂缝要严重得多——漏检意味着病害没有被发现,误检顶多让工人多跑一趟。

我当时给自己定的标准是:mAP@0.5做到0.78以上,召回率0.85左右,才敢进入实测阶段。

5.2 混淆矩阵怎么读

Yolov5训练完会生成confusion_matrix.png,这张图能直接告诉你模型在哪些类别之间容易混淆。我的项目里最常见的误检有三类:

第一类是路面伸缩缝。这是道路工程施工时人为留的缝,视觉上和裂缝实在太像,连人眼都要凑近了才能分辨。模型把它检测成裂缝几乎不可避免,我的办法是在后处理阶段加一个"已知伸缩缝GPS坐标"的过滤规则,检测框落在伸缩缝区域就过滤掉。

第二类是阴影和轮胎印。这个靠数据解决——多采集一些有阴影、有轮胎印的路面负样本,专门训练模型学会把它们和裂缝区分开。负样本在裂缝检测里几乎和正样本一样重要。

第三类是横裂和纵裂的分类混淆。尤其是斜向裂缝,介于横和纵之间,模型容易给出错误的类别。这种只能靠提高分辨率、让模型看清裂缝的走向来缓解。

5.3 视频流上的稳定性问题

单帧图片检测得好,不代表视频流里就稳定。我实测时发现一个大问题:同一段裂缝,这一帧有框,下一帧没框,再下一帧又有框了。这种时断时续的输出在业务系统里没法用,不可能每一帧都触发一次报警。

解决办法是在检测后加一个简单的时序过滤模块:连续三帧以上都检测到同一个位置的裂缝,才触发上报;框的位置按跟踪ID聚合,避免重复上报。这个模块用OpenCV的跟踪器或者简单的IoU匹配就能实现,不需要上复杂的多目标跟踪算法。

我另外测了夜晚、雨天、逆光三个场景,发现逆光下的漏检率最高。后续补拍了一些逆光数据重新训练,情况改善了很多。数据集对光照的覆盖度,直接影响模型上线后的表现,这一条经验在任何视觉项目里都适用。

6. 把模型塞进Jetson Nano:TensorRT导出与边缘部署

6.1 为什么选Jetson Nano做边缘端

巡检场景通常是要把摄像头装到小车或者路侧设备上,不可能扛着训练用的GPU服务器到处跑。而Jetson Nano这块板子功耗只有5~10W,CPU+GPU整合在一块小板上,完事可以跑深度学习推理,生态也成熟,是这个项目当时最合适的选择之一。

选型时我对比过树莓派,但树莓派没有NVIDIA CUDA生态,跑Yolov5只能靠CPU硬扛,延迟完全不可接受。Jetson Nano虽然算力也不算强,但至少能跑TensorRT加速,实时性勉强够用。

6.2 Jetson上跑Yolov5的环境细节

Jetson Nano用的是ARM架构,和普通PC的x86环境差异很大,不能直接照搬PC上的环境配置。我的步骤如下:

  1. 烧录JetPack系统镜像,注意选对版本,老版本JetPack 4.6 自带的是PyTorch 1.10左右。
  2. 用miniforge(conda的ARM版)创建Python3.8环境。
  3. 从NVIDIA官方渠道安装PyTorch和torchvision的ARM版wheel包。
  4. 把PC上训练好的weights拷过来。

一个非常重要的细节是swap空间。Jetson Nano内存只有4G,跑PyTorch稍微大点的模型很容易OOM。我设置了一个4G的swapfile,训练不行,但推理有很大改善。

6.3 TensorRT导出与FP16量化

在PC上直接导出engine文件,再拷贝到Jetson上,我试过不一定能跑通,因为TensorRT和CUDA版本可能不一致。最稳妥的方式是在Jetson上现场导出:

python export.py \ --weights best.pt \ --include engine \ --device 0 \ --half

TensorRT的原理是先把PyTorch的动态图模型转换成静态计算图,然后做层融合、精度校准、kernel自动选择。对于裂缝检测这种灰度目标,FP16精度几乎没有损失,但推理速度能提升很多。

导出的时候如果报维度相关的错误,可以手动指定固定输入尺寸:

python export.py --weights best.pt --include engine --device 0 --half --img 640

6.4 实测表现

我最终在Jetson Nano上跑Yolov5s + FP16 + TensorRT,输入640x640,实测推理速度大约8~12 FPS。看起来不高,但对道路巡检这种低速移动场景已经够了,拍摄一张、停顿一下、再检测一帧,完全可以实现实时巡检。

显存占用很低,主要的瓶颈在CPU端后处理和图像读取上。如果你觉得FPS不够,有两个优化方向:一是改用更小的Yolov5n模型,速度几乎翻倍,精度也就低一两个点;二是把图像读取和推理放到两个线程并行处理,用队列缓存帧,FPS提升明显。

部署时不建议直接套用官方detect.py脚本,那个脚本加载了很多调试功能,正式环境内存开销大。我最终自己写了一个TensorRT推理封装,加载engine、预处理、推理、后处理NMS、输出坐标,全部在一个Python类里完成,简洁很多,也好维护。

7. 裂缝检测做深之后的几个方向与个人建议

7.1 用分割代替检测

边界框有一个天然劣势:它给不出裂缝的精确轮廓和宽度。如果你的业务需要计算裂缝的宽度、判断裂缝的发展趋势,那检测模型就帮不上忙了。这时候可以考虑Yolov5-seg或者Yolov8-seg,输出的是像素级的掩膜,能精确到裂缝的每一个像素。

分割对标注的要求高很多,可能要手工逐像素描边,工作量成倍增长。不过现在有一些半自动标注工具可以帮忙:先用检测模型框出裂缝,再用分割模型在框内自动生成掩膜,人工只做微调,效率能提上来。

7.2 网络结构的轻量改造

裂缝是低层纹理特征主导的目标,不需要太强的高层语义信息。如果你在实验中发现Yolov5s的模型容量溢出了,或者说小裂缝漏检严重,可以考虑在backbone里加一个轻量的注意力模块。我做过实验,在C3模块后面接一个简单的SE注意力,mAP能再涨1~2个点。但这类改动属于锦上添花,先把数据和训练调稳,再来动结构才不至于浪费调试时间。

7.3 落地时的三个工程建议

第一,摄像头角度固定后,把画面里跟路面无关的区域裁掉再推理。只保留路面区域,等于变相提高了模型对有效区域的注意力,漏检率和推理时间都能改善。

第二,上线后要坚持收集bad case。把每天检测漏掉的、误检的图定期汇总,回标后增量训练。视觉模型的提升就是个持续迭代的过程,没有一劳永逸的模型。

第三,第一次跑这个项目的同学,我建议的路线是:先用公开数据集把完整流程跑通,再换自己的数据,最后才去优化指标。很多人一上来就拿自己的数据折腾,环境、代码、参数同时出问题,根本定位不了原因。流程先通,优化是后面的事。

最后说一点个人体会。做完整个道路裂缝检测项目,最大的感受是:Yolov5本身不难,难的是数据一致性。模型从能跑到能用的关键,从来不是某条命令或者某个超参数,而是你愿不愿意花几天时间把数据标准和标注规范想清楚。另一个经验是测试模型不要只看指标,拿一段真实的道路视频让它跑,视频里模型的犹豫和失误比任何指标都更能告诉你下一步该优化什么。希望这篇记录能帮到正在做类似项目的你。

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

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

立即咨询