news 2026/9/23 20:38:55

基于计算机视觉的道路坑洼检测:多种算法模型对比与Python实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
基于计算机视觉的道路坑洼检测:多种算法模型对比与Python实战

简介:这份资源是面向计算机相关专业学生与项目实战学习者的道路坑洼检测课程设计资料,基于计算机视觉方法实现路面病害识别,并横向对比AlexNet、LeNet-5、LeNet-5 2.0等多种算法模型的检测效果,适合作为毕设、课设、期末大作业或算法入门练习的参考方案。压缩包共14个文件,约10.49MB,以11个Python源码文件为主,涵盖模型定义、训练、预测与测试等模块,另含2个h5权重模型文件和1份README说明文档,便于直接加载模型复现结果。目前已有155人学习下载。读者可从中获得完整的坑洼检测实现流程、多模型对比实验思路、可运行的训练与推理脚本,以及已训练好的模型权重,既能快速跑通项目,也能在此基础上修改网络结构或迁移到其他路面缺陷识别任务,对理解卷积神经网络在图像分类中的实际应用有较直接的帮助。

1. 道路坑洼检测为什么总在减速带和井盖上翻车

城市道路巡检车顶着一台普通 USB 摄像头跑一圈,回来把视频丢给检测模型,结果减速带、井盖、修补后的沥青补丁全被框成坑洼——这是做基于计算机视觉的道路坑洼检测最常遇到的翻车现场。坑洼本身没有固定形状,光照一变、积水一反光,边缘特征就散了;而减速带和井盖在灰度图上和坑洼的暗区高度相似,模型很容易把它们当成同一类目标。这个方向要解决的核心问题,就是在一段连续路面图像里稳定地找出真正的坑洞区域,并给出可复核的位置框或分割掩码。

它适合三类人:做市政巡检、道路养护数字化的工程团队;拿计算机视觉大作业或毕业设计练手的学生;以及想用python把检测模型真正跑起来、而不是停在 notebook 里的开发者。标题里那句「多种算法模型对比」才是重点——单跑一个 YOLO 谁都会,难的是把传统阈值法、两阶段检测、单阶段检测放在同一套数据和同一套评价口径下比出结论。这篇就按「数据怎么造 → 模型怎么选 → 怎么训 → 怎么比 → 坑在哪」的顺序讲透,源码和模型权重按常见工程结构组织,你照着能复现。

2. 数据与标注:坑洼检测的地基怎么打

2.1 为什么坑洼数据集不能直接拿 COCO 凑

坑洼检测属于典型的「小目标 + 类内差异极大」任务。同一个坑,晴天拍是深灰凹陷,雨天拍是反光水洼,夜间补光灯下又变成高对比暗斑。COCO、VOC 这类通用数据集里根本没有「pothole」这个类,硬拿预训练权重直接微调,模型学到的还是「圆形暗色物体」这种粗特征,遇到井盖必翻车。

常见做法是自己采集。巡检车视角、行车记录仪视角、手机俯拍三种都要有,因为落地时输入源不固定。采集时注意三点:一是同一路段要覆盖不同时段,避免模型把「阴影方向」当成坑洼线索;二是刻意拍一批减速带、井盖、油污、修补补丁作为负样本,这类难负样本直接决定误检率;三是分辨率别一味求高,1080p 已经够用,4K 只会拖慢标注和训练。

标注格式上,检测任务用 YOLO 的 txt 或 COCO 的 json 都行,分割任务用多边形掩码。我一般先用矩形框标一遍快速出基线,等基线模型稳定了再补掩码做精细分割,这样不会一上来就被标注工作量劝退。

2.2 从原始视频到 YOLO 格式的转换脚本

采集回来的是视频,得先抽帧、去重、再转标注格式。下面这段脚本做三件事:按固定间隔抽帧、用感知哈希去掉几乎重复的帧、把 LabelImg 导出的 VOC xml 转成 YOLO txt。

