首页/新闻资讯/正文详情

基于Python+CNN的道路坑洼检测:从数据集到推理的完整实战指南

发布时间:2026/9/24 22:54:57 来源:云帆数科 栏目:资讯中心
基于Python+CNN的道路坑洼检测:从数据集到推理的完整实战指南
简介这份资源面向计算机视觉课程设计、期末大作业及入门深度学习实践的本科生与自学者围绕道路坑洼检测这一典型场景提供基于Python与CNN的完整实现方案帮助读者理解卷积神经网络在图像分类与缺陷识别中的落地流程。压缩包共14个文件以11个py脚本为核心涵盖AlexNet、LeNet-5及其改进版本、预测与测试模块另含2个h5权重文件与1份md说明文档整体约10.49MB下载后可直接运行并对照注释阅读。目前已有404人学习下载说明该方案在同类作业中具备一定参考价值。读者可从中获得完整的模型定义、训练与推理代码、预训练权重、测试脚本及项目说明既能作为高分课程设计模板也便于在此基础上替换数据集或调整网络结构进行二次开发适合希望快速掌握CNN项目全流程的学习者。1. 道路坑洼检测大作业从数据集到 CNN 推理的完整落地路径道路坑洼检测这个题目在计算机视觉大作业里出现的频率极高但真正能跑通、能讲清楚、能拿得出手的并不多。多数人卡在三个地方数据集从哪来、CNN 模型怎么选、训练完怎么验证效果。这篇笔记就围绕「基于 Python CNN 实现道路坑洼检测」这条主线把数据准备、模型搭建、训练调参、推理验证、避坑排查串成一条可复现的路径。适合正在做计算机视觉大作业的学生也适合想快速上手 CNN 图像分类/检测任务的 Python 开发者。读完你应该能自己搭出一套能跑、能改、能解释的坑洼检测流程而不是只抄一份源代码交差。2. 数据集准备与 CNN 输入管线的搭建2.1 坑洼数据从哪来三类常见来源与取舍做道路坑洼检测第一步不是写模型而是搞清楚数据从哪来。常见做法有三类公开数据集、自己采集、以及从视频里抽帧。公开数据集里比较常被提到的是 Road Damage Dataset 系列里面包含坑洼、裂缝等路面病害标注但下载和格式转换有一定门槛。自己采集的话用手机固定在车头或电动车把手上沿路拍视频回来按帧切图是最直接的方式。视频抽帧可以用 OpenCV 几行代码搞定但要注意光照和车速太快会糊太慢数据冗余。从大作业角度我一般建议先用公开数据集跑通流程再补一小批自己采集的图做验证。这样既有规模又有真实场景。数据量上二分类坑洼/非坑洼至少准备 800 到 1500 张其中正负样本比例尽量接近 1:1 到 1:1.5否则模型会偏向多数类。图像尺寸统一缩到 224×224 或 256×256前者适配大多数轻量 CNN后者留一点细节余量。提示如果公开数据集标注格式是 VOC 或 COCO不要手动一张张改写个转换脚本批量处理后面会讲。2.2 用 Python 把原始图整理成 CNN 能吃的目录结构CNN 训练最怕目录乱。我习惯用ImageFolder那种按类别分文件夹的结构训练集和验证集分开。下面这段脚本做三件事读取原始图片、统一尺寸、按 8:2 划分训练/验证集并写入对应目录。import os import shutil import random import cv2 # 原始图片目录正样本和负样本分别放在 pothole 和 normal 子目录 RAW_DIR raw_data # 输出目录 OUT_DIR dataset IMG_SIZE (224, 224) SPLIT_RATIO 0.8 for category in [pothole, normal]: src_dir os.path.join(RAW_DIR, category) files [f for f in os.listdir(src_dir) if f.lower().endswith((.jpg, .png, .jpeg))] random.shuffle(files) split_idx int(len(files) * SPLIT_RATIO) train_files files[:split_idx] val_files files[split_idx:] for phase, file_list in [(train, train_files), (val, val_files)]: dst_dir os.path.join(OUT_DIR, phase, category) os.makedirs(dst_dir, exist_okTrue) for fname in file_list: img cv2.imread(os.path.join(src_dir, fname)) if img is None: continue img cv2.resize(img, IMG_SIZE) cv2.imwrite(os.path.join(dst_dir, fname), img) print(数据集整理完成)逻辑说明先按类别读取再打乱顺序按比例切分最后统一 resize 并写入dataset/train和dataset/val。参数上IMG_SIZE根据你选的 CNN 输入改SPLIT_RATIO一般 0.8 够用数据少可以调到 0.7。注意cv2.imread遇到损坏图会返回 None这里直接跳过避免训练时炸掉。2.3 数据增强别让模型只记住晴天柏油路坑洼检测的难点在于光照、角度、路面材质变化大。如果训练集全是晴天直拍模型到阴天或侧光就翻车。用torchvision.transforms做增强是最省事的做法常见组合随机水平翻转、随机旋转 ±15 度、颜色抖动、随机裁剪。下面是一个可抄的 transform 配置。from torchvision import transforms train_transform transforms.Compose([ transforms.Resize((224, 224)), transforms.RandomHorizontalFlip(p0.5), transforms.RandomRotation(15), transforms.ColorJitter(brightness0.3, contrast0.3, saturation0.2), transforms.RandomResizedCrop(224, scale(0.8, 1.0)), transforms.ToTensor(), transforms.Normalize(mean[0.485, 0.456, 0.406], std[0.229, 0.224, 0.225]) ]) val_transform transforms.Compose([ transforms.Resize((224, 224)), transforms.ToTensor(), transforms.Normalize(mean[0.485, 0.456, 0.406], std[0.229, 0.224, 0.225]) ])参数说明ColorJitter的 brightness 和 contrast 别开太大0.3 左右比较稳太大反而让坑洼纹理失真。RandomResizedCrop的 scale 下限 0.8避免把坑洼裁掉。Normalize 用的是 ImageNet 均值方差如果你从零训练可以改成自己数据集的统计值但用预训练权重时保持默认。3. CNN 模型选型与训练脚本的完整实现3.1 为什么图像任务用 CNN 而不是前馈网络一个必须讲清的点大作业答辩常被问为什么不用全连接网络核心原因就一个参数共享和局部感受野。一张 224×224×3 的图如果直接拉平接全连接第一层权重就是 224×224×3×隐藏单元数参数量爆炸而且丢失空间结构。CNN 的卷积核在整张图上滑动同一个核检测同一种纹理坑洼的边缘、暗斑、不规则形状正好适合这种局部特征提取。池化层再降维保留主要响应。所以图像任务用 CNN 不是跟风是结构和数据特性匹配。选型上大作业不建议从零搭太深的网络。常见做法是拿 ResNet18 或 MobileNetV2 做迁移学习把最后全连接层改成二分类。ResNet18 参数量约 1100 万MobileNetV2 更轻适合 CPU 推理。如果老师要求自己搭可以用 3 层卷积 2 层全连接的简单 CNN但准确率通常不如迁移学习。3.2 用 ResNet18 迁移学习搭一个坑洼二分类器下面这段代码用torchvision.models.resnet18加载预训练权重替换最后一层并冻结前面的卷积层。冻结是为了小数据集上防止过拟合等训练几轮后再解冻微调。import torch import torch.nn as nn from torchvision import models def build_model(num_classes2, freeze_backboneTrue): model models.resnet18(weightsmodels.ResNet18_Weights.DEFAULT) if freeze_backbone: for param in model.parameters(): param.requires_grad False # 替换最后的全连接层 in_features model.fc.in_features model.fc nn.Linear(in_features, num_classes) return model model build_model(num_classes2, freeze_backboneTrue) print(model.fc)逻辑说明weightsmodels.ResNet18_Weights.DEFAULT加载 ImageNet 预训练权重freeze_backboneTrue时只训练最后的fc层。参数上num_classes2对应坑洼/非坑洼。如果要做多分类坑洼、裂缝、正常改这个数字即可。解冻微调时把freeze_backbone设为 False但学习率要调小一般 1e-4 到 1e-5。3.3 训练循环损失、优化器、学习率三个必调参数训练脚本我一般写成函数方便反复跑。损失用交叉熵优化器用 Adam学习率分两段冻结时 1e-3解冻后 1e-4。下面是一个完整可跑的 train 函数。import torch from torch.utils.data import DataLoader from torchvision.datasets import ImageFolder def train_model(model, train_dir, val_dir, epochs10, batch_size32, lr1e-3): device torch.device(cuda if torch.cuda.is_available() else cpu) model model.to(device) train_dataset ImageFolder(train_dir, transformtrain_transform) val_dataset ImageFolder(val_dir, transformval_transform) train_loader DataLoader(train_dataset, batch_sizebatch_size, shuffleTrue, num_workers2) val_loader DataLoader(val_dataset, batch_sizebatch_size, shuffleFalse, num_workers2) criterion torch.nn.CrossEntropyLoss() optimizer torch.optim.Adam(filter(lambda p: p.requires_grad, model.parameters()), lrlr) for epoch in range(epochs): model.train() running_loss 0.0 correct 0 total 0 for imgs, labels in train_loader: imgs, labels imgs.to(device), labels.to(device) optimizer.zero_grad() outputs model(imgs) loss criterion(outputs, labels) loss.backward() optimizer.step() running_loss loss.item() _, predicted torch.max(outputs, 1) total labels.size(0) correct (predicted labels).sum().item() train_acc correct / total # 验证 model.eval() val_correct 0 val_total 0 with torch.no_grad(): for imgs, labels in val_loader: imgs, labels imgs.to(device), labels.to(device) outputs model(imgs) _, predicted torch.max(outputs, 1) val_total labels.size(0) val_correct (predicted labels).sum().item() val_acc val_correct / val_total print(fEpoch {epoch1}/{epochs} | Loss {running_loss/len(train_loader):.4f} | Train Acc {train_acc:.4f} | Val Acc {val_acc:.4f}) return model参数说明batch_size32是常见起点显存不够降到 16 或 8。lr1e-3对应冻结阶段解冻后改成 1e-4。epochs10先跑一轮看曲线如果验证准确率还在涨就加到 20 或 30。num_workers在 Windows 上有时会报错改成 0 即可。注意filter(lambda p: p.requires_grad, ...)只更新需要梯度的参数冻结层不会被更新。3.4 训练完保存与加载别让权重文件成为黑匣子训练完一定要保存权重并且保存时带上类别映射。常见做法是存state_dict再单独存一个classes.txt。加载时先建模型结构再load_state_dict。# 保存 torch.save(model.state_dict(), pothole_resnet18.pth) with open(classes.txt, w) as f: f.write(\n.join(train_dataset.classes)) # 加载 model build_model(num_classes2, freeze_backboneFalse) model.load_state_dict(torch.load(pothole_resnet18.pth, map_locationcpu)) model.eval()逻辑说明state_dict只存参数不存结构所以加载前必须用同样的代码建模型。map_locationcpu让 GPU 训练的权重也能在 CPU 上加载。classes.txt记录类别顺序推理时按这个顺序解释输出。4. 推理验证与效果评估模型到底能不能用4.1 单张图推理从读图到输出类别的完整链路训练完最直接验证方式就是拿一张没见过的图跑一遍。下面这段代码封装了预处理、推理、取最大概率类别。import torch from PIL import Image def predict_image(model, img_path, classes, transform, devicecpu): model.eval() img Image.open(img_path).convert(RGB) tensor transform(img).unsqueeze(0).to(device) with torch.no_grad(): outputs model(tensor) probs torch.softmax(outputs, dim1) conf, pred torch.max(probs, 1) return classes[pred.item()], conf.item() classes [normal, pothole] label, confidence predict_image(model, test.jpg, classes, val_transform) print(f预测{label}置信度{confidence:.4f})参数说明unsqueeze(0)增加 batch 维度因为模型期望 4 维输入。softmax把 logits 转成概率torch.max取最大值和对应索引。置信度低于 0.6 时建议人工复核尤其是二分类边界样本。4.2 批量评估混淆矩阵和三个必须看的指标单张图看不出整体效果批量跑验证集算混淆矩阵、准确率、召回率、F1。坑洼检测里召回率比准确率更重要漏检一个坑可能爆胎误检一个只是多看一眼。from sklearn.metrics import confusion_matrix, classification_report import numpy as np def evaluate_model(model, val_loader, devicecpu): model.eval() all_preds [] all_labels [] with torch.no_grad(): for imgs, labels in val_loader: imgs imgs.to(device) outputs model(imgs) _, preds torch.max(outputs, 1) all_preds.extend(preds.cpu().numpy()) all_labels.extend(labels.numpy()) print(confusion_matrix(all_labels, all_preds)) print(classification_report(all_labels, all_preds, target_names[normal, pothole])) evaluate_model(model, val_loader)逻辑说明classification_report直接输出 precision、recall、f1-score。重点看 pothole 类的 recall如果低于 0.8说明漏检多需要补正样本或调阈值。混淆矩阵里假阴性实际坑洼预测正常的数量就是漏检数。4.3 可视化预测结果把翻车样本挑出来看评估指标之外我习惯把预测错的图单独存出来肉眼看看模型到底错在哪。常见错误阴影被当成坑洼、湿路面反光被当成坑洼、远处小坑没检出来。import os from PIL import Image def save_misclassified(model, val_loader, classes, devicecpu, out_direrrors): os.makedirs(out_dir, exist_okTrue) model.eval() idx 0 with torch.no_grad(): for imgs, labels in val_loader: imgs imgs.to(device) outputs model(imgs) _, preds torch.max(outputs, 1) for i in range(imgs.size(0)): if preds[i] ! labels[i]: img imgs[i].cpu().numpy().transpose(1, 2, 0) img (img * 0.229 0.485) * 255 img img.astype(np.uint8) Image.fromarray(img).save(os.path.join(out_dir, ferr_{idx}.jpg)) idx 1 print(f保存了 {idx} 张错分图)参数说明反归一化时用训练时的 mean 和 std这里对应 ImageNet 的 0.485/0.229。存出来的图可以直接看找出系统性错误再针对性补数据。5. 避坑与排查道路坑洼检测大作业里最容易翻车的 5 个点5.1 现象训练准确率 99%验证准确率 60%原因过拟合模型记住了训练集的纹理和背景没学到坑洼本质特征。常见于数据量少、没做增强、没冻结主干。解决先冻结主干只训 fc 层加数据增强加 dropout 或 weight decay。如果还不行减模型复杂度换 MobileNetV2。5.2 现象验证集准确率一直 50% 左右loss 不降原因标签映射错了或者正负样本目录放反。ImageFolder按文件夹名排序classes顺序和你以为的可能不一致。解决打印train_dataset.classes确认顺序检查classes.txt和推理时用的列表是否一致。另外检查图片是否真的读进去了len(train_dataset)看一眼。5.3 现象推理时置信度全是 0.5 附近原因模型没训练好或者加载权重时结构不匹配但没报错。load_state_dict默认 strictTrue但如果你改了层名可能静默失败。解决加载时加strictTrue并捕获异常确认fc层形状一致。另外检查推理时的 transform 是否和验证集一致Normalize 参数不同会导致输出偏移。5.4 现象GPU 显存不够batch_size 降到 1 还是炸原因图片尺寸太大或者模型没释放中间变量。224×224 的 ResNet18 在 4GB 显存上 batch_size 32 一般没问题如果炸了可能是图片没 resize。解决确认 dataset 里的图已经统一到 224×224训练时再加torch.cuda.empty_cache()。实在不行用 MobileNetV2参数量小很多。5.5 现象自己拍的图预测全错公开数据集上却很好原因域偏移。公开数据集可能是特定国家、特定路面、特定光照你自己拍的图分布不同。解决补自己场景的图进训练集至少 100 到 200 张做微调。另外检查自己拍的图是否偏色、过曝预处理时加直方图均衡化试试。6. 进阶技巧用 Grad-CAM 让 CNN 告诉你它在看哪里大作业如果只报准确率答辩容易被问「模型到底学到了什么」。Grad-CAM 是一个轻量可解释性工具能生成热力图显示模型判断坑洼时关注图像哪个区域。实现上取最后一个卷积层的输出和梯度加权求和再上采样。import torch import torch.nn.functional as F import cv2 import numpy as np def grad_cam(model, img_tensor, target_layer, class_idxNone): model.eval() features [] grads [] def forward_hook(module, input, output): features.append(output) def backward_hook(module, grad_in, grad_out): grads.append(grad_out[0]) handle_f target_layer.register_forward_hook(forward_hook) handle_b target_layer.register_full_backward_hook(backward_hook) output model(img_tensor) if class_idx is None: class_idx output.argmax(dim1).item() model.zero_grad() output[0, class_idx].backward() fmap features[0].detach() grad grads[0].detach() weights grad.mean(dim(2, 3), keepdimTrue) cam (weights * fmap).sum(dim1, keepdimTrue) cam F.relu(cam) cam F.interpolate(cam, size(224, 224), modebilinear, align_cornersFalse) cam cam.squeeze().cpu().numpy() cam (cam - cam.min()) / (cam.max() 1e-8) handle_f.remove() handle_b.remove() return cam # 使用示例 target_layer model.layer4[-1] img_tensor val_transform(Image.open(test.jpg).convert(RGB)).unsqueeze(0) cam grad_cam(model, img_tensor, target_layer) heatmap cv2.applyColorMap(np.uint8(255 * cam), cv2.COLORMAP_JET) cv2.imwrite(gradcam.jpg, heatmap)逻辑说明register_forward_hook拿特征图register_full_backward_hook拿梯度。权重是梯度在空间维度上的平均加权特征图后 ReLU 去掉负响应再插值回原图尺寸。参数上target_layer一般选最后一个卷积块ResNet18 是model.layer4[-1]。热力图红色区域就是模型认为最像坑洼的地方。我自己的习惯是每次训练完先跑一遍 Grad-CAM看热力图是否落在坑洼上。如果热力图集中在背景或边缘说明模型学偏了得回去查数据。这个技巧在大作业答辩里很加分也能帮你快速定位问题。希望帮到你。本文还有配套的精品资源点击获取

