简介:一套基于YOLOv8的交通道路标线磨损监测系统,面向目标检测、深度学习的毕设与课程设计场景,也适合计算机相关专业学生快速上手实战。资源包含完整源码、可视化交互界面、标注数据集与部署说明,运行环境简单,下载后按README提示即可复现训练和检测流程。包内共8个文件,主要包括3个Python脚本(可视化界面、视频检测、模型训练)、3个模型权重文件(yolov8n.pt、best.pt、yolo11n.pt)以及2个说明文档,压缩包整体约15.91MB,结构清晰、便于按需调用。目前已有53人学习下载。代码经测试稳定运行,可输出混淆矩阵、F1分数曲线、精确率-召回率曲线、验证集预测结果等核心评估图表,能直观支撑毕设答辩展示;同时提供数据集和训练权重,支持二次修改以适配其他检测任务。整体属于拿来即用的完整方案,对毕设、课设或项目初期演示都有较高参考价值。
1. 道路标线磨损监测系统:一个开箱即用的 YOLOv8 毕设选题
城市道路的白色实线、斑马线和导向箭头被车轮反复碾压后,会先出现裂纹、再成片剥落,最终只剩断断续续的残迹。养护单位目前大多靠人工开车巡检或翻监控截图,效率低、漏报多。这套《基于YOLOv8的交通道路标线磨损监测系统》解决的就是这件事:用 YOLOv8 训练一个能识别磨损标线的检测模型,再套一个可视化界面,让不懂代码的人也能上传图片、视频直接看到检测结果。系统附带源码、界面、数据集和部署教程,下载后在本地把环境搭起来就能跑,很适合毕设或课程设计收尾。下面我把这套系统的完整落地路径拆开讲。
2. 先搞清检测目标:磨损标线的视觉难点与 YOLOv8 选型理由
2.1 磨损标线和普通目标检测的差异在哪
常见的目标检测数据集里,目标是"完整"的:人、车、猫、狗都有清晰的轮廓和边界。磨损标线恰恰相反,它是"残缺的目标"——一段白色实线磨掉一半之后,剩下的部分边缘参差不齐,和路面裂缝、污水痕迹、树影混在一起,人眼都要靠近看才能辨认。
这带来三个具体问题。第一是边界模糊,标注员在 labelme 里画框时,框多大算磨损、多大算正常,主观性很强,导致同一张图不同人标注差异明显;第二是小目标占比高,标线是细长条,磨损区域往往只有几十个像素宽,在 640×640 的输入尺寸下很容易被下采样丢掉;第三是光照变化大,逆光、树荫、夜间车灯照射下,磨损区域和阴影的表现几乎一样。
所以这类项目不能直接拿 COCO 预训练模型就完事,必须针对"残缺、细长、低对比度"做数据和训练策略上的调整。
2.2 类别定义与数据集目录结构
类别怎么定,直接决定训练难度。见过有人把类别细分成"轻度磨损""中度磨损""重度磨损",训练出来效果普遍不行——三个类之间边界太模糊,连人都分不清,模型 AP 自然上不去。
我一般建议二分类起手:
- 0:worn_marking(磨损标线)
- 1:intact_marking(完整标线)
加"完整标线"这个类不是多余的。磨损检测是单分类时,模型容易把完整标线也框出来,因为两者在底特征上是连续过渡的;把完整类作为负样本参与训练,模型才会去学"残缺"这个判别特征。如果只想检测磨损,训练后推理时只输出 worn_marking 即可。
数据集目录结构按 Ultralytics 约定组织:
dataset/ ├── images/ │ ├── train/ # 训练集图片 │ └── val/ # 验证集图片(建议再从 train 里抽 10% 做 test) ├── labels/ │ ├── train/ # 与图片同名的 .txt 标注文件 │ └── val/ └── data.yaml # 类别名和路径配置data.yaml 内容如下:
path: /path/to/dataset train: images/train val: images/val names: 0: worn_marking 1: intact_marking注意path最好写绝对路径,YOLO 训练时相对路径在跨平台迁移时经常出问题;Windows 和 Linux 路径分隔符不通用,写完 data.yaml 后先跑一次yolo detect train的 dry-run 确认路径无报错。
2.3 拿到数据集先做的三件事:统计、清洗、增强
拿到"完整数据集"之后,先别急着训练。我一般先跑三段脚本做检查:
第一,统计类别数量,看是否有严重不平衡。磨损类样本数量最好是完整类的 1 到 2 倍,如果某类只有几十张,训练时该类 AP 基本起不来。
第二,统计标注框的分辨率分布。用下面这段脚本算出所有 GT 框的宽高占比。
import os import numpy as np img_w, img_h = 1280, 720 # 按实际训练图片尺寸改 areas = [] for f in os.listdir("labels/train"): if not f.endswith(".txt"): continue with open(os.path.join("labels/train", f)) as fp: for line in fp: cls, xc, yc, w, h = map(float, line.split()) areas.append(w * h) # 归一化面积 areas = np.array(areas) print(f"框数量: {len(areas)}, 中位面积占比: {np.median(areas):.4f}") print(f"小框(<0.01)占比: {(areas < 0.01).mean():.2%}")如果小框占比超过 30%,训练时就要考虑把imgsz提到 960 或 1280,或者直接用切片推理;否则模型会在小目标上大量漏检。
第三,做数据增强。磨损检测推荐这组参数,在 data.yaml 同级目录的 hyp.yaml 里配置:
hsv_h: 0.02 # 色相轻微扰动,避免路面颜色偏移干扰 hsv_s: 0.6 # 饱和度扰动,模拟不同光照 hsv_v: 0.5 # 亮度扰动,逆光和阴影场景 degrees: 15.0 # 旋转,箭头方向多样 flipud: 0.3 # 垂直翻转,考虑桥上俯拍视角 mosaic: 0.8 # 前中期开启,后期建议调低Mosaic 增强对磨损检测是双刃剑:它能提升小目标鲁棒性,但会把磨损碎片和完整标线拼在一起,让模型学到错误的上下文。我习惯的做法是前 60% epoch 用 mosaic=0.8,后 40% 降到 0.2,让模型在接近真实分布的图上收敛。
3. YOLOv8 训练磨损模型:环境、格式转换与参数整定
3.1 CPU 环境搭建:Ubuntu 20.04 与 Windows 都能跑
这套系统标称"简单部署即可运行",环境搭建就是第一道关卡。没有 NVIDIA 显卡的机器完全能训能跑,只是速度慢,下面给出 CPU 环境的标准做法。
conda create -n yolov8 python=3.9 -y conda activate yolov8 pip install ultralytics pip install labelme如果 pip 下载慢,换国内 PyPI 镜像:
pip install -i https://pypi.tuna.tsinghua.edu.cn/simple ultralytics装完验证:
yolo predict model=yolov8n.pt source=https://ultralytics.com/images/bus.jpgCPU 机器上第一次跑会先下载预训练权重,几十 MB 很快。出了检测结果图说明环境通了。
有 NVIDIA 显卡的话,先确认驱动和 CUDA:
nvidia-smi python -c "import torch; print(torch.cuda.is_available())"返回 True 表示 PyTorch 能用 GPU,训练时加device=0即可。CPU 机器上该参数写device=cpu,不写也能自动降级。
这里有一个高频翻车点:conda 装完 ultralytics 后提示ImportError: libGL.so.1,是 opencv-python 缺系统库。Ubuntu 执行sudo apt install libgl1 libglib2.0-0解决,Windows 上一般不会遇到。
3.2 Labelme 标注转 YOLO 格式:转换脚本与四个边界坑
数据集里如果提供的是 labelme 的 JSON 格式,需要转成 YOLO 的 txt。转换脚本核心逻辑如下:
import json import os def labelme_to_yolo(json_path, img_w, img_h, class_map): with open(json_path, "r", encoding="utf-8") as fp: data = json.load(fp) lines = [] for shape in data["shapes"]: label = shape["label"] if label not in class_map: continue points = shape["points"] # [[x1,y1], [x2,y2], ...] xs = [p[0] for p in points] ys = [p[1] for p in points] xmin, xmax = min(xs), max(xs) ymin, ymax = min(ys), max(ys) w = (xmax - xmin) / img_w h = (ymax - ymin) / img_h xc = (xmin + xmax) / 2 / img_w yc = (ymin + ymax) / 2 / img_h lines.append(f"{class_map[label]} {xc:.6f} {yc:.6f} {w:.6f} {h:.6f}") txt_path = os.path.splitext(json_path)[0] + ".txt" with open(txt_path, "w") as fp: fp.write("\n".join(lines))注意几个必踩的坑:
- 一个 JSON 里可能有多条 shape,points 是多边形顶点数组,上面代码取所有顶点的最小外接矩形。凹多边形会框入大量背景,磨损检测这种低对比度目标尤其吃亏,标注时尽量用矩形框而不是精细多边形。
- 类别名必须和 class_map 的 key 完全一致,多一个空格都会导致该目标被丢弃,且不会报错。
- 如果数据集是单类(只有磨损),class_map 里的 id 也要写成 0,YOLO 类别编号从 0 开始。
- 图片路径带中文会出诡异问题:训练时图片能加载,但验证阶段画图时 OpenCV 报错。项目目录一律用英文。
3.3 训练命令与关键参数:imgsz、epochs、batch、device 怎么设
数据整理完,训练命令如下:
yolo detect train \ model=yolov8n.pt \ data=data.yaml \ imgsz=640 \ epochs=100 \ batch=16 \ device=cpu \ workers=2 \ patience=15参数说明:
model=yolov8n.pt:n 是 nano 版,参数最少,CPU 训练首选。显存够(8G 以上)可以换yolov8s.pt,mAP 通常能高 2~4 个点,但训练时间翻倍。imgsz=640:标线细长,640 会小幅压缩;如果你的 GT 框小目标占比高,提到 960。CPU 上跑 960 会很慢,用小目标切片推理更划算。batch=16:CPU 训练时显卡显存不影响,但内存不够会报 OOM。16G 内存的机器建议降到 8。patience=15:验证集指标连续 15 个 epoch 不涨就提前停,省时间。最后会用最好的权重而不是最后的权重。workers=2:Windows 上超过 4 经常卡在数据加载,2 最稳。
训练中断了想接着跑,不要重新执行上面的命令,用:
yolo detect train resume model=runs/detect/train/weights/last.ptresume会从断点继续,优化器状态也会保留,不会白跑前面的 epoch。
3.4 训练曲线与损失函数:怎么判断模型真的收敛了
训练结束后看runs/detect/train/目录下的results.png,里面有 loss 曲线、precision、recall、mAP 变化。重点看三行 loss:box_loss(定位)、cls_loss(分类)、dfl_loss(分布聚焦损失,YOLOv8 用来精修框边)。
磨损检测里,cls_loss和dfl_loss比box_loss重要。因为标注本身主观,框稍微偏一点不算大错;但如果分类 conf 值低,推理阶段置信度过不了阈值,就会漏报。
验证集指标是锯齿状震荡的,不用慌。小数据集上 val 曲线一跳一跳很正常,看最后 10 个 epoch 的平均趋势。再打开confusion_matrix.png检查:如果 worn_marking 大量被预测为 intact_marking,说明完整类样本太少了,回第 2.3 步做增强;如果误检背景,先检查标注框是不是框入了大片路面。
4. 把模型装进可视化界面:Gradio 与 PyQt5 两条路线
4.1 Gradio:三小时做出可演示的 Web 界面
毕设答辩和课程汇报场景,Gradio 是最省事的路线,一条命令起一个本地服务,支持图片上传和摄像头输入。界面代码如下:
import gradio as gr from ultralytics import YOLO model = YOLO("runs/detect/train/weights/best.pt") def detect(image, conf): results = model.predict(image, conf=conf, iou=0.45, verbose=False) annotated = results[0].plot() # 画框后的图像 boxes = results[0].boxes count = len(boxes) return annotated, f"检测到 {count} 处磨损标记" gr.Interface( fn=detect, inputs=[gr.Image(type="numpy"), gr.Slider(0.1, 0.8, value=0.25, label="置信度阈值")], outputs=[gr.Image(), gr.Textbox()], title="道路标线磨损监测", ).launch()results[0].plot()是 Ubuntu 训练环境下的 OpenCV 默认配色,黄绿色框在路面图上可读性不错,不用自己画。conf暴露成滑块是有意的——磨损标注主观,固定阈值容易被验证集带偏,答辩现场可以现场演示调阈值看效果变化,这比静态结果更有说服力。
如果演示现场没有网络,launch()默认监听 127.0.0.1,改成launch(server_name="0.0.0.0", server_port=7860)可以让同局域网的其他电脑访问,答辩时评委用手机扫码就能看。
4.2 PyQt5 桌面端:视频、摄像头和结果统计
Gradio 适合演示,但处理视频文件体验一般。要"可视化界面"做得像完整软件,PyQt5 是常见选择。核心问题是推理不能放主线程,否则视频播放会一卡一卡地翻车。典型做法是用 QThread 跑推理,信号回传结果:
class DetectWorker(QThread): frame_ready = pyqtSignal(object, object) def __init__(self, model_path, conf): super().__init__() self.model = YOLO(model_path) self.conf = conf self.running = True def run(self): cap = cv2.VideoCapture(self.video_path) while self.running and cap.isOpened(): ret, frame = cap.read() if not ret: break results = self.model.predict(frame, conf=self.conf, imgsz=640) annotated = results[0].plot() count = len(results[0].boxes) self.frame_ready.emit(annotated, count) cap.release()界面里用一个 QLabel 显示帧,QLabel 更新用setPixmap。这里的frame_ready信号里传了检测结果和计数,主界面刷新帧的同时更新"已检测磨损数量"的数值。
PyQt5 做视频处理有一个隐藏问题:results[0].plot()返回的是 BGR 数组,QLabel 显示前要转 RGB 并转 QImage。忘了转换的话,画面会整体偏蓝,这个坑几乎每个人都会踩一次。
4.3 封装推理逻辑:置信度怎么设、结果表格与导出
不管用哪种界面,推理逻辑建议封装成独立函数,方便调试也方便复用:
from ultralytics import YOLO import pandas as pd class MarkingDetector: def __init__(self, weights="best.pt", conf=0.25, iou=0.45): self.model = YOLO(weights) self.conf = conf self.iou = iou def predict_frame(self, frame): results = self.model.predict(frame, conf=self.conf, iou=self.iou, verbose=False)[0] rows = [] for box in results.boxes: x1, y1, x2, y2 = map(float, box.xyxy[0]) conf = float(box.conf[0]) cls = int(box.cls[0]) rows.append({ "x1": int(x1), "y1": int(y1), "x2": int(x2), "y2": int(y2), "confidence": round(conf, 3), "class": results.names[cls], }) return pd.DataFrame(rows), results.plot()这里的conf=0.25是 Ultralytics 默认值,但磨损检测建议降到 0.15~0.2。原因在于磨损区域天然模糊,模型输出的置信度普遍低于完整目标,卡 0.25 会大量漏检。真实场景宁可多框几个可疑区域,也不要在第一版本就追求精确。
结果表格导出 CSV 是毕设交付的加分项:
df, _ = detector.predict_frame(frame) df.to_csv("detect_result.csv", index=False, encoding="utf-8-sig")utf-8-sig是为了让 Excel 直接打开不乱码,这个细节在答辩演示时很讨喜。
5. 部署避坑:CPU 推理加速与五个高频翻车点
5.1 从 PyTorch 到 ONNX:CPU 机器上跑得更快
PyTorch 推理在 CPU 上速度不理想,一个 640×640 帧往往要 200~400ms。导出 ONNX 后用 onnxruntime 跑,通常能快 1.5~2 倍;再用 OpenVINO 的 EP,CPU 上还能再快一截。导出命令很简单:
yolo export model=runs/detect/train/weights/best.pt format=onnx imgsz=640 opset=12导出的best.onnx在同目录下。ONNX Runtime CPU 推理代码:
import onnxruntime as ort from ultralytics import YOLO session = ort.InferenceSession("best.onnx") model = YOLO("best.onnx") # ultralytics 直接支持 onnx 推理 results = model.predict(frame, conf=0.2, imgsz=640)注意 ultralytics 加载 ONNX 会自动走 onnxruntime,但首次推理有一段预热时间,视频流场景要做一次空帧预热,否则前几帧卡顿明显。
如果后期想把系统部署到边缘盒子(比如 RK3588),ONNX 也是必经之路——瑞芯微的工具链只认 ONNX 转换输入,不直接吃 PyTorch 权重。先导出 ONNX 再转换,能少踩很多坑。
5.2 高频踩坑记录:现象、原因、解决
第一条,训练 loss 前几轮下降后就不再动了,最后 mAP@0.5 只有 0.3 左右。原因多半是标注数据里混入了大量错误框,比如把完整标线的阴影当成磨损框进去,模型学到的特征不纯。解决:抽 50 张训练图,把 GT 框画在原图上人工逐张过一遍,删掉明显错框和漏标严重的图。这个过程枯燥,但是提点最有效的一步。
第二条,磨损检测框特别大,经常把一整条标线框住。原因是 labelme 转 YOLO 格式时,多边形顶点取了整段标线的最小外接矩形,框内既包含磨损又包含完整段。解决:转格式脚本里按线段 seg 分组,每段独立生成框;稍微放宽 NMS 的 iou 阈值到 0.5,避免相邻框被并掉。
第三条,CPU 训练一个 epoch 要 40 分钟。原因是原始图片是 4K 监控截图,虽然写了imgsz=640,但解码和缩放开销仍在原图上。解决:训练前先把图片统一 resize 到 1280×720 再入数据集,内存占用和读取速度都会有数量级提升。公开的路面数据集很多是 5472×3648 的大图,这一条几乎必踩。
第四条,PyQt5 界面视频播着播着界面假死。原因是推理放在主线程,阻塞了事件循环。解决思路就是 4.2 里那样用 QThread 分离推理;如果 QThread 还卡,检查是不是把frame_ready信号连到了耗时的 UI 刷新操作上,信号处理函数里只做画图,不做 I/O。
第五条,模型把路面上的人影、轮胎印误检为磨损。原因是训练集全是白天顺光图,缺乏逆光和阴影样本。解决:从监控视频里抽不同时段帧补数据,重点补早晚低角度光照的图;增强配置里把hsv_v亮度扰动从 0.5 提到 0.7,模型对光照变化的鲁棒性会明显改善。
6. 不依赖测试集的三招坏图实测法
训练集和验证集都来自同一个数据分布,mAP 再高,换一批真实监控画面也可能翻车。我习惯在项目收尾时用三个土办法实测,不写代码也能操作,但能暴露绝大多数问题。
第一招,直接找一段没参与训练的路口监控视频,按秒截 30 帧,打印出来,让一个没做过标注的人逐张数出"你认为磨损的位置",再和模型输出对比。人工数出来的磨损数量就是召回率的分母,模型检出的数量是分子。这个办法能直观暴露漏检率,比看 PR 曲线更有说服力。我一般要求视频里至少含一个弯道、一个路口、一段树荫覆盖的路段,覆盖不了的环境就是模型的盲区。
第二招,把同一张图做不同程度的亮度、对比度扰动,跑模型看检测框是否稳定。做法很朴素:用手机相册自带的调光工具拉出暗、正常、过曝三张,分别跑界面,统计检测框数量差异。如果暗图直接漏检,说明训练时的亮度增强不够;如果过曝图出现大量误检,说明模型对高光路面还没有形成抑制。
第三招,换一个看不到训练集采集路口的摄像头视角。交通标线检测最考验泛化的就是视角变化——俯拍 vs 平视、顺光 vs 逆光,同一段磨损标线在两种视角下完全是两种视觉表现。我会带一张打印的测试图到医院、停车场这种不同路面环境测一遍,看起来简陋,但每次都能测出几个预想不到的误检。
这三招测试的结果我会存成一个badcase文件夹,后续每次调参都重新跑一遍。毕设答辩时把这个文件夹展示出来,解释每个测试图的失败原因和对应调整,比念 mAP 数字有用得多。一套系统交付的关键不是跑得多快,而是你清楚它会在哪些场景下失效,这份边界认知才是毕设里最值钱的部分。希望帮到你。
本文还有配套的精品资源,点击获取