简介:这份资源面向计算机视觉学习者与道路安全检测方向的开发者,提供一套基于YOLOv8实现路面坑洼识别的完整Python项目,适合具备一定深度学习基础、希望动手实践目标检测全流程的中高级用户。压缩包共10个文件,约170.66MB,包含4个py脚本、2个pt权重文件、1个txt依赖清单、1个mp4演示视频、1个md说明文档和1个png效果图,覆盖数据预处理、模型训练、推理与后处理等环节。项目围绕坑洼特征标注、YOLOv8训练、特征提取、非极大值抑制及mAP评估展开,配套说明文档梳理了数据集信息、参数设置与结果分析,训练好的权重可直接用于检测。目前已有423人学习下载,读者可借此理解从数据准备到模型部署的完整链路,并迁移至交通标志识别、路面破损评估等相似任务。
1. 路面坑洼检测为什么值得用 YOLOv8 重做一遍
市政巡检车每天跑几十公里,回传的影像动辄上万帧,靠人眼在监控室里逐帧找坑洼,漏检率高得离谱,而且同一个坑在连续帧里被反复标记,统计口径完全乱套。这正是「基于 YOLOv8 实现的路面坑洼检测方法系统」要解决的问题:把坑洼当成目标检测任务,用 YOLOv8 在自建路面数据集上训练,输出带类别和置信度的边界框,再叠加去重和面积估算,形成可落地的巡检报告。它适合三类人:做智慧交通/市政方向的学生和工程师、手里有行车记录仪或巡检影像想跑通检测的开发者、以及需要把模型部署到边缘盒子(比如 RK3588 这类平台)的落地团队。整套东西的核心不是模型多新,而是数据标注规范、训练参数和推理后处理这三件事能不能对齐真实路况。
2. 从数据集到 YOLOv8 训练:一条能跑通的链路
2.1 路面坑洼数据集怎么标才不返工
坑洼检测翻车,八成翻在标注上。路面影像里坑洼边界模糊,水渍、修补痕迹、井盖阴影都容易被误标成坑洼。我一般会先定一份标注规范再动手:只标有明显凹陷或破损、且面积大于图像短边 1% 的区域;边界框贴紧破损外沿,不把周围裂纹圈进去;对反光水面下的疑似坑洼,标记为ignore区域而不是硬标成负样本。类别上建议先只保留两类——pothole(坑洼)和patch(修补块),修补块和坑洼在视觉上高度相似,分开标能显著降低误检。
标注工具用 LabelImg 或 X-AnyLabeling 都行,导出 YOLO 格式。YOLO 格式每行是class_id x_center y_center width height,全部归一化到 0~1。这里有个血泪经验:不同标注人导出的坐标精度不一致,有人保留 6 位小数有人保留 2 位,合并时会出现框偏移。统一用脚本重写一遍,保留 6 位小数。
import os def normalize_label_file(label_path): """把 YOLO 标签统一成 6 位小数,避免多人标注精度不一致""" with open(label_path, 'r') as f: lines = f.readlines() out = [] for line in lines: parts = line.strip().split() if len(parts) != 5: continue # 跳过空行或异常行 cls_id = parts[0] coords = [f"{float(v):.6f}" for v in parts[1:]] out.append(" ".join([cls_id] + coords)) with open(label_path, 'w') as f: f.write("\n".join(out)) # 批量处理 labels 目录 for root, _, files in os.walk("datasets/labels"): for name in files: if name.endswith(".txt"): normalize_label_file(os.path.join(root, name))这段脚本做的是格式归一化,:.6f控制小数位,len(parts) != 5用来过滤掉标注工具偶尔写出的空行。跑完记得抽查几个文件,确认类别 id 没有越界。
2.2 目录结构与 data.yaml 的四个必填项
YOLOv8 对目录结构有约定,常见做法是 images 和 labels 平行放置,train/val 分开:
datasets/ images/ train/ val/ labels/ train/ val/data.yaml里四个字段必须写对:path指向数据集根目录,train/val写相对路径,nc是类别数,names是类别名列表。很多人path写成绝对路径后换机器就报找不到文件,建议用相对路径并在训练命令里cd到项目根目录。
path: ./datasets train: images/train val: images/val nc: 2 names: ['pothole', 'patch']提示:
nc和names长度必须一致,否则训练启动时会直接抛索引错误,这个报错信息很不直观,容易卡半天。
2.3 用命令行跑通第一次训练
环境配置是新手最容易卡住的地方。Python 建议 3.8~3.10,先装 PyTorch(按显卡 CUDA 版本选),再装 ultralytics。GTX1660Ti 这类 6G 显存卡跑 YOLOv8n 完全够用,batch 设 8~16。
# 创建环境并安装依赖 conda create -n pothole python=3.10 -y conda activate pothole pip install torch torchvision --index-url https://download.pytorch.org/whl/cu118 pip install ultralytics opencv-python numpy # 开始训练 yolo detect train \ model=yolov8n.pt \ data=./data.yaml \ epochs=100 \ imgsz=640 \ batch=16 \ lr0=0.01 \ patience=20 \ project=runs/pothole \ name=exp1参数说明:model=yolov8n.pt用官方预训练权重做迁移学习,小数据集上比从头训收敛快得多;imgsz=640是输入分辨率,坑洼目标偏小可以提到 960,但显存和速度会明显变差;lr0=0.01是初始学习率,数据量小于 2000 张时建议降到 0.005;patience=20表示 20 轮验证指标不提升就早停,省时间。训练完在runs/pothole/exp1/weights/下拿到best.pt和last.pt,部署用best.pt。
2.4 损失曲线怎么看才算正常
训练日志里box_loss、cls_loss、dfl_loss三条曲线是判断训练是否健康的黑匣子。正常情况是前 10 轮快速下降,之后缓慢收敛并伴随小幅震荡。如果cls_loss一直不降,多半是类别不平衡或标注里类别 id 写错;如果box_loss下降但mAP不涨,通常是验证集和训练集分布差异太大,比如训练集全是晴天、验证集全是雨天。用 ultralytics 自带的results.csv画曲线最省事:
import pandas as pd import matplotlib.pyplot as plt df = pd.read_csv("runs/pothole/exp1/results.csv") df.columns = df.columns.str.strip() # 列名常带空格,必须清理 plt.plot(df["epoch"], df["train/box_loss"], label="box_loss") plt.plot(df["epoch"], df["train/cls_loss"], label="cls_loss") plt.plot(df["epoch"], df["metrics/mAP50(B)"], label="mAP50") plt.legend() plt.xlabel("epoch") plt.savefig("loss_curve.png", dpi=150)df.columns.str.strip()这行别省,ultralytics 导出的列名前后有空格,直接按名字取会 KeyError。
3. 推理、后处理与坑洼面积估算
3.1 单张与批量推理的最小代码
训练完只是拿到权重,真正出结果靠推理。单张图推理用model.predict,批量处理视频或图片目录用循环加stream更省内存。
from ultralytics import YOLO import cv2 model = YOLO("runs/pothole/exp1/weights/best.pt") # 单张推理 results = model.predict("test.jpg", conf=0.35, iou=0.5, imgsz=640) for r in results: for box in r.boxes: cls_id = int(box.cls) conf = float(box.conf) x1, y1, x2, y2 = map(int, box.xyxy[0]) label = f"{model.names[cls_id]} {conf:.2f}" cv2.rectangle(r.orig_img, (x1, y1), (x2, y2), (0, 255, 0), 2) cv2.putText(r.orig_img, label, (x1, y1 - 5), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 0), 2) cv2.imwrite("result.jpg", results[0].orig_img)conf=0.35是置信度阈值,路面场景建议 0.3~0.4,太低会把水渍误检成坑洼;iou=0.5是 NMS 的 IoU 阈值,坑洼密集时调到 0.6 能减少相邻框被误删。box.xyxy[0]拿的是左上右下坐标,画框前转 int,否则 OpenCV 会报类型错误。
3.2 连续帧去重:同一个坑别数三遍
巡检视频里同一个坑会在连续几十帧里被检出,直接统计数量会严重虚高。常见做法是引入简单的跟踪或基于位置的去重:记录已确认坑洼的中心坐标,新检测框中心落在已有坑洼半径内就合并,只保留置信度最高的那次。
import math confirmed = [] # 存 (cx, cy, conf) def dedup(cx, cy, conf, radius=80): """中心距离小于 radius 视为同一个坑,保留高置信度""" for i, (px, py, pc) in enumerate(confirmed): if math.hypot(cx - px, cy - py) < radius: if conf > pc: confirmed[i] = (cx, cy, conf) return False confirmed.append((cx, cy, conf)) return Trueradius=80是像素阈值,跟分辨率和车速强相关:分辨率 1080p、车速 30km/h 时,80 像素大约对应 1.5 米,基本能覆盖同一个坑在相邻帧的位移。车速快或分辨率低就调大,反之调小。这个方案简单但有效,比上完整跟踪算法省事得多。
3.3 从像素框估算真实坑洼面积
巡检报告里光有框没用,得给出面积。核心是标定:在拍摄平面上放一个已知尺寸的参照物(比如 30cm 的标定板),算出每像素对应的实际长度scale(米/像素),再用框的宽高乘 scale 得到近似面积。
| 参数 | 含义 | 典型取值 |
|---|---|---|
| scale_x | 水平方向米/像素 | 由标定板宽度除以像素宽得到 |
| scale_y | 垂直方向米/像素 | 由标定板高度除以像素高得到 |
| area | 坑洼近似面积 | 框宽×scale_x × 框高×scale_y |
注意:相机有俯角时垂直方向存在透视畸变,scale_y 会随位置变化,粗略估算可以接受,要精确就得做透视校正,这是另一个话题。
4. 部署到边缘设备与踩坑排查
4.1 导出 ONNX 与 RK3588 部署路径
模型要在边缘盒子上跑,先导出 ONNX,再用 RKNN 工具链转成 RK3588 能吃的格式。导出时imgsz必须和训练一致,opset建议 12。
# 导出 ONNX yolo export model=runs/pothole/exp1/weights/best.pt format=onnx opset=12 imgsz=640 # 在 RKNN 工具链环境里转换(示意) python convert_rknn.py --onnx best.onnx --output best.rknn --target rk3588转换脚本里要指定mean_values和std_values,必须和训练时的归一化一致(YOLOv8 默认 0~1,即 mean=0、std=255),否则精度会掉一大截。RK3588 上推理用 RKNN Runtime 的 Python 或 C++ API,NPU 跑 YOLOv8n 单帧大概几十毫秒,具体数字跟量化方式有关,INT8 量化后速度提升明显但小目标精度会降,建议先用 FP16 验证精度再决定是否量化。
4.2 坑洼检测常见问题排查
现象一:训练 loss 正常但推理全是误检。原因通常是验证集和实际场景光照差异大,模型过拟合到训练集的光照。解决:在数据增强里加hsv_h、hsv_v扰动,并补一批阴天、逆光、夜间补光的数据。
现象二:小坑洼完全检不出。原因是 640 分辨率下小目标特征被下采样丢掉了。解决:把imgsz提到 960 或 1280,或者在数据里对小目标做复制粘贴增强,提高小目标占比。
现象三:同一个坑在视频里被反复计数。这是没做去重,参考 3.2 的中心距离合并,或者引入 ByteTrack 做跨帧关联。
现象四:ONNX 转 RKNN 后精度暴跌。多半是归一化参数没对齐,或者量化校准集和真实分布差太远。解决:核对 mean/std,量化校准集从验证集里随机抽 200~500 张覆盖各种光照。
现象五:训练时显存爆了。GTX1660Ti 只有 6G,batch=16加imgsz=960很容易 OOM。解决:降 batch 到 8,或者开amp=True混合精度,再不行就降 imgsz。
5. 把 mAP 从 0.6 推到 0.8 的几个实操技巧
模型能跑通只是及格线,真正决定这套系统能不能交付的是 mAP 和误检率。我踩过的坑里,提升最明显的往往不是换更大的模型,而是数据层面的动作。第一招是难例挖掘:先用初版模型跑一遍验证集和实际巡检视频,把漏检和误检的帧挑出来重新标注,加进训练集,通常一轮就能涨 5~10 个点。第二招是类别平衡,坑洼和修补块数量差太多时,对少样本类做过采样,或者在 loss 里给类别权重,YOLOv8 本身不直接暴露类别权重参数,但可以通过复制样本文件实现等效过采样。
第三招是锚框和输入尺寸的匹配。YOLOv8 是无锚框的,但输入尺寸对小目标影响极大。我做过对比:同一份数据,imgsz=640时 mAP50 约 0.62,提到imgsz=960后到 0.71,代价是单帧推理从 12ms 涨到 28ms(GTX1660Ti)。如果部署端算力有限,可以训练用 960、推理用 640,精度损失大概 2~3 个点,但速度翻倍,这个取舍在边缘设备上很常见。
第四招是 TTA(测试时增强),推理时对图像做水平翻转和不同尺度缩放,把多次预测做 NMS 融合。ultralytics 推理时加augment=True就能开启,mAP 通常能再涨 1~3 个点,但推理耗时翻几倍,只适合离线出报告的场景,实时巡检别开。
验证方法上,别只看 mAP 一个数。我习惯同时看三个指标:mAP50 看整体、recall 看漏检、precision 看误检。市政场景里漏检比误检更不可接受,所以我会把conf阈值调低到 0.25 保 recall,再用后处理规则过滤明显误检(比如框面积过小、长宽比异常的)。最后留一句我自己的习惯:每次改完数据或参数,一定固定用同一批 200 张的「回归测试集」跑一遍,对比前后指标,不然改了什么、涨了还是跌了全靠玄学。希望帮到你。
本文还有配套的精品资源,点击获取