道路坑洼检测实战:LeNet-5与AlexNet双模型协同设计
2026/9/15 4:30:19 网站建设 项目流程

简介:本资源是一份面向计算机相关专业本科生的高分课程实践项目,聚焦道路坑洼智能检测这一典型计算机视觉任务,适用于期末大作业、课程设计及深度学习实战训练。项目基于Python与CNN构建端到端检测流程,含完整训练、验证与预测代码,模型涵盖LeNet-5及其改进版本,并提供预训练权重(.h5文件)与详细说明文档(README.md),便于快速复现与二次开发。压缩包共14个文件,包括11个核心Python脚本(如网络定义、主训练逻辑、预测模块等)、2个模型权重文件及1个Markdown说明文档,整体大小为10.49MB,结构清晰、模块分工明确,显著降低初学者理解门槛。目前已有905人学习下载,项目获导师评审98分,附带完整实验流程、数据处理逻辑与模型调优思路,可直接用于答辩展示或作为CV入门级项目范例。

1. 道路坑洼检测不是“拍张图识别个圆圈”——它要解决真实路面图像中的尺度畸变、光照不均与小目标漏检问题

很多初学者拿到“道路坑洼检测”题目,第一反应是用OpenCV找轮廓或Hough变换检测圆形区域。但实际采集的路面图像里,坑洼形态极不规则:有纵向裂缝、龟裂网状纹、深陷凹坑,甚至被雨水反光或落叶遮盖;同一段路在正午强光和傍晚逆光下灰度分布差异巨大;而手机或行车记录仪拍摄时,因透视关系导致远处坑洼仅占几十像素,远低于常规CNN输入尺寸下感受野的有效覆盖范围。这个98分大作业的核心价值,正在于它没有回避这些工程现实——它用LeNet-5与AlexNet双模型对比验证,通过图像预处理链(CLAHE增强+自适应二值化+透视校正ROI裁剪)显式对抗光照与形变,更关键的是,在sampleLeNet-5.h5权重中固化了针对32×32小尺寸坑洼块的特征提取能力。适合计算机专业大三学生直接复现课程设计,也适合作为CV入门者理解“从数据到部署”的完整闭环:不是调通一个model.fit()就结束,而是要能解释为什么test.py里必须先做cv2.warpPerspective再送入Predictor.py,以及为何excel.py导出的检测坐标需经np.floor()取整而非四舍五入。

2. LeNet-5与AlexNet双模型架构选型:为什么不用ResNet或YOLO?——小样本、低算力场景下的务实选择

2.1 模型轻量化与数据集规模的硬约束

该作业明确限定使用校园道路实拍图(约800张标注图像),且未采用COCO或Cityscapes等大型公开数据集迁移学习。在这种小样本条件下,ResNet50参数量超2300万,训练极易过拟合;YOLOv5s虽支持端到端检测,但其anchor机制对坑洼这种无固定长宽比的目标泛化性差。作者选择LeNet-5(约6万参数)与AlexNet(约6000万参数但结构更浅)形成对比实验,本质是验证模型复杂度与任务粒度的匹配性:LeNet-5用于二分类(坑洼/非坑洼)的patch级判别,AlexNet用于整图级定位。LeNet-5.py中定义的卷积核尺寸(5×5)、池化步长(2)及全连接层节点数(120→84→2)均针对32×32输入优化,而AlexNet.py将原始227×227输入压缩至128×128,并移除LRN层以适配TensorFlow 2.x环境。

提示:LeNet-5 2.0.py是关键迭代版本——它在原LeNet-5基础上增加BatchNormalization层并改用ReLU激活,使训练收敛速度提升40%,这是98分评审中被导师特别标注的改进点。

2.2 双模型协同工作流:从Patch分类到全局定位

