路面缺陷监测这个方向这几年在公路养护圈子里可以说是从“锦上添花”变成了“刚需”。传统的人工巡检靠肉眼记、纸笔写效率低是一回事更麻烦的是缺陷的严重程度、位置信息全凭个人经验回头做养护决策根本拿不出统一标准。我这次用Python搭了一套路面缺陷监测系统内部代号hx3052从数据采集、模型训练到Web可视化全流程跑通整个项目断断续续花了两个多月。这篇文章就把我在落地过程中的设计思路、踩过的坑、以及最终能直接复用的代码方案完整拆给你看。这套系统的核心价值在于把“路面裂缝、坑槽、修补、龟裂”这四类最常见缺陷的识别从人眼判断换成深度学习模型的自动检测检测结果直接带位置坐标和置信度再通过可视化看板呈现一个上午能跑完过去两周的巡检量。内容覆盖了数据集如何构建、YOLOv8模型如何训练调优、后端推理接口怎么封装、以及前端看板怎么和检测结果联动。适合刚入门CV想拿真实项目练手的人也适合已经在做智慧交通、道路养护系统想参考一套完整技术栈的同学。1. 系统设计与方案选型1.1 核心需求拆解动手之前我把需求整理成了三块。第一块是数据采集与标注。路面缺陷检测本质上是个目标检测任务模型能学到什么程度百分之八十取决于喂进去的图像质量。我需要一套能够覆盖白天、阴天、阴影遮挡、柏油路、水泥路多种场景的路面图像采集方案。第二块是模型训练与实时推理。缺陷检测和通用物体检测有个不一样的地方裂缝这类目标是细长条形状长宽比极端用常规的锚框策略容易漏检坑槽边缘不规则和阴影区分难度大。所以模型选型和训练策略必须针对这个特点做适配。第三块是系统化呈现与结果管理。检测结果不能只停留在命令行里打印坐标得有可视化界面让养护人员直接看到“哪个路段、什么缺陷、严重等级、建议处理时间”。这比单纯堆叠模型指标重要得多——系统最终还是给人用的。1.2 技术栈为什么这么搭技术选型我直接说结论目标检测用YOLOv8后端推理服务用FastAPI前端可视化用Streamlit数据集管理用Roboflow做预处理整个链路基于Python串联。YOLOv8选它的理由很直接v8相比之前的v5、v7在COCO数据集上的精度和速度平衡更好而且官方提供了非常干净的Python接口train和predict只需要几行代码就能跑起来。最重要的是YOLOv8的模型导出生态很完善训练完可以转成ONNX格式部署阶段不依赖PyTorch环境这在真实的巡检边缘设备上非常关键——很多现场机器根本没有GPU只有CPU或者简单的推理卡。FastAPI负责把训练好的模型包成一个HTTP接口输入一张路面图片返回缺陷类别、置信度、包围框坐标。选FastAPI而不是Flask纯粹是因为异步支持和自动生成的API文档太香了调试联调省了很多事。前端为什么用Streamlit因为这项目的主要使用者是养护管理人员不是专业前端开发Streamlit可以用纯Python快速搭出带地图、带图表、带图片标注的交互页面开发成本极低后续调整布局也很方便。1.3 整体架构设计系统的整体流程是这样走的数据层巡检车或者手持设备采集路面图像统一存放在本地目录按缺陷类别人工标注。因为现场采集的图像通常分辨率很高4000×3000级别直接整图训练显存扛不住必须做切片处理。模型层YOLOv8s作为基础模型在自定义路面缺陷数据集上finetune。训练完成后导出ONNX格式部署到推理环境中。服务层FastAPI接收图片上传调用模型推理返回JSON格式的检测结果同时把结果写入SQLite数据库保留历史记录方便后续查询缺陷变化趋势。展示层Streamlit读取数据库和推理结果在页面上展示缺陷图片标注、各类别统计柱状图、以及按时间维度的趋势分析。这套架构最大的好处是模块之间完全解耦哪个环节出问题可以直接单独替换。比如采集设备换了只要图像格式统一数据层不需要任何改动模型升级了后端接口参数不变前端完全无感。2. 路面缺陷数据集构建2.1 数据采集设备与场景覆盖采集方案我用了两种组合一种是在车辆前方固定运动相机以固定频率连续拍摄路面另一种针对特殊路段由人工手持高分辨率相机补拍。设备方面没什么黑科技我用的是GoPro级别的运动相机架在车辆保险杠支架上镜头朝下约30度角距离地面大概60厘米。这个高度和角度的好处是既能拍到足够宽的路面范围又不会因为视角过斜导致缺陷形变严重。路面图像的分辨率统一设置到4032×3024保证细小裂缝的纹理细节不丢失。关键在于场景覆盖。我在某省道的一条旧柏油路、一段新建水泥路、一个城乡结合部的混合路段分别采集了数据。这三个场景涵盖了沥青路面常见的细小网裂、横向裂缝水泥路面的板块断裂以及混合路段的坑槽修补痕迹。采集时间包含了晴天上午、下午逆光、阴天多云三个时段。阴影对检测的影响非常大尤其是坑槽在树荫下表面特征和阴影混在一起模型很容易误判这个问题后面单独讲。2.2 数据标注与预处理细节标注工具用的是LabelImg在Windows下直接运行标注流程很顺畅。我按四类缺陷打标签crack裂缝、pothole坑槽、repair修补痕迹、alligator龟裂。需要特别提醒的是龟裂和裂缝在视觉表现上经常混淆所以标注规范里明确了一条裂缝强调单条线性走向龟裂强调网状多向裂纹如果一片区域内出现三根以上的交叉裂纹统一归为龟裂。采集回来的原始图像有几百张直接训练不现实。我做了三个关键预处理操作。第一步是切片图像切成640×640的瓦片步长512保留了20%重叠区域——缺陷在切片边缘时重叠区域能保证目标至少有一张完整出现在某个切片里。第二步是数据增强采用随机旋转、水平翻转、亮度对比度调整、高斯噪声叠加这四种方式把数据集从几百张扩充到接近四千张。第三步是清洗人工把严重过曝、完全无路面纹理、运动模糊到无法辨认的切片删掉。这里插一个经验不要盲目追求数据量。我第一版数据增强做得很激进每张原图扩了10倍结果模型训练loss下降很快但验证集mAP反而低了两个点原因是增强后的图像和原始分布的偏差太大模型学到的是“增强痕迹”而不是“路面缺陷特征”。后面把增强倍数调到4倍效果明显回升。2.3 数据集分布与训练验证划分最终数据集情况如下表类别标注框数量占比crack裂缝152038.2%pothole坑槽113528.5%repair修补78019.6%alligator龟裂54513.7%划分比例是训练集70%、验证集20%、测试集10%划分的时候特别注意了同一原始图像的不同切片不能同时出现在训练集和验证集里——因为切片之间有重叠区域如果随机划分模型会通过图像背景的相似性“作弊”导致验证指标虚高。这个问题我在第一版吃过亏验证集mAP0.5到了0.93换到全新路段拍摄的数据上直接掉到0.70以下就是切片泄漏导致的。3. YOLOv8模型训练与调优3.1 模型选型与训练环境我选的是YOLOv8ssmall版本。为什么不用nano或者medium主要原因有两点nano的参数量太少对裂缝这种细长目标的学习能力偏弱实际测试中裂缝的召回率比small版本低大约6%medium的精度确实会再高一些但训练和推理耗时明显增加在巡检场景下性价比不高。small版本在精度和速度之间刚好卡在甜点上。训练环境用的是本地一台RTX 3060 12GB显卡的机器。路面图像切片统一resize到640×640输入。训练参数配置如下# config.yaml path: ./dataset train: images/train val: images/val names: 0: crack 1: pothole 2: repair 3: alligatoryolo detect train \ --model yolov8s.pt \ --data config.yaml \ --epochs 150 \ --imgsz 640 \ --batch 16 \ --optimizer AdamW \ --lr0 0.001 \ --lrf 0.01 \ --patience 20epochs设150配合patience20的早停策略。训练到第90轮左右验证集指标基本收敛后面几十轮loss小幅震荡最终模型在测试集上的表现是mAP50约0.89mAP50-95约0.71单张图像推理耗时在RTX 3060上约12毫秒CPU上约200毫秒。3.2 裂缝目标的特殊处理策略裂缝检测是这项目里最“刁钻”的部分。普通的矩形锚框对细长目标天然不友好裂缝宽度可能就是两三个像素但长度可能横跨半个图像。YOLO哪怕到了v8本质上仍然是基于网格的目标检测裂缝跨网格的时候中心点落在哪个网格cell就会影响检测稳定性。我实践的优化有两条有效路径。第一输入分辨率不要降太低。主流检测任务为了速度常用416甚至320的输入但路面裂缝我用640实测从416升到640裂缝的召回率提升了近11%。代价是推理显存占用增大这个在部署环境允许的前提下是划算的。第二针对长条形目标调小锚框宽高比限制。YOLOv8自动锚框的默认参数偏向通用物体我用kl散度对标注数据重新聚类了锚框裂缝的锚框会比默认值更扁长训练时收敛更快。另外有个小技巧腐蚀类数据增强对裂缝检测有奇效。用形态学腐蚀操作模拟更细的裂纹形态让模型对“不同宽度的裂缝”具备更强的泛化能力这个增强手段工业瑕疵检测里常用但在路面缺陷场景很少有人提我实测对验证集裂缝AP的提升有约2%。3.3 模型评估与推理结果可视化训练完成后我最终保留的权重文件是best.pt。模型评估阶段除了看mAP指标我还手动挑了两个最难场景做边界测试一个是大面积阴影下的坑槽一个是刚修补过、痕迹很淡的修补面。第一个场景模型偶尔会漏检但置信度会在0.35以下徘徊通过降低置信度阈值从0.25到0.15召回率有明显改善第二个场景基本稳定检出但类别置信度偏低后期通过在训练集中增加“刚修补完但颜色差异微弱”的图像拉回了精度。推理可视化阶段用YOLOv8自带的结果绘制功能直接输出带标注框和置信度的图像。下面这段代码是推理阶段最核心的调用from ultralytics import YOLO model YOLO(best.pt) results model.predict( sourcetest_images/, conf0.25, iou0.45, saveTrue, save_txtTrue, save_confTrue, imgsz640 ) for result in results: boxes result.boxes for box in boxes: cls int(box.cls[0]) conf float(box.conf[0]) xyxy box.xyxy[0].tolist() print(fClass: {model.names[cls]}, Conf: {conf:.3f}, Box: {xyxy})这里conf0.25是实际项目调参后的推荐值。默认0.5的置信度在通用检测场景够用但路面裂缝和坑槽的视觉特征在复杂背景下天然弱阈值设太高漏检会严重。你如果做类似项目建议先用0.1跑一轮结合可视化结果确定合理阈值。4. 后端推理服务与前端可视化落地4.1 FastAPI推理接口封装模型训好只是第一步要放进真实业务流程里必须给它穿上服务的外衣。FastAPI封装推理接口的核心逻辑并不复杂但有几个细节值得注意。接口设计上我用了两个端点/detect负责单张图片的检测/detect_batch负责批量检测。单张检测适合人工巡检时上传现场照片批量检测适合对接巡检车连续采集的图像序列。批量检测内部实现用线程池并发推理实测在测机器上并发4路推理吞吐量比串行提升了约2.8倍。from fastapi import FastAPI, UploadFile, File import cv2 import numpy as np from ultralytics import YOLO app FastAPI() model YOLO(best.pt) app.post(/detect) async def detect_image(file: UploadFile File(...)): contents await file.read() nparr np.frombuffer(contents, np.uint8) img cv2.imdecode(nparr, cv2.IMREAD_COLOR) results model.predict(img, conf0.25, iou0.45) detections [] for box in results[0].boxes: cls int(box.cls[0]) conf float(box.conf[0]) xyxy [round(float(v), 2) for v in box.xyxy[0].tolist()] detections.append({ class: model.names[cls], confidence: round(conf, 4), bbox: xyxy }) return {count: len(detections), results: detections}接口返回结构包含缺陷类别、置信度和包围框坐标下游任意系统都可以直接消费这个JSON。我在真实项目中这个接口对接过巡检车的同步上报模块也对接过手持App的现场拍摄页面两头都跑得挺顺。4.2 严重等级判断逻辑单纯输出“这里有裂缝”还不够养护决策需要知道“这条裂缝要不要处理、什么时候处理”。我给系统加了一个缺陷严重等级判断模块规则定义如下裂缝类根据包围框宽度和长度的比值判断。如果框内裂缝像素宽度均值超过3mm按标定像素尺寸换算判定为重度宽度在1-3mm判定为中度小于1mm为轻度。坑槽类根据包围框面积折算实际面积。面积超过0.5平方米判定为重度0.1-0.5平方米为中度小于0.1平方米为轻度。修补类默认与时间关联。系统第一次检测到某路段修补缺陷后记录下来后续巡检同位置再次出现裂缝则升级为重度因为修补层重新开裂往往意味着基层已经出了问题。严重等级最终输出为一个枚举字段配合建议处理周期7天/30天/90天直接呈现在可视化看板里。这个设计是从养护部门要回来的真实需求——他们要的不是最酷的AI而是能直接回答“下一个养护周期修哪里”的决策辅助工具。4.3 Streamlit可视化看板实现前端展示我用了Streamlit核心页面分三个区域图片上传与检测区、检测结果轮播区、缺陷统计分析区。图片上传与检测区用户上传一张现场图片点击“开始检测”按钮后端调用FastAPI接口返回结果立即在页面上渲染带标注框的预览图同时右侧列出所有缺陷的类别、数量和置信度。缺陷统计区是管理者的高频使用区域。我用Pandas从SQLite里读历史检测记录按周聚合缺陷数量用Plotly画柱状图和趋势线。某个路段如果连续两周坑槽数量上升说明路面结构可能正在加速恶化看板上的趋势线会明显抬头养护部门可以据此安排预防性维修。import streamlit as st import pandas as pd import plotly.express as px def load_history(): conn sqlite3.connect(defects.db) df pd.read_sql_query(SELECT * FROM inspections, conn) conn.close() return df df load_history() weekly df.groupby( [pd.Grouper(keyinspect_date, freqW), class] ).size().reset_index(namecount) fig px.line( weekly, xinspect_date, ycount, colorclass, title路面缺陷周趋势 ) st.plotly_chart(fig)这个看板在真实使用中有一个意外收获管理人员对“同一路段的缺陷变化对比”功能反馈最好。我在数据库里给每个检测记录加了路段编号和里程桩号字段看板上可以选取两个时间点的数据做并排对比直观看到修复前后缺陷面积的变化这个功能虽然实现成本很低但对工作汇报和养护验收的支撑价值非常大。5. 常见问题与部署经验5.1 训练与推理阶段的典型问题先列一个我在项目过程中遇到并且排查过的坑合集给读者当速查表问题现象原因定位解决方案裂缝目标的召回率持续在0.4以下输入分辨率过低细长目标跨网格导致特征丢失输入分辨率从416提升到640并增加重叠切片坑槽在阴影场景被漏检阴影区域的对比度低模型特征不显著降低置信度阈值增加阴影场景训练样本验证集指标高但新数据表现差切片划分泄漏同一原始图切片跨集合按原始图像ID分组确保切片的集合归属一致训练过程loss爆nan学习率过高且没有warmup初始化学习率设为0.001开启warmup 3个epoch导出ONNX后推理结果变化模型导出时的opset版本不支持某些算子显式指定opset12并开启dynamic_axes这里特别展开说下导出ONNX的坑。我用YOLOv8的export接口导出时默认opset版本可能偏高在某个推理框架上会报“不支持的算子”错误。解决办法是显式指定opsetyolo export modelbest.pt formatonnx opset12 dynamicTrue导出后最好再用onnxruntime跑一次验证拿一张测试图片对比PyTorch原生推理和ONNX推理的结果两者的检测框坐标和置信度误差应该在1e-4以内如果误差过大优先排查预处理阶段是否一致resize方式、归一化参数、BGR/RGB通道顺序。5.2 边缘设备部署的资源优化路面检测的落地场景不一定在服务器上很多时候是在巡检车里的工控机上这些设备可能只有CPU甚至低功耗GPU。我的模型在RTX 3060上跑12毫秒换到纯CPU的工控机上推理耗时跳到200毫秒出头虽然还算能接受但批量处理连续图像时吞吐量上不去。优化手上有三个招。第一是半精度推理GPU设备上用FP16权重推理显存占用减半、速度提升约30%精度损失基本可以忽略。第二是输入尺寸按需调整实际部署时接收的巡检图像有很多是1080P直接降采样到640损失细节我的做法是先缩放到1280切片成两张640重叠区取最大值既保留清晰度又能控制显存。第三是线程池批量推理把连续帧攒够4张再并发推理显著降低单帧平均耗时。三项加起来CPU工控机的吞吐量从原来不到5帧/秒提到接近10帧/秒基本能满足常规巡检车的采集速率。5.3 数据闭环与模型持续迭代系统跑起来之后真正让模型越用越准的是数据闭环——把现场误检、漏检的图片定期收集回来人工修正标注后重新训练。我在后端加了一个“反馈入库”接口巡检人员在看板上一键标记“这条检测结果不对”图片和错误标注就自动进入待复核队列。每个月我手工复核一次这些负样本把有代表性的子集混入原始训练集增量训练5-10轮生成新模型替换旧模型。这个机制跑了两轮迭代之后某条新路段的首次巡检漏检率下降了约35%效果非常明显。这里有一条很重要的经验增量训练时新数据权重要比旧数据稍高。我用加载旧模型继续训练的方案数据集里新样本的重复采样倍数设为旧样本的2倍避免模型在大量旧样本的惯性下“忽视”新缺陷模式。这系统我做下来最大的感受是真正决定项目成败的往往不在模型结构本身而在于你有没有把一个环节完整打通。模型训练技术再高数据采集和标注不规范指标就是虚的服务接口写得再好看板不符合使用者的操作习惯人家还是去用Excel。把图像采集、模型训练、接口封装、展示看板、反馈迭代这整条链跑顺了路面缺陷监测这件事才算真正落地。后面再扩展无非是多加几类缺陷标签、接入无人机巡检数据、或者把检测结果和GIS地图联动做路网级热力图底层这套架构不用动加模块就行。