☰
基于YOLOv8的道路裂缝检测:龟裂、纵缝与横缝识别系统
2026/10/14 6:29:33 网站建设 项目流程

如果你在公路养护、市政巡检或者智慧交通相关的岗位待过一段时间,大概率见过这种场景:巡检员顶着大太阳沿着路面走,看到裂缝蹲下来拿粉笔圈个号,再用手机拍一张,回来之后把照片导进电脑,对着原图手工判断裂缝类型、记录位置和走向。遇上几公里的路,一整个下午就耗进去了。图像数据一旦积累起来,全靠人眼判读,又累又容易漏,而且不同人判读的标准还不统一。

我这次做的就是一个基于YOLOv8的3类道路裂缝检测系统,专门识别龟裂、纵向裂缝、横向裂缝三类主要病害,支持中英文双版输出,带完整源码和效果演示。跑通之后,巡检照片可以批量推理,一分钟处理几百张图不是问题,每一张图能自动标出裂缝位置、类型和置信度,还能导出结构化报告。文章会从病害判读标准、数据标注、模型训练到系统搭建、踩坑修复完整走一遍,适合正在做道路病害识别、或者第一次用目标检测处理细长小目标的开发者参考。

1. 从巡检现场出发:3类裂缝检测要解决的到底是什么

1.1 为什么偏偏是龟裂、纵向裂缝、横向裂缝

道路裂缝的种类很多,但日常巡检里最需要优先记录的就是这三类。龟裂,也叫网状裂缝,是典型的面层疲劳信号,出现龟裂往往意味着基层承载力已经下降,病害正在往下发展,需要尽快安排处治;纵向裂缝大多和施工接缝质量、路基不均匀沉降有关,反映的是条带状薄弱位置;横向裂缝则主要来自温度收缩或下层裂缝反射,体现的是路面整体抗变形能力的问题。

这三类裂缝放在一起检测,等于给巡检镜头装了一个自动初筛器。以前拍回来的照片要先人工分拣,现在算法先分好类、框好位置,人工只需要复核和决策。这个流程上的改变,才是我做这个系统最核心的动机。项目里的目标是"检测",也就是输出每个裂缝实例的类别和边界框,不需要逐像素勾勒裂缝轮廓。

1.2 用目标检测而不是像素级分割,我是怎么权衡的

很多人看到"裂缝"两个字,第一反应是用语义分割,毕竟裂缝是线状目标,分割看起来更精确。我也做过对比:分割模型的标注成本高出一大截,要沿着裂缝边缘一点一点打多边形,而目标检测只要画一个外接矩形。关键是巡检日常关心的不是裂缝宽度的像素级测量,而是"哪一段路出现了哪种病、范围多大、数量多少"。边界框完全承载得了这个信息量。

另外从部署角度看,YOLOv8的分割版本权重更大、推理更慢,对边缘设备不友好。我的结论是:如果后续业务确实需要统计裂缝宽度,再在检测基础上接一个分割子模块,而不是一上来就用分割模型扛全部任务。检测先解决"分类和定位"这个主要矛盾,性价比最高。

1.3 YOLOv8在这个场景里的三个适配点

选YOLOv8而不是其他框架,我考虑了三件事。第一是速度,道路巡检经常要处理视频流或者无人机连拍,单帧推理必须足够快;第二是anchor-free设计加上多尺度特征金字塔,P3、P4、P5三个尺度天然覆盖不同大小的裂缝目标,对细长小目标比较友好;第三是生态,训练、验证、导出ONNX、部署推理一条龙都集成在一个工具包里,不用自己拼装一堆脚本。

还有一个现实原因:YOLOv8的预训练权重在COCO这类通用数据集上效果不错,可以用迁移学习快速起步,裂缝数据量不够大时,收敛速度和最终精度都明显优于从零训练。实测下来,我在自己这批数据上用预训练权重做微调,比随机初始化从头训快了大几倍。

2. 三类裂缝的判读标准:模型学的不是标签名称,而是形态特征

2.1 龟裂:多边形网状交织,别当成多条独立裂缝

龟裂最直观的视觉特征是"成片、交织、像鳄鱼皮或者蛛网"。一片区域内有多条裂缝互相交叉,切出大小不等的小多边形网格,单个网格的边长通常在几十厘米以内。严重的情况下,网格边缘还会出现碎块、坑槽甚至掉粒。这类裂缝常见于车轮轨迹带,是反复荷载疲劳的结果。

