此示例显示如何使用深度学习训练语义分段网络。
语义分割网络对图像中的每个像素进行分类,从而产生按类别分割的图像。语义分割的应用包括用于自主驾驶的道路分割和用于医学诊断的癌细胞分割。有关详细信息,请参阅语义分段基础知识(计算机视觉系统工具箱)。
为了说明训练过程,本例训练SegNet [1],一种设计用于语义图像分割的卷积神经网络(CNN)。用于语义分段的其他类型网络包括完全卷积网络(FCN)和U-Net。此处显示的培训程序也可以应用于这些网络。
此示例使用剑桥大学的CamVid数据集 [2]进行培训。此数据集是包含驾驶时获得的街道视图的图像集合。该数据集为32种语义类提供了像素级标签,包括汽车,行人和道路。
建立
此示例创建具有从VGG-16网络初始化的权重的SegNet网络。要获得VGG-16,请安装适用于VGG-16网络的Deep Learning Toolbox™模型。安装完成后,运行以下代码以验证安装是否正确。
vgg16();
警告:重命名重复的类名。
此外,下载预训练版的SegNet。预训练模型允许您运行整个示例,而无需等待培训完成。
pretrainedURL = 'https: //www.mathworks.com/supportfiles/vision/data/segnetVGG16CamVid.mat ' ; pretrainedFolder = fullfile(tempdir,'pretrainedSegNet'); pretrainedSegNet = fullfile(pretrainedFolder,'segnetVGG16CamVid.mat'); 如果〜存在(pretrainedFolder,'dir') MKDIR(pretrainedFolder); disp('下载预训练的SegNet(107 MB)......'); websave(pretrainedSegNet,pretrainedURL); 结束
强烈建议使用具有计算能力3.0或更高版本的支持CUDA的NVIDIA™GPU来运行此示例。使用GPU需要Parallel Computing Toolbox™。
下载CamVid数据集
从以下URL下载CamVid数据集。
imageURL = 'http: //web4.cs.ucl.ac.uk/staff/g.brostow/MotionSegRecData/files/701_StillsRaw_full.zip ' ; labelURL = 'http: //web4.cs.ucl.ac.uk/staff/g.brostow/MotionSegRecData/data/LabeledApproved_full.zip ' ; outputFolder = fullfile(tempdir,'CamVid'); 如果〜存在(outputFolder,'dir') MKDIR(outputFolder) labelsZip = fullfile(outputFolder,'labels.zip'); imagesZip = fullfile(outputFolder,'images.zip'); disp('下载16 MB CamVid数据集标签......'); websave(labelsZip,labelURL); unzip(labelsZip,fullfile(outputFolder,'labels')); disp('下载557 MB CamVid数据集图像......'); websave(imagesZip,imageURL); 解压缩(imagesZip,fullfile(outputFolder,'images')); 结束
注意:数据的下载时间取决于您的Internet连接。上面使用的命令会阻止MATLAB,直到下载完成。或者,您可以使用Web浏览器首先将数据集下载到本地磁盘。要使用从Web下载的文件,请将outputFolder
上面的变量更改为下载文件的位置。
加载CamVid图像
使用imageDatastore
加载CamVid图像。在imageDatastore
使您能够高效地装载大量收集图像的磁盘上。
imgDir = fullfile(outputFolder,'images','701_StillsRaw_full'); imds = imageDatastore(imgDir);
显示其中一个图像。
I = readimage(imds,1); 我= histeq(I); imshow(I)
加载CamVid像素标签图像
使用pixelLabelDatastore
加载CamVid像素标签图像数据。A pixelLabelDatastore
将像素标签数据和标签ID封装到类名映射中。
按照原始SegNet论文[1]中使用的程序,将CamVid中的32个原始类分组为11个类。指定这些类。
class = [ “Sky” “Building” “Pole” “Road” “Pavement” “Tree” “SignSymbol” “Fence” “Car” “Pedestrian” “Bicyclist” ];
要将32个类减少为11个,将原始数据集中的多个类组合在一起。例如,“Car”是“Car”,“SUVPickupTruck”,“Truck_Bus”,“Train”和“OtherMoving”的组合。使用支持函数返回分组的标签ID,该函数camvidPixelLabelIDs
在本示例的末尾列出。
labelIDs = camvidPixelLabelIDs();
使用类和标签ID来创建 pixelLabelDatastore.
labelDir = fullfile(outputFolder,'labels'); pxds = pixelLabelDatastore(labelDir,classes,labelIDs);
通过将其叠加在图像上来读取并显示其中一个像素标记的图像。
C = readimage(pxds,1); cmap = camvidColorMap; B = labeloverlay(I,C,'ColorMap',cmap); imshow(B) pixelLabelColorbar(CMAP,班);
没有颜色叠加的区域没有像素标签,在训练期间不使用。
分析数据集统计
要查看CamVid数据集中类标签的分布,请使用countEachLabel
。此函数按类标签计算像素数。
tbl = countEachLabel(pxds)
tbl = 11×3表
名称PixelCount ImagePixelCount
____________ __________ _______________
'Sky'7.6801e + 07 4.8315e + 08
'建筑'1.1737e + 08 4.8315e + 08
'极'4.7987e + 06 4.8315e + 08
'道路'1.4054e + 08 4.8453e + 08
'路面'3.3614e + 07 4.7209e + 08
'树'5.4259e + 07 4.479e + 08
'SignSymbol'5.2242e + 06 4.6863e + 08
'围栏'6.9211e + 06 2.516e + 08
'汽车'2.4437e + 07 4.8315e + 08
'Pedestrian'3.4029e + 06 4.4444e + 08
'自行车'2.5912e + 06 2.6196e + 08
按类可视化像素计数。
frequency = tbl.PixelCount / sum(tbl.PixelCount); 杆(1:numel(类),频率) xticks(1:numel(类)) xticklabels(tbl.Name) xtickangle(45) ylabel('频率')
理想情况下,所有类都有相同数量的观察。但是,CamVid中的类是不平衡的,这是街道场景的汽车数据集中的常见问题。由于天空,建筑物和道路覆盖了图像中的更多区域,因此这些场景具有比行人和骑车像素更多的天空,建筑物和道路像素。如果处理不当,这种不平衡可能对学习过程有害,因为学习偏向于支配阶级。稍后在此示例中,您将使用类权重来处理此问题。
调整CamVid数据的大小
CamVid数据集中的图像为720 x 960.为减少训练时间和内存使用量,请将图像和像素标签图像的大小调整为360 x 480 resizeCamVidImages
,resizeCamVidPixelLabels
并支持本示例末尾列出的功能。
imageFolder = fullfile(outputFolder,'imagesResized',filesep); imds = resizeCamVidImages(imds,imageFolder); labelFolder = fullfile(outputFolder,'labelsResized',filesep); pxds = resizeCamVidPixelLabels(pxds,labelFolder);
准备培训和测试集
使用来自数据集的60%的图像训练SegNet。其余图像用于测试。以下代码将图像和像素标签数据随机分成训练和测试集。
[imdsTrain,imdsTest,pxdsTrain,pxdsTest] = partitionCamVidData(imds,pxds);
60/40分割产生以下数量的训练和测试图像:
numTrainingImages = numel(imdsTrain.Files)
numTrainingImages = 421
numTestingImages = numel(imdsTest.Files)
numTestingImages = 280
创建网络
使用segnetLayers
创建SegNet网络使用VGG-16的权重初始化。segnetLayers
自动执行从VGG-16传输权重所需的网络转换,并添加语义分段所需的附加层。输出segnetLayers
是LayerGraph
表示SegNet 的对象。甲LayerGraph
对象封装网络层和所述层之间的连接。
imageSize = [360 480 3]; numClasses = numel(classes); lgraph = segnetLayers(imageSize,numClasses,'vgg16');
警告:重命名重复的类名。
基于数据集中图像的大小选择图像大小。根据CamVid中的类选择类的数量。
使用类权重的余额类
如前所示,CamVid中的类不平衡。要改进培训,您可以使用班级加权来平衡班级。使用先前计算的像素标签计数countEachLabel
并计算中值频率类权重。
imageFreq = tbl.PixelCount ./ tbl.ImagePixelCount; classWeights = median(imageFreq)./ imageFreq
classWeights = 11×1
0.3182
0.2082
5.0924
0.1744
0.7103
0.4175
4.5371
1.8386
1.0000
6.6059
⋮
使用a指定类权重pixelClassificationLayer
。
pxLayer = pixelClassificationLayer('Name','labels','Classes',tbl.Name,'ClassWeights',classWeights)
pxLayer = PixelClassificationLayer具有属性: 名称:'标签' 职业:[11×1分类] ClassWeights:[11×1双] OutputSize:'auto' 超参数 LossFunction:'crossentropyex'
pixelClassificationLayer
通过删除当前pixelClassificationLayer
并添加新图层,使用new更新SegNet网络。电流pixelClassificationLayer
名为'pixelLabels'。使用删除它,使用removeLayers
添加新的addLayers
,并使用新层连接到网络的其余部分connectLayers
。
lgraph = removeLayers(lgraph,'pixelLabels'); lgraph = addLayers(lgraph,pxLayer); lgraph = connectLayers(lgraph,'softmax','labels');
选择培训选项
用于训练的优化算法是具有动量的随机梯度下降(SGDM)。使用trainingOptions
指定用于SGDM的超参数。
options = trainingOptions('sgdm',... 'Momentum',0.9,...... 'InitialLearnRate',1e-3,...... 'L2Regularization',0.0005,...... 'MaxEpochs',100,...... ' MiniBatchSize',4,...... 'Shuffle','every-epoch',... 'CheckpointPath',tempdir,...... 'VerboseFrequency',2);
小于4的小批量用于减少训练时的内存使用量。您可以根据系统上的GPU内存量增加或减少此值。
另外,'CheckpointPath'
设置为临时位置。此名称 - 值对可在每个训练时期结束时保存网络检查点。如果由于系统故障或停电而导致培训中断,您可以从保存的检查点恢复培训。确保指定的位置'CheckpointPath'
有足够的空间来存储网络检查点。例如,保存100个SegNet检查点需要大约11 GB的磁盘空间,因为每个检查点都是107 MB。
数据扩充
在训练期间使用数据增加来向网络提供更多示例,因为它有助于提高网络的准确性。这里,随机左/右反射和+/- 10像素的随机X / Y平移用于数据增强。使用imageDataAugmenter
指定这些数据扩充参数。
augmenter = imageDataAugmenter('RandXReflection',true,... 'RandXTranslation',[ - 10 10],'RandYTranslation',[ - 10 10]);
imageDataAugmenter
支持其他几种类型的数据扩充。选择它们需要经验分析,并且是超参数调整的另一个层次。
开始训练
使用以下方法组合训练数据和数据增强选择pixelLabelImageDatastore
。该pixelLabelImageDatastore
读取的训练数据的批次,施加数据增强,并且增强的数据发送给训练算法。
pximds = pixelLabelImageDatastore(imdsTrain,pxdsTrain,... 'DataAugmentation',augmenter);
trainNetwork
如果doTraining
标志为真,则开始训练。否则,加载预训练网络。
注意:培训在具有12 GB GPU内存的NVIDIA™Titan X上进行了验证。如果您的GPU内存较少,则可能会耗尽内存。如果发生这种情况,请尝试MiniBatchSize'
在使用中将'设置为1 trainingOptions
。培训这个网络大约需要5个小时。根据您的GPU硬件,可能需要更长时间。
doTraining = false; 如果做训练 [net,info] = trainNetwork(pximds,lgraph,options); 其他 data = load(pretrainedSegNet); net = data.net; 结束
在一个图像上测试网络
作为快速健全检查,在一个测试图像上运行训练有素的网络。
我=读(imdsTest); C = semanticseg(I,net);
显示结果。
B = labeloverlay(I,C,'Colormap',cmap,'Transparency',0.4); imshow(B) pixelLabelColorbar(cmap,classes);
将结果C
与存储的预期基本事实进行比较pxdsTest
。绿色和洋红色区域突出显示分割结果与预期的基本事实不同的区域。
expectedResult = read(pxdsTest); actual = uint8(C); expected = uint8(expectedResult); imshowpair(实际,预期)
从视觉上看,语义分割结果与道路,天空和建筑等类很好地重叠。然而,行人和汽车等较小的物体并不那么准确。可以使用交叉联合(IoU)度量(也称为Jaccard索引)来度量每类的重叠量。使用此jaccard
功能测量IoU。
iou = jaccard(C,expectedResult); 表(类,IOU)
ans = 11×2表
上课
____________ ________
“天空”0.92659
“建筑”0.7987
“极”0.16978
“道路”0.95177
“路面”0.41877
“树”0.43401
“SignSymbol”0.32509
“围栏”0.492
“汽车”0.068756
“行人”0
“骑自行车的人”0
IoU指标确认了视觉结果。道路,天空和建筑类具有较高的IoU分数,而诸如行人和汽车等类别的分数较低。其他常见的分割度量包括dice
和bfscore
轮廓匹配分数。
评估受过训练的网络
要测量多个测试图像的准确度,请semanticseg
在整个测试集上运行。小块大小为4用于在分割图像时减少内存使用。您可以根据系统上的GPU内存量增加或减少此值。
pxdsResults = semanticseg(imdsTest,net,... 'MiniBatchSize',4,...... 'WriteLocation',tempdir,...... '详细',false);
semanticseg
将测试集的结果作为pixelLabelDatastore
对象返回。每个测试图像的实际像素标签数据imdsTest
在'WriteLocation'
参数指定的位置写入磁盘。使用evaluateSemanticSegmentation
来衡量测试的结果集语义分割指标。
metrics = evaluateSemanticSegmentation(pxdsResults,pxdsTest,'Verbose',false);
evaluateSemanticSegmentation
返回整个数据集,各个类以及每个测试图像的各种度量标准。要查看数据集级别指标,请检查metrics.DataSetMetrics
。
metrics.DataSetMetrics
ans = 1×5表
GlobalAccuracy MeanAccuracy MeanIoU WeightedIoU MeanBFScore
______________ ____________ _______ ___________ ___________
0.88204 0.85097 0.60893 0.79795 0.6098
数据集指标提供了网络性能的高级概述。要查看每个类对整体性能的影响,请使用检查每个类的度量标准metrics.ClassMetrics
。
metrics.ClassMetrics
ans = 11×3表
准确度IoU MeanBFScore
________ _______ ___________
天空0.93493 0.89243 0.88152
建筑物0.79777 0.75263 0.59707
极点0.72635 0.18663 0.52252
道路0.93676 0.90672 0.71043
路面0.90674 0.72865 0.70362
树0.86657 0.73747 0.6642
SignSymbol 0.75591 0.3452 0.434
栅栏0.82807 0.50592 0.5083
汽车0.91187 0.75001 0.64351
行人0.84866 0.35046 0.4555
自行车骑士0.84705 0.54208 0.46818
虽然整体数据集的性能是相当高的,该类指标显示,代表性不足类,例如Pedestrian
,Bicyclist
和Car
不分段,以及类,如Road
,Sky
和Building
。包含更多代表性不足的类的样本的其他数据可能有助于改善结果。
支持功能
function labelIDs = camvidPixelLabelIDs() %返回与每个类对应的标签ID。 %% CamVid数据集有32个类。 按照最初的SegNet培训方法[1]将 它们分为11个等级。%% 11个班级是: %“天空”“建筑物”,“杆”,“道路”,“路面”,“树”,“SignSymbol”, %“围栏”,“汽车”,“行人”和“骑自行车”。 %% CamVid像素标签ID作为RGB颜色值提供。将它们分组为 %11类,并将它们作为M-by-3矩阵的单元阵列返回。在 %原装CamVid类名称列并排RGB值。 注意%下面排除了Other / Void类。 labelIDs = { ... %“天空” [ 128 128 128; ...... %“天空” ] % “建造” [ 000 128 064; ...... %“桥” 128 000 000; ...... %“建筑” 064 192 000; ... %“Wall” 064 000 064; ...... %“隧道” 192 000 128; ...... %“拱门” ] %“极” [ 192 192 128; ... %“Column_Pole” 000 000 064; ... %“TrafficCone” ] %Road [ 128 064 128; ...... %“道路” 128 000 192; ...... %“LaneMkgsDriv” 192 000 064; ...... %“LaneMkgsNonDriv” ] %“路面” [ 000 000 192; ... %“人行道” 064 192 128; ... %“ParkingBlock” 128 128 192; ...... %“RoadShoulder” ] %“树” [ 128 128 000; ...... %“树” 192 192 000; ...... %“VegetationMisc” ] %“SignSymbol” [ 192 128 128; ... %“SignSymbol” 128 128 064; ... %“Misc_Text” 000 064 064; ...... %“TrafficLight” ] %“围栏” [ 064 064 128; ...... %“围栏” ] %“车” [ 064 000 128; ... %“Car” 064 128 192; ... %“SUVPickupTruck” 192 128 192; ... %“Truck_Bus” 192 064 128; ... %“火车” 128 064 064; ... %“OtherMoving” ] % “行人” [ 064 064 000; ...... %“行人” 192 128 064; ...... %“孩子” 064 000 192; ... %“CartLuggagePram” 064 128 064; ...... %“动物” ] %“骑自行车者” [ 000 128 192; ...... %“自行车手” 192 000 192; ...... %“MotorcycleScooter” ] }; 结束 函数 pixelLabelColorbar(cmap,classNames)%将 颜色条 添加到当前轴。colorbar的格式为%以显示带有颜色的类名。 颜色表(GCA,CMAP) %将颜色条添加到当前图形。 c = colorbar('peer',gca); %使用类名作为刻度线。 c.TickLabels = classNames; numClasses = size(cmap,1); %Center刻度标签。 c.Ticks = 1 /(numClasses * 2):1 / numClasses:1; %删除刻度线。 c.TickLength = 0; end function cmap = camvidColorMap() %定义CamVid数据集使用的颜色映射。 cmap = [ 128 128 128 %天空 128 0 0 %建筑物 192 192 192 %极点 128 64 128 %道路 60 40 222 %路面 128 128 0 %树 192 128 128 %SignSymbol 64 64 128 %围栏 64 0 128 %车 64 64 0 %行人 0 128 192 %自行车手 ]。 %在[0 1]之间归一化。 cmap = cmap ./ 255; 结束 函数 imds = resizeCamVidImages(imds,imageFolder) %将图像大小调整为[360 480]。 如果〜存在(imageFolder,'dir') MKDIR(imageFolder) 其他 imds = imageDatastore(imageFolder); 回归 ; %如果图像已经调整 结束,则跳过 复位(IMDS) 而 hasdata(imds) %阅读图像。 [我,信息] =读取(imds); %调整图像大小。 我= imresize(I,[360 480]); % 写入磁盘。 [〜,filename,ext] = fileparts(info.Filename); imwrite(I,[imageFolder filename ext]) 结束 imds = imageDatastore(imageFolder); 结束 函数 pxds = resizeCamVidPixelLabels(pxds,labelFolder) %将像素标签数据调整为[360 480]。 classes = pxds.ClassNames; labelIDs = 1:numel(classes); 如果〜存在(labelFolder,'dir') MKDIR(labelFolder) 其他 pxds = pixelLabelDatastore(labelFolder,classes,labelIDs); 回归 ; %如果图像已经调整 结束,则跳过 复位(pxds) 而 hasdata(pxds) %读取像素数据。 [C,info] = read(pxds); %从分类转换为uint8。 L = uint8(C); %调整数据大小。使用“最近”插值来 保留标签ID。 L = imresize(L,[360 480],'最近'); %将数据写入磁盘。 [〜,filename,ext] = fileparts(info.Filename); imwrite(L,[labelFolder filename ext]) 结束 labelIDs = 1:numel(classes); pxds = pixelLabelDatastore(labelFolder,classes,labelIDs); 结束 函数 [imdsTrain,imdsTest,pxdsTrain,pxdsTest] = partitionCamVidData(imds,pxds) %通过随机选择60%的数据进行训练,对CamVid数据进行分区。在 %其余的用于测试。 %设置初始随机状态,例如再现性。 RNG(0); numFiles = numel(imds.Files); shuffledIndices = randperm(numFiles); %使用60%的图像进行训练。 N =圆形(0.60 * numFiles); trainingIdx = shuffledIndices(1:N); %使用其余的进行测试。 testIdx = shuffledIndices(N + 1:end); %创建用于培训和测试的图像数据存储。 trainingImages = imds.Files(trainingIdx); testImages = imds.Files(testIdx); imdsTrain = imageDatastore(trainingImages); imdsTest = imageDatastore(testImages); %提取类和标签ID信息。 classes = pxds.ClassNames; labelIDs = 1:numel(pxds.ClassNames); %创建用于训练和测试的像素标签数据存储。 trainingLabels = pxds.Files(trainingIdx); testLabels = pxds.Files(testIdx); pxdsTrain = pixelLabelDatastore(trainingLabels,classes,labelIDs); pxdsTest = pixelLabelDatastore(testLabels,classes,labelIDs); 结束
参考
[1] Badrinarayanan,V.,A。Kendall和R. Cipolla。“SegNet:用于图像分割的深度卷积编码器 - 解码器架构。” arXiv preprint arXiv:1511.00561,2015。
[2] Brostow,GJ,J。Fauqueur和R. Cipolla。“视频中的语义对象类:高清地面实况数据库。” 模式识别字母。卷。30,Issue 2,2009,pp 88-97。
关注公众号: MATLAB基于模型的设计 (ID:xaxymaker) ,每天推送MATLAB学习最常见的问题,每天进步一点点,业精于勤荒于嬉。
打开微信扫一扫哦!