ARTICLE DETAIL

资讯详情

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

道路坑洼检测用CNN二分类:大作业拿高分的关键思路与实现

道路坑洼检测用CNN二分类:大作业拿高分的关键思路与实现 简介这是一份计算机视觉课程大作业的高分参考项目基于Python与CNN实现道路坑洼检测适合正在完成课程设计或期末大作业的计算机相关专业学生也适合希望借助完整项目进行CNN实战练习的学习者。资源包共14个文件涵盖11个Python脚本、2个H5模型权重文件与1个Markdown说明文档代码与模型均直接可用压缩包仅10.49MB。项目包含AlexNet、LeNet-5等经典CNN网络的完整实现与训练好的权重另有预测、测试、结果导出等模块可快速复现道路坑洼识别流程便于对照学习模型构建、训练与推理的核心步骤Markdown文档则用于说明项目结构与运行方式。整套方案曾获评审98分并已吸引905人学习下载对于需要高质量课程设计范本或想要深入理解CNN图像分类项目的读者具有不错的参考价值。1. 道路坑洼检测大作业用CNN做二分类反而比目标检测更好拿分每年都有不少人在“计算机视觉大作业”里选道路坑洼检测然后一猛子扎进目标检测在YOLO、Faster R-CNN里折腾两个月最后交上去的代码连自己在哪都可能没调通。这里有个反直觉的经验基于PythonCNN实现道路坑洼检测绝大多数课程只要求“识别出画面里有坑”并不严格区分坑的具体位置和边界。把它当成一个图像分类任务只输出“正常/坑洼”两类用迁移学习跑一个深度CNN通常就已经能达到一类课程90分的水平。这门课的核心要求往往不是模型SOTA而是流程完整数据加载、模型定义、训练、评估、可视化演示。下面是拆开的过程从任务设计到数据构造再到可运行的训练代码、答辩加分项和避坑实录最后再教你把它做成一个能现场演示的Demo。这套方案适合三种人正在赶大作业的学生、想快速补一个计算机视觉项目的入门者以及需要给这题做技术预研的工程师。2. 把“坑洼检测”翻译成CNN能学的任务分类还是目标检测决定了你熬夜的程度2.1 先定任务粒度为什么“检测”可以被降级成“分类”“道路坑洼检测”按字面意思是目标检测Object Detection要在图上用框框出每个坑。但课程大作业里如果允许只给“这张图片有没有坑”的判断那么这题的方向就完全变了它是一个二分类问题一条5行字的答案就省掉了几十行还要做梯度裁剪的锚框计算。分类方案的核心价值在于CNN的卷积特征本身就能捕捉坑的纹理特征。坑洼是个局部特征坑洞边缘有断裂、阴影、颜色突变这些东西在ImageNet预训练特征里已经有很多相似的纹理响应。你只需要用新的数据集对模型做微调把最后一层换上2个神经元即可。相比YOLO的head部分要重新理解锚框和NMS分类任务几行代码就能跑通而且训练时间短、调参难度低。这套思路的时间预算大概是一个装有CPU就跑得动的笔记本电脑MobileNetV2作为骨架10个epoch下分类任务大概30分钟能训完。如果用单卡训练若干G的显存通常能到几分钟一个epoch。如果老师明确要求“给出坑的位置”那就在这个分类模型的基础上叠加一个heatmap或者滑动窗口但那是后话。冷知识很多大作业评分表里写了“检测”但答辩时看到混淆矩阵和几条PR曲线就已经能拿一个还不错的分数了。2.2 数据集构造三处来源、一种目录结构、一套划分脚本大作业第一个坎儿就是数据集。常见做法是去Kaggle或GitHub上找公共的道路坑洼图片集搜关键词“Pothole detection dataset”“pothole image classification”能翻出不少压缩包。有的包按坑/无坑已经分好了有的给的是原始道路图和标注json。如果你的导师发了指定数据直接用导师的资料但目录结构一定要统一这一步不规整后面训练代码会牵扯出无限的心力消耗。我一般会把数据整理成这种结构pothole_data/ |-- train/ | |-- pothole/ (300张) | |-- normal/ (300张) |-- validation/ | |-- pothole/ (60张) | |-- normal/ (60张) |-- test/ |-- pothole/ (30张) |-- normal/ (30张)去重是必做的一步。公共数据集里常有同一场景的连续帧原封不动分进训练验证测试会把准确率推到95%以上但这是一种虚假的繁荣答辩现场放几张新图就会现原形。去重可以粗放一点先肉眼翻一遍把相机角度几乎相同的片子删掉要更省力的做法是计算感知哈希把汉明距离小于阈值的留在主集里。目录建好之后写一个简单的划分脚本。你的源代码包里就算只放训练代码这20行也是值得的因为老师可能硬性要求看到“数据准备部分”。# split_data.py # 从原始图片文件夹随机划分 train / validation / test import os import random import shutil src_root raw_images # 原始图像文件夹下面有 cracked/ 和 intact/ 两个类别子文件夹 dest_root pothole_data ratio (0.8, 0.1, 0.1) # 训练:验证:测试 常见比例数据量大时可改为 0.9:0.05:0.05 for cls in [pothole, normal]: class_src os.path.join(src_root, cls) imgs [f for f in os.listdir(class_src) if f.lower().endswith((.jpg, .jpeg, .png))] random.shuffle(imgs) n_train int(len(imgs) * ratio[0]) n_val int(len(imgs) * ratio[1]) splits {train: imgs[:n_train], validation: imgs[n_train:n_trainn_val], test: imgs[n_trainn_val:]} for split_name, split_imgs in splits.items(): out_dir os.path.join(dest_root, split_name, cls) os.makedirs(out_dir, exist_okTrue) for f in split_imgs: shutil.copy2(os.path.join(class_src, f), os.path.join(out_dir, f))逻辑说明上面这段脚本把raw_images下两个类别按8:1:1分成三份。选择copy而不是move是为了保留原始数据万一划分出了幺蛾子比如验证集里恰好全是晴天的照片还能重新划分这也算是留了个后悔药。参数说明ratio按你自己的图片量去改如果总共只有200张图建议改成0.7/0.15/0.15让验证集和测试集各多几张如果图量超过2000张可以接受0.9/0.05/0.05让训练集更充裕。到这里数据这关就过了。下一章把模型从零开始搭起来源代码里核心的就是这几段。3. 用Python搭一个能上皮的CNN迁移学习MobileNetV2以及一批关键参数3.1 选型逻辑为什么是好是坏都该从MobileNetV2下手CNN骨架有很多选择ResNet50、VGG16、EfficientNet但大作业场景里我最常用MobileNetV2。原因不是指标最强而是它最贴合“演示”这个核心诉求。第一参数量少14MB的权重CPU机器也能跑第二推理速度快单张图片在普通笔记本上只要几十毫秒答辩时现场预测时不会一直让考官干等第三它对小数据集友好迁移学习的特征提取层能直接冷启动训练。对比起来ResNet系列容易取得稍高一些的准确率但收敛更慢训练时间成本高如果没有GPU建议优先考虑MobileNetV2。CNN的结构解释起来也特别适合答辩Depthwise Separable Convolution深度可分离卷积是MobileNet系列的标志操作它把标准卷积拆成深度卷积和逐点卷积两步参数数量大幅减少这个知识点熟练背下来基本可以应对考官对网络结构的追问。如果老师指定要“自己设计一个CNN”那就别用现成的预训练骨架改写一个三层卷积# models/simple_cnn.py # 适合“从零搭建CNN”要求的小网络 from tensorflow.keras import layers, models def build_simple_cnn(input_shape(224, 224, 3)): model models.Sequential(namesimple_cnn) model.add(layers.Rescaling(1.0/255, input_shapeinput_shape)) model.add(layers.Conv2D(32, (3, 3), activationrelu, paddingsame)) model.add(layers.MaxPooling2D((2, 2))) model.add(layers.Conv2D(64, (3, 3), activationrelu, paddingsame)) model.add(layers.MaxPooling2D((2, 2))) model.add(layers.Conv2D(128, (3, 3), activationrelu, paddingsame)) model.add(layers.Flatten()) model.add(layers.Dense(64, activationrelu)) model.add(layers.Dense(2, activationsoftmax)) return model参数说明输入尺寸固定为224x224这和MobileNetV2一致后面切换预训练模型时无需改图尺寸。三个卷积层的filter数量32→64→128逐步增加符合CNN逐层抽象特征的习惯每层都用paddingsame避免特征图快速缩边——其实用valid也行但对于坑洼这种小尺度纹理same保留更多边缘信息首轮训练会更容易拟合。最后接两层全连接一个64维的中间层做特征汇总一个2维softmax做分类输出。3.2 训练主文件数据增强、冻结权重、模型存档一下到位迁移学习是我在真实做项目时的首选代码往下落# train_transfer.py # 基于MobileNetV2微调给大作业用了“冻结骨干只训头部”的两段式策略 import tensorflow as tf from tensorflow.keras.applications import MobileNetV2 from tensorflow.keras import layers, models, optimizers from tensorflow.keras.callbacks import ModelCheckpoint, EarlyStopping IMG_SIZE (224, 224) BATCH_SIZE 16 EPOCHS 10 # 1. 数据加载ImageDataGenerator做了在线数据增强 train_datagen tf.keras.preprocessing.image.ImageDataGenerator( rescale1.0/255.0, rotation_range20, width_shift_range0.2, height_shift_range0.2, zoom_range0.2, horizontal_flipTrue, fill_modenearest ) val_datagen tf.keras.preprocessing.image.ImageDataGenerator(rescale1.0/255.0) train_generator train_datagen.flow_from_directory( pothole_data/train, target_sizeIMG_SIZE, batch_sizeBATCH_SIZE, class_modecategorical ) val_generator val_datagen.flow_from_directory( pothole_data/validation, target_sizeIMG_SIZE, batch_sizeBATCH_SIZE, class_modecategorical ) # 2. 骨架weightsimagenetinclude_topFalse去掉全连接层 base_model MobileNetV2(weightsimagenet, include_topFalse, input_shape(224, 224, 3)) base_model.trainable False # 先冻结这一阶段只训练分类头 model models.Sequential([ base_model, layers.GlobalAveragePooling2D(), layers.Dropout(0.5), # 防过拟合小数据集上尤其重要 layers.Dense(2, activationsoftmax) ]) model.compile( optimizeroptimizers.Adam(learning_rate1e-3), losscategorical_crossentropy, metrics[accuracy] ) callbacks [ ModelCheckpoint(best_model.h5, monitorval_accuracy, save_best_onlyTrue, verbose1), EarlyStopping(monitorval_loss, patience3, restore_best_weightsTrue) ] # 3. 第一阶段只训练新加的全连接层 history model.fit( train_generator, steps_per_epochtrain_generator.samples // BATCH_SIZE, validation_dataval_generator, validation_stepsval_generator.samples // BATCH_SIZE, epochsEPOCHS, callbackscallbacks ) # 4. 第二阶段解冻骨干网络的后半段用更小学习率微调 base_model.trainable True for layer in base_model.layers[:100]: layer.trainable False # 前100层继续冻结 model.compile( optimizeroptimizers.Adam(learning_rate1e-5), losscategorical_crossentropy, metrics[accuracy] ) # 如果阶段一已经取得较好效果这里Epochs可以减半 history_finetune model.fit( train_generator, steps_per_epochtrain_generator.samples // BATCH_SIZE, validation_dataval_generator, validation_stepsval_generator.samples // BATCH_SIZE, epochsEPOCHS // 2, callbackscallbacks ) model.save(pothole_cnn_final.h5)逻辑说明这段训练脚本里最关键的是两段式训练。先用1e-3的学习率只训练新加的Dense层理由是一个随机初始化的分类头如果直接接在预训练特征上求出的梯度会非常大容易把前面已经训好的特征破坏掉。把骨干冻结跑完第一轮之后再从第101层开始解冻用1e-5的学习率微调——这五分之一的学习率差值是经验值目的就是让梯度更新慢一点避免跨越原来那个优秀的损失地形。参数说明Batch Size在CPU机器上不要超过3216是个稳妥值ImageDataGenerator的旋转、缩放用了20%如果坑洼图片里很多是远处延伸路面的视角旋转角度太大反而会制造出反物理的样本如果数据集偏小不到400张可以只保留水平翻转和缩放把旋转调回10度dropout 0.5是个常用中间值调大能压过拟合但太小又会欠拟合。ModelCheckpoint保存的是验证集准确率最高的那一版权重不是最后一轮的这个“后悔药”装置很值。4. 用评估矩阵和推理脚本撑起答辩的15分钟混淆矩阵、精确率/召回率、现场预测4.1 让“95分”有据可依别只报准确率画出混淆矩阵课程评分老师见过的报告里超过一半只写一行“准确率95.2%”。这个数字说服力有限因为如果正负样本比例偏斜光说准确率根本说明不了问题。要把分拿稳评估环节至少展示四个数字准确率、精确率Precision、召回率Recall、F1-score再配一张混淆矩阵。坑洼检测场景里“把坏路判断成好路”的代价比“把好路误判成坏路”更严重所以要把recall列出来证明模型确实把坑捞出来了。# evaluate.py # 对best_model.h5做评估输出混淆矩阵和分类报告 import numpy as np from tensorflow.keras.models import load_model from tensorflow.keras.preprocessing.image import ImageDataGenerator from sklearn.metrics import confusion_matrix, classification_report import seaborn as sns import matplotlib.pyplot as plt model load_model(best_model.h5) datagen ImageDataGenerator(rescale1.0/255.0) test_generator datagen.flow_from_directory( pothole_data/test, target_size(224, 224), batch_size16, class_modecategorical, shuffleFalse # 必须关闭否则预测顺序和文件名对不上 ) test_gen.reset() y_pred model.predict(test_generator, stepsnp.ceil(test_generator.samples / 16)) y_pred_classes np.argmax(y_pred, axis1) y_true test_generator.classes cm confusion_matrix(y_true, y_pred_classes) sns.heatmap(cm, annotTrue, fmtd, xticklabelstest_generator.class_indices.keys(), yticklabelstest_generator.class_indices.keys()) plt.xlabel(Predicted) plt.ylabel(True) plt.savefig(confusion_matrix.png, dpi150) plt.close() print(classification_report(y_true, y_pred_classes, target_namestest_generator.class_indices.keys()))逻辑说明这段代码里有三个容易踩坑的地方。第一shuffleFalse是硬性的flow_from_directory默认会打乱数据顺序一旦打乱y_pred和test_generator.classes就对不上号混淆矩阵纯属自嗨。第二test_generator.reset()要把迭代器指针拨回开头因为predict会消耗迭代器。第三steps要ceil向上取整否则最后一批不足16张的图片会被切掉导致预测结果的行数少于图片数。4.2 写一个“免训练”的推理脚本单张图、文件夹、现场出结果答辩现场最常见的尴尬是老师随手点开一张图让你预测你紧张地翻代码、改路径、跑训练三分钟没动静。所以推理脚本要单独成一个文件做成能被命令行直接调的这就是源代码包里很耐看的一个部分。# predict.py # 用法: python predict.py --image path/to/img.jpg import argparse import numpy as np from tensorflow.keras.models import load_model from tensorflow.keras.preprocessing import image model load_model(best_model.h5) CLASS_NAMES [normal, pothole] # 顺序必须和训练时的class_indices一致 ap argparse.ArgumentParser() ap.add_argument(--image, requiredTrue, helppath to input image) args ap.parse_args() img image.load_img(args.image, target_size(224, 224)) x image.img_to_array(img) x np.expand_dims(x, axis0) / 255.0 pred model.predict(x)[0] label CLASS_NAMES[np.argmax(pred)] conf np.max(pred) print(f预测结果: {label}, 置信度: {conf:.4f})注意CLASS_NAMES的顺序不是想当然的要确认训练时flow_from_directory里文件夹按字母序排过序。如果不放心可以在训练完把train_generator.class_indices顺手打印出来跟这里设的顺序比对顺序一旦颠倒所有结果就反了。上面的代码保存成predict.py后答辩现场只需要一个命令行就能演示台词大概是“老师我加载了训练好的权重输入一张图模型输出类别和置信度”。5. 道路坑洼检测的4个高频翻车现场现象、原因、解决5.1 训练验证准确率全上99%新图乱猜数据划分翻车现象训练集、验证集、测试集的准确率都高得离谱甚至一堆999%的日志但答辩时现场拍一张新图就识别错。原因最常见的是把同一组连续帧图片在去重前就划分了比如视频帧序列里第10帧到第20帧几乎一样划分时随机分配到训练和验证两个集合里模型在验证集上就等于见过原图。更隐蔽的一种是把同一条道路不同时间段的照片混在了一起模型学的其实不是“坑”而是“沥青色泽”。解决按“场景”而不是“单张图片”划分数据先按文件名或拍摄位置聚类把同一场景的照片全部分到同一个集合里。排查时看训练过程的loss曲线如果验证loss一开始很低后面却突然跳高多半是划分泄露。5.2 训练直接OOM进程被系统卡死“黑匣子”报错现象Keras跑着跑着报一个“ResourceExhaustedError”或者直接把Jupyter内核搞死没有任何有效traceback。原因显存或内存被足够的图片矩阵撑爆了尤其是224x224x3的RGB图片一张约0.6MB常规数据集几百张图不算大但ImageDataGenerator的缓存和MobileNetV2的中间特征层叠加起来16GB内存的电脑也可能卡死。解决第一优先调小batch_size到8甚至4如果还不行把workers参数设为0如果CPU机器还在卡死那多半是内存碎片重启内核后把flow_from_directory改为逐张读取的tf.data管道。经验值是先把batch size调到16跑通一遍再往上探。5.3 loss震荡一个epoch涨一个epoch跌准确率一直50%上下现象训练loss不收敛准确率在0.5附近震荡像随机猜。原因三种情况同时存在的概率很高——正负样本严重不平衡、两个类别的图片风格差异过大一张是白天大坑。另一类是夜间远距离的模糊小坑、以及第一段训练时学习率过大。解决先打印各类别图片数量如果差异小于1:2放宽心继续训如果差异到1:10必须用class_weight给少的那个类别加权。Keras里传入fit的class_weight可以这样写# 给训练集的类别配权重缓解不平衡 class_weights {0: 0.5, 1: 2.0} # 假设类别1图片更少 model.fit( train_generator, class_weightclass_weights, ... )还要确认生成器里的classes编号和你心理预期的对应。另外学习率从1e-3起跳没问题但也别少于1e-3了MobileNetV2在分类头上用1e-2可能在最开始的few epochs飞速上升准确率但后面会崩。5.4 模型能跑但打印出的置信度永远徘徊在0.5~0.6之间现象对坑洼图片预测为pothole但置信度只有0.55对正常图片预测为normal置信度也只有0.6。原因是数据增强做得太狠旋转、裁剪、缩放把坑的特征形状破坏掉了或者训练轮数不足分类头还停留在模糊的特征上。解决先把数据增强停掉只用rescale跑一轮看验证集准确率能否上到85%以上。如果能就说明增强策略过于激进还原折中做法是只保留horizontal_flip和zoom_range0.1。如果停掉增强后准确率仍然徘徊在0.6那可能数据量太少每个类别连100张都不足考虑先把图片复制改成伪增强加入亮度变换和弹性形变观察效果。5.5 在Jupyter里跑了很久突然发现tensorflow版本不兼容现象导入tensorflow.keras时各种AttributeError。原因是教学环境里的TensorFlow版本多半是2.x但代码里混着旧版Keras的API比如keras.layers或keras.preprocessing.image或反过来从tf.keras里调用新函数但版本过低。解决统一以from tensorflow.keras import ...导入务必用pip show tensorflow检查版本2.6以上基本稳定2.15以上对ImageDataGenerator支持更好。我还遇到过load_model加载h5文件时缺h5py的情况直接pip install h5py极快解决。最后一条实验室玄学如果能从命令行换成Anaconda环境新建的Python 3.9比在原有环境里折腾省心太多。6. 从大作业到能写进作品集的最后一公里把模型导出成实时Demo大作业交完之后源码包往往就躺在磁盘里吃灰了。如果想让它转成简历上真正硬核的“计算机视觉项目经验”把模型往前推一步做成一个能实时预测的界面很有必要。做法有两种一是用TensorFlow Serving包装成HTTP接口二是用Gradio做本地Web UI。课程答辩通常选Gradio两行界面代码不用学前端。# app.py # 基于Gradio的本地坑洼检测演示界面无需前端知识 import gradio as gr import numpy as np from tensorflow.keras.models import load_model from tensorflow.keras.preprocessing import image model load_model(pothole_cnn_final.h5) CLASS_NAMES [normal, pothole] def predict_pothole(img): x image.img_to_array(img) x np.expand_dims(x, axis0) / 255.0 pred model.predict(x)[0] label CLASS_NAMES[np.argmax(pred)] return f{label} (置信度: {np.max(pred):.4f}) gr.Interface( fnpredict_pothole, inputsgr.Image(typepil, image_modeRGB), outputstext, title道路坑洼检测 Demo, description上传道路图片CNN将判断是否存在坑洼 ).launch()这段代码在答辩现场的价值非常直观老师可以现场拍一张手机照片传上来看到结果输出。如果还想更极限一点可以加入视频帧的连续预测把cv2.VideoCapture(0)的画面帧喂给模型帧率目标10fps上下。这里有个性能经验要把输入帧先压缩到224x224再预测并且把预测放在一个独立线程里避免画面卡顿。从大作业到工程实践的边界点在于课上跑通准确率课下要跑通延迟。我的习惯是先确认模型推理一次的单帧耗时再决定是否上这种实时演示——如果单帧耗时超过200毫秒实时Demo做出来体验会很差不如专心做静态图上传。最后再提醒一件事交作业时把requirements.txt一并放进去写上tensorflow、gradio、numpy、scikit-learn的版本范围让老师能用自己的环境直接跑通这件小事往往比模型多调两个点更能体现工程素养。希望帮到你。本文还有配套的精品资源点击获取
返回列表