整个检测流程并非单一模型推理,而是分阶段流水线:

  1. 预处理阶段main.py调用pre.py对原始图像进行透视校正(cv2.getPerspectiveTransform获取M矩阵)→ CLAHE直方图均衡(cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8,8)))→ 自适应阈值分割(cv2.adaptiveThreshold, blockSize=11, C=2)
  2. Patch生成阶段:将校正后图像按滑动窗口切分为32×32子块,步长设为16以保证重叠覆盖
  3. 双模型判别阶段:每个子块同时送入sampleLeNet-5.h5(输出概率)和sampleLenet.h5(AlexNet简化版,输出类别索引)
  4. 后处理融合阶段Predictor.py依据LeNet-5置信度>0.85且AlexNet判定为“坑洼”才标记有效,再通过非极大值抑制(NMS)合并相邻高置信度框
# main.py 中关键片段:双模型加载与并行预测 from tensorflow.keras.models import load_model lenet_model = load_model('sampleLeNet-5.h5') # 输入shape=(32,32,1) alexnet_model = load_model('sampleLenet.h5') # 输入shape=(128,128,3) def predict_patch(patch): # LeNet-5要求灰度图归一化到[0,1] lenet_input = patch.astype(np.float32) / 255.0 lenet_input = np.expand_dims(lenet_input, axis=-1) # (32,32,1) # AlexNet要求RGB三通道,此处用cv2.cvtColor模拟伪彩色增强 alexnet_input = cv2.cvtColor(patch, cv2.COLOR_GRAY2RGB) alexnet_input = cv2.resize(alexnet_input, (128,128)) alexnet_input = alexnet_input.astype(np.float32) / 255.0 lenet_prob = lenet_model.predict(np.expand_dims(lenet_input, 0))[0][1] # 坑洼类概率 alexnet_pred = np.argmax(alexnet_model.predict(np.expand_dims(alexnet_input, 0))[0]) return lenet_prob, alexnet_pred

这段代码揭示了两个关键设计决策:

  • 输入通道适配:LeNet-5用单通道灰度图(保留纹理细节),AlexNet用伪彩色三通道(增强边缘对比度),避免盲目统一输入格式导致信息损失;
  • 置信度阈值设定lenet_prob > 0.85而非0.5,是因为在测试集上统计发现,当LeNet-5置信度低于0.85时,误检率跃升至37%,而AlexNet单独判别准确率仅72%——双模型交叉验证才是鲁棒性的来源。

2.3 模型权重文件命名逻辑与版本演进

项目中出现多个.h5文件并非冗余,而是体现迭代过程:

文件名架构训练数据关键特性适用场景
sampleLeNet-5.h5原始LeNet-5早期标注集(600张)无BN层,sigmoid激活基准对比
sampleLeNet-5.h5(实际为2.0版)LeNet-5+BN+ReLU全量800张+数据增强收敛快,val_acc达94.2%主检测模型
sampleLenet.h5简化AlexNet同LeNet-5数据集移除LRN,FC层减半定位辅助

注意sampleLenet.h5(小写L)与sampleLeNet-5.h5(大写L)的命名差异,这对应AlexNet.pymodel = Sequential()构建的简化版,其Conv2D(96, (11,11), strides=4)后直接接MaxPooling2D((3,3), strides=2),跳过了原始AlexNet的局部响应归一化(LRN)——因为TensorFlow 2.x已弃用LRN层,且实测移除后mAP仅下降0.3%但训练速度提升2.1倍。

3. 图像预处理链深度解析:透视校正、CLAHE增强与自适应阈值的参数调试逻辑

3.1 透视校正:为什么必须用cv2.getPerspectiveTransform而非简单仿射变换?

道路图像存在严重透视畸变:近处坑洼占据画面1/3,远处同样大小的坑洼仅剩几个像素。若直接对原始图做滑动窗口,远处小坑洼会被窗口切割失真。pre.py中实现的校正流程如下:

# pre.py 核心代码 def warp_perspective(img): h, w = img.shape[:2] # 手动标定四点:左上、右上、左下、右下(单位:像素) src_pts = np.float32([[w//3, h//4], [2*w//3, h//4], [0, h], [w, h]]) # 原图梯形区域 dst_pts = np.float32([[0, 0], [w, 0], [0, h], [w, h]]) # 目标矩形区域 M = cv2.getPerspectiveTransform(src_pts, dst_pts) warped = cv2.warpPerspective(img, M, (w, h)) return warped, M

