简介:这份资源面向计算机视觉方向的本科毕业生与深度学习入门者,提供了一套可直接运行的道路坑洞检测完整方案,用于解决路面病害识别类毕业设计或课程项目从零搭建困难的问题。压缩包共4个文件,约30.75MB,包含Python测试脚本、YOLOv8权重模型、演示视频与使用说明文档,覆盖从环境配置到推理验证的主要环节。其中脚本负责加载模型并对图像或视频执行检测,权重文件可直接调用省去训练成本,演示视频直观展示坑洞框选与置信度输出效果,说明文档则交代运行依赖与参数调整要点。目前已有283人学习下载,适合希望快速复现YOLOv8目标检测流程、理解道路坑洞识别原理并在此基础上做二次开发或论文实验的读者参考。
1. 道路坑洞检测毕设:从跑通到讲清楚的完整路径
道路坑洞检测这个题目,每年毕业季都会被大量同学选中。原因很直接:数据好找、场景直观、YOLOv8 开箱即用,而且答辩时老师一眼就能看懂你在做什么。但真正动手之后,翻车点往往不在模型本身,而在环境配置、数据标注格式、推理参数和结果呈现这几件事上。我见过太多人卡在 CUDA 版本不匹配、标签文件路径写错、测试视频跑出来框全飘这些环节。这篇笔记围绕「基于 YOLOv8 实现道路坑洞检测」这条主线,把环境搭建、数据准备、训练调参、推理验证、结果可视化到常见坑位全部拆开讲一遍。适合正在做计算机、电子信息、物联网方向毕设的本科生,也适合想快速验证一个检测方案能不能落地的工程师。读完你至少能做到:本地跑通训练、用测试视频验证效果、把损失曲线和热力图画出来放进论文。
2. 环境配置与 YOLOv8 安装:把地基打牢再谈训练
2.1 为什么推荐 conda 而不是直接 pip
YOLOv8 依赖 PyTorch,而 PyTorch 又和 CUDA 驱动版本强绑定。直接用系统 Python 的 pip 装,很容易出现 torch 装上了但 torch.cuda.is_available() 返回 False 的情况。conda 的好处是能把 Python 版本、CUDA runtime、cuDNN 锁在一个独立环境里,不和系统里其他项目打架。我一般会建一个 Python 3.9 或 3.10 的环境,这两个版本和 ultralytics 的兼容性最稳。Python 3.11 以上偶尔会遇到某些依赖包还没出对应 wheel 的情况,新手没必要去踩这个坑。
# 创建独立环境,Python 版本建议 3.9 或 3.10 conda create -n pothole python=3.10 -y conda activate pothole # 安装 PyTorch,注意 CUDA 版本要和驱动匹配 # 驱动是 11.8 就装 cu118,驱动是 12.1 就装 cu121 pip install torch torchvision --index-url https://download.pytorch.org/whl/cu118 # 安装 ultralytics pip install ultralytics # 验证 GPU 是否可用 python -c "import torch; print(torch.cuda.is_available())"上面这段里,--index-url指定 PyTorch 官方 wheel 源,比默认源快很多。最后一行验证如果输出 True,说明 GPU 可用;输出 False 就说明 CUDA 版本对不上,需要回退重装。GTX 1660 Ti 这类显卡用 cu118 完全够,没必要追最新。
2.2 目录结构怎么摆才不乱
毕设代码最怕的就是文件到处扔,过两周自己都找不到。我习惯按下面这个结构组织,训练、推理、测试视频、输出结果各归各位:
pothole_project/ ├── datasets/ │ └── pothole/ │ ├── images/ │ │ ├── train/ │ │ └── val/ │ ├── labels/ │ │ ├── train/ │ │ └── val/ │ └── pothole.yaml ├── weights/ │ └── yolov8n.pt ├── runs/ ├── test_videos/ │ └── road_test.mp4 └── train.pydatasets放数据,weights放预训练权重,runs是训练输出目录,test_videos放测试视频。这个结构不是强制的,但后面写 yaml 配置文件时路径引用会清晰很多。
2.3 数据集 yaml 文件的写法
YOLOv8 训练时需要一个 yaml 文件告诉它数据在哪、有几类。道路坑洞检测通常就一类,写起来很简单:
# pothole.yaml path: ./datasets/pothole # 数据集根目录 train: images/train # 训练集图片相对路径 val: images/val # 验证集图片相对路径 nc: 1 # 类别数,坑洞就 1 类 names: ['pothole'] # 类别名称path是根目录,train和val是相对于根目录的路径。这里最容易出错的是路径层级:如果你的 yaml 放在 datasets/pothole/ 下面,path 写./datasets/pothole就可能重复拼接。稳妥做法是 path 写绝对路径,或者把 yaml 放在项目根目录、path 写相对根目录的路径。训练报 “No labels found” 或者 “Dataset not found”,九成是这里路径没对上。
3. 数据标注与格式转换:VOC 转 YOLO 的脚本和四个边界坑
3.1 标注工具选哪个
LabelImg 是最常见的选择,输出 Pascal VOC 格式的 XML 文件。也有同学用 Labelme 或 Roboflow 在线标注。不管用哪个,最终都要转成 YOLO 需要的 txt 格式:每行是类别id 中心x 中心y 宽 高,且坐标全部归一化到 0 到 1 之间。这个转换过程看起来简单,但边界情况不少。
3.2 VOC 转 YOLO 的转换脚本
import os import xml.etree.ElementTree as ET def voc_to_yolo(xml_dir, txt_dir, classes): """ xml_dir: VOC 标注文件夹 txt_dir: 输出 YOLO 标签文件夹 classes: 类别名称列表,如 ['pothole'] """ os.makedirs(txt_dir, exist_ok=True) for xml_file in os.listdir(xml_dir): if not xml_file.endswith('.xml'): continue tree = ET.parse(os.path.join(xml_dir, xml_file)) root = tree.getroot() size = root.find('size') w = int(size.find('width').text) h = int(size.find('height').text) lines = [] for obj in root.iter('object'): cls_name = obj.find('name').text if cls_name not in classes: continue cls_id = classes.index(cls_name) bbox = obj.find('bndbox') xmin = float(bbox.find('xmin').text) ymin = float(bbox.find('ymin').text) xmax = float(bbox.find('xmax').text) ymax = float(bbox.find('ymax').text) # 边界裁剪,防止坐标越界 xmin = max(0, min(xmin, w)) xmax = max(0, min(xmax, w)) ymin = max(0, min(ymin, h)) ymax = max(0, min(ymax, h)) # 归一化并计算中心点 cx = (xmin + xmax) / 2.0 / w cy = (ymin + ymax) / 2.0 / h bw = (xmax - xmin) / w bh = (ymax - ymin) / h lines.append(f"{cls_id} {cx:.6f} {cy:.6f} {bw:.6f} {bh:.6f}") txt_name = xml_file.replace('.xml', '.txt') with open(os.path.join(txt_dir, txt_name), 'w') as f: f.write('\n'.join(lines)) if __name__ == '__main__': voc_to_yolo('./annotations', './labels', ['pothole'])这段脚本的核心逻辑是:读 XML 里的宽高和 bbox 坐标,先做边界裁剪,再归一化。max(0, min(xmin, w))这行是防止标注时手抖把坐标拖到图片外面。归一化用.6f保留六位小数,精度足够。转换完记得抽查几个 txt 文件,确认坐标都在 0 到 1 之间。
3.3 四个容易踩的边界坑
第一个坑是图片和标签文件名不一致。YOLO 是按文件名去匹配图片和标签的,001.jpg必须对应001.txt。转换后建议写个脚本检查一遍差集。
第二个坑是空标签文件。有些图片里没有坑洞,转换后会生成空 txt。这本身没问题,YOLO 会把它们当负样本。但如果你的数据集里负样本太多,模型会偏向于不检测,需要控制比例。
第三个坑是类别名大小写。XML 里写的是Pothole,yaml 里写的是pothole,转换时就会因为匹配不上而跳过所有目标,最后得到一堆空标签。统一用小写最省事。
第四个坑是图片旋转信息。手机拍的图片可能带 EXIF 旋转标记,标注工具显示的是旋转后的图,但实际像素没转。训练时读到的图和标注坐标就对不上。处理办法是用 PIL 读一遍重新保存,把 EXIF 信息抹掉。
4. 训练调参与损失曲线:让模型真正学到坑洞特征
4.1 训练命令和关键参数
YOLOv8 的训练入口很简洁,一条命令就能跑:
yolo detect train \ data=datasets/pothole/pothole.yaml \ model=weights/yolov8n.pt \ epochs=100 \ imgsz=640 \ batch=16 \ lr0=0.01 \ patience=20 \ project=runs/train \ name=pothole_expmodel选 yolov8n 还是 yolov8s 取决于你的显卡。GTX 1660 Ti 6G 显存跑 yolov8n 加 batch=16 没问题,跑 yolov8s 可能要降到 batch=8。epochs=100是起步值,坑洞数据集一般 100 到 200 轮够用。patience=20表示 20 轮验证指标不提升就早停,省时间。lr0=0.01是初始学习率,如果 loss 震荡厉害可以降到 0.001。
4.2 损失曲线怎么画才像样
训练完成后,runs/train/pothole_exp/ 下面会有 results.csv,记录了每轮的 box_loss、cls_loss、dfl_loss 和 mAP。论文里通常要画损失曲线和 mAP 曲线,用 matplotlib 几行就能搞定:
import pandas as pd import matplotlib.pyplot as plt df = pd.read_csv('runs/train/pothole_exp/results.csv') df.columns = df.columns.str.strip() # 去掉列名空格 fig, axes = plt.subplots(1, 2, figsize=(12, 4)) # 损失曲线 axes[0].plot(df['epoch'], df['train/box_loss'], label='box_loss') axes[0].plot(df['epoch'], df['train/cls_loss'], label='cls_loss') axes[0].set_xlabel('Epoch') axes[0].set_ylabel('Loss') axes[0].legend() axes[0].set_title('Training Loss') # mAP 曲线 axes[1].plot(df['epoch'], df['metrics/mAP50(B)'], label='mAP@0.5') axes[1].plot(df['epoch'], df['metrics/mAP50-95(B)'], label='mAP@0.5:0.95') axes[1].set_xlabel('Epoch') axes[1].set_ylabel('mAP') axes[1].legend() axes[1].set_title('Validation mAP') plt.tight_layout() plt.savefig('training_curves.png', dpi=300)df.columns.str.strip()这行很关键,results.csv 的列名有时带前后空格,不处理会 KeyError。保存用 dpi=300,论文打印出来才清晰。正常情况 box_loss 和 cls_loss 应该稳步下降,mAP 稳步上升。如果 loss 不降反升,检查学习率是不是太大;如果 mAP 一直上不去,检查标注质量。
4.3 数据增强参数怎么调
YOLOv8 默认开启 mosaic、HSV 抖动、随机翻转等增强。坑洞检测里,mosaic 增强能提升小目标检测能力,但坑洞通常占画面比例不小,mosaic 太强反而可能让模型学到不真实的拼接场景。我一般会把 mosaic 关掉或降低概率:
yolo detect train \ data=datasets/pothole/pothole.yaml \ model=weights/yolov8n.pt \ epochs=100 \ imgsz=640 \ batch=16 \ mosaic=0.5 \ hsv_h=0.015 \ hsv_s=0.7 \ hsv_v=0.4 \ degrees=0.0 \ translate=0.1 \ scale=0.5mosaic=0.5表示一半概率启用。degrees=0.0关掉旋转增强,因为道路坑洞的方向在真实场景里基本固定,旋转反而引入噪声。scale=0.5允许缩放,模拟不同距离拍摄。这些参数没有绝对标准,根据验证集表现微调就行。
5. 推理验证与可视化:测试视频、热力图和常见翻车排查
5.1 用测试视频跑推理
训练完拿到 best.pt,直接对测试视频推理:
yolo detect predict \ model=runs/train/pothole_exp/weights/best.pt \ source=test_videos/road_test.mp4 \ conf=0.25 \ iou=0.45 \ save=True \ project=runs/predict \ name=video_resultconf=0.25是置信度阈值,低于这个值的框不显示。iou=0.45是 NMS 的 IoU 阈值,控制重叠框合并。输出视频在 runs/predict/video_result/ 下面。如果框太多太杂,提高 conf 到 0.4 或 0.5;如果漏检严重,降低到 0.15 试试。
5.2 热力图可视化怎么做
论文里加一张热力图会显得工作更完整。YOLOv8 本身不直接提供热力图接口,但可以用 Grad-CAM 的思路对 backbone 最后一层做可视化。常见做法是装 pytorch-grad-cam 库,然后写一个包装类:
from pytorch_grad_cam import GradCAM from pytorch_grad_cam.utils.image import show_cam_on_image import cv2 import numpy as np from ultralytics import YOLO model = YOLO('runs/train/pothole_exp/weights/best.pt') # 取 backbone 最后一层作为目标层 target_layers = [model.model.model[-2]] # 读取图片 img = cv2.imread('test.jpg') img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) / 255.0 # 构造 cam cam = GradCAM(model=model.model, target_layers=target_layers) grayscale_cam = cam(input_tensor=...)[0] visualization = show_cam_on_image(img_rgb, grayscale_cam, use_rgb=True) cv2.imwrite('heatmap.jpg', cv2.cvtColor(visualization, cv2.COLOR_RGB2BGR))这段代码里model.model.model[-2]取的是 YOLOv8 backbone 的倒数第二层,具体层号可以打印模型结构确认。热力图能直观展示模型关注区域,答辩时是个加分项。注意 Grad-CAM 对检测模型的支持不如分类模型完善,可能需要调整 target_layers 才能出效果。
5.3 常见翻车排查
现象一:推理视频里框全飘,位置完全不对。原因通常是训练时 imgsz 和推理时不一致,或者图片预处理时 resize 方式不同。解决:训练和推理都用 imgsz=640,不要一个 640 一个 320。
现象二:训练 loss 正常下降,但验证 mAP 一直是 0。原因大概率是标签格式错误,比如坐标没归一化、类别 id 从 1 开始而不是 0。解决:打开一个 txt 标签,确认坐标都在 0 到 1 之间,类别 id 是 0。
现象三:显存爆了,报 CUDA out of memory。原因 batch 太大或 imgsz 太大。解决:batch 降到 8 或 4,imgsz 降到 416 试试。GTX 1660 Ti 6G 显存跑 640 加 batch=16 是极限,再大就爆。
现象四:训练速度极慢,一个 epoch 要十几分钟。原因可能是没用 GPU,或者 dataloader workers 设太少。解决:确认 torch.cuda.is_available() 是 True,训练时加 workers=4 或 8。
现象五:测试视频跑出来只有几帧有框。原因可能是视频帧率太高,模型推理速度跟不上,或者 conf 阈值太高。解决:降低 conf 到 0.15,或者用 vid_stride=2 跳帧处理。
6. 从毕设到落地:模型导出、RK3588 部署思路和答辩技巧
6.1 模型导出为 ONNX
如果答辩要求展示部署能力,或者你想把模型跑到边缘设备上,导出 ONNX 是第一步:
yolo export model=runs/train/pothole_exp/weights/best.pt format=onnx opset=12 simplify=Trueopset=12兼容性最好,simplify=True会做图优化。导出后在同目录得到 best.onnx,可以用 onnxruntime 验证推理是否正常。这一步做完,模型就不再依赖 ultralytics 库了。
6.2 RK3588 部署的大致路径
RK3588 是这几年毕设里很热门的边缘计算平台,NPU 算力够跑 YOLOv8n。部署路径通常是:ONNX 转 RKNN,用 rknn-toolkit2 在 PC 上转换,再用 rknn-toolkit-lite2 在板子上推理。转换时要注意量化方式,默认的 int8 量化可能掉点,可以先用 fp16 验证精度,再尝试 int8。输入尺寸要和训练时一致,预处理里的归一化和通道顺序也要对齐。这块展开能写一整篇,核心思路就是:ONNX 做中间格式,RKNN 做最终部署,精度和速度之间做权衡。
6.3 答辩时怎么讲清楚
答辩老师最常问的三个问题:为什么选 YOLOv8、数据集怎么来的、mAP 多少。第一个问题可以从速度和精度平衡角度答,YOLOv8n 在 1660 Ti 上能跑到实时,适合道路巡检场景。第二个问题如实说,自己标注或从公开数据集整理,强调标注规范和数量。第三个问题直接给数字,mAP@0.5 通常能到 0.85 以上,如果偏低就解释数据集难度和优化方向。
我自己的习惯是,答辩前把训练曲线、热力图、测试视频结果各准备一张图,老师问到哪个直接切过去。比干讲参数有说服力得多。另外,代码一定要能当场跑通推理,哪怕只是跑一张图。我见过有人答辩时环境崩了,场面很尴尬。提前在答辩用的电脑上把环境配好,权重和测试图片放在固定路径,写一个一键推理脚本,这是后悔药。
希望帮到你。
本文还有配套的精品资源,点击获取