尧图精选

基于CNN的道路坑洼检测:从LeNet-5到AlexNet的二分类实战

🕒 发布时间:2026/10/1 18:18:49 📁 来源:尧图网络
简介这是一份基于 Python 与 CNN 实现的道路坑洼检测课程设计项目适合正在完成计算机视觉相关大作业的本科生、研究生以及需要经典卷积神经网络实战案例的开发者。项目来源于大三期末大作业经导师指导并评审获得 98 分围绕 LeNet-5、AlexNet 等网络结构展开覆盖数据预处理、模型构建、训练验证、测试评估和单张图片预测等完整流程代码中又区分了 LeNet-5 与 LeNet-5 2.0 等多个版本并提供主程序、预测器、测试脚本、数据生成脚本和结果汇总脚本便于对照不同结构的性能差异也可以从原始图像到检测结果的完整调用链中理解工程模块间的协同方式。压缩包共有 14 个文件包含 11 个 Python 脚本、2 个训练好的 h5 模型权重和 1 份 README 文档整体大小约 10.49MB模块划分清楚便于快速运行和二次开发。目前已有 905 人学习下载对希望快速上手 CNN 检测任务、完成高质量课程设计或积累项目实战经验的学习者是一份很不错的完整参考。1. 道路坑洼检测大作业为什么说这道题选CNN分类比目标检测更稳刚拿到“道路坑洼检测”这个计算机视觉大作业题目时很多人第一反应是上目标检测画框、标anchor。但这份拿了98分的作业走的是另一条路线把坑洼检测当作图像二分类任务用CNN判断一张路面图像中是否存在坑洼。当时导师给这个方向的评语很直接——坑洼的形态差异太大裂缝、破损、阴影各有各的样子传统边缘检测和阈值分割会被路面纹理干扰到崩溃而CNN能把特征选择交给网络自己去学。压缩包里包含LeNet-5与AlexNet两组完整实现、训练好的h5权重、预测脚本和README说明文档适合正在做计算机视觉课程设计的学生直接复现也适合想快速上手深度学习图像分类的初学者。2. 解压后先清点文件14个脚本里哪几个才是主线2.1 模型定义三件套LeNet-5.py、LeNet-5 2.0.py与AlexNet.py压缩包里的模型定义文件有三份初看像重复代码实际上对应了三条不同的实验路线。LeNet-5.py是经典结构两个卷积层加三个全连接层卷积核尺寸5x5输入是单通道或三通道的灰度路面图。LeNet-5 2.0.py在原始版本上做了两处关键改动一是加入Dropout层来压制过拟合二是调整了全连接层的神经元数量让模型在数据量有限时更容易收敛。AlexNet.py则是五层卷积加三层全连接的深度结构通道数从64起步逐层增加参数量比LeNet-5高一个量级。对一个大作业来说同时保留两套模型的收益在于可以跑横向对比。我在复现时用同一份数据分别训练两个模型LeNet-5在CPU上单epoch耗时约40秒AlexNet约2分钟最终验证集准确率却只差1到2个百分点。这种“小模型够用、大模型可扩展”的对比写进课程设计报告比单独放一个模型的acc曲线更有说服力也更容易在答辩时回答“为什么选这个结构”这类追问。2.2 训练与预测主线main.py、mainz.py与Predictor.py主线脚本分两组。main.py和mainz.py是训练入口从文件命名方式看应该是不同阶段的版本mainz.py大概率承担了完整训练、验证和保存权重的职责。Predictor.py和Predictorz.py是预测脚本读取已经训练好的h5权重对单张图像或一批图像做推理输出有没有坑洼的概率。真正让这个项目完整的是权重文件sampleLenet.h5和sampleLeNet-5.h5。这意味着自己不动手训练也能把整个链路跑通——先加载权重再传一张路面图像脚本直接输出判别结果。对课程设计演示来说这很关键答辩现场不用等训练跑完复现的稳定性立刻就能体现。h5权重文件是Keras的标准保存格式包含了模型结构和参数加载后可以直接推理。2.3 辅助脚本pre.py、excel.py、testmodel.py与README.mdpre.py承担图像预处理职责包括统一尺寸、归一化、通道转换等操作是训练和预测的前置环节。excel.py从命名判断是用来做结果统计的很可能把预测结果写进Excel表格做误报分析。testmodel.py是模型评估脚本加载权重后在测试集上计算准确率、返回预测标签。README.md是整个项目唯一的文档说明描述了项目结构、环境要求和使用步骤正好是报告写作的骨架。这种“模型定义、训练、预测、评估、结果导出”五个环节拆分的结构是我比较欣赏的一点。大作业最怕的就是所有代码堆在一个文件里调参、换模型、改数据都无从下手。这套拆分虽然脚本多了些但每个环节可以单独替换。比如把AlexNet.py里的卷积层数改掉或者把LeNet-5 2.0.py的Dropout率调低其他脚本基本不用动。3. 先把环境跑通依赖清单、数据组织与训练流程3.1 依赖版本TensorFlow 2.x环境下的安装组合从h5权重文件格式和模型脚本判断这是TensorFlow/Keras框架的项目。如果你在Windows上做我建议按这个组合装pip install tensorflow2.10.0 numpy1.24.3 Pillow opencv-python matplotlib为什么不直接装最新版因为TensorFlow 2.11之后Windows官方不再提供GPU支持而2.10是最后一个稳定支持CUDA 11.2的版本对新手来说环境配置成本最低。如果机器是NVIDIA显卡且已装好CUDA这个版本能直接识别GPU如果只是CPU跑2.10也完全够用训练64x64这样的小图像不会慢到哪去。装完先验证环境再加载权重文件看是否报错import tensorflow as tf import numpy as np print(tf.__version__) print(tf.config.list_physical_devices(GPU)) # 空列表说明在CPU上跑 from tensorflow.keras.models import load_model model load_model(sampleLeNet-5.h5) print(model.summary())这里的关键是load_model这一行。很多人在环境配置上卡住报错往往是Unknown layer或无法匹配的配置。常见做法是把模型脚本放在h5文件同目录让加载时能找到Keras层定义如果用的是自定义层还需在load_model时传入custom_objects参数不过我看了这个项目的模型结构都是标准Conv2D和Dense不需要额外处理。3.2 数据组织方式二分类数据集的目录结构这份作业没有附带原始数据集需要自己组织。我一般是按这样的目录结构放pothole_data/ train/ positive/ # 有坑洼的路面图像 negative/ # 正常路面图像 val/ positive/ negative/ test/ positive/ negative/然后训练脚本里用ImageDataGenerator做数据增强和归一化rescale设为1.0/255再做rotation_range、zoom_range这些操作。数据增强对这个任务尤其重要因为坑洼检测最容易翻车的地方就是样本量不够——如果只有几百张图模型很快过拟合训练集acc接近100%验证集却只有70%。augmentation能有效扩大样本分布让模型看到更多角度的坑洼形态。3.3 main.py训练脚本逻辑与参数解读由于压缩包里的main.py是完整训练脚本我结合复现理解把核心部分拆出来分析from tensorflow.keras.models import Sequential from tensorflow.keras.layers import Conv2D, MaxPooling2D, Flatten, Dense, Dropout from tensorflow.keras.preprocessing.image import ImageDataGenerator # 数据增强与归一化 train_datagen ImageDataGenerator( rescale1.0/255, rotation_range15, width_shift_range0.1, height_shift_range0.1, zoom_range0.2, horizontal_flipTrue ) train_generator train_datagen.flow_from_directory( pothole_data/train, target_size(64, 64), batch_size32, class_modebinary ) # LeNet-5式模型结构 model Sequential([ Conv2D(6, (5, 5), activationrelu, input_shape(64, 64, 3)), MaxPooling2D(pool_size(2, 2)), Conv2D(16, (5, 5), activationrelu), MaxPooling2D(pool_size(2, 2)), Flatten(), Dense(120, activationrelu), Dense(84, activationrelu), Dense(1, activationsigmoid) ]) model.compile(optimizeradam, lossbinary_crossentropy, metrics[accuracy])逻辑说明输入图像统一resize到64x64模型是两个卷积块加三个全连接层的LeNet-5变体。二分类问题的输出层只有一个神经元激活函数用sigmoid损失函数用binary_crossentropyoptimizer用adam。这里target_size的参数值要和预测脚本保持一致否则后面加载权重推理时尺寸对不上会直接报维度错误。几个参数值得展开说。rotation_range15表示图像随机旋转15度模拟车辆行驶中拍摄角度的变化zoom_range0.2做20%的随机缩放模拟摄像头远近导致的尺寸差异。这两个增强操作对路面场景很实用因为坑洼照片大多是从行车记录仪或手机拍的角度和距离都不固定。4. 模型训练与预测实战训练脚本增强与推理链路4.1 训练阶段EarlyStopping回调与模型保存策略在mainz.py这类改进版本训练脚本里通常会加两个关键的Keras回调——EarlyStopping和ModelCheckpointfrom tensorflow.keras.callbacks import EarlyStopping, ModelCheckpoint early_stop EarlyStopping( monitorval_loss, patience5, restore_best_weightsTrue ) checkpoint ModelCheckpoint( best_pothole_model.h5, monitorval_accuracy, save_best_onlyTrue ) model.fit( train_generator, epochs20, validation_dataval_generator, callbacks[early_stop, checkpoint] )EarlyStopping的patience设为5意味着验证集loss连续5个epoch不下降就停止训练restore_best_weightsTrue会把权重回滚到验证集表现最好的那个epoch。这个参数对大作业的实际意义是训练中途不用死盯loss曲线挂机离开也没事。ModelCheckpoint自动保存验证集准确率最高的权重避免最后保存的模型是过拟合后的残次版本。判断训练是否正常的标准训练loss应当在前3到5个epoch内明显下降如果6个epoch后loss还在原地打转说明学习率可能偏高或数据没有正确归一化。再深一步如果训练acc升到95%以上但验证acc只有70%就是过拟合的典型信号此时优先检查Dropout层是否存在。4.2 推理阶段Predictor.py的预测逻辑与阈值调节Predictor.py读取训练好的权重并对待检测图像做预测核心流程拆出来是这样的import cv2 import numpy as np from tensorflow.keras.models import load_model model load_model(sampleLeNet-5.h5) def predict_pothole(image_path, threshold0.5): img cv2.imread(image_path) img cv2.cvtColor(img, cv2.COLOR_BGR2RGB) img cv2.resize(img, (64, 64)) img img.astype(float32) / 255.0 # 增加batch维度从(64,64,3)变成(1,64,64,3) img np.expand_dims(img, axis0) prob model.predict(img, verbose0)[0][0] label pothole if prob threshold else normal return label, prob result, probability predict_pothole(test_image.jpg) print(f预测结果: {result}, 置信度: {probability:.4f})逻辑说明预测前必须完成resize、归一化、expand_dims三步顺序不能乱。resize要与训练时的target_size保持一致这里都是64x64归一化方式必须和训练时一致训练用了rescale1.0/255预测时就要手动除以255expand_dims是因为模型输入要求四维张量格式是(batch_size, height, width, channels)单张图像只有三维必须显式增加一个batch维度。置信度阈值0.5是二分类的默认值。如果实际场景中漏检的代价高于误报可以把阈值往下调到0.3意思是只要模型有30%的把握就报坑洼。这个调参习惯在做路面检测时很实用——漏掉一个坑洼可能导致车辆损伤多报一次误检只是多一次人工复核代价完全不对称。如果你在对比不同阈值对结果的影响可以直接改threshold参数跑测试集画一条阈值与误报率的曲线这也是报告里不错的素材。4.3 两个h5权重文件的对应关系压缩包里的sampleLenet.h5和sampleLeNet-5.h5是两个不同版本的权重。加载时不要混用LeNet-5.py对应sampleLenet.h5LeNet-5 2.0.py或mainz.py对应sampleLeNet-5.h5。如果想验证这个对应关系用load_model加载后直接看summary里的层结构数一下全连接层的神经元数量就能区分。这个细节在答辩演示时尤其重要如果加载了不匹配的权重模型输出结果会非常离谱可能把所有图像都判成同一类。5. 避坑指南从解压到出结果这五个问题最常遇到5.1 现象load_model加载h5权重报错现象执行load_model时抛异常报错信息类似ValueError: Unknown Layer或KeyError。原因h5文件里记录了模型结构信息如果当前代码环境里找不到对应的层定义就会报Unknown Layer。最常见的原因是模型定义脚本不在当前目录或者是TensorFlow/Keras版本差异导致层名称解析失败。解决把模型定义脚本和h5文件放在同一目录加载模型之前先执行一遍模型脚本。如果还是不行改用model.load_weights只加载权重不依赖h5里的结构信息from LeNet_5 import build_lenet5 model build_lenet5() model.load_weights(sampleLeNet-5.h5)这种方式要求你手动构建一个与训练时完全一致的模型结构然后用load_weights填入参数不会触发结构解析逻辑。5.2 现象训练loss不降或验证acc始终在50%附近徘徊现象训练了10个epoch训练acc还在60%以下波动loss下降非常缓慢。原因多数情况下是数据预处理没对齐图像没有做归一化像素值直接以0到255的范围喂给模型导致梯度更新不稳定。另一种可能是类别极度不均衡比如负样本是正样本的近10倍模型学会了无脑全部预测成负类acc虚高但loss表现异常。解决先检查训练日志里每类的样本数量不均衡就做数据增强、过采样或欠采样。再确认ImageDataGenerator里rescale1.0/255有没有正确传入。最后把输入图像可视化出来看看确认不会是全黑或全灰。5.3 现象预测时报维度错误现象模型predict时报错提示expected shape (None, 64, 64, 3) but got array with shape (64, 64, 3)。原因忘了加batch维度。模型的输入张量是四维的单张图像是三维的差的就是最前面的batch_size这一维。解决用np.expand_dims(img, axis0)补上batch维度或者直接img.reshape(1, 64, 64, 3)。这是CNN图像分类里最低级的翻车点但它出现频率真的很高我自己也栽过一次——在批量预测多张图时直接从列表里取了一张图就传给模型少看一眼shape就报错了。5.4 现象验证集精度很高但预测新图结果全是同一类别现象训练时打印的val_acc在90%以上但拿一张新图预测无论图像内容是什么输出结果都是pothole或都是normal。原因模型过拟合到训练集的背景特征了。当训练样本很少时模型学到的是“图像左上角有天空”这类场景线索而不是坑洼本身的纹理。另一个常见原因是预测脚本里图像预处理顺序和训练脚本不一致比如训练时先resize再归一化预测时先归一化再resize结果完全不同。解决用Grad-CAM可视化模型关注区域确认模型是盯着路面还是盯着背景。如果是背景说明数据里正负样本的拍摄环境差异太大需要重新采集或做更激进的裁剪增强。同时严格比对训练和预测脚本里的预处理顺序这条值得每次复现时都检查一遍。5.5 现象权重加载成功但推理结果与训练时的val_acc完全对不上现象训练时验证acc是92%用同一个权重文件在测试集上重新评测acc掉到70%左右。原因训练时的验证集数据被做了数据增强增广后的图像和原始图像分布不一致。验证集和测试集都不应该做数据增强只做归一化和resize增强只会让两张几乎相同的图像变成不同输入干扰评估结果。解决val和test生成器只保留rescale去掉rotation_range、zoom_range、horizontal_flip这类增强参数。这个细节如果能注意到提交报告时会显得思路清晰很多同学就是栽在这个“看起来不重要”的地方。6. 进阶用法用混淆矩阵和Grad-CAM让模型从“能用”到“能讲”一份98分的作业靠单一准确率数字撑不起来。导师真正会追问的是模型为什么错、错在哪类样本上、误报的图像长什么样。所以我推荐把两个可视化方法加进项目里这也是我自己在复现后认为最值得做的补充。第一个是混淆矩阵对二分类任务来说只有四格但信息量很大from sklearn.metrics import confusion_matrix, classification_report import seaborn as sns import matplotlib.pyplot as plt y_true, y_pred [], [] for img_path, label in test_samples: _, prob predict_pothole(img_path, threshold0.5) y_true.append(label) y_pred.append(1 if prob 0.5 else 0) cm confusion_matrix(y_true, y_pred) sns.heatmap(cm, annotTrue, fmtd, cmapBlues) plt.xlabel(Predicted) plt.ylabel(True) plt.savefig(confusion_matrix.png, dpi200)这段代码的价值在于把误报和漏检拆开分析。比如FN数量集中在裂缝较浅的图像上说明模型对浅层异常不够敏感FP集中在阴影或路面纹理复杂的图像说明模型把阴影误当成了坑洼。报告中放两张典型误报图像加一段原因分析就把项目从“跑通”提升到了“理解问题”的层面。第二个是Grad-CAM可视化。在卷积神经网络里最后一个卷积层的输出保留着空间位置信息把类别置信度对这些特征图的梯度做加权平均就能得到模型最关注的区域热力图。实现方式不复杂不需要重新训练或微调模型直接加载已有权重就行import tensorflow as tf def grad_cam(model, img_array, layer_nameconv2d_2): grad_model tf.keras.models.Model( inputsmodel.input, outputs[model.get_layer(layer_name).output, model.output] ) with tf.GradientTape() as tape: conv_output, predictions grad_model(img_array) loss predictions[:, 0] grads tape.gradient(loss, conv_output) pooled_grads tf.reduce_mean(grads, axis(0, 1, 2)) conv_output conv_output[0] heatmap tf.reduce_sum(conv_output * pooled_grads, axis-1) heatmap tf.maximum(heatmap, 0) / tf.maximum(tf.reduce_max(heatmap), 1e-8) return heatmap.numpy()将heatmap用OpenCV缩放回原图尺寸再与原图按0.5的权重叠加就能直观看到模型在判定“有坑洼”时聚焦的是路面中央还是图像边缘。如果热力图落在路沿、树影或护栏上说明模型学到的是场景偏差而非坑洼纹理这时候最有效的修正不是调网络结构而是回去整理数据让两类样本的拍摄背景更一致。这两个方法补上去之后整个项目的完整性会明显提升。答辩时的展示逻辑可以这样组织先用准确率和混淆矩阵说明模型整体表现再用Grad-CAM热力图解释模型决策依据最后指出当前模型的短板和后续可改进的方向。从那以后我每次复现一份CNN大作业都会强制自己把混淆矩阵和Grad-CAM跑一遍第一是验证模型没有“假学习”第二是给报告留出真正可讨论的技术点。希望这个习惯也能帮到你。本文还有配套的精品资源点击获取
上一篇/下一篇内容由系统自动关联 返回资讯列表 →