标注龟裂时,正确做法是把整片网状区域当作一个实例,用一个大框把主要聚集区域包住,不要拆成一条一条直线去标注。拆开标注会让模型学到完全错误的目标定义——它会把"龟裂区域里的单条裂缝"当成一个独立对象,而不是把"成片网纹"当成整体。我在第一版标注里就犯过这个错误,后面重做标注才改过来。

2.2 纵向裂缝与横向裂缝:走向才是核心判别依据

纵向裂缝和横向裂缝在形态上其实都很简单,基本上就是一条线,区别全在走向。判读时以道路中心线或者说行车方向作为参照:走向与道路方向近似平行,是纵向裂缝;走向近似垂直,是横向裂缝。纵向裂缝常见于行车道边缘、施工接缝处,或者有路基不均匀沉降的路段;横向裂缝常常横跨整个车道,而且间隔有一定规律,一段路上可能出现多条。

实际操作中不可能每条裂缝都绝对平行或者绝对垂直,所以我给标注环节定的规则是:裂缝主轴与道路方向夹角小于30度,归为纵向;大于60度,归为横向;30到60度之间的,结合裂缝的连续性和延伸趋势人工判断,并且尽量全程保持一致。定一个明确的夹角规则,比让标注员凭感觉靠谱得多。

2.3 模糊样本的处理规则:交叉裂缝、块状裂缝怎么标

路面上的裂缝不会乖乖按教材长,交叉、组合、模糊样本不可避免。我遇到过几种情况,这里直接给处理规则。

L型和T型交叉的裂缝,本质是两条不同走向的裂缝交汇,按两条独立实例分别标注,各按各自走向归类。成片网格但网格很大的块状裂缝,严格说和龟裂成因不同,但项目只有三类,如果网格密集、多边形边长偏小,归入龟裂;如果网格边长明显偏大、主要沿纵横两个方向展开,就把其中最长、最清晰的那条主裂缝挑出来单独标注,按走向归入纵向或横向。阴影和水渍也要提防,很容易被误认为裂缝,标注时要求框内主要区域确实存在连续裂缝纹理,而不是只有一条颜色差异。

还有一点特别重要:标注规则必须写下来,让所有参与标注的人照着执行。模型学的不是标签名称本身,而是你给的框和类别对应的视觉特征。标注标准不一致,模型学到的就是一段混乱的特征分布,后面再怎么调参都救不回来。

3. 数据集工程:从原始照片到YOLO格式的完整链路

3.1 数据来源、数量与标注工具选择

我这里按一个中等规模项目来举例:总共收集了超过3000张路面照片,标注出约9000个裂缝实例。数据来源包括公开的路面图像数据,以及用手机、工业相机自采的现场照片,覆盖不同地区、不同光线和不同路面老化程度。三类裂缝的样本量尽量拉均衡,尤其要防止龟裂样本爆多、横向裂缝样本寥寥无几。

标注工具我用的是一般的矩形框标注工具,支持直接导出YOLO格式的txt文件。标注时有三条注意事项:框要紧贴裂缝外接矩形,不要留大块空白;长而弯曲的裂缝,不要硬塞进一个又大又斜的框,可以切成2到4段分别标注,后续统计报告里再按图片合并;同一张图里多段裂缝就多个框,每个框单独给类别。

3.2 YOLO标签格式与坐标换算

YOLO格式的每个标签文件对应一张图片,文件内容是一行一个目标:

class_id x_center y_center width height

所有坐标都是归一化到0到1之间的小数,分别代表边界框中心点x、中心点y、框宽、框高,除以图片原始宽高得到。举个例子:

0 0.5123 0.4532 0.2312 0.1765

这一行表示类别id为0的龟裂目标,中心点在图片约51.2%横向、45.3%纵向的位置,框约占图片宽度23.1%、高度17.7%。如果从COCO格式或者其他标注格式转换,脚本里记得用像素坐标除以宽高,同时把越界的数值clip到0到1之间,否则训练时会出警告甚至异常。

类别id的顺序要和data.yaml里的names列表严格对应。我建议英文名作为内部统一命名,因为预训练模型默认的类名空间是英文,后面做中英文双版映射的时候也最不容易乱。

3.3 数据增强:裂缝样本普遍偏少的补救办法

