ARTICLE DETAIL

资讯详情

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

MATLAB与深度学习结合的路面裂缝检测两阶段实现

MATLAB与深度学习结合的路面裂缝检测两阶段实现 简介这是面向计算机视觉与深度学习初学者的MATLAB路面裂缝检测识别实战项目围绕路面图像自动识别裂缝这一场景完整覆盖图像预处理、特征提取、CNN卷积神经网络建模、训练评估与可视化等环节适合进行课程设计、毕业设计或自学练手。资源共18个文件压缩包仅275KB其中源代码以m脚本为主14个另有1个xls结果表格、1个fig图形界面以及jpg/png示例图片m脚本涉及图像灰度化、滤波去噪、裂缝判定、桥梁裂缝与方向识别等模块fig文件对应可视化操作界面xls用于保存识别统计便于逐模块学习与调试。已有214人学习下载。整套代码覆盖从数据准备到应用部署演示的主要流程可帮助读者掌握MATLAB深度学习工具箱的使用并理解CNN结构选择、超参数调整、泛化能力分析等关键问题作为课程设计或工程预研的参考。1. 路面裂缝检测为什么需要两阶段方案路面裂缝检测是道路养护中最常见的计算机视觉需求之一MATLAB在图像处理与深度学习上提供了完整的工具箱因此这类实战项目常被选作课程设计和原型验证。工程包里的实现没有直接上端到端神经网络而是先用灰度化、直方图均衡、中值滤波和形态学处理快速找到候选裂缝区域再用方向判别、断缝桥接消除漏检最后可以接一个轻量CNN对候选块做二次分类。这套两阶段结构的好处在于传统视觉部分负责“找出可能像裂缝的东西”深度学习部分负责“确认它到底是不是裂缝”避免在整张大图上反复推理计算开销和标注成本都更可控。适合正在做路面病害检测、需要快速搭出可演示系统的人群也适合作为计算机视觉与深度学习课程作业的完整基线。2. 原始图像预处理的参数化实现灰度化、直方图均衡与滤波去噪2.1 灰度化、Gamma校正与直方图均衡的MATLAB实现路面图像来自工业相机或手机通常是RGB彩色图和压缩后的JPEG存在光照不均、阴影、油渍和颗粒噪声。直接做边缘检测会得到大量虚假边缘所以第一步是把图像转到灰度空间并做对比度增强。工程包中的disp_gray.m完成RGB转灰度hist_con.m做直方图均衡adjgamma.m提供Gamma校正。这三个脚本是后续滤波和二值化的前置条件。function gray_img disp_gray(rgb_img) % 输入RGB路面图输出双精度灰度图 gray_img im2double(rgb2gray(rgb_img)); endim2double把图像数值范围从uint8的0~255缩放到double的0~1避免后续数学运算出现截断误差。注意如果输入已经是灰度图需要先判断通道数否则rgb2gray会报错。Gamma校正脚本adjgamma.m适合处理强光或逆光场景function im_adj adjgamma(im, g) % g小于1时提亮暗部g大于1时压暗暗部路面建议0.7~0.9 im_adj im.^g; end当g小于1时暗部灰度值会被拉高裂缝在阴影里更容易显现但当g过小时沥青颗粒噪声也会同步放大所以一般把g控制在0.7到0.9之间。实际使用中我习惯把Gamma校正放在灰度化之后、直方图均衡之前这样增强后的灰度分布可以直接交给histeq做二次拉伸。hist_con.m的实现是最直接的全局直方图均衡function im_eq hist_con(im_gray) im_eq histeq(im_gray, 256); endhisteq第二个参数是输出灰度级数默认为64这里用256是为了让均衡后的细节更丰富。对于光照强烈不均的图片全局均衡容易导致局部过亮可以换成adapthisteq并把ClipLimit设为0.02这是工程中处理阴影遮挡的常见做法。2.2 中值滤波与形态学开运算去除路面噪声对比度增强之后紧接着要去除噪声。med_process.m封装的是中值滤波function im_f med_process(im, win) if nargin 2, win 3; end im_f medfilt2(im, [win win], symmetric); endwin是滤波窗口宽度。win3能较好地保留1~2像素宽的细裂缝win5对阴影边缘的噪声更鲁棒但可能抹掉头发丝裂缝。symmetric边界选项让图像边缘按照镜像方式补齐避免边界出现黑框。在1080p的路面图像上我一般用3如果采集设备是无人机运动模糊会导致噪点更大可以放宽到5。bw_filter.m承担二值化和形态学去杂两项工作function bw bw_filter(im, th) % 二值化并删除小于50像素的孤立连通块 if nargin 2 th 0.6 * mean(im(:)); end bw imbinarize(im, th); bw bwareaopen(bw, 50); % 水平方向线性结构元素做开运算断开路面纹理连接 se strel(line, 3, 0); bw imopen(bw, se); endbwareaopen的第二个参数是面积阈值单位是像素。1280x720分辨率下50像素可以滤掉大部分散点1920x1080下建议提高到150。imopen用3像素长的水平线结构元素处理目的是断开横向颗粒状纹理与裂缝目标之间的弱连接避免它们被归入同一个连通域。这里结构元素方向选0是为了保留横向裂缝如果处理纵向裂缝需要把方向改成90或对两个方向分别开运算再取并集。2.3 自动阈值迭代与参数调优固定阈值在换场景后经常失效所以工程包里的IterProcess.m用了一个简单有效的迭代策略function [bw, th_out] IterProcess(im) % 迭代法求最优二值化阈值返回二值图和阈值 th 1/3 * (min(im(:)) max(im(:)) mean(im(:))); for i 1:10 bw imbinarize(im, th); fg mean(im(bw)); bg mean(im(~bw)); th_new (fg bg) / 2; if abs(th_new - th) 0.002 break; end th th_new; end th_out th; end每次迭代按当前阈值把图像分成前景和背景再取两类灰度均值的中点作为新阈值。裂缝目标在路面图像中通常占比很小直接使用Otsu计算时背景灰度均值占主导阈值会偏亮迭代法相当于反复修正两类中心对裂缝这类小目标更友好。在MATLAB R2020及以上版本中也可以直接用graythresh计算全局阈值但工程包里保留迭代脚本更利于教学展示。关于参数选择的经验可以参考下面这张表图像分辨率中值滤波win面积阈值(px)桥接gap_len深度学习输入尺寸640x480320~405~10224x2241280x7203~550~808~15227x2271920x10805100~20015~25320x320表里的数值是起点不是终点。沥青路面颗粒大、纹理深时面积阈值要上调水泥路面裂缝更细阈值要下调。一个常见误用是先把滤波窗口设到9x9结果裂缝宽度只有2像素中值滤波直接把目标抹平了后面再怎么调阈值都找不出来。建议始终从最小窗口开始逐级试到3x3、5x5、7x7观察裂缝边缘是否还连续。3. 裂缝连通域分析、方向判别与断缝桥接3.1 连通域标记与目标几何特征提取识别裂缝不只是检测出暗色像素而是要把断裂的碎片连成语义完整的目标。Identify_Object.m使用bwconncomp得到8连通分量再用regionprops提取面积、外接矩形、质心、长短轴和方向。function obj Identify_Object(bw) cc bwconncomp(bw, 8); s regionprops(cc, Area, BoundingBox, Centroid, ... MajorAxisLength, MinorAxisLength, Orientation); % 滤掉面积过小的噪声块 idx find([s.Area] 30); obj s(idx); end这里使用8连通而不是4连通是为了让斜向裂缝的相邻像素能连成一片。面积再次过滤是因为桥接之后会产生新的小块骨架分支这些分支面积不大容易被误识别成独立目标。MajorAxisLength和MinorAxisLength对应拟合椭圆的长短轴用于判断目标是否呈细长条状BoundingBox会在后面被imcrop用来裁剪候选裂缝区域送给深度学习网络做二次分类。3.2 方向判别与裂缝类型判定Judge_Direction.m根据regionprops得到的Orientation字段判断裂缝走向function dir Judge_Direction(s) % 根据Orientation判断H横向、V纵向、D斜向 if abs(s.Orientation) 30 dir H; elseif abs(s.Orientation) 60 dir V; else dir D; end endOrientation是最小外接椭圆长轴与x轴的夹角范围是-90到90度。横向裂缝的长轴接近水平所以Orientation接近0纵向裂缝接近±90度。需要特别留意的是当目标接近正方形时长短轴方向不稳定Orientation会在某个角度附近跳变所以必须结合长宽比使用。Judge_Crack.m把方向和几何形状综合起来function ctype Judge_Crack(obj) ar obj.MajorAxisLength / max(obj.MinorAxisLength, 1); if ar 5 if abs(obj.Orientation) 30 ctype 横向裂缝; else ctype 纵向裂缝; end elseif ar 2.5 ctype 斜向裂缝; else ctype 碎裂/修补区域; end end路面裂缝宽度通常只有几像素而长度有几十到上百像素因此长宽比大于5的才是典型线状裂缝。修补块、掉边区域形状接近矩形或圆形长宽比小会被归为最后一类。实际工程中如果路面有大量网状裂缝需要把ar1.5单独作为“网状”类型因为网状裂缝由很多短小分支组成长宽比不高但面积大。下表是常用的几何特征判定表类别长宽比方向范围面积特征横向裂缝50°~30°中等纵向裂缝560°~90°中等斜向裂缝2.530°~60°中等网状裂缝1.5~3不规则较大修补块1.5不规则不确定3.3 断缝桥接与避免过度连接裂缝在图像中经常因磨损、沙石遮挡而断成几段。Bridge_Crack.m使用线性结构元素做闭运算把间隙桥接起来function bw2 Bridge_Crack(bw, gap, angle) if nargin 3, angle 0; end se strel(line, gap, angle); bw2 imclose(bw, se); bw2 bwmorph(bw2, skel, Inf); bw2 bwmorph(bw2, spur, 5); endgap是桥接的最大像素长度代表裂缝断口相距多远时可以被连起来。angle是结构元素方向对于横向裂缝应传0纵向裂缝传90如果无法确定主方向可以分别对0、45、90度做闭运算再取三张结果的并集。闭运算后做骨架化是为了恢复裂缝的线宽否则膨胀会让裂缝变成粗白条spur操作剔除短毛刺目的是清除树叶、石子造成的假分支。桥接的副作用也很明显它会把两条平行的细小裂缝连成一片导致面积激增后续的裂缝类型判断全部出错。所以桥接后必须重新执行Identify_Object和Judge_Crack并且把桥接gap控制在合理范围内。对于1080p图gap超过30之后误连概率会显著上升。另一个容易被忽略的问题是不均衡光照如果裂缝一段处在阴影里一段处在强光下二值化可能只保留亮区裂缝暗区裂缝被阈值丢掉。这种情况下优先在预处理阶段加局部均衡化而不是盲目增大gap。第四轮还可以用Project.m来做整图的多尺度检测先用原始分辨率检测一遍再缩小到0.75倍检测一遍把两次结果做区域合并。小图能抓住长而宽的主裂缝大图能保留细裂纹合并后召回率会高很多但代价是运行时间翻倍。4. GUI批处理流程与结果导出的工程化实现4.1 InitAxes与Gui_Main界面初始化和坐标轴复用Gui_Main.fig是基于普通figure构建的界面Gui_Main.m负责回调逻辑。InitAxes.m用于初始化主窗口内的多个坐标轴一般会放置原图、预处理结果、最终识别结果三个axesfunction InitAxes(fig) axs findobj(fig, Type, axes); for i 1:numel(axs) cla(axs(i)); axis(axs(i), image); colormap(axs(i), gray); set(axs(i), Xtick, [], Ytick, []); end end注意findobj返回的axes顺序不一定与创建顺序一致正式工程中建议在创建GUI时把每个axes句柄保存到handles结构体里通过guidata更新避免因顺序错乱导致图像显示在错误的坐标轴。colormap设置为gray后灰度图和二值图都能正常显示如果要在同一个axes里显示彩色原图就不能人为设置gray否则颜色失真。这个脚本虽然简单但它是GUI稳定运行的第一步很多初学MATLAB GUI的同学把plt都写在同一个axes里导致窗口重叠InitAxes就是用来避免这种问题的。4.2 Process_Main将预处理到识别的整链条封装成函数Process_Main.m是系统的算法主干输入一张图像路径输出一个包含阈值、目标数量和类型的结果结构体。它把前面所有步骤串起来是GUI回调里最核心的调用对象function res Process_Main(impath) I imread(impath); if size(I, 3) 3 g disp_gray(I); else g im2double(I); end g2 adjgamma(g, 0.8); eq hist_con(g2); f med_process(eq, 3); [bw0, th] IterProcess(f); bw bw_filter(f, th); bw bwmorph(bw, bridge, Inf); bw Bridge_Crack(bw, 15, 45); obj Identify_Object(bw); res.threshold th; res.count numel(obj); for i 1:numel(obj) res.dir{i} Judge_Direction(obj(i)); res.type{i} Judge_Crack(obj(i)); end res.bw bw; end开关参数比较隐蔽IterProcess已经返回了二值图bw0但bw_filter又对灰度图做了一次二值化这两种方式在实际中只保留一条就够了。如果希望保留迭代阈值的效果就直接把bw0交给后续的bwmorph和Bridge_Crack不再调bw_filter如果场景对速度要求高直接用bw_filter的默认阈值得更快。工程包里保留两者是为了让使用者能在“自动阈值”和“固定阈值”之间切换。调用方式可以写进一个脚本res Process_Main(images/test.png); figure; imshow(res.bw); title(sprintf(Image %s, crack count: %d, test.png, res.count));这样只要替换impath就能对单张图快速验证。4.3 批量处理与result.xls导出路面巡检图像不是一张一张看而是要批量处理。工程包里的result.xls就是最后的汇总输出导出脚本可以这样封装function ExportResult(results, xlsname) % results为struct数组每个元素对应一张图 T struct2table(results); writetable(T, xlsname); end在批量主循环中收集结果files dir(images/*.png); for k 1:numel(files) name fullfile(files(k).folder, files(k).name); res Process_Main(name); results(k).filename files(k).name; results(k).crack_num res.count; results(k).threshold res.threshold; if ~isempty(res.type) results(k).crack_type strjoin(unique(res.type), ,); else results(k).crack_type none; end end ExportResult(results, result.xls);result.xls里的字段名使用英文如filename、crack_num、threshold这是为了避免MATLAB在写入中文表头时出现编码兼容问题。如果必须显示中文表头可以用cell数组配合writecell但新版本MATLAB更推荐writetable它的类型保持能力更好。批量处理前还有一个容易踩的坑不同批次图像分辨率不一致。建议先对每张图做imresize把长边固定到1280否则同一套阈值在4K图上表现会完全不同。GUI按钮回调里可以调用Process_Main并更新界面function btn_process_Callback(~, ~, handles) [f, p] uigetfile({*.png;*.jpg,路面图像}); res Process_Main(fullfile(p, f)); handles.currentRes res; guidata(gcbf, handles); end使用uigetfile让用户选择文件比硬编码路径更符合实际使用习惯。guidata把结果结构体保存到当前窗口的数据池中后续其他按钮可以通过handles.currentRes直接访问避免重复计算。这个设计虽然简单但足够支持一个课程设计级别的检测系统。5. 用迁移学习对候选裂缝做二次分类消除误检5.1 从传统视觉候选区到CNN输入传统规则判断在沥青纹理重、水渍多的路面上会把斑马线、伸缩缝、修补边界误认为裂缝。一个低成本改进办法是把Process_Main输出的每个BoundingBox区域裁剪下来作为正负样本训练一个二分类CNN。正样本是人工确认的裂缝块负样本是同一批图中被算法误检但实际不是裂缝的块。在MATLAB中用imageDatastore管理两个文件夹就能准备数据。imds imageDatastore(candidate_data, IncludeSubfolders, true, ... LabelSource, foldernames); [trainds, valds] splitEachLabel(imds, 0.8);裁出来的块尺寸不同所以在进入网络前必须统一尺寸。SqueezeNet输入是227x227ResNet18输入是224x224可以用augmentedImageDatastore做缩放augds augmentedImageDatastore([227 227], trainds);5.2 微调轻量预训练网络在MATLAB中微调SqueezeNet需要先得到网络结构再替换分类层net squeezenet; lgraph layerGraph(net); classes categories(imds.Labels); numClasses numel(classes); % 不同版本层名不同这里以R2023a为参考 lgraph removeLayers(lgraph, {pool10, prob, ClassificationLayer_Predictions}); lgraph addLayers(lgraph, [ fullyConnectedLayer(64, Name, fc_small) reluLayer(Name, relu_small) dropoutLayer(0.5, Name, drop_small) fullyConnectedLayer(numClasses, Name, fc_crack) softmaxLayer(Name, softmax_crack) classificationLayer(Name, cls_crack)]); lgraph connectLayers(lgraph, drop9, fc_small);不同MATLAB版本的SqueezeNet层名可能不同例如pool10可能被命名为pool10或global_average_pooling2d_layer。实际工程中务必先执行analyzeNetwork(net)查看层名再执行removeLayers。connectLayers中的drop9也需要根据实际最后一层名称修改如果结构不一致可以在addLayers之后用lgraph.Layers查看连接信息。训练参数设置options trainingOptions(sgdm, ... InitialLearnRate, 1e-4, ... MaxEpochs, 8, ... MiniBatchSize, 32, ... Plots, none); netTrained trainNetwork(augds, lgraph, options);初始学习率设为1e-4比从头训练的默认值低很多因为预训练网络已经学到了通用特征只需要小步调整分类器。最大轮数8轮足够在小数据集上收敛继续增加轮数容易过拟合。5.3 集成与阈值调优把训练好的网络接回传统流程对每个候选块分类bbox obj(i).BoundingBox; patch imresize(imcrop(I, bbox), [227 227]); [label, score] classify(netTrained, patch); if label crack max(score) 0.75 keep(i) true; end传统算法负责召回CNN负责精确率。调优时用测试集统计precision和recall根据业务偏好调整0.75这个阈值如果希望不漏检就降到0.5如果希望上报结果可信度高就升到0.9。修改完阈值后重新循环验证集观察平衡点移动到哪里。这个技巧在实际项目中可以直接落地也是这个工程包从“课程作业”走向“可演示原型”的关键一步。本文还有配套的精品资源点击获取
返回列表