资讯动态

基于YOLOv8的道路裂缝检测系统:从训练到边缘部署全流程实战

发布时间:2026/10/9 8:59:33 来源:尧图企业网站定制
1. 道路裂缝检测为什么值得用YOLOv8重做一遍道路裂缝检测这个方向我前前后后接触过三种技术路线早期用传统图像处理做阈值分割和边缘检测后来用两阶段目标检测网络再到现在用YOLO系列做单阶段检测。说实话每次技术迭代带来的精度提升都是肉眼可见的但真正让我决定把整套系统重写成YOLOv8的是它在精度、速度和部署成本这三者之间找到了一个非常舒服的平衡点。先说说这个系统到底解决什么问题。公路养护部门每年要巡检的路面里程是个天文数字人工巡检效率低、主观性强、夜间和危险路段还容易漏检。用车载摄像头加边缘计算设备做自动检测理论上可以做到边开车边出结果。但实际落地时会遇到几个硬骨头裂缝在图像里往往只占几十个像素属于典型的小目标裂缝形态多样有横向、纵向、网状、块状还有和路面阴影、油渍、修补痕迹混淆的情况车载设备算力有限模型必须足够轻量。YOLOv8恰好在这几个维度上都有针对性的设计这也是我选它而不是继续用Faster R-CNN或者SSD的核心原因。这篇文章我会把整套系统的设计思路、数据集构建、模型训练调参、部署优化、问题排查全部拆开讲一遍。适合两类人看一类是想找一个完整深度学习项目练手的同学道路裂缝检测这个场景数据获取相对容易、评价指标清晰、落地价值明确非常适合作为第一个端到端项目另一类是做智慧交通、市政养护相关工作的工程师可以直接参考里面的工程细节。我不会只贴代码重点会放在为什么这么设计以及实际跑起来会遇到什么坑上。2. 系统整体设计与技术选型拆解2.1 为什么是YOLOv8而不是其他检测器选检测器这件事我一般从四个维度打分精度、推理速度、训练难度、部署友好度。把常见方案拉个表对比一下就很清楚了。检测器精度(mAP)推理速度训练难度部署友好度适合场景Faster R-CNN高慢中一般离线高精度分析SSD中快中好通用检测YOLOv5较高快低很好工业落地YOLOv8高快低很好小目标落地DETR系列高中高一般研究探索YOLOv8相比v5最大的几个改动直接决定了它更适合裂缝检测。第一是Anchor-Freev5还是基于锚框的需要针对数据集聚类出合适的先验框尺寸裂缝这种细长目标聚类出来的框往往很怪v8改成无锚框后直接预测中心点和宽高对细长目标的适应性明显更好。第二是C2f模块替代了C3梯度流动更充分小目标的特征保留更完整。第三是解耦头分类和回归分支分开对于裂缝这种位置准但类别少的任务回归分支能更专注地优化定位精度。第四是Task-Aligned Assigner正负样本分配策略比v5的分配方式更合理训练收敛更稳。我实测过同一批裂缝数据v5s和v8s在相同训练轮次下v8s的mAP50大概高出3到5个百分点小裂缝的召回率提升更明显。这个差距在裂缝检测里很关键因为漏检一条细微裂缝可能就意味着几个月后它发展成需要大修的坑槽。2.2 整体系统架构怎么搭整套系统我分成四个模块数据采集与标注、模型训练与验证、推理服务、结果可视化与告警。这四个模块可以独立迭代也可以串成一条流水线。数据采集这块我用的是车载行车记录仪加手机补拍的方式。行车记录仪负责常规路段的大批量采集手机负责特殊路段比如桥面、隧道口的定点补拍。采集时要注意几个点尽量覆盖不同光照清晨、正午、黄昏、夜间补光、不同天气晴天、阴天、雨后、不同路面材质沥青、水泥。我一开始只用了晴天数据结果模型一到阴天就疯狂误检把路面纹理当成裂缝后来补了阴天和雨后数据才好转。标注环节我用LabelImg和X-AnyLabeling配合。LabelImg适合纯手工精标X-AnyLabeling可以先用SAM之类的分割模型预标注再人工修正效率能提升三四倍。裂缝的标注框我建议贴着裂缝外边缘画不要留太多余量因为裂缝本身很细框画大了会让模型学到大量背景信息定位反而不准。模型训练用Ultralytics官方框架这个框架封装得很好改配置文件就能跑。推理服务我一开始用Flask搭了个简单接口后来发现并发一高就卡换成了FastAPI加异步推理配合ONNX Runtime做加速。结果可视化这块除了在图上画框我还加了一个裂缝密度热力图把检测结果按位置聚合养护人员一眼就能看出哪段路病害最集中。2.3 数据集构建的核心考量裂缝检测的数据集质量直接决定模型上限。我总结了几条经验。第一类别划分要克制。很多人一上来就分横向裂缝、纵向裂缝、网状裂缝、块状裂缝、坑槽、修补六大类结果每类样本都不够模型学得一塌糊涂。我建议先做二分类裂缝和非裂缝把检测做扎实了再考虑细分。如果一定要多分类至少保证每类有800到1000个实例。第二负样本要足够。裂缝检测最大的敌人是误检路面上的车道线、阴影、水渍、油渍、井盖边缘、修补痕迹都容易被误判成裂缝。我专门收集了2000多张不含裂缝的路面图作为负样本训练时按一定比例混入误检率能降一半以上。第三数据增强要针对性强。常规的翻转、缩放、色彩抖动都要用但针对裂缝我额外加了几个随机遮挡模拟车辆、行人遮挡、运动模糊模拟车载抖动、亮度对比度大幅调整模拟隧道进出。这些增强让模型在真实车载场景下的鲁棒性提升很明显。第四训练集验证集测试集要按路段划分不能随机划分。这点特别重要。如果随机划分同一段路的相似图像会同时出现在训练集和验证集里验证指标虚高实际部署就露馅。我按路段划分后验证mAP比随机划分低了差不多8个点但这才是真实水平。3. 核心细节解析与实操要点3.1 环境配置与依赖安装环境配置是新手最容易卡住的地方我把完整流程和踩过的坑都列出来。我用的组合是Ubuntu 22.04 CUDA 11.8 PyTorch 2.0 Python 3.10这套组合在30系和40系显卡上都验证过比较稳。# 创建虚拟环境 conda create -n crack_det python3.10 -y conda activate crack_det # 安装PyTorchCUDA 11.8版本 pip install torch2.0.1 torchvision0.15.2 --index-url https://download.pytorch.org/whl/cu118 # 安装Ultralytics pip install ultralytics8.0.200 # 其他依赖 pip install opencv-python albumentations fastapi uvicorn onnx onnxruntime-gpu这里有几个坑要提醒。第一PyTorch版本和CUDA版本必须匹配装错了会报CUDA driver version is insufficient或者直接跑CPU。装完用torch.cuda.is_available()验证一下返回True才算成功。第二Ultralytics版本不要用最新的新版本有时候会改API网上教程对不上。我锁在8.0.200这个版本稳定。第三如果你用的是GTX 1660 Ti这类没有Tensor Core的卡训练速度会比30系慢不少但跑YOLOv8n/s还是够用的只是batch size要调小。提示安装完先跑一遍官方示例yolo predict modelyolov8n.pt sourcehttps://ultralytics.com/images/bus.jpg能出结果说明环境没问题再开始搞自己的数据。3.2 数据标注规范与格式转换标注质量直接决定模型上限我把标注规范整理成几条硬性要求。框必须紧贴裂缝外边缘误差控制在3到5个像素以内细长裂缝如果中间断开按实际连通性决定是否合并一般断开超过20像素就分成两个框网状裂缝用一个大框包住整个区域不要拆成很多小框图像边缘被截断的裂缝如果可见部分超过一半就标否则不标模糊到人眼都难以判断的直接跳过不要硬标标注完成后数据格式要转成YOLO格式。每张图对应一个txt文件每行是类别id 中心x 中心y 宽 高坐标都归一化到0到1之间。我写了个脚本批量转换顺便做了数据校验检查有没有坐标越界、类别id错误、空标注文件这些问题。import os import cv2 def convert_to_yolo(label_path, img_path, class_map): img cv2.imread(img_path) h, w img.shape[:2] lines [] with open(label_path, r) as f: for line in f: parts line.strip().split() cls_name parts[0] x1, y1, x2, y2 map(float, parts[1:5]) # 归一化并转中心点格式 cx (x1 x2) / 2 / w cy (y1 y2) / 2 / h bw (x2 - x1) / w bh (y2 - y1) / h # 边界裁剪防止越界 cx, cy min(max(cx, 0), 1), min(max(cy, 0), 1) bw, bh min(bw, 1), min(bh, 1) lines.append(f{class_map[cls_name]} {cx:.6f} {cy:.6f} {bw:.6f} {bh:.6f}) return lines转换完一定要抽查我遇到过标注软件导出的坐标是左上右下但脚本按中心点格式处理结果框全错位的情况。抽查方法很简单写个脚本把YOLO格式的框画回原图上看一眼对得上就没问题。3.3 模型配置文件的关键参数YOLOv8的配置文件我基于yolov8s.yaml改的主要动了几个地方。类别数改成自己的如果做二分类就是nc: 2。数据配置文件crack.yaml里指定训练、验证、测试路径和类别名。# crack.yaml path: /data/crack_dataset train: images/train val: images/val test: images/test names: 0: crack 1: non_crack模型规模选择上我建议从yolov8s起步。n版本太快但精度不够m和l版本精度高但训练慢、部署吃资源。s版本在精度和速度之间平衡最好实测在RTX 3060上训练100轮大概两三个小时推理单张图在10毫秒以内。训练超参数这块我调过很多轮最后稳定用的配置是这样的from ultralytics import YOLO model YOLO(yolov8s.yaml) model.train( datacrack.yaml, epochs150, imgsz640, batch16, lr00.01, lrf0.01, momentum0.937, weight_decay0.0005, warmup_epochs3, cos_lrTrue, close_mosaic10, augmentTrue, mixup0.1, copy_paste0.1, device0, workers8, projectruns/crack, nameexp1 )几个参数解释一下。imgsz640是输入尺寸裂缝是小目标理论上用更大尺寸比如1280效果更好但显存和速度代价大640是个折中。close_mosaic10表示最后10轮关闭Mosaic增强让模型在真实分布上收敛这个技巧对精度提升很明显。mixup和copy_paste是额外的增强对裂缝这种小目标有帮助但比例不要太高0.1左右就行太高了反而学不好。3.4 损失函数与训练监控YOLOv8的损失由三部分组成分类损失BCE、回归损失CIoU加DFL、目标性损失。训练时看损失曲线是判断模型状态最直接的方式。我一般关注三个信号。第一训练损失和验证损失是否同步下降。如果训练损失一直降但验证损失开始上升说明过拟合了要加正则或者减模型规模。第二mAP曲线是否还在涨。如果连续20轮不涨基本可以停了。第三各类损失的比例是否合理。如果分类损失远大于回归损失可能是类别不平衡反过来可能是定位太难。画损失曲线的代码很简单训练完Ultralytics会在runs/crack/exp1/下生成results.csv直接读出来画就行。import pandas as pd import matplotlib.pyplot as plt df pd.read_csv(runs/crack/exp1/results.csv) df.columns df.columns.str.strip() fig, axes plt.subplots(1, 2, figsize(14, 5)) axes[0].plot(df[epoch], df[train/box_loss], labeltrain box) axes[0].plot(df[epoch], df[val/box_loss], labelval box) axes[0].set_xlabel(epoch) axes[0].set_ylabel(box loss) axes[0].legend() axes[1].plot(df[epoch], df[metrics/mAP50(B)], labelmAP50) axes[1].plot(df[epoch], df[metrics/mAP50-95(B)], labelmAP50-95) axes[1].set_xlabel(epoch) axes[1].set_ylabel(mAP) axes[1].legend() plt.savefig(training_curve.png, dpi150)我踩过的一个坑是一开始没注意results.csv的列名带空格直接df[train/box_loss]报KeyError后来加了strip()才正常。这种小问题看着不起眼但能卡你半天。4. 实操过程与核心环节实现4.1 从零跑通一次完整训练我把完整流程按顺序列一遍你可以直接照着做。第一步准备数据。把标注好的图像和标签按images/train、images/val、images/test和对应的labels/目录放好。图像和标签文件名要一一对应只是扩展名不同。第二步写数据配置文件crack.yaml路径指向你的数据集根目录。第三步下载预训练权重。用yolov8s.pt做迁移学习比从零训练收敛快得多精度也高。下载地址在Ultralytics官方仓库直接YOLO(yolov8s.pt)会自动下载。第四步启动训练。用上面那段训练代码把data改成你的配置文件路径。训练过程中终端会实时打印每轮的损失和mAP也可以开TensorBoard看。第五步训练完在runs/crack/exp1/weights/下会有best.pt和last.pt。best.pt是验证集上表现最好的一般用这个。第六步验证。用model.val()在测试集上跑一遍看最终指标。model YOLO(runs/crack/exp1/weights/best.pt) metrics model.val(datacrack.yaml, splittest) print(fmAP50: {metrics.box.map50:.4f}) print(fmAP50-95: {metrics.box.map:.4f}) print(fprecision: {metrics.box.mp:.4f}) print(frecall: {metrics.box.mr:.4f})我第一批模型跑下来mAP50大概0.82mAP50-95大概0.51。这个水平对于裂缝检测来说算及格但离好用还有距离。后面通过补数据、调增强、换更大输入尺寸慢慢把mAP50提到了0.91左右。4.2 推理服务怎么搭才不卡训练完的模型要变成能用的服务中间还有不少工程活。我一开始用Flask写了个同步接口单张图推理没问题但一上并发就排队延迟飙升。后来改成FastAPI加异步配合ONNX Runtime吞吐量提升了差不多5倍。导出ONNX的代码from ultralytics import YOLO model YOLO(runs/crack/exp1/weights/best.pt) model.export(formatonnx, imgsz640, simplifyTrue, opset12)导出后得到一个.onnx文件用ONNX Runtime加载推理。这里有个关键点预处理和后处理要自己写不能依赖Ultralytics的封装否则性能上不去。预处理就是把图像resize到640、归一化、转成NCHW格式后处理就是把输出解码成框、做NMS。import onnxruntime as ort import numpy as np import cv2 class CrackDetector: def __init__(self, onnx_path, conf_thres0.25, iou_thres0.45): self.session ort.InferenceSession(onnx_path, providers[CUDAExecutionProvider, CPUExecutionProvider]) self.conf_thres conf_thres self.iou_thres iou_thres self.input_name self.session.get_inputs()[0].name def preprocess(self, img): img_resized cv2.resize(img, (640, 640)) img_rgb cv2.cvtColor(img_resized, cv2.COLOR_BGR2RGB) img_norm img_rgb.astype(np.float32) / 255.0 img_trans img_norm.transpose(2, 0, 1) return np.expand_dims(img_trans, axis0) def infer(self, img): input_tensor self.preprocess(img) outputs self.session.run(None, {self.input_name: input_tensor}) return self.postprocess(outputs, img.shape) def postprocess(self, outputs, orig_shape): # 解码逻辑含NMS predictions outputs[0][0].T boxes, scores, class_ids [], [], [] for pred in predictions: cls_scores pred[4:] cls_id int(np.argmax(cls_scores)) conf float(cls_scores[cls_id]) if conf self.conf_thres: continue cx, cy, w, h pred[:4] x1 (cx - w / 2) * orig_shape[1] / 640 y1 (cy - h / 2) * orig_shape[0] / 640 x2 (cx w / 2) * orig_shape[1] / 640 y2 (cy h / 2) * orig_shape[0] / 640 boxes.append([x1, y1, x2, y2]) scores.append(conf) class_ids.append(cls_id) indices cv2.dnn.NMSBoxes(boxes, scores, self.conf_thres, self.iou_thres) return [(boxes[i], scores[i], class_ids[i]) for i in indices]FastAPI接口这块用async def定义路由把推理放到线程池里跑避免阻塞事件循环。实测在单张RTX 3060上640尺寸的图单张推理大概8到12毫秒加上前后处理总共20毫秒左右能跑到40到50 FPS满足车载实时检测需求。4.3 部署到边缘设备的优化思路车载场景不可能背个服务器必须部署到边缘设备。RK3588这类带NPU的板子是常见选择它自带6 TOPS算力跑YOLOv8s量化后的模型能到30 FPS以上。部署流程大致是PyTorch模型转ONNXONNX转RKNNRKNN在板子上推理。转换过程中有几个关键点。第一量化方式选INT8精度损失大概1到2个mAP点但速度提升3倍以上值得。第二量化校准集要覆盖真实场景用训练集里随机抽200到500张图做校准不要用验证集否则量化参数会偏向验证分布。第三输入尺寸尽量用640RK3588的NPU对640支持最好改成其他尺寸可能要重新调优。转换命令大概是这样# ONNX转RKNN python convert_rknn.py --onnx yolov8s.onnx --rknn yolov8s.rknn --dataset calibration_images/ --quantize int8板子上的推理代码用RKNN Toolkit的Python接口或者用C接口追求极致性能。我建议先用Python跑通确认精度和速度都达标再考虑用C重写。注意RK3588的NPU对某些算子支持不完整比如某些激活函数和自定义算子。转换时如果报unsupported op要么换算子要么把不支持的层放到CPU上跑。我遇到过SiLU激活在旧版工具链上不支持升级工具链后解决。4.4 结果可视化与告警逻辑检测结果光有框还不够养护人员需要的是哪段路需要修、优先级多高。我加了一层业务逻辑把检测框按位置聚类计算每公里路段的裂缝密度和严重程度生成热力图和告警列表。严重程度我按三个维度打分裂缝长度、裂缝宽度用框的短边近似、裂缝数量。综合得分超过阈值的路段标红中等标黄轻微标绿。这个逻辑用简单的规则引擎就能实现不需要再上模型。def assess_severity(detections, img_width, img_height): total_score 0 for box, conf, cls_id in detections: x1, y1, x2, y2 box length max(x2 - x1, y2 - y1) width min(x2 - x1, y2 - y1) # 长度和宽度归一化后加权 score (length / img_width) * 0.6 (width / img_height) * 0.4 total_score score * conf if total_score 0.8: return severe elif total_score 0.3: return moderate return mild热力图用OpenCV的applyColorMap叠加到原图上或者用matplotlib画散点密度图。我倾向后者因为可以按GPS坐标聚合生成路段级的病害分布图更直观。5. 常见问题与排查技巧实录5.1 训练不收敛或指标异常这是新手最常遇到的问题我把典型现象和排查思路整理成表。现象可能原因排查方法解决方案损失不下降学习率过大/过小看前10轮损失曲线调lr0到0.001或0.05试损失震荡batch太小看损失波动幅度增大batch或加梯度累积mAP一直为0标签格式错误可视化标签框检查坐标归一化和类别id验证mAP远低于训练过拟合对比两条曲线加数据增强、减模型规模某类召回率极低类别不平衡统计各类实例数补数据或加类别权重训练中途崩溃显存不足看报错信息减batch或imgsz我印象最深的一次是mAP死活上不去查了两天才发现是标注文件里类别id从1开始编号但配置文件里names从0开始导致所有标签都错位。这种低级错误一定要在训练前用脚本校验一遍。5.2 误检和漏检怎么针对性优化误检和漏检是裂缝检测的两大顽疾优化方向完全不同。误检多通常是负样本不够或者模型把背景纹理当成了裂缝。解决办法补负样本特别是容易被误检的场景车道线、阴影、水渍、修补痕迹提高置信度阈值从0.25提到0.4试试在损失函数里加大分类损失的权重。漏检多通常是小目标学不好或者正样本不够。解决办法增大输入尺寸到960或1280用更小的模型步长比如把P3层的特征加强补小裂缝样本降低置信度阈值。我实测下来补数据永远比调参有效。与其花一周调超参数不如花两天标500张新图后者带来的提升往往更大。5.3 部署后精度下降的排查训练时mAP0.9部署后实际效果差很多这种情况太常见了。原因一般有三个。第一预处理不一致。训练时用的是Ultralytics的预处理letterbox填充部署时如果直接resize长宽比变了框的位置就偏了。解决办法是部署时也用letterbox保持长宽比。第二量化损失。INT8量化会损失精度特别是小目标。如果量化后掉点太多可以试试混合量化把敏感层保持FP16。第三数据分布差异。训练数据是晴天拍的部署时遇到阴天模型就懵了。这个只能靠补数据解决没有捷径。提示部署前一定要用真实场景的视频流做端到端测试不要只看单张图的指标。视频流里连续帧的一致性、抖动、光照变化都是单张图测试发现不了的。5.4 实操避坑清单最后把我踩过的坑整理成一份清单你照着检查能省不少时间。训练前务必校验标签格式用脚本把框画回原图看一眼数据集按路段划分不要随机划分负样本至少占正样本的30%否则误检压不住预训练权重一定要用从零训练收敛慢且精度低训练日志和权重按实验编号存好不然跑多了分不清哪个是哪个部署前用真实视频流测试不要只测单图边缘设备部署先跑通Python版本再考虑C优化模型版本和推理代码版本要对应改了模型记得同步更新后处理逻辑我个人在实际操作中的体会是道路裂缝检测这个项目模型本身只占三成工作量剩下七成都在数据和工程上。数据质量决定了模型上限工程细节决定了能不能真正落地。YOLOv8已经把模型这块的门槛降得很低了真正拉开差距的是你有没有耐心把数据标好、把部署链路调通、把真实场景的坑一个个填上。如果你正准备做这个项目我的建议是先把数据采集和标注的流程跑顺别急着调模型数据扎实了后面的事都是水到渠成。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