☰
基于Matlab的道路缺陷检测:语义分割技术实践
2026/10/17 8:32:06 网站建设 项目流程

立项之前先说点实在的。道路缺陷检测这两年越来越多人关注,但真正能把方案落地、而不是停留在"跑了几个demo"层面的其实不多。原因也简单,路面病害这个东西,数据和场景的坑远比模型结构多。这篇内容我会从"到底要解决什么问题"讲起,把技术选型的逻辑、Matlab端到端的代码链路、训练评估的细节、以及工程落地容易翻车的点完整串一遍。代码部分可以直接抄,但更重要的是理解每一步为什么这么做。

我默认读者对Matlab基础操作和深度学习基本概念有一定了解,但哪怕你是刚接触语义分割,只要按着这个思路走,也能把一套道路裂缝检测系统从零搭起来。

1. 谁在需要道路缺陷检测,以及我为什么选这条技术路线

1.1 道路病害的真实形态:裂缝、坑槽、修补区各自的特征差异

先别急着聊模型,我们得把"道路缺陷"这四个字拆开。实际巡检中,最常见的路面病害大体分为三类:裂缝、坑槽、修补区。这三类的视觉特征差异非常大,决定了后续算法设计的走向。

裂缝是线状结构,有的细到只有一到两个像素宽,有的则是网状的龟裂。它的问题在于"长而窄",整张512x512的图像里,裂缝区域往往只占不到百分之五的像素。坑槽则是面状缺陷,边界比较清晰,但形状不规则,而且在阳光下会产生明显的阴影,处理不好就会和目标混淆。修补区是已经修复过的地方,颜色和周围路面往往有明显差异,但它是"允许存在"的区域,对于养护决策来说需要单独标记出来。

这三类缺陷如果不分开建模,统一当作"异常区域"处理,后面做长度统计、面积评估、养护优先级判断时都会出问题。所以我从一开始就明确了:这是一个像素级的分类任务,不是单纯的"有没有病"的二分类。

1.2 传统图像方法的局限和深度学习方案的契机

也许有人会问,Canny边缘检测加形态学处理,不是也能把裂缝提出来吗?我一开始也是这么想的,毕竟传统方法不需要标注数据、不需要显卡,跑得又快。

但实际拿到路面图像就会发现,传统方法有几个致命问题。

第一,路面的纹理噪声太严重。沥青路面的颗粒在边缘检测里会产生大量伪边缘,你是无法靠一个固定阈值区分"裂缝"和"颗粒阴影"的。第二,光照不均导致同一病害在不同区域的表现差异极大。树荫下的裂缝和阳光直射下的裂缝,对比度能差出好几倍,固定参数根本无从适应。第三,路面标线、油渍、轮胎痕迹都会造成干扰,传统方法会把它们一并割出来。

我在这类对比试验上消耗过不少时间,最后得出的结论很直接:道路缺陷检测本质上不是一个低层图像结构问题,而是一个语义理解问题。你需要让算法理解"什么是病害、什么是正常路面",这恰恰是深度学习方法擅长的。

2. 方案选型拆解:分割任务为何比目标检测更适合路面病害

2.1 目标检测 vs 语义分割:病害形态直接决定了任务定义

现在做视觉检测,大家第一反应是目标检测。YOLO、Faster R-CNN确实在工业缺陷检测里用得多,但放到道路缺陷场景中,目标检测有个天然的错配:路面病害不是"框"出来的,而是"画"出来的。

裂缝的形态是细长曲线,其长度和走向是评估病害等级的核心指标。如果用锚框去做位置回归,一个框里包含了大量背景像素,裂缝的真实形态信息会完全丢失。更麻烦的是,一条贯穿整幅图的裂缝,目标检测会把它切成好几个框,后续还需要做框的后融合,多一环就多一堆错误。坑槽虽然接近矩形区域,但边界并不规则,检测框会同时带上周围完好的路面,养护计算面积的时候偏差极大。

相比之下,语义分割对每一个像素做类别预测。裂缝、坑槽、修补区、背景,每个像素都有归属,形态、长度、面积、连通域信息都能直接从分割结果中提取。这也是为什么公路检测相关的公开数据集大多采用像素级标注,而不是矩形框标注。所以我的建议是:只要你的目标涉及形态评估、面积统计、等级判定,语义分割就是更合适的起点。

