尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

MATLAB道路语义分割实战:U-Net训练与后处理全流程解析

MATLAB道路语义分割实战:U-Net训练与后处理全流程解析 简介本资源是一套面向MATLAB初学者与自动驾驶算法入门者的道路语义分割实战项目聚焦图像识别核心任务解决无人驾驶场景中道路区域像素级精准分割问题。资源包共164个文件含101张PNG与54张JPG格式的道路实拍图像用于训练/验证、5个MAT格式标注数据存储像素级标签掩膜、2个M脚本含数据预处理与模型训练主流程及2个MLX交互式文档含关键步骤可视化分析整体压缩后59.58MB结构清晰、开箱即用。已有6428人学习下载覆盖高校课程实践、智能交通课题研究及算法工程师快速上手需求。用户可直接运行代码复现基于ResNet-18的端到端语义分割流程获得完整数据集组织方案、自定义网络修改方法、IoU评估实现及预测结果可视化脚本显著降低深度学习在MATLAB平台落地门槛。 前段时间我折腾了一个道路语义分割的MATLAB实例本来只是想验证一下U-Net在自己的数据上能不能跑通结果越做越深入最后把训练、评估、后处理全串了一遍。现在把整套流程和踩过的一些坑整理出来。这个项目非常适合正在学图像处理、或者准备用MATLAB做深度学习视觉任务的朋友尤其是学校大作业、毕业设计里经常出现“道路识别”“车道区域分割”这类需求。看完这篇你能直接拿这套思路去复现一个能用的道路分割demo也能明白每个环节为什么要这么做。1. 项目概述与整体设计思路1.1 道路语义分割到底是什么能解决什么问题道路语义分割属于图像语义分割的一种典型应用。语义分割的任务就是对图像里的每个像素做分类而不是像目标检测那样只画一个框。比如一张俯视街道的图片有天空、建筑、车辆、行人、道路分割模型要输出一张和原图尺寸一样的标签图每个像素标注它属于哪个类别。对无人车、辅助驾驶、道路巡检这些场景来说知道“哪个像素是道路”比知道“这里有个框”要实用得多因为车辆需要了解可行驶区域的具体轮廓。用MATLAB来做这件事最大的优势是工具箱集成度高。图像标注、数据增强、网络训练、结果可视化、甚至部署到硬件都在同一个环境里不用来回切换语言。传统的做法可能是用OpenCV做图像处理、用Python训练模型、再用某些库可视化中间嵌套一堆格式转换。MATLAB这边可以直接从imread切到trainNetwork代码量小很多特别适合想快速验证算法思路的人。1.2 为什么这个实例适合选U-Net作为基础网络我做这个项目时一开始考虑过用DeepLabv3也考虑过FCN但最终选了U-Net。原因是道路分割的数据集通常不是特别大比如CamVid一共只有几百张图训练样本量有限。U-Net结构经典、收敛稳定、对显存要求相对友好而且它在医疗图像这种样本量不大的任务里已经反复被验证有效。对道路场景来说虽然有更多结构化的边缘但U-Net的跳层连接能恢复细节效果往往意外地好。U-Net的基本逻辑是编码器-解码器架构。编码器部分不断下采样将分辨率降低、通道数增加让网络看到更大的感受野学到“道路的纹理和上下文”。解码器部分逐步上采样把低分辨率的高级语义特征恢复到原图尺寸。关键在于每一次上采样后会把对应层编码器的特征图拼过来这样网络能同时保留“全局语义”和“局部细节”。MATLAB里搭建这个结构不需要从零手写卷积使用deepNetworkDesigner可视化搭建或者直接用代码逐层定义即可。1.3 环境准备与工具链选型本人用的环境是MATLAB R2022bWindows 11显卡是RTX 3070 8GB显存。道路语义分割这种逐像素分类任务训练时显存消耗确实不小如果没有NVIDIA GPU用CPU跑也不是不行但训练时间会让人怀疑人生。建议至少用有CUDA支持的NVIDIA显卡并且安装好MATLAB对应的Parallel Computing Toolbox。工具箱方面必须安装的是Deep Learning Toolbox核心负责网络搭建和训练Computer Vision Toolbox图像存储、标注、评估指标Image Processing Toolbox图像预处理、形态学后处理如果你的MATLAB版本比较老比如R2019a之前很多预训练模型接口可能不支持建议还是升级到R2020b之后。另外训练过程中我强烈建议开启GPU加速在命令行里先执行gpuDevice确认GPU能被识别。如果显示找不到去检查CUDA和驱动版本MATLAB文档里一般列出了每个版本对应的CUDA版本要求。2. 数据准备与标注处理2.1 数据集选择CamVid还是Cityscapes道路语义分割最常用的开源数据集有两个一个是CamVid另一个是Cityscapes。CamVid是剑桥大学的路面视频数据集分辨率960×720一共700多张类别标注比较粗大约11类包括道路、车道、汽车、行人、建筑等。Cityscapes规模大很多5000张精细标注30个类别分辨率1024×2048质量也非常好适合做正式研究。但作为快速验证的MATLAB实例我推荐先用CamVid。理由是文件小、标注简单、类别少等代码流程跑通之后再换Cityscapes也不迟。如果连自己的图片数据集还没有完全可以去官网下载CamVid然后按目录组织成图片文件夹和标签文件夹。要注意标签图片是RGB编码的不是普通索引图比如红色表示道路、绿色表示树、蓝色表示天空每个类别对应一个固定的RGB值。这种格式处理起来要小心需要转换成类别索引才能喂给网络。2.2 用imageDatastore和pixelLabelDatastore管理数据MATLAB里做语义分割核心数据容器是pixelLabelImageDatastore它需要两个数据源一个是imageDatastore保存原图一个是pixelLabelDatastore保存标签图。pixelLabelDatastore创建的时候要传入标签图路径、类别名称和对应的像素标签ID。前面说过标签图是RGB格式所以还要有一个颜色表把RGB值映射成类别的整数ID。实际代码类似imageDir fullfile(dataPath, images); labelDir fullfile(dataPath, labels); imds imageDatastore(imageDir); classNames [road, car, background]; % example labels labelIDs [1 2 3]; % RGB mapping is hidden here pxds pixelLabelDatastore(labelDir, classNames, labelIDs); ds combine(imds, pxds); % or pixelLabelImageDatastore(imds, pxds)我一开始在这个环节翻过车因为标签图不是按普通图像路径那么直接读取的。必须保证imageDatastore和pixelLabelDatastore的文件顺序一致否则训练时图像和标签对不上。如果自己下载的数据集文件夹里文件名排序方式不同建议先用readall抽查几个样本显示一下图像与标签是否匹配。一个小技巧创建好pxds之后用preview方法预览一下它会自动显示原图和标签叠加图能很快发现对应关系有没有错位。2.3 没有现成标签时怎么办如果手里只有自己拍的街道照片没有标签那就要自己做标注。MATLAB里提供了imageLabeler这个交互式标注工具直接在App菜单里打开可以画多边形、画线框标记道路区域。标注路面时我建议沿着路缘石边缘一点一点打点不需要太细但要注意别把阴影里的区域漏掉。标注结果可以导出成ground truth然后继续生成pixelLabelDatastore。但纯手工标注很痛苦几十张图就得花半天。后来我换了个思路先用预训练模型对图像做一个粗略预测然后在这个预测结果上人工修正。比如用MATLAB自带的deeplabv3plusLayers配合ResNet18下载好预训练权重在未标注数据上先做一次预测把预测掩膜导出来再用imageLabeler加载进去修改效率高很多。这个思路本质上就是“半自动标注”在实际工程项目里很常见。3. 语义分割模型搭建与原理拆解3.1 网络结构选型从FCN到DeepLabv3刚开始做语义分割的人通常会看到FCN、U-Net、DeepLab系列一堆名词。FCN是最早的全卷积网络它把全连接层换成卷积层但上采样结果比较粗糙边缘细节恢复不好。DeepLab系列引入了空洞卷积和ASPP优点是能扩大感受野同时不损失分辨率对小物体和边缘更友好。U-Net的亮点在于跳层融合结构简单数据量少也不容易过拟合。我实际测试下来U-Net在CamVid上训练IoU大概能到0.8左右DeepLabv3在相同条件下可能略高但训练时间也更长。如果你关注“能在一个晚上训练完并且效果看得过去”U-Net是首选。如果你的任务里道路占比大、背景复杂、需要更精细的边界可以上DeepLabv3。MATLAB里创建DeepLabv3很简单一条命令lgraph deeplabv3plusLayers(imageSize, numClasses, resnet18);但注意加载这个网络需要你有预训练权重第一次运行会联网下载。如果不方便联网可以用U-Net自己训练彻底避免下载依赖。3.2 MATLAB中搭建U-Net的核心代码MATLAB从R2020a开始提供unetLayers这个函数可以直接生成U-Net网络。使用方法imageSize [360 480 3]; numClasses 11; lgraph unetLayers(imageSize, numClasses);如果你不想用自带函数也可以自己定义层。自建的优势是可以深度理解结构尤其适合教学。一个最小化的U-Net编码器部分大概是input imageInputLayer([360 480 3], Name, input); conv1 convolution2dLayer(3, 64, Padding, same, Name, conv1); relu1 reluLayer(Name, relu1); pool1 maxPooling2dLayer(2, Stride, 2, Name, pool1);解码器部分则需要用transposedConv2dLayer做上采样然后通过depthConcatenationLayer把编码器特征拼回来。如果手写要特别注意每一层的尺寸对齐尤其是Padding和Stride错一个维度训练直接报错。所以我强烈建议先用analyzeNetwork检查网络结构它会画出每一层的尺寸变化。如果尺寸对不上就会在上面的界面里直接标红。3.3 损失函数与类别权重为什么不能忽略语义分割的常见损失是交叉熵损失。MATLAB里训练语义分割网络默认就是用交叉熵但有一个隐藏问题类别不平衡。道路图像里道路区域一般占的比例很大而车辆、行人可能只占几个像素如果不处理不平衡模型训练出来会倾向于把所有像素都预测成道路因为这样损失最小。这个现象很典型我一开始就遇到了验证集准确率看着很高但车和行人完全没分割出来。解决办法是给每个类别设置权重让少数类在损失中占比更大。具体地先统计每个类别在训练集里出现的像素比例然后算中位数频率平衡权重。MATLAB里可以使用imageLabeler生成标签后用countEachLabel统计类别像素占比。得到权重后在损失层之前加一个pixelClassificationLayer的类权重设置。如果用的unetLayers可以通过属性OutputLayer改成带权重的层。classWeights [1.2 10.5 0.8]; % 根据实际统计调整 lgraph replaceLayer(lgraph, Segmentation-Layer, ... dicePixelClassificationLayer(Classes, classNames, ClassWeights, classWeights));这里的dicePixelClassificationLayer是Dice损失它天然对类别不平衡更友好对道路分割这种前景占比大的问题也很有用。如果不想改动太多直接用带权重的交叉熵也是可以的。关键是别忘了做这一步。4. 完整训练流程与参数调优4.1 训练选项配置学习率、BatchSize、轮数怎么定MATLAB的训练选项用trainingOptions设置。我比较常用的一组配置是options trainingOptions(adam, ... InitialLearnRate, 1e-3, ... MaxEpochs, 60, ... MiniBatchSize, 8, ... Shuffle, every-epoch, ... Verbose, true, ... Plots, training-progress, ... ValidationData, dsVal, ... ValidationFrequency, 50, ... LearnRateSchedule, piecewise, ... LearnRateDropFactor, 0.5, ... LearnRateDropPeriod, 10);学习率1e-3是相对稳妥的开始如果训练后期损失震荡可以降一半。BatchSize要根据显存调整。我的8GB显存跑360×480的输入BatchSize设为8已经接近极限了如果改成1024×2048输入BatchSize可能只能设为1。MiniBatchSize越小梯度噪声越大但有时反而能跳出局部最优。训练轮数方面CamVid比较小60轮足够。问题在于验证指标每50次迭代才评估一次所以最好在训练过程中时刻盯着曲线。如果看到训练损失下降但验证损失一直上升明显过拟合了需要提前终止或增强数据。Plots设为training-progress可以在训练过程生成实时曲线这是我最常用的功能能看到损失下降趋势和验证准确率。如果训练界面卡顿可以改用Verbose和VerboseFrequency输出日志。4.2 数据增强与正则化别让模型死记硬背道路分割很容易过拟合的一个原因是训练集太少。CamVid虽然有几百张图但场景变化有限。MATLAB里做数据增强非常方便可以用randomAffine2d创建随机仿射变换然后在augmentedImageDatastore里应用aug augmentedImageDatastore([360 480], dsTrain, ... DataAugmentation, ... imageDataAugmenter( ... RandRotation, [-10 10], ... RandXTranslation, [-10 10], ... RandYTranslation, [-10 10], ... RandXScale, [0.9 1.1], ... RandYScale, [0.9 1.1], ... FillValue, 0));注意数据增强中翻转和旋转要小心。道路分割中左右翻转是合理的但上下翻转会引起语义问题比如天空在下面实际中不存在。所以我通常不用RandYReflection或将其设为false。旋转角度也不要太大10度以内足够角度太大会导致标签错位明显模型学到的几何失真严重。除了数据增强还可以在卷积层后加入dropoutLayer。不过语义分割本身对空间相关性要求高dropout设置过高会丢失细节一般取0.2左右比较好。4.3 训练日志监控与断点续训训练过程中我习惯把日志保存下来方便后续分析。可以用OutputFcn自定义回调在每个epoch结束时保存当前网络和训练状态。这样即使训练中途断电或者崩了也不用从头再来。outputFcn (info) myOutputFcn(info, netPath); options.OutputFcn outputFcn;myOutputFcn里的逻辑很简单当info.State iteration时如果到了指定迭代次数就用save保存网络对象。断点续训时加载保存好的网络把训练选项里的InitialLearnRate再调低一点继续trainNetwork即可。这个技巧在长任务里特别管用我试过训练到第30轮时机器重启如果没有断点保存前面的时间全部浪费。5. 结果评估与可视化5.1 评估指标IoU、全局准确率、类别准确率训练完模型后第一件事不是截图发朋友圈而是计算评估指标。语义分割最常用的指标是IoUIntersection over Union也就是预测区域和目标区域的交集除以其并集。IoU越高说明分割越准。MATLAB提供了evaluateSemanticSegmentation函数一行代码就能得到多种指标metrics evaluateSemanticSegmentation(net, dsTest, Verbose, false);它会输出全局准确率、平均准确率、平均IoU、每个类别的IoU等。对我这个道路分割项目来说我更关注road类的IoU因为项目核心是道路区域提取。全局准确率只是一个宏观指标有可能背景占比太大全局准确率很高但道路边缘全乱所以还是要看类别IoU。如果要手动计算某个类别的IoU公式是IoU TP / (TP FP FN)在MATLAB里可以通过预测结果和真实标签逐像素比较来计算但直接用evaluateSemanticSegmentation更省事。输出结果里还会带一个混淆矩阵可以看哪些类别容易互相混淆比如行人和骑手容易混、道路和车道线容易混。我看了混淆矩阵才发现我的模型把不少“车道线”像素分到了“道路”类虽然IoU上道路很高但车道线细节丢失严重。5.2 分割结果可视化与置信度分析可视化很简单用semanticseg对测试图片预测输出是一个类别索引图然后用labeloverlay把分割结果叠加到原图上C semanticseg(I, net); mask labeloverlay(I, C, Colormap, colorMap, Transparency, 0.5); imshow(mask);这样能非常直观地看到道路分割边界是否干净。不过只看叠加图不能发现所有问题。我建议再做一步“置信度图”用predict得到每个像素的类别概率取最大值作为置信度然后用伪彩色显示低置信度区域。低置信度区域往往是边缘、阴影、遮挡部分这些地方最有改进空间。置信度可视化的代码大致是[~, scores] predict(net, I); % scores is HxWxNumClasses [maxScore, predLabel] max(scores, [], 3); imagesc(maxScore); colorbar;5.3 道路区域提取与后处理分割结果直接用于下游任务前一般还需要后处理。MATLAB里形态学操作很方便。我常用的一个流程是先提取出“道路”这一类的二值掩膜然后用imopen去掉小的孤立噪声再imclose填补道路内部的空洞。最后用bwareafilt保留最大的连通区域因为一般可行驶道路是图像里最大的前景区域这样能过滤掉误检成道路的小块。roadMask C 1; % 假设road是第一个类 roadMask imopen(roadMask, strel(disk, 3)); roadMask imclose(roadMask, strel(disk, 5)); roadMask bwareafilt(roadMask, 1);后处理不只是为了好看实际上很多自动驾驶应用需要的正是这种“干净的可行驶区域”。如果道路被树荫遮挡分割结果可能出现空洞通过闭运算可以部分缓解。但也要注意平滑过度会吞掉小障碍物边界不能为了指标好看而把车和行人覆盖掉否则下游避障就危险了。6. 常见问题与排查技巧实录6.1 显存不足或OOM最大的坑就是显存溢出。我在尝试把输入尺寸调成1024×1024的时候BatchSize2直接OOM。解决办法有几条降低输入分辨率比如从960×720降到480×360缺点是小目标会丢失减小BatchSize到1或2配合梯度累积有些版本MATLAB没有内置梯度累积那就只减小BatchSize换成轻量级骨干网络比如用unetLayers的EncoderDepth参数减少层数使用混合精度训练trainingOptions里设置ExecutionEnvironment,auto同时启用DispatchInBackground有些GPU可以自动用半精度。最推荐的是第一种先降分辨率跑通流程再评估大分辨率的必要性。很多道路分割任务中480×360已经足够用了。6.2 类别不平衡导致道路漏检或者说背景误检前面提过类别不平衡我再补充一个现象如果classWeights设置过大模型可能把背景像素强行预测成少数类导致大量假阳性。平衡不是越极端越好。我建议先跑一个epoch看验证集上各类别的IoU趋势再微调权重。还有一种变通方法是给训练数据做裁剪让每个mini-batch里道路和背景的比例接近比如随机裁剪时优先选择包含道路的区域这样不需要手调权重。6.3 边界模糊、分割结果锯齿严重边界模糊是语义分割的通病尤其是U-Net在边缘细节上有时会拉出“毛边”。我用的方法组合是训练时加入边界感知损失或者在预测后对分割边界做条件随机场DenseCRF平滑。MATLAB里没有直接内置CRF但可以用Image Processing Toolbox里的imgaussfilt或medfilt2对概率图平滑。另外把输入图像与分割结果做引导滤波也可以提升边缘质量。如果边界模糊伴随验证损失降不下去可能是网络容量不足或数据太少。可以考虑换DeepLabv3或者用更多数据做预训练。6.4 训练速度极慢CPU占满但GPU低占用这种情况通常是数据读取和预处理成了瓶颈。MATLAB里augmentedImageDatastore如果不启用后台调度GPU会一直等数据。设置DispatchInBackground为true可以让数据预处理在后台进行。还有图像标签对读取如果频繁做RGB到索引映射也会拖慢速度。可以提前把标签转成PNG索引图减少训练时的转换开销。我测试过同样配置下开启DispatchInBackground后训练速度提升接近30%尤其是数据增强操作的随机旋转缩放比较多时改善更明显。7. 使用中积累的几点经验与下一步可以做的工作做这个道路语义分割实例我个人最大的感受是MATLAB整个流程闭环很方便但细节在数据准备和训练调参。很多人以为跑通代码就完事了实际上最后的性能上限更多取决于数据质量、类别权重和数据增强设置。我建议初学者一定先可视化训练数据和预测结果每次修改参数前都清楚地知道是“为了改善什么”。还有一个实用小技巧训练结束后把模型导出为DAGNetwork或dlnetwork再用MATLAB Compiler或者GPU Coder部署到嵌入式设备。道路分割如果只停留在离线测试意义有限真正落地需要能实时跑。MATLAB在这方面有一整套部署工具链后续可以往这个方向扩展。我自己也在尝试把分割结果和车道线检测结合起来进一步提取可行驶区域内的车道级信息这就不是一两篇文章能讲完的了。最后再提一句如果你在跑这个项目的时候遇到各种错误不用慌先去命令行里看堆栈信息大多数问题都是数据尺寸不匹配、路径错误或者GPU内存不够。把这类错误多踩几遍你对语义分割的理解会比只看文档深得多。本文还有配套的精品资源点击获取
返回列表