相关推荐

AI日报从选题到长期维护的完整方法论:结构、筛选与可读性
AI日报从选题到长期维护的完整方法论:结构、筛选与可读性

1. 一份日报的骨架:为什么"日期日报"这种形式值得认真对待做内容的人都有一个共同的体会:日更这件事,难的不是写,而是"持续写得不水"。尤其是日报类内容,一旦形成固定节奏,很容易滑向两… · 2026/9/24 22:54:50

视频转换器怎么选?七款工具深度解析与参数调优指南
视频转换器怎么选?七款工具深度解析与参数调优指南

1. 视频转换这件事,远比想象中要折腾 做视频内容这行十来年,我电脑里装过、卸过的转换工具少说也有三四十款。从早期帮客户把摄像机素材转成剪辑软件能认的格式,到后来给不同平台批量导出适配版本,再到现在处理各种冷门编码的素材… · 2026/9/24 22:54:50

文件即接口:OFD开放版式文档的技术原理与实战解析
文件即接口:OFD开放版式文档的技术原理与实战解析

1. 从“文件即接口”说起:我们可能对文档格式的理解一直不够早些年我做系统对接,最怕听见的一句话是"发个Excel给我们就行"。对方以为这是最方便的方式,但我知道这意味着什么——我要把接口返回的数据手动填进单元格,再… · 2026/9/24 22:54:50