2.2 网络结构选型:U-Net负责精细结构,DeepLabV3+负责语义轮廓

网络选型是一个经常被过度神化的问题。我试过的方案里,真正适合路面病害场景的无非两类:U-Net系列和DeepLab系列。

U-Net的整体结构是编码器-解码器加跳层连接。编码器不断下采样提取语义特征,解码器逐步恢复分辨率,跳层连接把底层的细节特征直接传给解码器。这个设计对裂缝这种"大背景里的细小目标"很友好,因为裂缝的像素级细节可以通过跳层保留下来。我日常实验里,U-Net配合一个足够好的编码器骨干,在裂缝分割任务上是最稳妥的起点。

DeepLabV3+的核心优势在于空洞卷积带来的多尺度感受野。它能在不增加参数量的前提下看到更大范围的上下文,对坑槽这种边界模糊、周围环境变化大的目标是加分项。缺点是结构更复杂,推理速度相对慢,对小裂缝的细节恢复能力弱于U-Net。

实际上我在项目里做的是两手准备:先用U-Net跑通整个流程,保底效果,再替换成DeepLabV3+做对比实验。两个网络的训练、评估、后处理管线完全复用,切换成本很低。

另外要强调的是,编码器骨干的选择往往比网络主体结构更能影响结果。同样的U-Net,把骨干从几层简单卷积换成预训练的残差结构,mIoU的差距可能比换整个网络结构还大。对于在Matlab里快速验证的场景,我建议优先使用自带预训练的骨干网络,而不是从零开始训练一个深层编码器,训练时间和效果都会好很多。

3. Matlab实现路径:从图像标注到训练推理的完整链路

3.1 数据准备:采集规范与像素级标注

先讲数据采集。路面图像的采集质量直接决定了模型的上限,后期算法再强也很难弥补输入端的缺失。我总结下来有几个必须关注的规范:

  • 拍摄设备尽量固定在车尾或侧方,机位高度和角度保持一致,避免不同批次图像的视角漂移。
  • 尽量选择照度均匀的时间段采集,阴天比晴天好,雨天不采,因为积水会产生反光干扰。
  • 注意力放在图像重叠率的控制上,相邻图之间有百分之二十左右的重叠即可,太多会引入大量相似样本,太少会漏检。

采集完图像之后,标注是第一个耗时大户。Matlab自带的图像标注器提供了像素标注工具,可以手动在图上描出裂缝、坑槽、修补区的边界。我常用的流程是先将图像导入图像标注器,定义好类别名称和标签ID,再逐张描边。裂缝比较细的时候,建议把图像放大到百分之一百五十以上再标注,不然很容易描出断断续续的伪标注,后面训练阶段全得返工。

需要明确的是,标注好的掩膜图像需要保存为与类别名称对应的索引图像,或者通过像素标签数据存储来管理。标注输出一般是一张灰度标签图,每个像素的灰度值对应类别索引,例如0表示背景、1表示裂缝、2表示坑槽、3表示修补区。这个对应关系在后面构造数据存储时要保持一致,否则训练和评估会出现类别错位。

下面给出的是一个典型的数据存储组织方式:

% 根目录下分别存放原始图像和标签掩膜 rootPath = 'D:/road_defect_project'; imgDir = fullfile(rootPath, 'images'); labelDir = fullfile(rootPath, 'labels'); % 类别定义必须与标注时的ID一一对应 classNames = ["background"; "crack"; "pothole"; "patch"]; labelIDs = [0 1 2 3]; % 影像Datastore与像素标签Datastore imds = imageDatastore(imgDir); pxds = pixelLabelDatastore(labelDir, classNames, labelIDs);

3.2 训练前的数据增强:为什么要做以及怎么做

路面缺陷数据集的自采成本高,样本量普遍不够,所以数据增强不是锦上添花,而是必需品。