import os import cv2 import xml.etree.ElementTree as ET from imagededup.methods import PHash # 1. 抽帧:每 15 帧取一张,避免相邻帧高度重复 def extract_frames(video_path, out_dir, step=15): os.makedirs(out_dir, exist_ok=True) cap = cv2.VideoCapture(video_path) idx, saved = 0, 0 while True: ret, frame = cap.read() if not ret: break if idx % step == 0: cv2.imwrite(os.path.join(out_dir, f"frame_{saved:05d}.jpg"), frame) saved += 1 idx += 1 cap.release() return saved # 2. 感知哈希去重:汉明距离小于 5 视为重复帧 def dedup_frames(img_dir, threshold=5): phasher = PHash() encodings = phasher.encode_images(image_dir=img_dir) duplicates = phasher.find_duplicates(encoding_map=encodings, max_distance_threshold=threshold) removed = 0 for _, dups in duplicates.items(): for d in dups: p = os.path.join(img_dir, d) if os.path.exists(p): os.remove(p) removed += 1 return removed # 3. VOC xml -> YOLO txt,类别只保留 pothole CLASS_MAP = {"pothole": 0} def voc_to_yolo(xml_dir, out_dir, img_w, img_h): 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() lines = [] for obj in root.findall("object"): name = obj.find("name").text if name not in CLASS_MAP: continue bnd = obj.find("bndbox") x1 = float(bnd.find("xmin").text) y1 = float(bnd.find("ymin").text) x2 = float(bnd.find("xmax").text) y2 = float(bnd.find("ymax").text) # YOLO 需要归一化的中心点 + 宽高 cx = (x1 + x2) / 2.0 / img_w cy = (y1 + y2) / 2.0 / img_h w = (x2 - x1) / img_w h = (y2 - y1) / img_h lines.append(f"{CLASS_MAP[name]} {cx:.6f} {cy:.6f} {w:.6f} {h:.6f}") out_name = xml_file.replace(".xml", ".txt") with open(os.path.join(out_dir, out_name), "w") as f: f.write("\n".join(lines))

逻辑说明:抽帧的step参数控制采样密度,巡检场景 15 到 30 都合理,太小会导致相邻帧几乎一样、训练集冗余。去重用的感知哈希对光照变化不敏感,但对视角变化敏感,所以同一路段不同角度拍的帧不会被误删。VOC 转 YOLO 时最容易错的是归一化基准——img_wimg_h必须和实际图片尺寸一致,如果标注时图片被缩放过了,这里不修正就会导致框整体偏移。

参数说明:threshold=5是汉明距离阈值,值越大删得越狠,一般 3 到 8 之间调;CLASS_MAP里只留 pothole,是因为负样本(井盖、减速带)在 YOLO 里不需要单独成类,它们作为「无目标」背景参与训练即可,这样模型学的是「什么不是坑洼」,比强行加类更稳。

2.3 数据集划分与类别不平衡处理

坑洼在整幅图里占比通常不到 5%,属于极端前景背景不平衡。划分时按路段划分而不是随机划分——同一路段的不同帧如果被分到训练集和验证集,验证指标会虚高,这是很多人指标好看但一上路就废的原因。

处理不平衡有两个实用手段:一是训练时用 mosaic、mixup 增强,让单张图里出现多个坑洼;二是损失函数里给正样本更高权重,YOLO 系列默认的分类损失已经带了一定平衡,但如果你自己改损失,记得把cls的权重往上调。验证集里必须保留足够多的难负样本,否则你根本不知道误检率是多少。

3. 多种算法模型对比:传统法、两阶段、单阶段怎么选

3.1 传统阈值与形态学方法还值不值得做

很多人一上来就上深度学习,其实传统方法在坑洼检测里有它的位置——作为基线,也作为快速验证数据质量的工具。核心思路是:坑洼区域通常比周围路面暗,用自适应阈值或局部对比度增强把暗区提出来,再用形态学开闭运算去掉噪点,最后用轮廓面积和长宽比过滤。

