简介:这份资源是面向计算机视觉方向的课程设计与毕业设计参考项目,聚焦道路坑洼检测任务,适合计算机、人工智能、通信工程、自动化等专业学生及需要项目实战练习的学习者。项目以Python实现,围绕AlexNet、LeNet-5及LeNet-5 2.0等多种算法模型展开对比实验,可用于课程设计、期末大作业或毕设初期立项演示。压缩包共14个文件,包含11个py源码、2个h5模型权重和1个md说明文档,整体约10.49MB,源码与训练好的模型均已测试运行成功,便于直接复现与二次修改。资源已有155人学习下载,作者为RuanJian_GC。读者可从中获得完整的坑洼检测实现流程、多模型对比思路、模型加载与预测脚本以及项目结构参考,既能用于答辩展示,也可在此基础上扩展其他检测功能。
1. 道路坑洼检测:从一段行车记录仪视频说起
路面上的坑洼,轻则让乘客颠一下,重则爆胎、伤悬挂。市政巡检靠人眼扫街,一天跑不了几十公里,漏检率还高。把行车记录仪或巡检车摄像头拍到的画面交给计算机视觉模型,自动框出坑洼位置,这件事在工程上已经能跑通。这个标题里的「python源码+模型(多种算法模型对比)」,核心就是一套可复现的检测流程:数据标注、模型训练、推理部署,外加把 YOLO、Faster R-CNN、SSD 这类主流检测器放在同一份数据上比一比,看谁在坑洼这种小目标、低对比度场景下更稳。适合做计算机视觉大作业的学生,也适合想给养护系统加自动巡检模块的工程师。下面按「数据怎么准备 → 模型怎么选 → 怎么训 → 怎么比 → 坑在哪」推一遍。
2. 数据与标注:坑洼检测的第一道门槛
2.1 为什么坑洼数据比想象中难标
道路坑洼在图像里通常只占几十到几百像素,边缘和阴影、水渍、井盖、修补痕迹高度相似。我见过不少开源数据集直接把「深色不规则区域」全标成坑洼,训出来的模型把井盖当坑洼报,上路就是灾难。常见做法是定义清楚标注边界:只标路面结构层已经破损、有明显凹陷或材料剥落的区域,阴影和水渍不标。标注格式优先选 YOLO 的 txt(每行class x_center y_center width height,归一化到 0~1),因为后面换模型时转换成本最低。
如果手头只有原始视频,先用 ffmpeg 抽帧,再筛掉模糊和重复帧。抽帧间隔别太密,城市道路 30fps 视频每 10 帧取一张就够,否则相邻帧几乎一样,训出来模型对场景多样性不敏感。
# 每 10 帧抽一张,输出到 frames 目录 ffmpeg -i road.mp4 -vf "select=not(mod(n\,10))" -vsync vfr frames/frame_%05d.jpgselect=not(mod(n\,10))表示帧号能被 10 整除时保留,-vsync vfr防止输出帧率错乱。抽完帧后建议用图像哈希去重,否则同一处坑洼会出现几十张几乎一样的图,训练集和验证集还会泄漏。
2.2 标注工具与目录结构
标注用 LabelImg 或 CVAT 都行,导出 YOLO 格式。目录按下面组织,后面训练脚本直接读这个结构:
dataset/ images/ train/ val/ labels/ train/ val/ data.yamldata.yaml内容:
path: ./dataset train: images/train val: images/val nc: 1 names: ['pothole']nc是类别数,坑洼检测通常就一类。如果还要区分「坑洼」和「修补」,改成 2 并对应调整 names。标注完成后一定要写个脚本统计每张图的框数量和框面积分布,框面积中位数低于图像面积 0.5% 时,说明目标极小,后面选模型和调 anchor 都要针对性处理。
2.3 数据增强的边界
坑洼检测的增强不能乱来。水平翻转、轻微旋转、亮度对比度扰动是安全的;垂直翻转要谨慎,因为路面纹理有方向性,翻过来不自然;马赛克增强(Mosaic)对小目标有帮助,但四张图拼接后坑洼可能被裁掉一半,建议开启但把概率控制在 0.5 左右。雨天、夜间样本少的话,优先补真实数据,别指望靠增强凭空造出夜间泛化能力。
3. 模型选型:YOLO、Faster R-CNN、SSD 到底怎么比
3.1 三类检测器的本质差异
YOLO 系列是单阶段检测器,一次前向就出框和类别,速度快,适合巡检视频实时处理。Faster R-CNN 是两阶段,先出候选区域再分类回归,精度通常更高,但推理慢,适合离线批量分析。SSD 也是单阶段,用多尺度特征图预测,对小目标比早期 YOLO 友好,但 anchor 设计敏感。坑洼属于小目标且背景复杂,选型时不能只看 mAP,还要看小目标召回率和推理帧率。
我一般会先跑 YOLOv8n 或 YOLOv5s 作为基线,因为训练快、社区资料多,能快速判断数据质量。如果基线模型 mAP50 都上不去,换 Faster R-CNN 也救不了,问题在标注或数据分布。基线跑通后再上 Faster R-CNN 对比精度上限,最后看 SSD 在边缘设备上的速度优势是否值得牺牲精度。
3.2 统一实验配置才能公平对比
多种算法模型对比最容易翻车的地方是:每个模型用不同输入尺寸、不同 epoch、不同增强,最后比出来的数字没有意义。正确做法是固定输入分辨率(比如 640×640)、固定训练轮数、固定数据划分,只换模型结构。下面是一个统一的训练入口示例,用 Ultralytics 跑 YOLO,用 torchvision 跑 Faster R-CNN 和 SSD:
import torch from ultralytics import YOLO # YOLO 基线训练 model = YOLO("yolov8n.pt") model.train( data="dataset/data.yaml", epochs=100, imgsz=640, batch=16, device=0, project="runs/pothole", name="yolov8n_baseline" )imgsz=640是输入边长,batch=16按显存调整,device=0指定第一块 GPU。训练日志里重点看metrics/mAP50(B)和metrics/mAP50-95(B),前者宽松,后者严格。坑洼检测里 mAP50 高但 mAP50-95 低,说明框的位置不够准,可能是标注框偏大或回归损失权重问题。
Faster R-CNN 用 torchvision 的预训练权重微调:
import torchvision from torchvision.models.detection.faster_rcnn import FastRCNNPredictor # 加载预训练 Faster R-CNN,替换分类头为 2 类(背景+坑洼) model = torchvision.models.detection.fasterrcnn_resnet50_fpn(weights="DEFAULT") in_features = model.roi_heads.box_predictor.cls_score.in_features model.roi_heads.box_predictor = FastRCNNPredictor(in_features, 2) # 优化器只调需要更新的参数 params = [p for p in model.parameters() if p.requires_grad] optimizer = torch.optim.SGD(params, lr=0.005, momentum=0.9, weight_decay=0.0005)FastRCNNPredictor(in_features, 2)里 2 是背景加坑洼。Faster R-CNN 的输入是 0~1 归一化的 tensor 列表,不是 YOLO 那种直接读 yaml,数据加载要自己写 Dataset 返回(image_tensor, target_dict),target 里包含 boxes 和 labels。这一步写错的话,训练 loss 会一直不降,别问我是怎么知道的。
3.3 对比指标怎么记录
建一张表,每个模型跑完后填一行,至少包含 mAP50、mAP50-95、小目标召回率、单帧推理耗时、模型体积。小目标召回率可以按框面积小于 32×32 像素的样本单独统计,这个指标比整体 mAP 更能反映坑洼检测的实际可用性。
| 模型 | mAP50 | mAP50-95 | 小目标召回 | 推理耗时(ms) | 体积(MB) |
|---|---|---|---|---|---|
| YOLOv8n | 待填 | 待填 | 待填 | 待填 | 待填 |
| Faster R-CNN | 待填 | 待填 | 待填 | 待填 | 待填 |
| SSD300 | 待填 | 待填 | 待填 | 待填 | 待填 |
表格里的数字必须来自同一验证集、同一置信度阈值。置信度阈值不统一,比出来的召回率没有意义。
4. 训练与推理:把模型跑到能用的程度
4.1 训练参数怎么设才不白跑
学习率是最容易翻车的参数。YOLO 微调一般用 0.01 起步,配合余弦退火;Faster R-CNN 用 0.005 的 SGD。如果 loss 在前几个 epoch 剧烈震荡,先把学习率降一个数量级。权重衰减设 0.0005,防止过拟合。坑洼数据量通常不大,几千张图就算多了,所以早停(patience=20)很有必要,验证集 mAP 连续 20 轮不涨就停。
另一个关键是冻结策略。用预训练权重时,前几轮可以冻结 backbone,只训检测头,让模型先适应坑洼这个新类别,再解冻全网络微调。Ultralytics 里用freeze=10冻结前 10 层,torchvision 里手动设置requires_grad=False。
4.2 推理脚本与后处理
训练完导出权重后,推理脚本要处理视频流和单图两种输入。YOLO 的推理很直接:
from ultralytics import YOLO model = YOLO("runs/pothole/yolov8n_baseline/weights/best.pt") results = model.predict( source="test_video.mp4", conf=0.35, iou=0.45, save=True, project="runs/detect", name="pothole_result" )conf=0.35是置信度阈值,低于它的框不输出;iou=0.45是 NMS 的 IoU 阈值,控制重叠框合并。坑洼检测里 conf 设太低会满屏误报,设太高会漏掉浅坑,建议在验证集上画 P-R 曲线选拐点。save=True会把带框的视频存下来,方便肉眼复核。
Faster R-CNN 推理要手动做 NMS 和阈值过滤,torchvision 的detection.fasterrcnn_resnet50_fpn输出已经是后处理过的,但置信度阈值要自己卡。推理时记得model.eval()和torch.no_grad(),否则显存会爆。
4.3 部署前的量化与加速
如果要在 Jetson 或树莓派上跑,YOLOv8n 导出 ONNX 再转 TensorRT 是常见路线。导出命令:
yolo export model=best.pt format=engine half=True device=0half=True用 FP16 精度,速度提升明显,精度掉得不多。Faster R-CNN 部署相对麻烦,算子多,TensorRT 支持不如 YOLO 好,边缘设备上一般优先选 YOLO 或 SSD。量化后一定要在验证集上重新跑一遍 mAP,确认掉点可接受,别直接上线。
5. 避坑与排查:那些让实验白跑的细节
5.1 验证集 mAP 很高,实拍视频全是误报
现象:验证集 mAP50 到 0.85,拿手机拍一段路测视频,模型把井盖、阴影、车道线全框成坑洼。原因通常是训练集和验证集来自同一段视频或同一路段,分布太像,模型没学到泛化特征。解决:按路段划分数据集,训练集和验证集不能有同一路段的帧;补入不同光照、不同路面的负样本,尤其是井盖和修补痕迹。
5.2 训练 loss 正常但 mAP 一直是 0
现象:YOLO 训练 loss 在降,但验证 mAP 始终为 0。原因多半是 data.yaml 路径写错或标签格式不对,模型实际在学背景。解决:先跑一遍yolo checks确认数据能被正确读取;再可视化几张训练图的标注框,确认框和坑洼对齐;检查标签文件里坐标是否归一化,有没有超出 0~1 范围。
5.3 显存不够,batch 调小后 mAP 掉得厉害
现象:batch 从 16 降到 4 后,mAP 掉 5 个点以上。原因是小 batch 下 BN 层统计量不稳定,梯度噪声大。解决:改用梯度累积,batch=4但累积 4 次等效 batch=16;或者换更小的输入尺寸,但输入尺寸太小会进一步恶化小目标检测,坑洼场景不建议低于 512。
5.4 模型把水渍当坑洼,雨天误报翻倍
现象:晴天正常,雨天视频里水渍被大量误检。原因是训练集缺少雨天样本,模型把「深色反光区域」和坑洼关联了。解决:补雨天标注数据,水渍明确标为背景;推理时如果知道是雨天,可以适当提高 conf 阈值,但根本办法还是数据覆盖。
5.5 多种模型对比时,Faster R-CNN 训练不收敛
现象:Faster R-CNN 的 loss 震荡或直接 NaN。原因常见于学习率过高、数据归一化不一致、target 里 boxes 格式错误。解决:学习率降到 0.001 试;确认图像 tensor 是 0~1 且通道顺序为 CHW;boxes 必须是[x_min, y_min, x_max, y_max]且归一化到图像尺寸,不是 YOLO 的 center 格式。格式转错是最高频的翻车点。
6. 进阶技巧:用切片推理把远处小坑洼捞回来
坑洼在图像里往往集中在画面下半部分,远处路面上的坑只有十几个像素,直接整图推理很容易漏。一个实用技巧是切片推理(SAHI 思路):把原图切成有重叠的小块,每块单独推理,再把结果映射回原图做 NMS。这样小目标在局部图里相对变大,召回率能明显提升,代价是推理耗时增加。
from sahi import AutoDetectionModel from sahi.predict import get_sliced_prediction # 加载 YOLO 权重做切片推理 detection_model = AutoDetectionModel.from_pretrained( model_type="yolov8", model_path="best.pt", confidence_threshold=0.3, device="cuda:0" ) result = get_sliced_prediction( "road_far.jpg", detection_model, slice_height=320, slice_width=320, overlap_height_ratio=0.2, overlap_width_ratio=0.2 ) result.export_visuals(export_dir="runs/sahi")slice_height和slice_width是切片尺寸,320 适合 640 原图切四块;overlap_ratio=0.2保证边缘目标不被切断。切片太小会引入大量背景块,推理变慢;切片太大又失去小目标放大的意义。我一般从 320 起步,在验证集上对比切片前后的召回率,涨点不明显就调回整图推理。
另一个技巧是测试时增强(TTA),对同一张图做水平翻转和多尺度推理,把结果融合。TTA 能涨 1~2 个点 mAP,但推理时间翻倍,实时巡检场景要权衡。如果只是离线出报告,TTA 值得开。
验证模型是否真的可用,别只看 mAP。我会额外做两件事:一是把验证集里漏检和误检的图各挑 20 张,逐张看原因,归类是标注问题、光照问题还是模型容量问题;二是拿一段完全没参与训练的路测视频跑一遍,统计每公里误报次数和漏报次数。这两个数字比 mAP 更能说服自己这个模型能不能上线。
踩过的坑里,最贵的一个是数据集泄漏:训练集和验证集有同一段视频的相邻帧,mAP 虚高到 0.9,实际路测一塌糊涂。从那以后我划分数据集一定按视频源或路段分,绝不按帧随机分。希望帮到你。
本文还有配套的精品资源,点击获取