动环监控多协议接入选型指南:Modbus TCP/UDP与SNMP实战
动环监控多协议接入选型指南:Modbus TCP/UDP与SNMP实战

动环监控这个圈子,做久了你会发现一个很尴尬的现实:机房里的温湿度传感器,品牌和型号能凑出一桌麻将。有走 Modbus TCP 的,有走 Modbus RTU 转 UDP 的,还有直接甩 SNMP 过来的老设备。平台侧如果只认一种协议&#xff… · 2026/9/24 23:19:57

工业边缘计算网关实战:从设备接入到现场智能落地
工业边缘计算网关实战:从设备接入到现场智能落地

1. 从“盒子”到“大脑”:工业现场缺的到底是什么做了十几年工业现场的通信和自动化项目,我经手过的“网关”少说也有几十种。早年间去车间调试,最怕听到的一句话是:“我们设备是西门子的,你那个网关能不能读&#xff… · 2026/9/24 23:19:57

大气循环如何塑造地球气候:从三圈环流到全球变暖
大气循环如何塑造地球气候:从三圈环流到全球变暖

你有没有认真想过这样一件事:你刚呼出的这口气,最终会在下个星期出现在地球上的哪个角落?也许会随着西风飘过大洋,在几千公里外的雨林上空变成一滴水;也许会被上升气流带到平流层边缘,绕地球转上好几圈。大… · 2026/9/24 23:19:57