import cv2 import numpy as np def traditional_pothole_detect(img_path, min_area=800): img = cv2.imread(img_path) gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # CLAHE 增强局部对比度,应对光照不均 clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8, 8)) gray = clahe.apply(gray) # 自适应阈值:坑洼比周围暗,用反向二值化 binary = cv2.adaptiveThreshold(gray, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY_INV, 25, 8) # 形态学去噪:先开后闭,去掉小噪点、填补内部空洞 kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (5, 5)) binary = cv2.morphologyEx(binary, cv2.MORPH_OPEN, kernel) binary = cv2.morphologyEx(binary, cv2.MORPH_CLOSE, kernel) contours, _ = cv2.findContours(binary, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) boxes = [] for c in contours: area = cv2.contourArea(c) if area < min_area: continue x, y, w, h = cv2.boundingRect(c) # 长宽比过滤:坑洼不会特别细长 if max(w, h) / max(min(w, h), 1) > 4: continue boxes.append((x, y, w, h)) return boxes

逻辑说明:adaptiveThresholdblockSize=25决定局部邻域大小,太小会把纹理当坑洼,太大对暗区不敏感;C=8是阈值偏移,值越大越保守。形态学核用椭圆而不是矩形,是因为坑洼边缘通常不规则,椭圆核更贴合。面积和长宽比过滤是传统法的命门——它决定了误检率,但也决定了漏检率,调参时要在两者之间找平衡。

传统法的边界很清楚:光照均匀、路面干净时召回率能到 70% 左右,但一到阴影、积水、夜间就崩。它的价值在于给你一个「不用训练就能跑」的参照,以及帮你确认标注质量——如果传统法都能检出的明显坑洼,你的标注里却没有,那就是漏标。

3.2 两阶段检测(Faster R-CNN)在坑洼上的表现

Faster R-CNN 这类两阶段检测器先出候选区域再分类,对小目标和密集目标更友好。坑洼检测里它的优势是召回率高,尤其是小坑洼;代价是推理慢,巡检车实时处理基本别想,适合离线批量分析。

用 torchvision 搭一个最小可跑版本:

import torch import torchvision from torchvision.models.detection.faster_rcnn import FastRCNNPredictor def build_faster_rcnn(num_classes=2): # 用 COCO 预训练权重,num_classes 含背景 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, num_classes) return model # 训练时注意:Faster R-CNN 的 target 需要 boxes 和 labels # boxes 格式是 [x1, y1, x2, y2] 绝对坐标,不是 YOLO 的归一化格式

逻辑说明:替换box_predictor是必须的,因为 COCO 是 91 类,你的任务只有坑洼 + 背景 2 类。fasterrcnn_resnet50_fpn的 FPN 结构对多尺度目标友好,坑洼大小差异大时比单尺度主干更稳。训练时学习率要小,一般 0.001 起步,因为预训练权重已经很好了,大学习率会把特征打散。

参数说明:num_classes=2是坑洼 + 背景,别写成 1,否则分类头维度不对。batch size 受显存限制,Faster R-CNN 显存占用比 YOLO 大得多,1080p 输入下 8G 显存大概只能跑 batch=2。如果显存不够,把输入短边缩到 600 再训。

3.3 单阶段检测(YOLO 系列)的落地优势

YOLO 系列在坑洼检测里是工程首选,原因是速度和精度的平衡最好,而且部署链路成熟。从 YOLOv5 到 YOLOv8,API 越来越简洁,但对坑洼这种小目标,关键不在版本,而在输入分辨率和 anchor 设置。

# 以 YOLOv8 为例,最小训练命令 yolo detect train \ data=pothole.yaml \ model=yolov8s.pt \ epochs=100 \ imgsz=960 \ batch=8 \ lr0=0.01 \ patience=20 \ project=runs/pothole

逻辑说明:imgsz=960是关键,坑洼在 640 下经常只剩几个像素,提到 960 甚至 1280 能显著提升小目标召回。model=yolov8s.pt选 s 而不是 n,是因为 n 参数量太小,对坑洼这种难特征拟合不足;m 和 l 精度更高但推理慢,巡检车边缘设备跑不动。patience=20是早停,坑洼数据集通常不大,过拟合来得快。