这里src_pts的选取不是随意的——w//32*w//3确保覆盖车道线内侧区域,h//4作为消失点高度参考,而[0,h][w,h]锚定地平线。若将src_pts设为[[0,0],[w,0],[0,h],[w,h]](即仿射变换),则无法纠正透视压缩,远处坑洼仍会模糊。实测表明,正确透视校正后,模型对5米外坑洼的召回率从61%提升至89%。

3.2 CLAHE增强:clipLimit=2.0tileGridSize=(8,8)的物理意义

普通直方图均衡易放大噪声,而CLAHE(限制对比度自适应直方图均衡)通过分块控制。clipLimit=2.0表示每个块内直方图峰值被截断至均值的2倍,避免过度增强;tileGridSize=(8,8)将图像划分为8×8网格(每块约16×16像素),既保证局部对比度提升,又防止块边界产生人工痕迹。对比实验显示:

  • clipLimit=1.0:坑洼边缘细节不足,LeNet-5判别置信度普遍<0.7
  • clipLimit=3.0:路面噪点被放大,误检率上升22%
  • tileGridSize=(4,4):网格过大导致光照渐变补偿不足
  • tileGridSize=(16,16):网格过小引发马赛克效应

因此clipLimit=2.0(8,8)是该校正参数的帕累托最优解。

3.3 自适应阈值:blockSize=11C=2的抗干扰设计

cv2.adaptiveThresholdblockSize=11指11×11邻域均值计算,C=2表示从均值中减去2作为阈值偏移。此参数组合能有效抑制以下干扰:

  • 雨天反光blockSize=11足够覆盖水渍区域(通常直径<10像素),避免将其误判为坑洼;
  • 落叶遮挡C=2使阈值略低于局部均值,确保落叶边缘的暗部坑洼仍被保留;
  • 沥青色差:老旧路面灰度值偏低,C=2防止整体过曝丢失细节。

若使用全局阈值cv2.threshold(img, 127, 255, cv2.THRESH_BINARY),在阴天图像中会漏检35%以上坑洼——因为全局127阈值对低照度区域过于激进。

4. 检测结果后处理与评估:NMS阈值、坐标映射与Excel导出的精度保障机制

4.1 NMS(非极大值抑制)的IoU阈值设定:0.3而非0.5的工程权衡

Predictor.py中NMS实现如下:

def non_max_suppression(boxes, scores, iou_threshold=0.3): # boxes: (N,4) [x1,y1,x2,y2], scores: (N,) indices = cv2.dnn.NMSBoxes(boxes, scores, score_threshold=0.5, nms_threshold=iou_threshold) return boxes[indices.flatten()], scores[indices.flatten()]

这里iou_threshold=0.3是经过大量测试确定的——坑洼目标常呈长条状或不规则多边形,其预测框IoU天然偏低。若设为常规目标检测的0.5,则相邻小坑洼(间距<20像素)会被合并,导致漏检;而0.3能在保留独立坑洼的同时,滤除同一坑洼的重复预测框。实测在测试集上,iou_threshold=0.3使F1-score达0.87,0.5时降至0.72。

4.2 坐标逆映射:如何将校正图中的检测框还原到原始图像?

透视校正会改变像素坐标系,mainz.py中通过逆变换矩阵M_inv实现坐标还原:

# mainz.py 片段 warped_img, M = pre.warp_perspective(original_img) # ...检测得到boxes_warped... M_inv = np.linalg.inv(M) # 计算逆变换矩阵 boxes_original = [] for box in boxes_warped: x1, y1, x2, y2 = box # 将四个角点转换回原图坐标 pts = np.array([[x1,y1,1],[x2,y1,1],[x2,y2,1],[x1,y2,1]], dtype=np.float32).T transformed = M_inv @ pts transformed /= transformed[2,:] # 齐次坐标归一化 x_orig = np.floor(transformed[0,:]).astype(int) y_orig = np.floor(transformed[1,:]).astype(int) boxes_original.append([x_orig.min(), y_orig.min(), x_orig.max(), y_orig.max()])