Matlab的imageDataAugmenter提供了常用的几何增强选项。我用的最多的是随机水平/垂直翻转、随机旋转、随机缩放和随机平移。这几种变换对路面病害不会改变其语义本质——裂缝翻转之后还是裂缝,坑槽旋转一下还是坑槽。而不像车牌识别那样翻转之后文字会变,所以几何增强在路面场景里非常安全。

色彩增强要谨慎。路面图像的灰度变化和色偏本身就和光照、老化程度有关,过度做颜色抖动可能让模型学到不正确的颜色关联。我一般只做很轻微的高斯模糊和对比度调整,或者干脆不开颜色增强,靠几何增强已经能带来足够增益。

在Matlab里,数据增强与pixelLabelImageDatastore配合使用,会自动对图像和标签做相同的几何变换,不需要手工同步:

aug = imageDataAugmenter(... 'RandXReflection', true, ... 'RandYReflection', true, ... 'RandRotation', [-10 10], ... 'RandScale', [0.8 1.2], ... 'RandXTranslation', [-20 20], ... 'RandYTranslation', [-20 20]); % 将图像与标签打包成可训练的数据存储 dsTrain = pixelLabelImageDatastore(imdsTrain, pxdsTrain, ... 'OutputSize', [512 512], ... 'DataAugmentation', aug);

OutputSize这里我统一设置成512x512。道路图像原图尺寸可能很大,直接送进网络会爆显存,自适应缩放又会改变裂缝的粗细比例。我的建议是先把原图裁剪成若干个512x512的块,再送入训练管线,这样既能保持病害形态比例,又能控制内存消耗。

3.3 网络搭建与训练选项配置

Matlab深度学习工具箱提供了分层搭建的函数,不需要手动写前向传播和反向传播,这对快速验证模型非常方便。搭建U-Net只需一行调用:

imageSize = [512 512 3]; numClasses = numel(classNames); % EncoderDepth控制下采样层级数,层数越深感受野越大 lgraph = unetLayers(imageSize, numClasses, ... 'EncoderDepth', 4);

如果想换用DeepLabV3+,同样是一行:

% 骨干网络可以用mobilenetv2或resnet18,根据算力和精度需求取舍 lgraph = deeplabv3plusLayers(imageSize, numClasses, 'mobilenetv2', ... 'DownsamplingFactor', 8);

训练选项的配置是整个流程里最值得花心思的地方。我的经验是,路面病害分割任务有几个关键的训练超参需要特别注意:

  • 学习率:不要一上来就用0.01这种分类任务常用的值。分割任务的损失面更复杂,初始学习率从0.001到0.005之间起步比较稳,配合分段衰减策略。
  • MiniBatchSize:受限于输入尺寸512x512,显存不够就用4或者8,不要为了凑大batch把图像缩到256x256,那样裂缝的细节就丢了。
  • MaxEpochs:自采数据量小时,60到100个epoch之间要配合早停和观察验证loss,防止过拟合。训练集loss持续下降而验证集loss上升,就该及时保存当前最优权重了。

下面是我常用的一个训练配置,可以直接套用:

options = trainingOptions('sgdm', ... 'InitialLearnRate', 3e-3, ... 'Momentum', 0.9, ... 'LearnRateSchedule', 'piecewise', ... 'LearnRateDropPeriod', 20, ... 'LearnRateDropFactor', 0.5, ... 'MiniBatchSize', 4, ... 'MaxEpochs', 80, ... 'Shuffle', 'every-epoch', ... 'VerboseFrequency', 20, ... 'Plots', 'training-progress', ... 'ExecutionEnvironment', 'gpu'); trainedNet = trainNetwork(dsTrain, lgraph, options);

特别说一下ExecutionEnvironment。能用GPU就用GPU,训练速度快的不只是一点半点。如果没有可用GPU,建议把OutputSize降到384x384,否则CPU训练512x512的U-Net会等到怀疑人生。

4. 训练与评估:关键指标怎么算,结果怎么解读

4.1 训练过程的监视:loss曲线不能只看像不像

训练一启动,Matlab会弹出训练进度图,很多人的注意力全在loss曲线降得漂不漂亮上。但我建议同时盯着验证集的指标曲线,因为路面缺陷数据不平衡非常严重,背景像素可能占九成以上,模型就算把所有像素都预测成背景,整体准确率依然能超过90%。