裂缝病害本身是小概率事件,三类样本天然不均衡,数据增强是必选项。ultralytics库默认开启的增强已经比较强,包括Mosaic拼图、水平翻转、HSV色域扰动、缩放和平移。这里面有两个针对本任务的细节。

第一个是旋转增强,ultralytics默认不开启任意角度旋转,这个务必保持默认。因为任意角度旋转会把纵向裂缝旋成横向裂缝,类别语义直接被破坏。第二个是Mosaic,它把四张图拼成一张训练,对丰富背景很有效,但代价是样本之间会相互遮挡、裂缝被截断,龟裂这种成片目标的边缘会变得模糊。我实践下来,把Mosaic比例降到0.5左右,比全开更稳,尤其在后半段训练时可以减少误检。

另外可以手动加强亮度、对比度的抖动范围,模拟树荫、阴影、逆光等真实场景。裂缝检测最怕的就是光线一变、模型的recall就掉,增强阶段多折腾阴影场景,等于提前给模型打预防针。

3.4 数据集划分的隐藏坑:别把同一路段的相邻帧同时放进train和val

这个坑很阴,我一开始就踩了。路面巡检照片经常是行车记录仪或者无人机连拍,同一段路上连续几十帧高度相似。如果按图片文件随机划分训练集和验证集,相邻帧很可能一张进了train、一张进了val,模型等于提前看见了"答案",验证集精度虚高得离谱。

正确做法是按拍摄批次或者路段分组划分。也就是说,同一批次、同一路段的照片必须整体落在训练集、验证集或测试集的某一个里面,不能拆散。我改成按批次划分之后,验证集的mAP比随机划分时低了好几个点,但那才是真实水平。别心疼这几个点,真实水平比漂亮数字重要。

4. 训练实录:模型选型、超参数配置与曲线解读

4.1 硬件环境与依赖安装

训练YOLOv8优先建议有NVIDIA独立显卡,有12GB左右显存就能舒服地跑yolov8s和中等batch。如果本机没有GPU,租一个云GPU平台按时计费也够用,两天之内能把一个项目跑完。

依赖安装很简单,核心就两个:

pip install ultralytics

再根据你的CUDA版本安装对应版本的PyTorch。装完先验证一下GPU是否可用:

import torch print(torch.cuda.is_available())

如果输出False,多半是装成了CPU版PyTorch,或者CUDA和PyTorch版本不匹配,这个问题我后面专门讲。

4.2 data.yaml与标注目录结构

ultralytics通过一个yaml文件描述数据集,训练前必须先写好。我的目录结构大概是这样的:

crack-dataset/ ├── images/ │ ├── train/ │ └── val/ └── labels/ ├── train/ └── val/

对应的data.yaml:

path: /data/crack-dataset train: images/train val: images/val nc: 3 names: 0: alligator_crack 1: longitudinal_crack 2: transverse_crack

path是数据集根目录的绝对路径,train和val分别是相对根目录的图片文件夹路径。labels文件夹的名字是固定的,ultralytics会自动在同级目录下找labels,不需要写在yaml里。

4.3 yolov8n到yolov8x:几档模型在裂缝任务上的取舍

YOLOv8按参数量从低到高分成n、s、m、l、x五档。我在这批裂缝数据上全部跑了一遍对比,结论很明确:最轻量的n模型虽然推理速度最快,但mAP50只有0.65到0.70左右,对细长裂缝的细节学习能力明显不足;s模型mAP50能到0.78左右,推理速度依然很快,是我最终的选择;再往上到m、l、x,精度提升有限,每提升1到2个点,推理时间几乎翻倍,在巡检批量处理场景下不划算。

模型参数量推理速度mAP50(本数据)结论
yolov8n约3.2M最快0.65-0.70轻量部署可用
yolov8s约9.5M快0.78推荐,综合最优
yolov8m约25.9M中等0.79-0.81精度提升有限
yolov8l/x40M+明显变慢0.80+边缘设备不推荐

裂缝属于小目标且纹理细节少,低参数模型学起来很吃力,所以至少从s档起步。

4.4 训练命令、学习率与迭代策略

训练命令非常简洁:

yolo detect train data=crack.yaml model=yolov8s.pt epochs=120 imgsz=640 batch=16 lr0=0.01 cos_lr=True