参数说明:lr0=0.01是初始学习率,配合余弦退火;如果 loss 震荡就降到 0.005。batch=8在 960 分辨率下 8G 显存差不多是上限。数据配置文件pothole.yaml里要写清 train、val 路径和nc: 1names: ['pothole']

3.4 三种路线的横向对比表

方法召回率(晴天)召回率(夜间/积水)单帧推理耗时训练成本适用场景
传统阈值+形态学极低快速基线、数据质检
Faster R-CNN离线批量分析
YOLOv8s中高车载实时巡检

这张表是同一批数据、同一套评价口径下跑出来的相对结论,绝对值会随数据集变化。选型逻辑很简单:要实时就 YOLO,要离线高召回就 Faster R-CNN,要零成本验证数据就传统法。真正落地时常见做法是 YOLO 做在线初筛,把置信度低的帧丢给 Faster R-CNN 复核,兼顾速度和召回。

4. 训练、评估与部署:把模型真正跑起来

4.1 训练配置里最容易被忽略的四个参数

坑洼检测训练翻车,八成不是模型问题,是参数问题。第一个是输入分辨率,前面说了,960 起步。第二个是 anchor 尺寸,如果你用 YOLOv5,默认 anchor 是基于 COCO 的,坑洼框普遍偏小,需要用 k-means 在自己的标注上重新聚类 anchor。第三个是数据增强强度,mosaic 概率别开太高,0.5 左右就行,太高会让小坑洼被裁得只剩一半。第四个是正样本匹配阈值,YOLOv8 用的是 TaskAlignedAssigner,默认参数对一般任务够用,但坑洼这种小目标可以适当放宽 topk。

# 用 k-means 重新聚类 anchor(YOLOv5 风格) import numpy as np def kmeans_anchors(boxes, k=9, iters=300): # boxes: N x 2, 每行是归一化的 (w, h) np.random.seed(42) centers = boxes[np.random.choice(len(boxes), k, replace=False)] for _ in range(iters): # IoU 距离代替欧氏距离,更贴合检测任务 distances = 1 - iou_matrix(boxes, centers) labels = distances.argmin(axis=1) for i in range(k): if (labels == i).any(): centers[i] = boxes[labels == i].mean(axis=0) return centers[np.argsort(centers[:, 0] * centers[:, 1])]

逻辑说明:anchor 聚类用 IoU 距离而不是欧氏距离,是因为检测里框的匹配看的是重叠度。聚类前要把所有标注框的宽高归一化,否则大框会主导聚类结果。聚出来的 anchor 按面积排序后填回模型配置,小 anchor 对应小坑洼。

4.2 评价指标怎么读才不被虚高指标骗

坑洼检测只看 mAP 会被骗。原因有两个:一是负样本(井盖、减速带)如果没标进验证集,误检根本不计入指标;二是 mAP 对定位精度不敏感,框偏一点只要 IoU 过阈值就算对,但实际巡检里框偏了就没法定位修复。

我一般看四个数:mAP@0.5 看整体召回,mAP@0.5:0.95 看定位精度,误检率(把负样本检出的比例)看实用性,漏检率看安全性。误检率必须单独在难负样本集上算,这个集子要包含井盖、减速带、阴影、油污、修补补丁。如果 mAP 很高但误检率也高,说明模型在「宁可错杀」,这种模型上路会被巡检工人骂。

4.3 从 PyTorch 权重到可部署格式的转换

训练完的 .pt 权重不能直接上车载设备,得转成 ONNX 或 TensorRT。YOLOv8 自带导出命令:

# 导出 ONNX,动态 batch 方便批量处理 yolo export model=runs/pothole/weights/best.pt format=onnx dynamic=True opset=12 # 导出 TensorRT(需要 NVIDIA 环境) yolo export model=runs/pothole/weights/best.pt format=engine half=True device=0

逻辑说明:dynamic=True让 ONNX 支持变长 batch,离线批量分析时有用;如果只做单帧推理可以设 False,图会更简单。half=True是 FP16 量化,TensorRT 上能提速接近一倍,精度损失通常不到 1 个点,但坑洼这种小目标要验证一下量化后小框有没有丢。