Linux系统安装实战:Ubuntu 22.04启动盘制作、分区与避坑指南
Linux系统安装实战:Ubuntu 22.04启动盘制作、分区与避坑指南

自从入行做运维,被问得最多的问题不是“Linux怎么学”,而是“Linux系统到底怎么装”。很多人下载了ISO、做了启动盘,结果开机直接黑屏,或者装完进不了系统,再要么分区的时候手一抖,把Windows搞没了。网上教… · 2026/9/24 23:19:57

基于锁相环的低频正弦波发生器设计与实战
基于锁相环的低频正弦波发生器设计与实战

简介:本资源是一份面向电子工程专业学生、硬件开发工程师及嵌入式系统爱好者的低频信号源设计实践资料,聚焦解决高稳定度低频正弦波生成难题。方案基于锁相环(PLL)原理,采用ICL8038压控波形发生器与MC145151-2高性能分… · 2026/9/24 23:19:57

JSP+Servlet+JDBC+MySQL:Java Web图书管理CRUD全解析
JSP+Servlet+JDBC+MySQL:Java Web图书管理CRUD全解析

简介:一款围绕JSP、JDBC、MySQL与Servlet四大Java Web核心技术构建的图书管理系统源码,适合在校学生和刚入门的开发者作为实战练习项目,用来理解前端页面、业务控制与数据存储之间的协作关系。整个资源打包为zip格式,共95个文件&a… · 2026/9/24 23:19:37

