首页 / 资讯中心 / 文章详情

基于CNN的道路坑洼检测实战:从数据标注到切片推理的完整方案

基于CNN的道路坑洼检测实战:从数据标注到切片推理的完整方案 ★ FEATURED ARTICLE
简介这份资源是面向计算机视觉课程学习者与深度学习入门者的道路坑洼检测项目基于Python与CNN实现可作为高分课程设计、期末大作业的参考方案也适合希望动手实践图像分类与目标识别的同学。压缩包共14个文件以11个py脚本为核心涵盖模型定义、训练、测试与预测等环节另含2个h5权重文件与1份md说明文档整体约10.49MB下载后即可运行。项目围绕AlexNet、LeNet-5等经典卷积网络展开代码注释较为完整便于理解网络结构、数据流程与推理逻辑也留有二次开发空间。目前已有404人学习下载说明其在同类作业中具备一定参考价值。读者可从中获得一套完整的坑洼检测实现思路、可复用的模型权重与预测脚本以及便于对照阅读的工程目录组织方式适合作为课程实践与项目复现的起点。1. 道路坑洼检测从一张颠簸的路面照片说起减速带前那一下剧烈颠簸大概率不是悬挂的锅而是路面坑洼在作祟。道路坑洼检测这件事本质上是让机器从一张路面图像里判断出哪里凹下去了、边界在哪、有多严重。传统做法靠人工巡检或激光断面仪成本高、覆盖慢而用 Python CNN 做一套端到端的检测方案恰好是计算机视觉大作业里既能体现卷积神经网络原理、又能落地到真实场景的选题。它适合已经学过 Python 基础、想用深度学习解决一个具体视觉任务的学生和初级工程师。这篇笔记不讲空泛概念而是把数据怎么造、模型怎么搭、训练怎么调、坑在哪一步步拆开讲清楚让你照着能跑通一套属于自己的道路坑洼检测流程。2. 数据从哪来坑洼图像采集与标注的完整链路做检测任务数据永远是第一道坎。道路坑洼检测不像猫狗分类那样有现成的大数据集绝大多数情况下你得自己攒。常见做法是手机或行车记录仪沿路拍摄再抽帧成图。这里有个容易被忽略的点坑洼在整张图里占比往往很小如果直接拿全图训练模型很容易学到整张图都是正常路面这种偷懒结论。所以采集阶段就要有意识地控制正负样本比例并且尽量覆盖不同光照、不同路面材质、不同积水状态。2.1 采集设备与拍摄策略手机固定在车前挡风玻璃内侧、镜头朝下前方约 30 度是最省事的方案。分辨率不用追求 4K1080P 足够因为后续训练输入通常会被缩到 416 或 512。拍摄时车速控制在 20 到 40 公里每小时太快会糊太慢效率低。每条路段至少来回各拍一次避免单向光照造成的阴影被误判成坑洼。阴天和正午强光下各拍一组这两类光照是模型翻车的高发区。采集到的原始视频用 ffmpeg 抽帧间隔按车速估算一般每 0.5 秒抽一帧避免相邻帧高度重复。# 每 0.5 秒抽一帧输出为 jpg ffmpeg -i road.mp4 -vf fps2 -q:v 2 frames/frame_%05d.jpgfps2表示每秒抽 2 帧对应 0.5 秒间隔-q:v 2控制 jpg 质量数值越小质量越高。抽完帧后人工过一遍删掉严重模糊和完全重复的图通常能砍掉三成冗余。2.2 标注格式选择与 labelImg 实操坑洼检测一般用目标检测框标注格式选 YOLO 的 txt 或 Pascal VOC 的 xml。YOLO 格式更简洁每行是类别 x_center y_center width height坐标都归一化到 0 到 1。用 labelImg 标注时框要贴紧坑洼的实际破损边缘不要把周围裂纹也框进去否则模型学到的边界会偏大。# 把 labelImg 导出的 VOC xml 转成 YOLO txt import xml.etree.ElementTree as ET import os def voc_to_yolo(xml_path, out_path, img_w, img_h): tree ET.parse(xml_path) root tree.getroot() lines [] for obj in root.findall(object): cls_name obj.find(name).text cls_id 0 # 本任务只有 pothole 一类 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) # 归一化并转为中心点加宽高 x_c (xmin xmax) / 2.0 / img_w y_c (ymin ymax) / 2.0 / img_h w (xmax - xmin) / img_w h (ymax - ymin) / img_h lines.append(f{cls_id} {x_c:.6f} {y_c:.6f} {w:.6f} {h:.6f}) with open(out_path, w) as f: f.write(\n.join(lines))这段脚本的关键在于归一化x_c、y_c、w、h全部除以图像宽高这样模型输入尺寸变化时标注依然有效。cls_id固定为 0因为道路坑洼检测通常只关心一类目标。转换后要抽查几张用可视化脚本把框画回原图确认没偏。2.3 数据增强与类别不平衡处理坑洼样本天然少于正常路面直接训练会让模型偏向背景。常见做法是离线增强正样本随机翻转、亮度扰动、加高斯噪声、随机裁剪。注意不要用垂直翻转因为路面坑洼有明确的重力方向上下颠倒不符合物理规律属于无效增强。import cv2 import numpy as np import random def augment(img, boxes): # 水平翻转 if random.random() 0.5: img cv2.flip(img, 1) w img.shape[1] boxes [[w - x - bw, y, bw, bh] for x, y, bw, bh in boxes] # 亮度扰动 if random.random() 0.5: gain random.uniform(0.7, 1.3) img np.clip(img.astype(np.float32) * gain, 0, 255).astype(np.uint8) return img, boxes水平翻转时框的 x 坐标要同步镜像否则标注和图像对不上这是新手最常犯的错。亮度扰动范围控制在 0.7 到 1.3 之间太极端会引入不真实的过曝或欠曝。增强后的正样本数量建议补到负样本的 1 到 2 倍让模型有足够机会学习坑洼特征。3. 用 CNN 搭检测模型从骨干网络到检测头数据准备好之后核心问题是用什么网络结构。道路坑洼检测属于目标检测不是简单分类所以不能只堆几个卷积层加全连接。常见路线有两条一是用 YOLO 系列做单阶段检测速度快、适合实时二是用 Faster R-CNN 做两阶段精度高但慢。大作业场景下我一般推荐 YOLOv5 或 YOLOv8 的轻量版本代码成熟、训练脚本现成能把精力放在数据和调参上。3.1 骨干网络为什么选 CSPDarknetYOLO 的骨干网络 CSPDarknet 用了跨阶段部分连接把特征图分成两部分一部分走卷积、一部分直接连到后面。这样做的好处是梯度流更顺、参数量更少对小目标坑洼这种需要保留空间信息的任务比较友好。相比 ResNet 那种纯残差堆叠CSP 结构在同等精度下推理更快适合部署到车载设备。如果你要自己从零写一个简化版 CNN 检测头核心是三层结构卷积提取特征、池化降维、再用 1x1 卷积输出类别和框偏移。但大作业里不建议从零造轮子直接用现成框架改配置更稳。3.2 训练配置与关键参数以 YOLOv8 为例数据配置文件pothole.yaml长这样path: ./dataset train: images/train val: images/val nc: 1 names: [pothole]nc是类别数本任务只有坑洼一类所以填 1names要和标注时的类别名一致写错会导致训练时找不到标签。训练命令yolo detect train datapothole.yaml modelyolov8n.pt epochs100 imgsz640 batch16 lr00.01modelyolov8n.pt用的是 nano 版本参数量小、训练快适合大作业imgsz640是输入尺寸坑洼目标偏小可以适当提到 768但显存要够lr00.01是初始学习率太大容易震荡太小收敛慢0.01 是常用起点batch16按显存调8G 显存跑 640 尺寸大概能到 16。训练过程中重点看三个指标box_loss是否稳定下降、mAP50是否上升、precision和recall是否平衡。如果box_loss下降但mAP不涨多半是过拟合要加增强或减轮数。3.3 推理与结果可视化训练完用验证集跑推理把框画出来看效果from ultralytics import YOLO import cv2 model YOLO(runs/detect/train/weights/best.pt) results model(test.jpg, conf0.4) for r in results: img r.plot() # 自动画框和置信度 cv2.imwrite(result.jpg, img)conf0.4是置信度阈值低于这个值的框会被过滤。阈值调低召回高但误检多调高则相反实际部署时按场景权衡巡检场景宁可多报也别漏报可以设 0.3统计报表场景要准设 0.5 以上。4. 避坑与排查道路坑洼检测最常见的五个翻车点这一章是我自己踩过的坑按现象、原因、解决三段写你对照排查能省不少时间。现象一训练 loss 一直不降mAP 接近零。原因通常是标注格式和配置文件对不上比如 YOLO 格式的坐标没归一化或者names里的类别名和标注文件里的不一致。解决方法是先用可视化脚本把标注框画回原图确认框的位置和类别都对再检查pothole.yaml的路径是否指向正确的 train 和 val 目录。现象二验证集 mAP 很高但实际路上跑全是误检。原因是训练集和实际场景分布不一致比如训练图都是干燥路面实际有积水反光。解决办法是补充积水、阴影、夜间等困难样本或者在推理时加一个颜色和纹理的预筛选把明显不是路面的区域先排除。现象三小坑洼检测不到大坑洼框偏大。原因是输入分辨率不够小目标在降采样过程中特征丢失。解决方法是把imgsz提到 768 或 896同时在数据增强里减少随机缩放的比例让模型多见到原始尺度的小目标。现象四训练到一半显存爆了。原因是batch设太大或者imgsz太高。解决方法是把batch降到 8 或 4或者开启梯度累积模拟大 batch。另外检查是否有其他进程占用显存nvidia-smi看一眼就清楚。现象五推理速度慢达不到实时。原因是用了大模型或者没做推理优化。解决方法是换 nano 或 small 版本导出 ONNX 或 TensorRT 加速输入尺寸从 640 降到 416。实测 416 尺寸下 nano 模型在普通 GPU 上能到 60 帧以上足够车载使用。5. 进阶技巧用切片推理和置信度融合提升小坑洼召回前面讲的都是标准流程但道路坑洼检测有个特殊难点坑洼在整张图里占比可能不到百分之一直接整图推理时小目标很容易被漏掉。我后来固定用一套切片推理加置信度融合的做法召回率能明显提上来。思路是把一张大图切成有重叠的小块每块单独推理再把所有框映射回原图坐标最后用非极大值抑制去重。重叠区域设成块尺寸的 20%保证跨块的坑洼不会被切断。import cv2 import numpy as np from ultralytics import YOLO def sliced_inference(model, img, slice_size640, overlap0.2): h, w img.shape[:2] step int(slice_size * (1 - overlap)) all_boxes [] for y in range(0, h, step): for x in range(0, w, step): patch img[y:yslice_size, x:xslice_size] if patch.shape[0] 32 or patch.shape[1] 32: continue results model(patch, conf0.3, verboseFalse) for r in results: for box in r.boxes: x1, y1, x2, y2 box.xyxy[0].tolist() conf float(box.conf[0]) # 映射回原图坐标 all_boxes.append([x1x, y1y, x2x, y2y, conf]) return all_boxes def nms(boxes, iou_thr0.5): if not boxes: return [] boxes sorted(boxes, keylambda b: b[4], reverseTrue) keep [] while boxes: best boxes.pop(0) keep.append(best) boxes [b for b in boxes if iou(best, b) iou_thr] return keep def iou(a, b): ax1, ay1, ax2, ay2 a[:4] bx1, by1, bx2, by2 b[:4] ix1, iy1 max(ax1, bx1), max(ay1, by1) ix2, iy2 min(ax2, bx2), min(ay2, by2) iw, ih max(0, ix2-ix1), max(0, iy2-iy1) inter iw * ih area_a (ax2-ax1) * (ay2-ay1) area_b (bx2-bx1) * (by2-by1) return inter / (area_a area_b - inter 1e-6)切片尺寸设 640 和训练输入一致避免尺度不匹配重叠 20% 是经验值再低会漏跨块目标再高计算量浪费。conf0.3在切片时故意放低因为单块里目标更清晰低阈值能多捞回一些最后 NMS 的iou_thr0.5去掉重叠框。这套做法代价是推理时间变成原来的三到四倍所以适合离线巡检或对实时性要求不高的场景。如果要在车上实时跑可以只对图像下半部分做切片因为坑洼基本出现在路面区域上半部分天空和远景直接跳过。参数上还有两个可以调的地方一是切片尺寸如果坑洼普遍很小可以降到 416块数变多但每块更聚焦二是 NMS 阈值如果发现同一个坑被切成两个框没合并把iou_thr降到 0.4 试试。我自己的习惯是每次换数据集先跑一遍整图推理再跑一遍切片推理对比两者的召回差异。如果切片提升不明显说明数据里小目标不多没必要上这套如果提升超过 10 个百分点那就值得固化到流程里。这套方案我用了大半年最大的教训是别一上来就堆复杂结构先把数据和推理策略理顺模型本身反而没那么关键。希望帮到你。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?
咨询建站