几个参数说明一下。model指定yolov8s.pt预训练权重,库会自动下载;imgsz默认640,对普通照片够用,但如果你手上是大尺寸高清图,后面我会介绍把imgsz提到1280的做法;batch根据显存调整,显存不够就降到8;lr0默认0.01,配合cos_lr余弦退火,对迁移学习微调是稳妥组合。

训练过程中每轮都会保存权重,epoch结束时会自动对比指标,保留val上表现最好的best.pt,同时保存最后一个epoch的last.pt。训练结束后的best.pt在runs/detect/train/weights/目录下。

4.5 从训练日志和曲线判断模型是否收敛

训练结束后,runs/detect/train/目录下会生成results.png,里面有loss曲线和mAP曲线,还有一个confusion_matrix.png混淆矩阵,这些是一定要看的。

判断收敛看三个信号:train和val的box_loss、cls_loss、dfl_loss都要平稳下降,最后趋于平缓;val的mAP50和mAP50-95不再明显上升;混淆矩阵里非对角线数字尽量小。我这批数据最终稳定在mAP50约0.78、precision约0.81、recall约0.75。混淆矩阵里最大的混淆集中在纵向裂缝和横向裂缝之间,这两个类的走向差异在部分弯曲路面上确实容易看错,龟裂的识别相对稳定。

5. 中英文双版系统:推理接口、界面与批量报告

5.1 双语的实现:类名映射与语言参数

模型内部输出的类名是英文id,中英文双版的核心就是一个映射表加一个语言参数。我的实现是维护一个字典,把内部英文类名分别映射到中文和英文显示名:

LANG_MAP = { "zh": { "alligator_crack": "龟裂", "longitudinal_crack": "纵向裂缝", "transverse_crack": "横向裂缝", }, "en": { "alligator_crack": "Alligator Cracking", "longitudinal_crack": "Longitudinal Cracking", "transverse_crack": "Transverse Cracking", }, }

命令行入口支持--lang zh或者--lang en参数,界面里也提供语言切换按钮。这样同一套模型权重,输出语言可以随时切换,不需要训练两套模型。

5.2 核心推理函数与结果结构化输出

推理部分直接调用ultralytics的Python API,拿到每个框的类别、置信度和坐标:

from ultralytics import YOLO model = YOLO("runs/detect/train/weights/best.pt") def infer_bilingual(image_path, lang="zh", conf=0.25, iou=0.45): results = model.predict(source=image_path, conf=conf, iou=iou, verbose=False)[0] detections = [] for box in results.boxes: cls_id = int(box.cls[0]) class_en = model.names[cls_id] label = LANG_MAP[lang].get(class_en, class_en) detections.append({ "label": label, "class": class_en, "confidence": round(float(box.conf[0]), 4), "bbox": [round(float(v), 4) for v in box.xyxy[0].tolist()], }) return detections

每个检测结果包含:显示标签、内部英文类名、置信度、边界框四角坐标。后续无论是画框、存CSV还是喂给业务系统,都是从这份结构化数据里取值。

5.3 大图滑窗推理:高清巡检图的正确打开方式

这是一个关键环节。巡检相机和无人机拍的原图往往有4000乘3000像素,直接缩放到640推理,等于把原本5像素宽的裂缝缩到不足1像素,模型根本不可能识别。解决思路是滑窗推理:把大图切成1280乘1280的小块,分别推理,再把结果拼回原图坐标。

def slide_infer(image, model, tile=1280, stride=1024, conf=0.25, iou=0.45): h, w = image.shape[:2] ys = list(range(0, max(h - tile, 0) + stride, stride)) if ys[-1] < h - tile: ys.append(h - tile) xs = list(range(0, max(w - tile, 0) + stride, stride)) if xs[-1] < w - tile: xs.append(w - tile) dets = [] for y0 in ys: for x0 in xs: y1, x1 = min(y0 + tile, h), min(x0 + tile, w) crop = image[y0:y1, x0:x1] res = model.predict(crop, conf=conf, iou=iou, verbose=False)[0] for box in res.boxes: a, b, c, d = box.xyxy[0].tolist() dets.append([x0 + a, y0 + b, x0 + c, y0 + d, float(box.conf[0]), int(box.cls[0])]) # 同一裂缝跨裁片被重复检出时,用NMS按置信度去重 return nms_boxes(dets, conf, iou)

