道路裂缝检测这个话题,我在工程实践里跟进了挺长时间。上个月刚落地一个基于YOLOv8的三类裂缝检测系统,可以把道路图像里的龟裂、纵向裂缝、横向裂缝自动识别并框出来,训练、推理、界面都做成了中英文双版本,源码和演示流程完整可复现。这篇文章就把这个项目从数据准备、模型训练到双语推理系统集成的全过程拆开讲一遍,源码思路和踩坑记录都会放出来,适合正在做道路病害识别、土木视觉检测,或者想拿YOLOv8练手的目标检测方向同学参考。
这个项目本质上解决的是道路巡检里的一个老问题:裂缝类型判断靠人眼、靠经验、靠纸质记录,效率低且标准不统一。而裂缝分型直接决定养护方案——龟裂往往意味着基层强度不足,要挖补罩面;纵向裂缝多与路基沉降有关,得做注浆填充;横向裂缝通常是温度收缩造成的,灌缝就能处理。养护决策如果不把裂缝类型分清楚,钱花了也修不到点子上。所以做一套能自动分型的检测系统,不是炫技,是刚需。
1. 方案选型:从问题定义到YOLOv8
1.1 三类裂缝的形态特征与成因差异
先把三类裂缝的物理特征捋清楚,因为标注、训练、后处理所有环节都围绕这些特征展开。
龟裂(Alligator Cracking)最常见,形态上是一串相互连接的块状裂纹,像鳄鱼皮一样聚成一片,通常出现在车轮轮流带区域。它的形成机制是路面基层承载能力不足,面层在反复荷载下疲劳开裂,裂纹先纵向发展再横向连接成网。在图像里,龟裂的明显特点是密集成片、多向交叉、轮廓不规则,框选时往往需要拉一个大框覆盖整片区域。
纵向裂缝(Longitudinal Cracking)平行于道路中心线,有的出现在轮迹带,有的沿着车道线边缘走。成因主要是施工接缝处理不当、路基不均匀沉降、以及冻胀作用。图像形态上和道路走向一致,单条或平行数条,有时会带一点弯曲但整体方向明确,框选时通常是一个瘦长矩形。
横向裂缝(Transverse Cracking)垂直或接近垂直道路中心线,基本贯穿路面宽度,轻则细线一条,重则伴随啃边和支缝。气温骤降时面层收缩、拉应力超过抗拉强度就开裂,属于典型温缩裂缝。图像上特征也非常清晰:几乎水平的长条,横向贯穿,和纵向裂缝正好正交。
可以拿一张实际病害图像来看,三种裂缝同时出现在一个画面里的情况很常见,这时候目标检测模型的优势就体现出来了——单个模型同时回归边界框和类别,而不是像传统图像分类那样把整张图分成一类,也不像语义分割那样逐像素标注。检测框的粒度在这个场景下刚好合适:养护人员需要知道什么位置、什么类型、多大范围,框加类别就已经能支撑决策。
1.2 为什么选YOLOv8而不是传统CV方法
很多人会问,裂缝检测用形态学操作、边缘提取这类传统方法不行吗。我的回答是:在实验室单张图上可以,拿到现场就崩。道路图像的光照变化、阴影、树影遮挡、轮胎印、沥青纹理噪声,都会让固定阈值的边缘提取方法瞬间失灵。传统方法需要针对每一条新道路手动调参,完全不具备泛化能力。而深度目标检测模型是用数据驱动的方式自动学习裂缝的纹理与形态特征,对光照和噪声的鲁棒性高出一个量级。
那在深度学习方案里,为什么最终选了YOLOv8而不是Faster R-CNN、SSD或者YOLOv5?主要考虑三点:
第一是检测速度。道路检测往往要处理无人机航拍拼接图或者车载相机连续帧,一张图只有几十毫秒的推理预算。YOLOv8在同等精度下速度远快于两阶段方法,这是硬指标。
第二是工程生态。YOLOv8的官方库ultralytics把训练、验证、导出、部署整套流程封装得很完整,自定义数据集只需要准备标注文件,不用自己写数据加载器、损失函数和NMS。开发周期可以压缩到以天计。
第三是原生支持灵活调整。YOLOv8有n/s/m/l/x五个规格,我实际测试下来,在中等算力条件下用m规格就已经能拿到不错的精度,想上生产环境可以无缝换l或者x再训一版。同一套代码和标注,换个模型名就能重新训练,这个灵活性对项目迭代非常友好。
1.3 关于中英文双版的需求思考
这个项目里加了中英文双版界面,不是额外添头,是实际使用场景逼出来的。国内养护单位汇报时用中文界面,而工程验收、技术推广、对外交流时往往又要切英文。设计上我采取的做法是一个语言切换参数,配置文件里写一个UI_LANGUAGE字段,界面文字和检测结果标签自动切换,不需要维护两套代码。这样既保证统一逻辑,又能适配不同汇报需求。
2. 环境搭建与核心依赖配置
2.1 硬件环境与软件版本清单
先说结论:这个项目对硬件要求不算苛刻,我用的一套配置是CPU版本i7、GPU版本RTX 3060 12G,训练m规格模型完全够用。如果手头只有8G显存,建议直接选择n或s规格,或者把batch size调小一点。详细环境清单如下表:
| 项 | 推荐配置 | 说明 |
|---|---|---|
| 操作系统 | Windows 10/11 或 Ubuntu 20.04+ | 推荐Linux,训练更稳定 |
| Python版本 | 3.8 ~ 3.10 | 3.9兼容性最佳 |
| GPU | NVIDIA显卡,显存≥8G | 有CUDA加速训练会快很多 |
| CUDA | 11.8或12.1 | 与PyTorch版本对应 |
| 深度学习框架 | PyTorch 2.x | YOLOv8依赖 |
| 检测库 | ultralytics | 核心依赖 |
| 标注工具 | LabelImg 或 X-AnyLabeling | 生成YOLO格式标注 |
版本对齐是一个很容易踩坑的环节。ultralytics官方要求PyTorch版本不低于1.8,但实际测试下来,PyTorch 2.0以上配合cu118跑训练最稳定。如果你机器上有旧版PyTorch,建议先卸载干净再装新版,避免出现各种莫名其妙的不兼容报错。
2.2 安装步骤实操记录
创建虚拟环境这一步我强烈建议不要省,特别是手头同时维护多个项目的同学。具体命令如下:
conda create -n crack python=3.9 conda activate crack pip install torch torchvision --index-url https://download.pytorch.org/whl/cu118 pip install ultralytics装完干净环境后,先用官方预训练权重做一次快速验证环境是否OK:
from ultralytics import YOLO model = YOLO("yolov8n.pt") results = model("https://ultralytics.com/images/bus.jpg")能正常出框,说明CUDA环境和依赖库没问题。这一步非常重要,很多同学装完环境直接跑自定义数据集,报错了还以为是数据问题,调试了半天才发现是环境没装对。
注意:CUDA、PyTorch、GPU驱动三者版本要匹配。有个快速验证方式:进入Python解释器执行
import torch; print(torch.cuda.is_available()),输出True说明GPU可用,之后训练参数里设置device=0就能用上显卡。
3. 数据集构建:源头决定精度的上限
3.1 数据采集与筛选策略
目标检测模型学习的是数据分布,所以训练数据的质量和覆盖面直接决定模型实际效果。我在这个项目里采集数据主要来自三个渠道:一是公开道路病害数据集,这类数据标注相对规范,但类别分布往往不均衡;二是自采道路图片,用手机、车载相机或者直接在公开地图街景截取画面;三是从道路养护单位获取的历史病害照片,这部分数据虽然角度、光照复杂,但真实度高、工程价值大。
采集完图像后做一轮人工筛选,原则如下:
- 保留包含目标裂缝的图像,丢弃纯背景图或目标占比过小的图像;
- 避免图像之间高度相似,相同路段连续帧只保留少数几张,否则训练集会有大量冗余,导致过拟合;
- 保留光照变化大、有阴影遮挡的困难样本,这些样本对提升模型鲁棒性很重要;
- 图像分辨率最好不低于640x640,否则训练时缩放到640会丢失纹理细节。
3.2 标注规范与实操要点
标注是整套流程里最耗时、最重要的一环。YOLOv8使用的标注格式是每个目标一行:class_id x_center y_center width height,坐标全部归一化到0~1之间。以图像左上角为原点,x_center表示目标框中心点的横坐标占比,width表示框宽度占图像宽度的比例,以此类推。
标注工具我推荐用X-AnyLabeling,它可以直接导出YOLO格式,并且模型辅助标注功能可以先用一个粗糙的预训练模型自动画框,人工微调后保存,效率能提升不少。如果只是小批量标注,LabelImg也够用。
标注规范这块,我在项目里归纳了几条硬性约定:
| 裂缝类型 | 框定原则 | 特殊处理 |
|---|---|---|
| 龟裂 | 用一个大框覆盖整片龟裂区域,不要把每一条裂纹单独框 | 裂纹较分散但同属一片时,适当外扩画框 |
| 纵向裂缝 | 框沿裂缝走向拉长,紧密贴合裂缝左右边缘 | 多条平行纵向裂缝距离很近时,尽量单独框 |
| 横向裂缝 | 框沿横向走向拉长,包含裂缝完整长度 | 裂缝延伸到图像边缘时,框到边界即可 |
| 混合区域 | 同一区域同时存在两种裂缝时,分别按各自规则框选 | 不建议一个框标两个类别,前后景交叠由NMS处理 |
标注时最忌“凭感觉画框”。我见过一些标注,同一个龟裂区域有人画正方形,有人画长条矩形,还有人干脆把整张图框进去,这样模型学到的形状特征会非常混乱。原则是:框要尽量紧贴目标边缘,背景占比控制在合理范围,同时保证同类目标框的宽高比分布相对集中。
3.3 数据划分与样本均衡
数据集划分按 8:1:1 的比例拆成训练、验证、测试三个集合。拆分时注意按路段或拍摄批次分组,不要把同一场景的连续帧同时分进训练集和验证集,否则验证集分数会虚高,模型实际泛化能力远不如预期。
类别不均衡是这个数据集的天然问题。我收集到的数据里,纵向裂缝和横向裂缝样本相对充足,龟裂因为形成条件特殊,图像量明显偏少。处理方式有两个:一是对龟裂样本做离线增强,旋转、翻转、亮度抖动各扩几份;二是在训练参数里设置类别权重,让损失函数对样本少的类别给更大的惩罚。实际测试下来,联合使用这两种方式,龟裂的mAP能提升4到8个百分点。
4. 模型训练全流程与关键参数
4.1 训练配置文件与YAML定义
YOLOv8训练自定义数据集,需要先写一个数据集描述文件,指向图像目录和标注目录,并列出类别名称。我的数据集YAML如下:
path: ./datasets/crack_data train: images/train val: images/val test: images/test nc: 3 names: 0: alligator_crack 1: longitudinal_crack 2: transverse_crack类别名称我直接用了英文,方便后续切换界面语言时不用改模型。模型在推理时输出的是类别索引和置信度,界面层再做一个索引到中英文标签的映射即可,这个设计在后面做双语版本时会体现优势。
4.2 训练超参数选择与计算逻辑
我用YOLOv8m做基准训练,关键超参数如下:
yolo detect train \ --model yolov8m.pt \ --data datasets/crack_data/crack.yaml \ --epochs 150 \ --batch 16 \ --imgsz 640 \ --optimizer AdamW \ --lr0 0.001 \ --lrf 0.01 \ --device 0 \ --workers 8 \ --patience 20参数选择背后有两层考虑。
先说batch size和显存的关系。12G显存跑m模型、640分辨率、batch=16是稳的,如果显存只有8G,把batch降到8或者分辨率降到512,否则很容易OOM。batch size还影响收敛稳定性:batch太小噪声大,batch太大收敛慢且容易陷入尖锐极小值。16在中型模型上是一个比较均衡的取值。
再说学习率。lr0=0.001配合AdamW是从零微调时的常用起点,lrf=0.01表示学习率按余弦退火降到初始值的1/100。如果想加速收敛,也可以直接用预训练权重yolov8m.pt做迁移学习,这时候学习率可以适当降低到0.0005,避免破坏已有特征提取器的低级视觉特征。
patience=20是早停机制的耐心值,连续20轮验证集mAP不提升就自动终止训练。这个参数很实用,我在训练过程中发现,150轮的设定很多时候实际跑到90~110轮就停住了,早停能省下大量无效训练时间。
4.3 训练过程监控与指标解读
训练启动后,ultralytics会实时打印每个epoch的loss以及各项指标。重点观察两个指标:一个是box_loss和cls_loss,分别代表边界框回归损失和分类损失;另一个是mAP50和mAP50-95,很多同学看训练集loss降得很快就很兴奋,实际上验证集mAP才是评价模型优劣的硬指标。
我在这个项目里训练过程的mAP曲线走势大致是:前20轮mAP50快速爬升到0.55左右,30到80轮进入平台期,期间会有小幅波动,90轮以后达到峰值。最终在测试集上的结果如下表:
| 类别 | Precision | Recall | mAP50 |
|---|---|---|---|
| 龟裂 | 0.817 | 0.723 | 0.801 |
| 纵向裂缝 | 0.876 | 0.849 | 0.893 |
| 横向裂缝 | 0.902 | 0.871 | 0.912 |
| 全部类别平均 | 0.865 | 0.814 | 0.869 |
从结果能看出来,纵向裂缝和横向裂缝因为形状规则、特征清晰,精度更高;龟裂形态复杂、边界模糊,mAP会低一截。这个结果已经可以投入实际使用:在真实巡检图片上,误检主要出现在阴影边缘、水渍污染和沥青修补痕迹上,这是目标检测模型的通病,可以通过引入负样本训练进一步提升。
5. 中英文双版推理系统实现
5.1 推理脚本主体设计
训练完成后就是落地环节。我设计的推理系统主体逻辑很简单:加载模型 -> 读取图片或视频 -> 逐帧推理 -> 绘制检测框 -> 叠加标签和置信度 -> 输出结果图。核心代码如下:
import cv2 from ultralytics import YOLO class CrackDetector: def __init__(self, model_path="best.pt", language="zh"): self.model = YOLO(model_path) self.language = language self.labels = { "en": {0: "Alligator Crack", 1: "Longitudinal Crack", 2: "Transverse Crack"}, "zh": {0: "龟裂", 1: "纵向裂缝", 2: "横向裂缝"}, } def set_language(self, lang): if lang in self.labels: self.language = lang def detect_image(self, image_path, conf_thres=0.5): img = cv2.imread(image_path) results = self.model(img, conf=conf_thres, verbose=False) names = self.labels[self.language] for r in results: boxes = r.boxes.xyxy.cpu().numpy() confs = r.boxes.conf.cpu().numpy() clss = r.boxes.cls.cpu().numpy().astype(int) for box, conf, cls_id in zip(boxes, confs, clss): x1, y1, x2, y2 = map(int, box) cv2.rectangle(img, (x1, y1), (x2, y2), (0, 255, 0), 2) text = f"{names[cls_id]} {conf:.2f}" label_bg = (0, 175, 0) (tw, th), _ = cv2.getTextSize(text, cv2.FONT_HERSHEY_SIMPLEX, 0.6, 1) cv2.rectangle(img, (x1, y1 - th - 6), (x1 + tw + 6, y1), label_bg, -1) cv2.putText(img, text, (x1 + 3, y1 - 3), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (255, 255, 255), 1) return img代码里的关键点是clss需要转成int,因为OpenCV的putText不支持numpy int类型作为索引。另一个细节是画文字背景框时先量取文字宽度,再绘制填充矩形,避免中文或英文标签长度不一样导致文字背景错位。
5.2 UI层的中英文切换设计
为了让系统可以直接给不同使用者展示,我做了一个简单的图形界面,基于Tkinter实现,语言切换用左上角下拉菜单或者快捷键实现。切换时只需要调用set_language(),界面上所有按钮文字、检测框上的标签、统计面板上的字段会全部更新,底层推理逻辑完全不动。
界面布局上,左侧放图片预览区域,右侧放检测结果统计:各类裂缝数量、平均置信度、单张处理耗时。底部是一排操作按钮——打开图片、打开视频、摄像头检测、语言切换。检测结果可以一键导出,生成带标注的JPG图片,方便直接贴到巡检报告里。
英文版界面对外展示时,单位、标题、按钮全部使用英文标准术语,避免出现生硬的直译。这一点在验收汇报时尤其重要,审阅方拿到英文界面,能直接理解系统的功能边界和输出格式。
6. 踩坑实录与性能优化
6.1 训练过程中的典型问题排查
做这个项目过程中踩了不少坑,挑几个高频问题列出来,给后来者省点时间。
第一个是CUDA out of memory。这个报错最常见,原因无非是batch size、分辨率、模型规格三者搭配不当。我的排查顺序是:先降batch到原来一半,再考虑把imgsz从640降到512,最后考虑换小模型。需要注意的是,降分辨率会直接影响小目标检测能力,而裂缝本身就属于中细长目标,分辨率降太多会导致纵向裂缝漏检,所以优先降batch而不是降分辨率。
第二个是验证集mAP始终在0.3左右徘徊,模型看起来学习了但效果不好。这种情况十有八九是标注格式错了——坐标没有归一化、类别索引从1开始而不是从0、图像路径没有对应上,都会导致训练过程混乱。我的排查方法是直接读取一张训练样本的标注txt并画出来,目测框的位置是否正确,这一步能把绝大多数数据问题定位出来。
第三个是训练到后期loss出现震荡不收敛。这通常跟学习率设置有关。lr0设置太高会在后期出现loss反复跳变,我的处理是适当降低初始学习率,或者把weight_decay从默认值稍微调大一点,增强正则化效果。
6.2 实测有效的精度提升技巧
在固定的数据集和算力条件下,有几个技巧能实打实提升精度,按效果排序如下:
一是使用预训练权重做迁移学习,不要从零训练。YOLOv8的COCO预训练权重已经在海量图像上学到了通用纹理和结构特征,道路裂缝虽然是新类别,但底层特征仍然可以复用。实测从yolov8m.pt继续训练比yolov8m.yaml从头训,mAP高出10个点以上,而且收敛更快。
二是合理使用测试时增强。推理阶段可以开启YOLOv8自带的TTA功能,把图像做多尺度缩放和翻转后综合预测,能提升2个点左右,但推理时间会明显增加。离线分析场景推荐开启,实时检测场景不建议。
三是增加高分辨率训练轮数。可以先用640分辨率训练,收敛后改用960分辨率微调20轮,细长裂缝的检测效果会有明显提升。这个做法类似fine-tune的第二阶段,效果非常显著。
四是负样本策略。在数据集中加入一些没有裂缝的普通路面图像,类别为空即可。这能显著降低误检率,尤其是阴影、路标、水渍这些容易和裂缝混淆的背景区域。
6.3 部署到实际场景还有哪些事
模型在测试集上效果不错,部署到真实巡检场景前还要做几件事:模型导出为ONNX或TensorRT格式加速推理;对连续帧做时间维度平滑,避免检测框闪烁;记录每张推理图的GPS信息和拍摄时间,方便后续和道路养护台账关联。这些工作在目前项目里已经完成了盘面和流程验证,下一步可以按实际巡检场景继续扩展。
7. 最后留下一份可参考的总结
我个人在实际操作中最深的一点体会是:这个项目技术难度其实不算高,YOLOv8把模型的训练和使用门槛降得很低,真正的壁垒在于数据——裂缝数据不是随便爬点图就能用的,类别标注标准、困难样本覆盖、数据平衡策略这些做不好,模型精度再调也上不去。另外双语界面这个看似简单的需求,如果一开始就在代码里做好标签映射和语言抽象,后面维护成本会低很多,不要等到界面写完了再硬改。
如果后续要在这个系统上继续扩展,我建议优先考虑两个方向:一是把检测结果和道路桩号、GIS坐标关联起来,自动生成病害分布图;二是加入裂缝宽度估计功能,单纯靠检测框拿不到宽度信息,需要引入分割模型或者边缘测量。前者是让系统真正融进养护工作流,后者是提升检测结果的工程价值。
这个项目从数据集整理到双语系统落地,整体花了三周左右,其中数据标注占了近一半时间。如果你准备复现,我的建议是先把数据集的类别规范定死,再开始标注,中间不要随意改标注规则,否则返工成本很高。源码结构和演示方式我已经整理成一套可复用的模板,按自己的数据替换路径和类别名就能跑通,祝你也一次训出理想的结果。