ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

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

基于计算机视觉的道路坑洼检测:多种算法模型对比与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, step15): os.makedirs(out_dir, exist_okTrue) 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, fframe_{saved:05d}.jpg), frame) saved 1 idx 1 cap.release() return saved # 2. 感知哈希去重汉明距离小于 5 视为重复帧 def dedup_frames(img_dir, threshold5): phasher PHash() encodings phasher.encode_images(image_dirimg_dir) duplicates phasher.find_duplicates(encoding_mapencodings, max_distance_thresholdthreshold) 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_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() 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_w、img_h必须和实际图片尺寸一致如果标注时图片被缩放过了这里不修正就会导致框整体偏移。参数说明threshold5是汉明距离阈值值越大删得越狠一般 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_area800): img cv2.imread(img_path) gray cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # CLAHE 增强局部对比度应对光照不均 clahe cv2.createCLAHE(clipLimit2.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逻辑说明adaptiveThreshold的blockSize25决定局部邻域大小太小会把纹理当坑洼太大对暗区不敏感C8是阈值偏移值越大越保守。形态学核用椭圆而不是矩形是因为坑洼边缘通常不规则椭圆核更贴合。面积和长宽比过滤是传统法的命门——它决定了误检率但也决定了漏检率调参时要在两者之间找平衡。传统法的边界很清楚光照均匀、路面干净时召回率能到 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_classes2): # 用 COCO 预训练权重num_classes 含背景 model torchvision.models.detection.fasterrcnn_resnet50_fpn( weightsDEFAULT) 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_classes2是坑洼 背景别写成 1否则分类头维度不对。batch size 受显存限制Faster R-CNN 显存占用比 YOLO 大得多1080p 输入下 8G 显存大概只能跑 batch2。如果显存不够把输入短边缩到 600 再训。3.3 单阶段检测YOLO 系列的落地优势YOLO 系列在坑洼检测里是工程首选原因是速度和精度的平衡最好而且部署链路成熟。从 YOLOv5 到 YOLOv8API 越来越简洁但对坑洼这种小目标关键不在版本而在输入分辨率和 anchor 设置。# 以 YOLOv8 为例最小训练命令 yolo detect train \ datapothole.yaml \ modelyolov8s.pt \ epochs100 \ imgsz960 \ batch8 \ lr00.01 \ patience20 \ projectruns/pothole逻辑说明imgsz960是关键坑洼在 640 下经常只剩几个像素提到 960 甚至 1280 能显著提升小目标召回。modelyolov8s.pt选 s 而不是 n是因为 n 参数量太小对坑洼这种难特征拟合不足m 和 l 精度更高但推理慢巡检车边缘设备跑不动。patience20是早停坑洼数据集通常不大过拟合来得快。参数说明lr00.01是初始学习率配合余弦退火如果 loss 震荡就降到 0.005。batch8在 960 分辨率下 8G 显存差不多是上限。数据配置文件pothole.yaml里要写清 train、val 路径和nc: 1、names: [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 重新聚类 anchorYOLOv5 风格 import numpy as np def kmeans_anchors(boxes, k9, iters300): # boxes: N x 2, 每行是归一化的 (w, h) np.random.seed(42) centers boxes[np.random.choice(len(boxes), k, replaceFalse)] for _ in range(iters): # IoU 距离代替欧氏距离更贴合检测任务 distances 1 - iou_matrix(boxes, centers) labels distances.argmin(axis1) for i in range(k): if (labels i).any(): centers[i] boxes[labels i].mean(axis0) return centers[np.argsort(centers[:, 0] * centers[:, 1])]逻辑说明anchor 聚类用 IoU 距离而不是欧氏距离是因为检测里框的匹配看的是重叠度。聚类前要把所有标注框的宽高归一化否则大框会主导聚类结果。聚出来的 anchor 按面积排序后填回模型配置小 anchor 对应小坑洼。4.2 评价指标怎么读才不被虚高指标骗坑洼检测只看 mAP 会被骗。原因有两个一是负样本井盖、减速带如果没标进验证集误检根本不计入指标二是 mAP 对定位精度不敏感框偏一点只要 IoU 过阈值就算对但实际巡检里框偏了就没法定位修复。我一般看四个数mAP0.5 看整体召回mAP0.5:0.95 看定位精度误检率把负样本检出的比例看实用性漏检率看安全性。误检率必须单独在难负样本集上算这个集子要包含井盖、减速带、阴影、油污、修补补丁。如果 mAP 很高但误检率也高说明模型在「宁可错杀」这种模型上路会被巡检工人骂。4.3 从 PyTorch 权重到可部署格式的转换训练完的 .pt 权重不能直接上车载设备得转成 ONNX 或 TensorRT。YOLOv8 自带导出命令# 导出 ONNX动态 batch 方便批量处理 yolo export modelruns/pothole/weights/best.pt formatonnx dynamicTrue opset12 # 导出 TensorRT需要 NVIDIA 环境 yolo export modelruns/pothole/weights/best.pt formatengine halfTrue device0逻辑说明dynamicTrue让 ONNX 支持变长 batch离线批量分析时有用如果只做单帧推理可以设 False图会更简单。halfTrue是 FP16 量化TensorRT 上能提速接近一倍精度损失通常不到 1 个点但坑洼这种小目标要验证一下量化后小框有没有丢。参数说明opset12是兼容性较好的版本太低不支持某些算子太高部分推理引擎不认。导出后务必用几张验证图跑一遍 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_threshold0.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_threshold0.5是常规检测口径如果你更关心定位精度可以同时跑 0.75。avg_ms是单帧平均耗时测的时候要 warmup 几次再计时否则第一次推理的初始化时间会污染结果。跑完这条流水线你会得到一张真正能指导选型的表而不是「YOLO 比 Faster R-CNN 快」这种谁都知道的废话。我的习惯是每次改完数据或模型都重跑一遍把结果存成 csv时间长了就能看出哪些改动是真提升、哪些只是噪声。坑洼检测这个方向数据质量的决定性远大于模型选择把评测流水线搭好你才有资格谈「对比」。希望帮到你。本文还有配套的精品资源点击获取
返回列表