stride设为1024意味着相邻裁片有约两成的重叠区域,这样裂缝正好在裁片边缘被切断时,至少能在一个裁片里完整出现。拼回原图后,用NMS把重复框合并。这个方案实测下来,漏检率比直接整图缩放低很多。

5.4 从单张检测到批量文件夹导出CSV报告

单张能跑通之后,批量处理就是一个循环。我提供了一个--source参数,支持传入单张图片、一个文件夹或者一段视频。每张图的检测结果追加到同一个CSV里:

import csv with open("report.csv", "w", newline="", encoding="utf-8-sig") as f: writer = csv.writer(f) writer.writerow(["image", "class_id", "class_en", "class_zh", "confidence", "x1", "y1", "x2", "y2"]) for img_path, dets in all_results.items(): for d in dets: writer.writerow([img_path, d["class"], d["class"], d["label"], d["confidence"], *d["bbox"]])

编码用utf-8-sig而不是utf-8,这样用Excel打开中文不会乱码。

界面部分我选了轻量级Web方案,用Python的网页组件库直接起一个本地页面,输入一张图片和一个语言选项,输出标注图和结构化检测结果。不想开浏览器的话,命令行版效果一样,只是少了交互界面。

6. 实测数据与踩坑记录:给想复现的人少走弯路

6.1 不同路面条件下的实测表现

模型在自己采集的测试集上,按场景拆分统计过一组数据,大概情况是这样:

场景precisionrecall备注
晴朗干燥0.820.76基线表现
树荫阴影0.750.70阴影造成纹理干扰
雨后湿润0.600.55反光把裂缝"藏"起来了
夜间补光0.500.45光照不足,纹理丢失严重

这个结果很说明问题:模型在理想光照下可用,但一到湿润和夜间场景,性能和常规场景差距非常大。如果项目要覆盖全天候巡检,要么补充夜拍数据重训,要么在采集端加更好的补光设备。目前的版本我会明确标注适用范围是日间干燥路面的快速初筛。

6.2 大坑:细长裂缝在缩小时后直接消失

这个坑几乎每个做裂缝检测的人都会遇到。我一开始图省事,所有图统一缩到640推理,结果recall惨到没法看。原因前面的滑窗部分已经解释过:3到5像素宽的裂缝,经过一次缩放就只剩亚像素级别,卷积核根本提取不到有效特征。

解决办法就是两条路一起走:训练时把imgsz提到1280,推理时用滑窗裁片而不是整图缩放。代价是显存占用和推理时间增加,但recall的提升是实打实的。如果显存紧张,也可以用960这个折中值。

6.3 标注质量与模型精度的关系:一次重标注带来的提升

第一版标注是我自己赶时间做的,长裂缝框得歪歪扭扭,龟裂区域只框了半片。训练结果mAP50卡在0.60左右,怎么调学习率、怎么换模型都上不去。后来我停下来做了两件事:第一,把文章第二部分那套判读规则写清楚;第二,找人按规则把标注全部检查并重做了一遍。模型参数一个没动,重新训练后mAP50直接涨了约10个点。

这件事给我的教训很深:调参能带来的收益,通常远不如把标注质量拉高。标注一致性是所有下游工作的地基,地基歪了,模型参数再合理也是空中楼阁。

6.4 环境依赖、版本和显存相关的常见报错

复现过程中最容易卡住的就是环境问题。第一类是torch.cuda.is_available()返回False,不用急着怀疑显卡坏了,基本是PyTorch装成了CPU版,或者CUDA版本和PyTorch编译版本不匹配,重装对应版本就能解决。第二类是训练到一半爆显存,OOM报错,优先调低batch,从16降到8甚至4;ultralytics默认开了混合精度训练,不要关掉,它能省将近一半显存。第三类是不同ultralytics版本之间接口有小差异,有些参数在旧版本里不支持,建议直接在项目里锁定你测试过的版本号,不要随手升级。

6.5 如果只让我给一条建议

训练完成之后,别急着晒mAP。先把验证集放一边,拿50张完全没见过的现场图,人工逐张核对漏检和误检,然后把指标拆到单类、单场景去看。裂缝检测的"工程可用"标准从来不是mAP数字,而是现场100条裂缝里你能自动找出几条、误报几条。我在实际使用中还有一个体会:工具永远要留一个"人审"环节,算法吐出来的建议清单,让有经验的巡检员过一遍再落到报告里,这条流水线才是真的能长期跑起来的。

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

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

立即咨询