简介:这份资源是面向计算机相关专业学生与项目实战学习者的道路坑洼检测课程设计资料,围绕计算机视觉技术展开,重点提供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_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() # 用图片真实尺寸做归一化,别信 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×1333,YOLO 常用 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_name="yolov8n.pt", epochs=100, imgsz=640): model = YOLO(model_name) results = model.train( data=data_yaml, # 数据集配置,指向 train/val 路径和类别 epochs=epochs, # 坑洼数据少,100 轮起步,早停可开 imgsz=imgsz, # 统一输入尺寸,保证多模型可比 batch=16, # 显存不够就降到 8 lr0=0.01, # 初始学习率,YOLO 默认值,别乱调 patience=20, # 20 轮无提升就停,省时间 project="runs/pothole", name="yolo_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, fieldnames=row.keys()) if not file_exists: writer.writeheader() writer.writerow(row) return row if __name__ == "__main__": train_and_log("data/pothole.yaml", model_name="yolov8n.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, conf=0.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, conf=conf, verbose=False) 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", ascending=False) print(df.to_string(index=False)) fig, ax = plt.subplots(1, 2, figsize=(12, 4)) df.plot(x="model", y="mAP50", kind="bar", ax=ax[0], legend=False) ax[0].set_title("mAP50 by model") df.plot(x="model", y="recall", kind="bar", ax=ax[1], legend=False) ax[1].set_title("Recall by model") plt.tight_layout() plt.savefig("runs/pothole/compare.png", dpi=150) 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,和权重放一起。过两周回头看,没有配置记录根本不知道当时为什么选这个参数。这个习惯帮我省了无数次重跑。希望帮到你。
本文还有配套的精品资源,点击获取