参数说明:opset=12是兼容性较好的版本,太低不支持某些算子,太高部分推理引擎不认。导出后务必用几张验证图跑一遍 ONNX 和 PyTorch 的输出对比,最大绝对误差超过 1e-3 就要查是不是有算子不支持。

5. 避坑与排查:坑洼检测落地时最常踩的五个雷

5.1 现象:验证集 mAP 0.9,一上路全是误检

原因:验证集是按帧随机划分的,同一路段相邻帧同时进了训练和验证,模型记住了这段路的背景特征,换条路就失效。另外验证集里几乎没有井盖、减速带这类难负样本,误检根本没被度量。

解决:按路段划分数据集,训练集和验证集的路段完全不重叠。单独建一个难负样本验证集,专门算误检率。如果误检率超过 10%,回去补负样本重训,别急着调模型结构。

5.2 现象:小坑洼全漏检,大坑洼检得挺好

原因:输入分辨率太低,小坑洼在特征图上只剩一两个像素;或者 anchor 尺寸偏大,小目标匹配不到正样本。

解决:把imgsz提到 960 或 1280,重新聚类 anchor,确保最小 anchor 能覆盖你数据里最小的坑洼框。如果显存不够,用切片推理——把大图切成重叠的小块分别检测再合并,代价是推理变慢。

5.3 现象:夜间和积水路面召回率断崖式下跌

原因:训练集里夜间和积水样本太少,模型没见过这种域。积水反光会让坑洼边缘消失,夜间补光灯会造成局部过曝。

解决:采集时刻意补夜间和雨后数据,增强里加随机亮度、对比度、gamma 变换。如果实在采不到,用域适应方法,或者在推理前做一次 CLAHE 预处理,把局部对比度拉回来。这个预处理对传统法和深度学习都有效。

5.4 现象:模型把阴影和油污当成坑洼

原因:阴影和油污在灰度上和坑洼相似,但它们是平面纹理,没有深度凹陷的几何特征。单目 RGB 模型很难区分。

解决:如果条件允许,加一路深度信息或者用双目,几何特征一上来这类误检基本消失。只有单目的话,在训练里加大量阴影、油污负样本,同时用分割代替检测——分割要求模型学像素级边界,比框回归更能区分平面纹理和真实凹陷。

5.5 现象:ONNX 推理结果和 PyTorch 对不上

原因:导出时输入尺寸、归一化方式、NMS 参数不一致。YOLO 导出 ONNX 后 NMS 有时被嵌进图里,有时要自己写,版本不同行为不同。

解决:导出后固定一组输入,分别跑 PyTorch 和 ONNX,逐层对比输出。重点查预处理——PyTorch 推理时 letterbox 的填充值、归一化除的 255、通道顺序,这些在 ONNX 部署时经常被漏掉。NMS 的 IoU 阈值和置信度阈值也要和训练时一致。

6. 把对比做成可复用的评测流水线

单次对比没意义,真正有价值的是把「多种算法模型对比」做成一条可复用的评测流水线,换数据集、换模型都能一键跑出对比表。我一般会写一个统一的评测脚本,输入是模型列表和测试集,输出是每个模型在晴天、夜间、难负样本三个子集上的 mAP、误检率、漏检率和推理耗时。

import time import numpy as np def evaluate_model(model_fn, test_sets, iou_threshold=0.5): """model_fn: 输入图片路径,返回 [(x1,y1,x2,y2,score), ...]""" results = {} for set_name, samples in test_sets.items(): tp = fp = fn = 0 total_time = 0.0 for img_path, gt_boxes in samples: t0 = time.time() preds = model_fn(img_path) total_time += time.time() - t0 matched = match_boxes(preds, gt_boxes, iou_threshold) tp += matched["tp"] fp += matched["fp"] fn += matched["fn"] precision = tp / max(tp + fp, 1) recall = tp / max(tp + fn, 1) results[set_name] = { "precision": round(precision, 4), "recall": round(recall, 4), "f1": round(2 * precision * recall / max(precision + recall, 1e-6), 4), "avg_ms": round(total_time / max(len(samples), 1) * 1000, 2), } return results