关键点在于:np.floor()而非round()——因为图像坐标必须为整数,且向下取整可确保框完全包含原始坑洼区域(向上取整可能导致框超出图像边界)。

4.3 Excel导出精度验证:excel.py如何避免浮点误差累积?

excel.py不仅导出坐标,还计算坑洼面积(像素数)与长宽比,其防错机制体现在:

# excel.py 关键逻辑 def save_to_excel(boxes, areas, ratios, filename="detection_result.xlsx"): df = pd.DataFrame({ 'X1': [box[0] for box in boxes], 'Y1': [box[1] for box in boxes], 'X2': [box[2] for box in boxes], 'Y2': [box[3] for box in boxes], 'Area_px': [int(area) for area in areas], # 强制转int防科学计数法 'Aspect_Ratio': [round(ratio, 3) for ratio in ratios] # 保留3位小数 }) # 添加校验列:宽度= X2-X1,高度= Y2-Y1 df['Width'] = df['X2'] - df['X1'] df['Height'] = df['Y2'] - df['Y1'] df.to_excel(filename, index=False)

此处int(area)强制转换避免了浮点面积值(如123.999999999)在Excel中显示为124.0引发的歧义;round(ratio,3)防止长宽比计算中因浮点精度导致1.999999≈2.000的误判——这对后续按形状分类(圆形vs裂缝)至关重要。

5. 实战调试技巧:如何快速定位test.py运行失败的三类典型错误?

5.1 路径错误:FileNotFoundError: No such file or directory 'test_images/'

test.py默认读取test_images/目录,但压缩包解压后该目录可能位于子文件夹中。正确做法

  1. test.py开头添加路径诊断代码:
import os print("当前工作目录:", os.getcwd()) print("test_images是否存在:", os.path.exists("test_images")) print("test_images内容:", os.listdir("test_images") if os.path.exists("test_images") else "不存在")
  1. 若输出显示test_images不存在,则执行:
mkdir test_images cp ../your_image_folder/*.jpg test_images/ # 将测试图复制到同级目录

注意:不要用相对路径../test_images,因test.pycv2.imread("test_images/xxx.jpg")默认从脚本所在目录查找。

5.2 模型加载失败:ValueError: Input 0 of layer "conv2d" is incompatible with the layer

此错误90%源于图像通道数不匹配。LeNet-5.h5要求单通道灰度图,但cv2.imread()默认读取BGR三通道。修复命令

# test.py 中替换原读图代码 img = cv2.imread("test_images/1.jpg") if len(img.shape) == 3: img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # 强制转灰度

若使用sampleLenet.h5(AlexNet版),则需:

img_rgb = cv2.cvtColor(img, cv2.COLOR_GRAY2RGB) # 灰度转伪彩色 img_resized = cv2.resize(img_rgb, (128,128)) # 严格匹配输入尺寸

5.3 坐标越界:IndexError: index 128 is out of bounds for axis 0 with size 128

这是cv2.warpPerspective输出图像尺寸与预期不符所致。warpPerspective的第三个参数(w,h)若设为img.shape[1], img.shape[0],当M矩阵导致部分区域映射到负坐标时,输出图像实际尺寸可能小于(w,h)安全写法

# pre.py 中修正 h, w = img.shape[:2] warped = cv2.warpPerspective(img, M, (w, h)) # 添加边界检查 warped = warped[:h, :w] # 确保不越界

此操作可避免后续cv2.adaptiveThreshold因输入尺寸异常而崩溃。

错误类型终端报错关键词快速定位命令根本原因
路径错误FileNotFoundErrorls -l test_images/解压目录层级混乱
通道错误"incompatible with the layer"python -c "import cv2; print(cv2.imread('test.jpg').shape)"读图模式与模型输入不匹配
尺寸越界index XXX is out of boundspython -c "import cv2; img=cv2.imread('test.jpg'); print(img.shape)"warpPerspective输出尺寸未校验

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询