这就是我在这个项目里反复强调的一个观点:准确率在类别极度不平衡的场景下没有参考价值。真正要看的是交并比和召回率。裂缝这一类如果交并比低,说明预测结果和真实标注的重合度不高,那后续的裂缝长度统计就没意义。所以训练过程中我习惯把每个epoch结束后的验证交并比打出来,而不是只看总loss。

如果你想在训练过程中更细粒度地观察每个类别的表现,可以把验证数据也封装成pixelLabelImageDatastore,设置'ValidationData'选项。不过验证数据要在增强之外单独准备,避免数据增强让标注形态发生变化影响验证指标的可解释性。

4.2 评估指标计算与可视化:用evaluateSemanticSegmentation一次性搞定

训练完成后,完整的评估流程分三步:先对验证集逐张做推理,再把预测结果和真实标注打包成像素标签数据存储,最后调用内置评估函数计算各类指标。

% 对验证图像批量推理 pxdsResult = semanticseg(imdsVal, trainedNet, ... 'MiniBatchSize', 4, ... 'WriteLocation', tempdir); % 与真实标注对比评估 metrics = evaluateSemanticSegmentation(pxdsResult, pxdsVal); metrics.ClassMetrics

metrics.ClassMetrics会输出每个类别的准确率、召回率以及IoU。我自己的判断标准是:

  • 裂缝IoU在0.55以上就算能接受,在0.65以上就很好了。裂缝太细,标注和预测差一个像素都会让IoU掉很多。
  • 坑槽IoU理应比裂缝高,因为它面积大,一般0.75以上才是合格。
  • 修补区和坑槽容易互相误判,重点看这两类的混淆情况,如果互相污染严重,要考虑是不是标注阶段边界定义不统一。

除了数值指标,我强烈建议做一轮可视化检查。不要只看指标,指标不会告诉你裂缝是不是中间断成了一段段,也不会告诉你预测的坑槽边界是不是往外膨胀了两圈。每个类别的可视化对比图是找问题的最快方式。

I = readimage(imdsVal, 8); C = semanticseg(I, trainedNet); B = labeloverlay(I, C); imshowpair(I, B, 'montage');

4.3 后处理:噪音滤除、细裂缝闭合与连通域统计

网络输出的原始分割图通常不能直接拿去做统计,因为里边会有大量离散的误检点、细裂缝断裂、边缘锯齿。后处理是整个流程里投入产出比最高的环节。

我在Matlab里的标准后处理包括这么几步:

第一,小面积区域滤除。路面图像上经常出现零星误检,可能是碎石子、油渍或者标注噪声导致的。用形态学开运算或者连通域面积阈值把小于一定像素数的区域去掉。裂缝属于细长结构,连通域面积并不大,直接用bwareaopen减去小区域时要小心阈值,我一般从50像素开始试,太大容易把真正的小裂缝也删掉。

第二,断裂裂缝闭合。裂缝被预测成一段一段的,是分割网络的老毛病。可以用不同角度方向的线段结构元素做形态学闭合,优先考虑水平、垂直、45度和135度四个方向:

crackMask = (C == "crack"); % 去除面积小于50像素的噪点 crackMask = bwareaopen(crackMask, 50); % 多方向闭合,缝补细裂缝的断点 lineLen = 7; for ang = [0 45 90 135] se = strel('line', lineLen, ang); crackMask = imclose(crackMask, se); end

第三,连通域统计。闭合之后,用bwlabel提取裂缝连通域,然后根据连通域长度分布判断裂缝等级。单条裂缝的长度、面积、宽度均值都可以直接关联到实际物理尺寸。

cc = bwlabel(crackMask); stats = regionprops(cc, 'Area', 'BoundingBox', 'Perimeter');

如果你关心的是裂缝走向和整体病害分布,还可以把连通域结果叠加到原图上输出一张带标注的巡检图,后续无论是人工复核还是接入养护管理系统,都比看一张纯色掩膜直观得多。

5. 工程化思考:把模型用起来的最后一公里

5.1 从离线检测到实时视频流

模型训练好之后,一般会面临一个现实问题:不能只在文件夹里测试零星图片,得让它处理真实巡检视频或相机实时帧。