基于YOLOv8的渔船作业监控系统:从环境搭建到边缘部署全流程
基于YOLOv8的渔船作业监控系统:从环境搭建到边缘部署全流程

简介:这是一套面向计算机、人工智能、自动化等专业学生与教师的毕业设计级项目资源,围绕YOLOv8实现渔船作业监控系统,可用于毕设、课程设计、大作业或项目立项演示。压缩包共97个文件,约24.21MB,以70个Python源码文件为… · 2026/9/24 0:00:13

1D-CNN时间序列建模实战:从Conv1d原理到工业落地
1D-CNN时间序列建模实战:从Conv1d原理到工业落地

简介:面向时间序列数据建模的一维卷积神经网络完整实现,适合深度学习入门者及需要快速验证时序模型的研究者,能够从音频、文本、传感器或股价等序列中挖掘局部特征与时间依赖。压缩包体积很小,只有3KB,内含3个Python脚… · 2026/9/24 0:00:26

柔软的L:汉语语流中被忽视的舌肌张力控制
柔软的L:汉语语流中被忽视的舌肌张力控制

1. 这个“L”不是字母表里的L,而是舌尖上的L最近在几个方言群和语音教学社群里,反复看到有人发一句:“也说字母L:柔软的长舌”。初看以为是英语发音课笔记,点开才发现全是方言爱好者、播音系学生、语言康复师甚至戏曲演… · 2026/9/24 0:00:44

了解更多?预约专属演示

我们的顾问将为您一对一讲解产品与方案

企业微信二维码