基于CNN的道路坑洼检测课程设计:从LeNet-5到AlexNet的完整实现与避坑指南 简介这份资源面向计算机视觉课程设计、期末大作业及入门深度学习的学习者提供一套基于Python与CNN实现道路坑洼检测的完整项目方案帮助解决从模型搭建到预测推理的全流程实践问题。压缩包共14个文件约10.49MB以11个Python脚本为核心涵盖AlexNet、LeNet-5等多种网络实现及训练、测试、预测模块另含2个h5权重文件和1份md说明文档代码注释清晰下载即可运行。目前已有404人学习下载适合作为高分课程设计参考。读者可从中获得完整的坑洼检测项目结构、可复用的模型训练与推理脚本、预训练权重文件以及项目说明文档既能直接运行观察效果也便于在此基础上进行二次开发与功能扩展。1. 道路坑洼检测项目拆包一份能跑通的 PythonCNN 课程设计长什么样路面坑洼检测这件事听起来像是市政养护的活但落到计算机视觉课程设计里它其实是个非常典型的二分类/多分类图像任务输入一张道路图像输出有坑还是没坑或者进一步标出坑洼位置。我带过几届学生的毕设和课设发现大家最卡的不是模型结构本身而是数据集怎么组织、训练脚本怎么串、预测脚本怎么调这三件事。这份资源给的就是一套完整闭环从 LeNet-5 到 AlexNet 的多个模型实现、训练脚本、预测脚本、模型权重文件.h5、以及一份 README 文档说明解压后目录里能看到 main.py、AlexNet.py、LeNet-5.py、Predictor.py、testmodel.py 等文件基本覆盖了训练—保存—加载—推理的全流程。它适合两类人一是正在赶课程设计、需要一份能直接跑起来参考的从业者或学生二是想拿一个轻量 CNN 项目练手、理解图像分类工程结构的人。下面我按资源是什么—怎么用—坑在哪的顺序把它拆开讲清楚。2. 环境搭建与数据准备把依赖、目录和标签对齐2.1 依赖清单与 Python 环境选择这份代码基于 Python CNN核心依赖是 TensorFlow/Keras因为出现了 .h5 权重文件和 LeNet-5、AlexNet 这类 Keras 常见写法、NumPy、OpenCVcv2、以及可能用到的 pandas/openpyxl目录里有 excel.py说明可能涉及 Excel 读取标签或结果。我一般建议用 Python 3.8 建一个独立虚拟环境原因是这个版本对 TensorFlow 2.x 和 OpenCV 的兼容性最稳不容易出现装完 import 报错的玄学问题。# 创建虚拟环境Windows/Linux 通用 python -m venv pothole_env # 激活环境 # Windows: pothole_env\Scripts\activate # Linux/Mac: source pothole_env/bin/activate # 安装核心依赖版本按兼容性给一个稳妥区间 pip install tensorflow2.10.0 pip install numpy1.23.5 pip install opencv-python4.8.0.74 pip install pandas openpyxl pip install matplotlib这里几个参数说明一下TensorFlow 选 2.10 是因为它自带 Keras不需要单独装 keras 包避免版本冲突NumPy 锁 1.23.5 是因为 1.24 之后有些旧代码用的 np.float 等别名被移除容易在数据预处理脚本里翻车opencv-python 用 4.8 系列读图、缩放、颜色转换的 API 稳定。装完之后建议跑一句python -c import tensorflow as tf; print(tf.__version__)验证能打印出版本号再往下走。2.2 数据集目录结构与标签组织CNN 图像分类项目翻车最多的地方就是目录结构。常见做法是采用 Keras 的ImageDataGenerator.flow_from_directory约定每个类别一个子文件夹文件夹名就是标签。坑洼检测一般是二分类目录长这样dataset/ ├── train/ │ ├── pothole/ # 有坑洼的图 │ └── normal/ # 正常路面 ├── val/ │ ├── pothole/ │ └── normal/ └── test/ ├── pothole/ └── normal/如果你拿到的数据是图片 一个 Excel/CSV 标签表的形式那就需要自己写映射脚本把图片按标签复制或软链到对应文件夹。目录里的 excel.py 很可能就是干这个的。我一般会先统计一下每个类别的样本数类别严重不均衡比如正常路面 5000 张、坑洼只有 300 张会直接导致模型全预测成正常也能有高准确率这是最隐蔽的坑。import os # 统计各类别样本数训练前必做 for split in [train, val, test]: split_path os.path.join(dataset, split) if not os.path.exists(split_path): continue print(f--- {split} ---) for cls in os.listdir(split_path): cls_path os.path.join(split_path, cls) if os.path.isdir(cls_path): n len([f for f in os.listdir(cls_path) if f.lower().endswith((.jpg, .png, .jpeg))]) print(f{cls}: {n} 张)这段脚本的作用是快速暴露数据分布问题。参数上没什么可调的重点看输出如果两个类别数量差 5 倍以上就得考虑加class_weight、做数据增强或者过采样否则后面评估指标会骗你。2.3 图像尺寸与归一化参数LeNet-5 原始输入是 32×32AlexNet 是 224×224。这份资源里同时有 LeNet-5.py 和 AlexNet.py说明作者可能做了不同输入尺寸的对比实验。你需要在训练脚本里统一target_size常见做法是from tensorflow.keras.preprocessing.image import ImageDataGenerator IMG_SIZE (128, 128) # 折中尺寸兼顾速度和精度 BATCH_SIZE 32 train_gen ImageDataGenerator( rescale1./255, # 归一化到 [0,1] rotation_range15, # 轻微旋转增强 horizontal_flipTrue, # 水平翻转 zoom_range0.1 ).flow_from_directory( dataset/train, target_sizeIMG_SIZE, batch_sizeBATCH_SIZE, class_modebinary )rescale1./255是最基础的归一化别小看它忘了写会让训练 loss 直接飙到 nan 或者收敛极慢。rotation_range和horizontal_flip是给坑洼图像做增强的因为路面坑洼方向不固定翻转和轻微旋转是合理的但注意不要用垂直翻转vertical_flip路面上下颠倒不符合真实分布属于无效甚至有害增强。class_modebinary对应二分类如果是多分类比如轻/中/重坑洼要改成categorical并配合 softmax。3. 模型选型与训练LeNet-5 和 AlexNet 到底该用哪个3.1 两个模型的定位差异LeNet-5 是 1998 年的老结构两层卷积 两层全连接参数量小、训练快适合 32×32 到 64×64 的小图作为课程设计的 baseline 非常合适。AlexNet 是 2012 年的结构五层卷积 三层全连接参数量大得多需要更大输入224×224和更多数据才能发挥优势。这份资源里两个都给了我的建议是如果你的数据集只有几百到一两千张先用 LeNet-5 跑通流程再换 AlexNet 对比如果数据上万张直接上 AlexNet 或者用迁移学习比如 MobileNetV2会更划算。# LeNet-5 简化实现Keras Sequential 风格 from tensorflow.keras import layers, models def build_lenet5(input_shape(128, 128, 3)): model models.Sequential([ layers.Conv2D(6, (5, 5), activationrelu, input_shapeinput_shape), layers.AveragePooling2D((2, 2)), layers.Conv2D(16, (5, 5), activationrelu), layers.AveragePooling2D((2, 2)), layers.Flatten(), layers.Dense(120, activationrelu), layers.Dense(84, activationrelu), layers.Dense(1, activationsigmoid) # 二分类输出 ]) return model model build_lenet5() model.compile(optimizeradam, lossbinary_crossentropy, metrics[accuracy]) model.summary()逻辑说明卷积层负责提取边缘、纹理等局部特征池化层降维全连接层做分类。参数上Conv2D(6, (5,5))里的 6 是卷积核数量原始 LeNet 就是 6 和 16我保留了这个设置方便对照最后一层用sigmoid输出 0~1 的概率配合binary_crossentropy损失。如果你改成多分类最后一层换成Dense(num_classes, activationsoftmax)损失换成categorical_crossentropy。model.summary()一定要看确认参数量和输出维度符合预期我见过有人输入尺寸写错导致 Flatten 后维度对不上报错信息还特别绕。3.2 训练脚本的关键参数与回调训练部分核心是fit的参数和回调callback。这份资源里 main.py、mainz.py 大概率是训练入口我按常见工程写法给一版from tensorflow.keras.callbacks import ModelCheckpoint, EarlyStopping, ReduceLROnPlateau callbacks [ # 只保存验证集上最好的模型避免保存到过拟合的权重 ModelCheckpoint(best_model.h5, monitorval_accuracy, save_best_onlyTrue, verbose1), # 验证集 loss 连续 5 轮不降就停省时间 EarlyStopping(monitorval_loss, patience5, restore_best_weightsTrue), # 学习率平台期衰减防止后期震荡 ReduceLROnPlateau(monitorval_loss, factor0.5, patience3, min_lr1e-6) ] history model.fit( train_gen, epochs50, validation_dataval_gen, callbackscallbacks )参数说明ModelCheckpoint的save_best_onlyTrue很关键否则最后一轮的权重可能已经过拟合测试效果反而差EarlyStopping的patience5表示连续 5 轮没进步就停restore_best_weightsTrue会自动回滚到最佳权重ReduceLROnPlateau的factor0.5是每次把学习率砍半min_lr设下限防止学习率降到 0。这套组合是我跑图像分类的标配能省掉大量手动调参时间。训练完把 history 画出来看 loss 和 accuracy 曲线训练集和验证集曲线分叉明显就是过拟合需要加 dropout 或增强。3.3 模型保存与加载的格式问题目录里有 sampleLenet.h5、sampleLeNet-5.h5 这类文件说明作者用的是 Keras 的 HDF5 格式保存。这里有个血泪经验.h5保存的是结构 权重加载时用load_model即可但如果你的代码里用了自定义层或自定义损失函数加载会报 Unknown layer这时候要么用custom_objects传进去要么改用 SavedModel 格式model.save(model_dir)。from tensorflow.keras.models import load_model # 加载已训练模型做推理 model load_model(sampleLeNet-5.h5) model.summary() # 确认结构和权重都加载成功 # 单张图片预测 import cv2 import numpy as np img cv2.imread(test.jpg) img cv2.resize(img, (128, 128)) img img / 255.0 img np.expand_dims(img, axis0) # 增加 batch 维度 pred model.predict(img)[0][0] print(坑洼概率:, pred) print(判定:, 有坑洼 if pred 0.5 else 正常)np.expand_dims(img, axis0)这步新手最容易漏模型predict要求输入是 (batch, H, W, C) 四维单张图只有三维不加这步会报维度错误。pred 0.5是二分类默认阈值实际部署时可以根据误报/漏报的代价调整比如宁可误报不可漏报就把阈值降到 0.3。4. 推理脚本与结果输出Predictor 类怎么用、结果怎么存4.1 Predictor.py 的典型结构与调用方式目录里有 Predictor.py 和 Predictorz.py从命名看是封装好的预测类。这类文件通常长这样一个Predictor类初始化时加载模型提供一个predict(image_path)方法返回类别和置信度。我按这个思路给一版可复现的实现import cv2 import numpy as np from tensorflow.keras.models import load_model class Predictor: def __init__(self, model_path, img_size(128, 128), threshold0.5): self.model load_model(model_path) self.img_size img_size self.threshold threshold def preprocess(self, image_path): img cv2.imread(image_path) if img is None: raise FileNotFoundError(f读不到图片: {image_path}) img cv2.cvtColor(img, cv2.COLOR_BGR2RGB) # OpenCV 默认 BGR img cv2.resize(img, self.img_size) img img.astype(float32) / 255.0 return np.expand_dims(img, axis0) def predict(self, image_path): x self.preprocess(image_path) prob float(self.model.predict(x, verbose0)[0][0]) label pothole if prob self.threshold else normal return {label: label, confidence: prob} if __name__ __main__: p Predictor(sampleLeNet-5.h5) print(p.predict(test.jpg))逻辑说明cv2.cvtColor把 BGR 转 RGB 这步必须做因为训练时如果用flow_from_directory读图Keras 内部是按 RGB 处理的推理时用 OpenCV 读进来是 BGR不转会导致颜色通道错位模型输出完全乱套——这是最典型的训练好好的预测全错的原因。threshold做成可配置参数方便你按业务调整。verbose0只是关掉 predict 的进度条输出不影响结果。4.2 批量推理与结果落盘实际做课程设计演示时往往要跑一整个测试集并输出准确率、混淆矩阵。目录里的 test.py、testmodel.py 应该就是干这个的。我一般会写一个批量评估脚本import os import pandas as pd from sklearn.metrics import confusion_matrix, classification_report predictor Predictor(sampleLeNet-5.h5) test_dir dataset/test records [] for cls in os.listdir(test_dir): cls_dir os.path.join(test_dir, cls) if not os.path.isdir(cls_dir): continue for fname in os.listdir(cls_dir): if not fname.lower().endswith((.jpg, .png, .jpeg)): continue path os.path.join(cls_dir, fname) result predictor.predict(path) records.append({ file: fname, true: cls, pred: result[label], confidence: result[confidence] }) df pd.DataFrame(records) df.to_excel(predict_result.xlsx, indexFalse) # 对应 excel.py 的输出 y_true df[true].map({normal: 0, pothole: 1}) y_pred df[pred].map({normal: 0, pothole: 1}) print(confusion_matrix(y_true, y_pred)) print(classification_report(y_true, y_pred))参数说明df.to_excel输出结果表方便写报告时截图confusion_matrix看四个象限——真正例、假正例、假负例、真负例比单一 accuracy 有信息量得多。如果假负例有坑判成正常特别多说明模型对坑洼特征学得不够要么加数据要么调阈值。classification_report会给出 precision、recall、f1课程设计报告里放这个比只写准确率 97%专业得多。4.3 可视化预测结果演示环节光有数字不够得把预测结果画在图上。常见做法是用 OpenCV 在原图上写标签import cv2 def draw_result(image_path, result, save_pathoutput.jpg): img cv2.imread(image_path) label result[label] conf result[confidence] color (0, 0, 255) if label pothole else (0, 255, 0) text f{label}: {conf:.2f} cv2.putText(img, text, (10, 30), cv2.FONT_HERSHEY_SIMPLEX, 1.0, color, 2) cv2.imwrite(save_path, img) draw_result(test.jpg, predictor.predict(test.jpg))这段代码把类别和置信度写在左上角红色表示坑洼、绿色表示正常。注意cv2.putText的中文支持很差写中文会变成问号所以标签用英文要显示中文得用 PIL 的 ImageDraw 配合中文字体文件这是另一个常见坑。5. 避坑与排查训练和推理阶段最容易翻车的五件事5.1 现象训练准确率很高测试准确率断崖下跌原因典型过拟合或者训练集和测试集有数据泄漏比如同一张图的不同裁剪同时出现在训练和测试里。解决先检查数据划分是否按路段/场景划分而不是随机划分同一段路的图应该整体进训练或整体进测试然后加 dropout、数据增强、L2 正则或者减少模型参数量。我见过最离谱的是把测试集图片也放进训练目录准确率 99%一换新图就废。5.2 现象loss 一直是 nan 或者不下降原因学习率太大、输入没归一化、标签编码错误比如二分类用了 one-hot 但损失函数是 binary_crossentropy。解决先把学习率降到 1e-4 试确认rescale1./255写了检查最后一层激活函数和损失函数是否匹配——二分类用 sigmoid binary_crossentropy多分类用 softmax categorical_crossentropy这个配对错了 loss 会直接爆炸。5.3 现象加载 .h5 模型报 Unknown layer 或维度不匹配原因保存和加载时的 Keras/TensorFlow 版本不一致或者代码里用了自定义层没注册。解决尽量保证训练和推理用同一个环境如果必须跨版本改用 SavedModel 格式保存自定义层要在load_model时通过custom_objects参数传入。维度不匹配通常是输入尺寸改了但权重还是旧的重新训练或调整target_size保持一致。5.4 现象预测结果全是同一个类别原因类别不均衡导致模型学到全预测多数类的偷懒策略或者推理时颜色通道 BGR/RGB 搞反了。解决先看训练集类别分布不均衡就加class_weight再检查推理预处理是否和训练一致OpenCV 读图记得转 RGB。这两个原因我各踩过至少三次排查顺序建议先查通道再查均衡。5.5 现象内存溢出OOM或训练极慢原因batch_size 太大、输入尺寸太大、或者用了 AlexNet 这种大模型但显存不够。解决把 batch_size 从 32 降到 16 或 8输入尺寸从 224 降到 128或者改用迁移学习的小模型。CPU 训练的话建议直接用 LeNet-5AlexNet 在 CPU 上跑一个 epoch 可能要几十分钟课程设计时间根本不够。6. 进阶玩法把这份课设改成能写进简历的项目跑通基础流程之后如果你想让它更有含金量我建议做三件事。第一把二分类升级成检测 定位现在只是判断有没有坑可以用 OpenCV 的轮廓检测或者简单阈值分割把坑洼区域框出来再叠加 CNN 分类结果输出带框的图视觉效果和实用性都上一个台阶。第二做模型对比实验把 LeNet-5、AlexNet 以及一个迁移学习模型比如 MobileNetV2 冻结主干只训分类头放在同一数据集上跑用表格对比参数量、训练时间、准确率、F1这张表放进报告里比任何文字都有说服力。模型参数量级输入尺寸训练速度适合场景LeNet-5约 6 万32~128快小数据集、CPU 环境AlexNet约 6000 万224慢大数据集、有 GPUMobileNetV2迁移约 350 万224中数据中等、追求性价比第三把推理封装成一个命令行工具或简单界面用 argparse 接收图片路径输出预测结果和可视化图这样演示时直接python predict.py --image test.jpg就能跑比在 notebook 里点来点去专业。如果还有余力可以加一个 Grad-CAM 热力图把模型看到哪里可视化出来判断它是不是真的在看坑洼区域而不是背景——这一步能帮你发现模型是不是在作弊。import argparse from Predictor import Predictor if __name__ __main__: parser argparse.ArgumentParser() parser.add_argument(--image, requiredTrue, help待检测图片路径) parser.add_argument(--model, defaultsampleLeNet-5.h5) parser.add_argument(--threshold, typefloat, default0.5) args parser.parse_args() p Predictor(args.model, thresholdargs.threshold) print(p.predict(args.image))这个命令行入口的好处是参数全部外置换模型、调阈值不用改代码。--threshold暴露出来之后你可以现场演示不同阈值下误报和漏报的权衡答辩时很加分。最后说个我自己的习惯每次拿到一份别人的课设代码我第一件事不是直接跑 main.py而是先看 README 和文件命名理清哪个是训练入口、哪个是推理入口、权重文件对应哪个模型结构然后建虚拟环境、跑通单张图预测、再回头跑训练。这个顺序能帮你快速定位问题出在环境、数据还是模型上。从那以后我每次复现别人的项目都强制走一遍环境→单图推理→批量评估→训练这条链路基本不会再被莫名其妙的报错卡住。希望这份拆解帮到你把这份课设真正跑起来、改起来。本文还有配套的精品资源点击获取