
简介这份资源是面向计算机相关专业学生与项目实战学习者的道路坑洼检测课程设计资料围绕计算机视觉技术展开重点提供AlexNet、LeNet-5及LeNet-5 2.0三种算法模型的对比实现可用于毕业设计、课程大作业或算法入门练习。压缩包共14个文件以11个Python源码文件为主涵盖模型定义、训练、预测与测试等环节另含2个h5模型权重文件和1份README说明文档整体约10.49MB结构清晰便于按模块查阅。目前已有155人学习下载适合需要完整项目参考的读者。通过这份资料读者可以获取多模型对比的完整代码框架、可直接加载的预训练权重以及模型训练与推理的排错思路既能用于答辩演示也可在此基础上修改扩展实现其他视觉检测功能。1. 道路坑洼检测为什么值得用计算机视觉重做一遍市政巡检的老师傅靠肉眼扫路面一天跑不了几公里漏检率还高。换成行车记录仪加计算机视觉做道路坑洼检测同样的路线能跑三遍还能把坑洼的位置、面积、置信度自动落库。这个方向属于计算机视觉与目标检测的典型落地场景也是很多计算机视觉大作业和计算机视觉项目的首选题目——数据好采、标注直观、效果肉眼可验证。标题里那份「python源码模型多种算法模型对比」的压缩包本质是一套可复现的工程骨架数据准备、模型训练、推理可视化、多模型指标对比。它解决的不是「能不能检测出坑」这种单点问题而是「换哪个模型、参数怎么调、指标怎么比」的选型问题。适合两类人一是想跑通第一个计算机视觉项目的新手二是需要横向对比检测模型性能的从业者。下面按我实际做过的路径拆开讲。2. 从原始路面图到可训练数据集标注、划分与增强2.1 坑洼检测的数据长什么样为什么标注比模型更决定上限道路坑洼检测的数据来源通常是车载摄像头或手机拍摄的路面视频抽帧。原始素材的典型问题是正样本坑洼占比极低一张 1920×1080 的图里坑洼可能只占几百像素。如果直接整图训练模型会被大量背景像素带偏所以第一步永远是裁剪感兴趣区域ROI把画面下半部分的路面切出来。标注格式上目标检测主流用两种PASCAL VOC 的 XML 和 YOLO 的 txt。VOC 存绝对坐标YOLO 存归一化的中心点加宽高。坑洼这种形状不规则的物体边界框标注会有歧义——同一个坑两个人框出来的大小可能差 30%。我的做法是定一条规则框只包住坑的破损边缘不含周围裂纹标注时统一放大到能看见坑底。规则写进标注文档比换模型更能提升指标一致性。数据划分别用随机划分。同一段视频抽出的帧高度相似随机划分会让训练集和验证集出现近邻泄漏验证指标虚高。正确做法是按视频源或按路段划分保证验证集的路段训练时没见过。常见比例是 7:2:1但坑洼数据少的时候验证集至少留 200 张正样本图否则 mAP 波动大到没法比。2.2 用脚本把 VOC 转成 YOLO 格式并做数据增强拿到标注后先统一转成 YOLO 格式方便后面多模型共用同一份数据。下面这个脚本处理 VOC XML 到 YOLO txt 的转换同时做一次尺寸校验。import os import xml.etree.ElementTree as ET from PIL import Image # 类别映射坑洼检测通常就一类多类时按实际扩展 CLASS_MAP {pothole: 0} def voc_to_yolo(xml_dir, img_dir, out_dir): os.makedirs(out_dir, exist_okTrue) 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() # 用图片真实尺寸做归一化别信 XML 里写的 size img_name root.find(filename).text img_path os.path.join(img_dir, img_name) w, h Image.open(img_path).size lines [] for obj in root.findall(object): cls_name obj.find(name).text if cls_name not in CLASS_MAP: continue 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, ymin max(0, xmin), max(0, ymin) xmax, ymax min(w, xmax), min(h, ymax) if xmax xmin or ymax ymin: continue # 无效框直接丢 cx (xmin xmax) / 2.0 / w cy (ymin ymax) / 2.0 / h bw (xmax - xmin) / w bh (ymax - ymin) / h lines.append(f{CLASS_MAP[cls_name]} {cx:.6f} {cy:.6f} {bw:.6f} {bh:.6f}) if lines: txt_name os.path.splitext(xml_file)[0] .txt with open(os.path.join(out_dir, txt_name), w) as f: f.write(\n.join(lines)) if __name__ __main__: voc_to_yolo(annotations/xml, images, labels/yolo)逻辑说明脚本遍历 XML读取每个 object 的类别和边界框用图片真实宽高做归一化。关键参数是CLASS_MAP坑洼检测一般单类如果要做「坑洼/裂缝/修补」多类在这里加映射即可。越界裁剪那几行是血泪经验——标注工具导出时经常有框超出图片边界不裁会导致训练时坐标异常。增强策略上坑洼检测别用随机旋转和垂直翻转路面有明确的重力方向翻转后坑的形态不真实。我一般用随机亮度对比度模拟阴天/晴天、随机裁剪模拟不同安装角度、轻微高斯噪声模拟夜间噪点。Mosaic 增强对小目标有效但坑洼本身不算小目标用不用差别不大数据少于 1000 张时建议开。提示增强只对训练集做验证集保持原图否则指标不可比。3. 多模型对比怎么搭YOLO 系、Faster R-CNN 与 SSD 的取舍3.1 三种检测范式的差异以及坑洼场景该选谁标题强调「多种算法模型对比」这是这份源码最有价值的部分。目标检测主流分三派两阶段的 Faster R-CNN、单阶段的 YOLO 系和 SSD。坑洼检测的诉求是实时性加中等精度因为巡检车要边跑边出结果。Faster R-CNN 精度稳但推理慢1080Ti 上单帧要 100ms 以上跑视频会卡。SSD 速度快但小目标召回差坑洼在远景里就是小目标容易漏。YOLO 系v5/v8在速度和精度之间平衡最好也是我实际项目里默认选的。对比实验的意义在于用同一份数据、同一套评估脚本把三者的 mAP、FPS、模型体积摆在一起你才知道自己的硬件和精度要求下该选谁。对比时有个坑不同框架的输入尺寸默认不一样。Faster R-CNN 常用 800×1333YOLO 常用 640×640。直接比 mAP 不公平要么统一输入尺寸要么在各自最优尺寸下比并注明。我一般统一到 640牺牲一点 Faster R-CNN 的精度换取可比性。3.2 用统一训练脚本跑通 YOLO 并记录对比指标下面是一个基于 ultralytics 的 YOLO 训练脚本同时把关键指标写进 CSV方便后面和别的模型对比。from ultralytics import YOLO import csv import os def train_and_log(data_yaml, model_nameyolov8n.pt, epochs100, imgsz640): model YOLO(model_name) results model.train( datadata_yaml, # 数据集配置指向 train/val 路径和类别 epochsepochs, # 坑洼数据少100 轮起步早停可开 imgszimgsz, # 统一输入尺寸保证多模型可比 batch16, # 显存不够就降到 8 lr00.01, # 初始学习率YOLO 默认值别乱调 patience20, # 20 轮无提升就停省时间 projectruns/pothole, nameyolo_baseline ) # 从训练结果里取最终指标 metrics model.val() row { model: model_name, mAP50: round(metrics.box.map50, 4), mAP50-95: round(metrics.box.map, 4), precision: round(metrics.box.mp, 4), recall: round(metrics.box.mr, 4), } csv_path runs/pothole/compare.csv file_exists os.path.exists(csv_path) with open(csv_path, a, newline) as f: writer csv.DictWriter(f, fieldnamesrow.keys()) if not file_exists: writer.writeheader() writer.writerow(row) return row if __name__ __main__: train_and_log(data/pothole.yaml, model_nameyolov8n.pt)逻辑说明data参数指向 YAML 配置文件里面写 train/val 路径和names: {0: pothole}。imgsz统一 640 是为了多模型对比公平。patience设 20 能省不少时间坑洼数据通常 50 轮内就收敛。指标写入 CSV 后换 Faster R-CNN 或 SSD 时用同样的字段追加最后用 pandas 读出来画柱状图。参数上重点说三个lr0别超过 0.01坑洼数据少学习率大了直接发散batch看显存16 是 8G 显存的稳妥值epochs配合patience用别硬跑 300 轮。如果 mAP50 卡在 0.5 上不去先查标注质量再查验证集有没有近邻泄漏最后才动模型。注意不同模型的 mAP 计算实现有细微差异对比时最好用同一套评估脚本重算一遍别直接抄各自框架的输出。4. 推理部署与可视化把检测框画回原图4.1 单图推理和视频流推理的代码差异训练完拿到 best.pt下一步是推理。单图和视频流的处理逻辑不同单图直接喂视频流要抽帧、推理、再合成。坑洼检测的实际部署场景是车载实时所以视频流推理更接近真实需求。import cv2 from ultralytics import YOLO def infer_video(model_path, video_path, out_path, conf0.4): model YOLO(model_path) cap cv2.VideoCapture(video_path) fps cap.get(cv2.CAP_PROP_FPS) w int(cap.get(cv2.CAP_PROP_FRAME_WIDTH)) h int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT)) writer cv2.VideoWriter(out_path, cv2.VideoWriter_fourcc(*mp4v), fps, (w, h)) while cap.isOpened(): ret, frame cap.read() if not ret: break # conf 阈值决定漏检和误检的平衡坑洼场景建议 0.35-0.5 results model(frame, confconf, verboseFalse) annotated results[0].plot() # 自带画框和标签 writer.write(annotated) cap.release() writer.release() if __name__ __main__: infer_video(runs/pothole/yolo_baseline/weights/best.pt, test_road.mp4, out_road.mp4)逻辑说明conf是置信度阈值坑洼检测里这个参数直接决定漏检率。设 0.5 以上暗光下的坑容易漏设 0.3 以下路面阴影和水渍会被误检成坑。我一般从 0.4 起调看实际视频效果。results[0].plot()是 ultralytics 自带的可视化画框加类别标签省得自己写。视频推理的性能瓶颈在解码和画框不在模型本身。如果 FPS 不够先把输入尺寸从 640 降到 416精度掉一点但速度翻倍。别一上来就换更小的模型先调尺寸。4.2 检测结果落库把坑洼位置和面积存成结构化数据巡检的最终产出不是视频是坑洼清单。推理时把每个检测框的坐标、置信度、估算面积存下来才能做后续的养护排期。import csv def save_detections(results, frame_id, out_csv): rows [] for box in results[0].boxes: x1, y1, x2, y2 box.xyxy[0].tolist() conf float(box.conf[0]) # 面积用像素数近似实际面积需要相机标定换算 area_px (x2 - x1) * (y2 - y1) rows.append({ frame: frame_id, x1: round(x1, 1), y1: round(y1, 1), x2: round(x2, 1), y2: round(y2, 1), conf: round(conf, 3), area_px: round(area_px, 1) }) with open(out_csv, a, newline) as f: writer csv.DictWriter(f, fieldnames[frame, x1, y1, x2, y2, conf, area_px]) if f.tell() 0: writer.writeheader() writer.writerows(rows)逻辑说明area_px是像素面积只能做相对大小排序要换算成真实面积得做相机标定这一步在源码包里通常没有需要自己补。conf存下来方便后续按阈值过滤。落库用 CSV 是图省事实际项目换 SQLite 或 PostgreSQL 都行字段结构一样。提示同一坑洼在连续帧里会被重复检测落库后要按位置做去重否则清单里一个坑出现几十次。5. 多模型对比实验里最容易翻车的几个地方5.1 指标虚高验证集泄漏和标注不一致现象训练时 mAP50 冲到 0.9上路实测漏检一半。原因验证集和训练集来自同一段视频近邻帧高度相似模型等于背答案。解决按视频源划分数据集验证集单独抽路段重跑后 mAP 通常会掉 10 到 20 个点这才是真实水平。现象两个模型对比A 比 B 高 5 个点但换一批数据结论反了。原因标注不一致同一批图两个人标框的大小差 30%模型学到的边界模糊。解决定标注规范抽 10% 复核IoU 低于 0.7 的框重标。5.2 训练不收敛学习率和 batch 的搭配问题现象loss 从第一轮就震荡mAP 一直是 0。原因学习率设太大坑洼数据少梯度爆炸。解决lr0降到 0.001 或 0.005配合 warmup。YOLO 默认带 warmup但自己改配置时容易关掉。现象batch 设 32 后显存溢出改成 4 又收敛慢。原因batch 太小BN 层统计不稳。解决用梯度累积模拟大 batch或者换更小的模型。8G 显存跑 640 尺寸batch 16 是甜点值。5.3 推理误检阴影、水渍和井盖被当成坑现象晴天路面阴影被框成坑置信度还不低。原因训练集里阴影样本太少模型没学会区分。解决负样本挖掘把误检的图加进训练集标成背景。这一步比调参管用。现象井盖边缘被误检。原因井盖和坑洼在灰度上相似。解决加一类「井盖」做多类检测或者在后处理里按位置过滤——井盖通常在路中间固定位置。5.4 部署性能FPS 不达标先查哪里现象训练时 GPU 利用率高推理时 FPS 只有 5。原因视频解码在 CPU成了瓶颈。解决用 GPU 解码或降低分辨率。先nvidia-smi看 GPU 利用率低于 50% 就是解码或预处理卡住。现象换 TensorRT 后 FPS 没提升。原因模型太小瓶颈不在计算。解决小模型别折腾 TensorRT直接调输入尺寸更快。6. 把对比实验做成可复用的评估流水线多模型对比最怕每次手动跑、手动抄指标。我的习惯是写一个评估脚本输入是各个模型的权重路径输出是一张对比表加柱状图。这样换数据集、加模型都只改配置。import pandas as pd import matplotlib.pyplot as plt def compare_models(csv_path): df pd.read_csv(csv_path) # 按 mAP50 排序一眼看出谁强 df df.sort_values(mAP50, ascendingFalse) print(df.to_string(indexFalse)) fig, ax plt.subplots(1, 2, figsize(12, 4)) df.plot(xmodel, ymAP50, kindbar, axax[0], legendFalse) ax[0].set_title(mAP50 by model) df.plot(xmodel, yrecall, kindbar, axax[1], legendFalse) ax[1].set_title(Recall by model) plt.tight_layout() plt.savefig(runs/pothole/compare.png, dpi150) if __name__ __main__: compare_models(runs/pothole/compare.csv)逻辑说明读 CSV 后按 mAP50 排序柱状图直观展示差距。关键在 CSV 的字段要统一每个模型跑完都往同一个文件追加字段名不能变。这套流水线跑通后加一个新模型只需要改训练脚本里的model_name评估部分完全复用。进阶技巧上坑洼检测真正难的不是模型选型是数据闭环。上路跑一段时间把误检和漏检的帧抽出来人工复核后加进训练集再跑一轮。这个循环做三轮mAP 的提升比换任何模型都大。我自己的项目里第一轮 mAP50 只有 0.62三轮数据闭环后到 0.81模型没换只加了 400 张难例。验证方法上别只看 mAP。坑洼检测的业务指标是「每公里漏检数」和「每公里误检数」。拿一段没参与训练的路测视频人工数出真实坑洼数再和模型输出对比算这两个数。mAP 高但漏检集中在暗光场景说明数据分布有偏得补夜间样本。最后说个习惯每次实验都记配置。学习率、batch、输入尺寸、增强策略全写进一个 config.yaml和权重放一起。过两周回头看没有配置记录根本不知道当时为什么选这个参数。这个习惯帮我省了无数次重跑。希望帮到你。本文还有配套的精品资源点击获取