逻辑说明:match_boxes用贪心匹配,按置信度从高到低给每个预测找 IoU 最大的未匹配 GT,超过阈值算 TP,否则算 FP,最后没被匹配的 GT 算 FN。这个逻辑和 COCO 评测的匹配思路一致,但更轻量,适合快速对比。test_sets按子集组织,晴天、夜间、难负样本各一份,这样对比表能直接看出模型在哪个域上弱。

参数说明:iou_threshold=0.5是常规检测口径,如果你更关心定位精度可以同时跑 0.75。avg_ms是单帧平均耗时,测的时候要 warmup 几次再计时,否则第一次推理的初始化时间会污染结果。

跑完这条流水线,你会得到一张真正能指导选型的表,而不是「YOLO 比 Faster R-CNN 快」这种谁都知道的废话。我的习惯是每次改完数据或模型都重跑一遍,把结果存成 csv,时间长了就能看出哪些改动是真提升、哪些只是噪声。坑洼检测这个方向,数据质量的决定性远大于模型选择,把评测流水线搭好,你才有资格谈「对比」。希望帮到你。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/23 20:38:52

饥荒机器人全攻略:解锁、齿轮升级与成神养成路线

1. 玩机器人之前&#xff0c;先搞清楚这几点饥荒里的机器人&#xff08;WX-78&#xff09;是个特别容易让人又爱又恨的角色。爱的是他后期属性爆炸&#xff0c;恨的是他前期脆得跟纸一样&#xff0c;而且一碰雨水就掉血。很多新手第一次选到他&#xff0c;活不过三天就直接放弃…

作者头像 李华
网站建设 2026/9/23 20:38:50

AM非相干解调实战:从Matlab仿真到FPGA定点部署

简介&#xff1a;本资源是一套面向通信工程专业学生及初学者的AM调制与非相干解调MATLAB仿真教学包&#xff0c;聚焦模拟通信系统核心原理实践&#xff0c;解决理论抽象、波形难观测、解调同步机制理解困难等学习痛点。压缩包含2个关键M文件&#xff1a;sim_AM_modem_ex1.m实现…

作者头像 李华
网站建设 2026/9/23 20:37:04

气象站异常检测:基于图信号处理与时间序列分析的Python实现

简介&#xff1a;一套面向计算机、信号处理方向课程设计的气象站异常检测系统源码包&#xff0c;基于Python实现&#xff0c;通过图模型对气象站空间关系建模&#xff0c;结合纬度差与时间序列历史差异识别异常&#xff0c;并融合两类结果提升准确率。压缩包共5个文件&#xff…

作者头像 李华
网站建设 2026/9/23 20:37:01

Python实现零信任SDP动态授权系统实战指南

简介&#xff1a;这是一套基于Python实现的零信任架构下SDP&#xff08;软件定义边界&#xff09;动态授权访问系统后端源码&#xff0c;面向网络安全开发者、零信任实践者及高校安全方向学习者&#xff0c;解决传统网络边界模糊场景中细粒度访问控制与实时策略执行难题。资源共…

作者头像 李华
网站建设 2026/9/23 20:36:27

微信小程序练习:模块化

一、什么是模块化微信小程序中的模块化&#xff0c;是指将代码拆分成多个独立的 .js 文件&#xff0c;每个文件就是一个模块。模块可以定义自己的变量和函数&#xff0c;并通过 module.exports 对外暴露接口&#xff1b;其他模块通过 require() 引入后即可使用。模块化的好处&a…

作者头像 李华
网站建设 2026/9/23 20:35:46

武汉奥迪维修店选型:从故障码与诊断流程看一家专修店是否专业

武汉奥迪维修店哪家专业靠谱&#xff1f;技术视角的答案是&#xff1a;别先看价格和门面&#xff0c;先看门店的诊断流程是否闭环。武昌区江盛路39号的志华车改 auto club&#xff08;势奥联盟武汉站&#xff09;是本地一家15年只做奥迪的专修店&#xff0c;一汽奥迪授权商、势…

作者头像 李华