Matlab里做实时推理的思路和离线推理略有不同。离线推理可以一批一批处理,让GPU尽量满载;实时推理则要控制单帧处理时间,这时模型的轻量化就变得重要了。如果训练时用的是U-Net+重型骨干,建议推理阶段换用更轻的骨干或者减小输入分辨率,在帧率和精度之间找一个平衡点。

我踩过的一个具体坑是:实时视频流里的帧与帧之间会有大量时间冗余。如果在连续帧上逐帧做全图推理,计算浪费非常大。简单有效的方案是做帧差检测或者固定间隔抽帧推理,两帧之间只有局部变化时,直接把上一次的分割结果复制过去,确认变化区域后再更新,能省下大量计算资源。

代码上,Matlab可以用webcam对象或者读取视频文件来做这件事。每读完一帧先缩放到训练输入尺寸,再调用semanticseg,把结果映射回原图坐标。映射这一步很多人会忽略,直接导致可视化时分割结果错位。记住缩放前后要记录缩放比例,做坐标逆变换。

5.2 实测最容易翻车的几个场景

模型在验证集上指标不错,放到真实道路上却常常露馅。我这里整理几个高频翻车场景,都是从实际测试中总结出来的。

第一,树荫和阴影。阴影下的裂缝和背景对比度非常低,网络很容易漏检。阴影边界本身又常常被误判成坑槽。应对思路是采集训练数据时有意包含大量阴影样本,并配合灰度归一化预处理。我做了一个简单的自适应直方图均衡化,放在推理前处理里,实测对阴影场景的召回率提升明显。

第二,湿滑路面反光。雨水或清晨露水会让路面出现高反光区域,反光区域的纹理被冲淡,裂缝几乎不可见。这种情况下连人工都很难判断,模型基本无能为力。我的建议是在采集规范里明确规定雨天不采,后端再对图像的平均亮度和高光占比做质量检查,质量不合格的图像直接标记为"待复核",而不是强行送进模型。

第三,道路标线干扰。白色标线和黄色标线容易被误判成修补区甚至裂缝,尤其是年久失修的标线,边缘破碎程度和龟裂非常相似。处理办法是单独增加一个"标线"类别参与训练,或者在后处理里通过颜色空间先粗筛标线区域,再对这些区域做抑制。我倾向于增加类别的方式,更干净,也更容易维护。

第四,不同路龄的路面纹理差异。刚铺的沥青路面和用了七八年的路面纹理差别很大。只用单一地区数据训练出来的模型,换到别的城市或别的道路等级上,性能往往掉得厉害。这个问题的根治办法是数据层面引入多地区、多路龄样本,模型层面增加随机纹理增强。

5.3 模型迭代改进的闭环思路

工程上模型上线只是开始,后续迭代才决定系统能不能长期用下去。我的迭代闭环很简单:推理结果回流到待标注池,人工只修正置信度低的结果,定期增量训练更新模型。

具体操作是,系统每天把检测结果导出成一张叠加了分割边界的巡检图,同时给每个连通域打一个置信度分数。置信度高的可以直接进入统计报表,不需要人工再看。置信度低的才会进入复核队列,由人工在标注工具里快速修正,修正后的数据积累到一定量级,就触发一次增量训练。这个闭环做起来之后,模型会随着使用时间越来越贴合该地区道路的真实情况,比一次性训练一个"万能模型"靠谱得多。

如果你的项目已经有历史巡检图像但没有任何标注数据,也可以先用训练好的模型做一次自动预标注,再在标注工具里修正。虽然预标注会有不少错漏,但对比从零开始逐像素描,能省掉至少一半的标注时间。这是我在多个类似项目里验证过的效率提升方法。

最后再分享一个小技巧。训练结束后不要只保留一个"最好模型"。我会把训练过程中不同阶段的权重各保存一份,放到一批固定的验证图像上对比。有些阶段权重在总体指标上不是最高,但在某类病害上的表现却格外好。把多个模型做结果融合,或者针对特定场景选择最合适的权重,这条路对于路面缺陷这类小样本场景来说,往往比钻研网络结构更能带来稳定的提升。

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

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

立即咨询