基于深度学习的语义分割
分析训练数据的语义分割
为了训练一个语义分割网络,你需要一个图像集合及其相应的像素标记图像集合。像素标记的图像是这样一种图像,其中每个像素值表示该像素的分类标签。
下面的代码加载了一小组图像及其相应的像素标记图像:
dataDir = fullfile(toolboxdir)“愿景”),“visiondata”);imDir = fullfile(dataDir,“建筑”);pxDir = fullfile(dataDir,“buildingPixelLabels”);
加载图像数据imageDatastore
.图像数据存储可以有效地表示大量图像,因为只有在需要时才将图像读入内存。
imds = imageDatastore(imDir);
读取并显示第一个图像。
I = readimage(imds,1);图imshow(我)
加载像素标签图像pixelLabelDatastore
来定义标签id和分类名称之间的映射。在这里使用的数据集中,标签是“天空”、“草地”、“建筑”和“人行道”。这些类的标签id分别为1、2、3、4。
定义类名。
classNames = [“天空”“草”“建筑”“人行道”];
为每个类名定义标签ID。
pixelLabelID = [1 2 3 4];
创建一个pixelLabelDatastore
.
pxds = pixelLabelDatastore(pxDir,classNames,pixelLabelID);
读取第一个像素标签图像。
C = readimage(pxds,1);
输出C
分类矩阵在哪里C (i, j)
是像素的分类标签吗我(I, j)
.
C (5,5)
ans =分类天空
在图像上叠加像素标签,以查看图像的不同部分是如何被标记的。
B = labeloverlay(I,C);图imshow (B)
分类输出格式简化了需要按类名执行的任务。例如,你可以为建筑创建一个二进制掩码:
buildingMask = C ==“建筑”;图imshowpair(I, buildingMask)“蒙太奇”)
创建语义分割网络
创建一个简单的语义分割网络,并学习在许多语义分割网络中发现的常见层。语义分割网络中的一个常见模式需要在卷积层和ReLU层之间对图像进行下采样,然后对输出进行上采样以匹配输入大小。这个操作类似于使用图像金字塔的标准尺度空间分析。然而,在此过程中,网络使用针对您想要分割的特定类集优化的非线性过滤器执行操作。
创建一个图像输入层
语义分割网络从imageInputLayer
,它定义了网络可以处理的最小图像大小。大多数语义分割网络都是完全卷积的,这意味着它们可以处理大于指定输入大小的图像。这里,网络处理64x64 RGB图像时使用的图像大小为[32 32 3]。
inputSize = [32 32 3];imgLayer = imageInputLayer(inputSize)
imgLayer = ImageInputLayer with properties: Name: " InputSize: [32 32 3] Hyperparameters: DataAugmentation: 'none' Normalization: 'zerocenter'
创建下采样网络
从卷积层和ReLU层开始。选择卷积层填充,使卷积层的输出大小与输入大小相同。这使得构建网络变得更容易,因为大多数层之间的输入和输出大小随着网络的进展保持不变。
filterSize = 3;numFilters = 32;conv = convolution2dLayer(filterSize,numFilters,“填充”1);relu = reluLayer();
下采样是使用最大池化层执行的。创建一个最大池化层,通过设置步
参数设置为2。
poolSize = 2;maxPoolDownsample2x = maxPooling2dLayer(poolSize,“步”2);
将卷积层、ReLU层和最大池化层堆叠在一起,创建一个网络,将其输入的采样降低4倍。
downsamplingLayers = [conv relu maxPoolDownsample2x conv relu maxPoolDownsample2x]
downsamplingLayers x1 = 6层阵列层:1“卷积32 3 x3的隆起与步幅[1]和填充1 1 1 1 2”ReLU ReLU 3”麦克斯池2 x2马克斯池步(2 - 2)和填充(0 0 0 0)4”卷积32 3 x3的隆起与步幅[1]和填充[1 1 1 1]5”ReLU ReLU 6”马克斯池2 x2马克斯池步(2 - 2)和填充[0 0 0 0]
创建上采样网络
上采样是使用转置卷积层(通常也称为“反卷积”或“反卷积”层)完成的。当使用转置卷积进行上采样时,它同时进行上采样和滤波。
创建一个转置卷积层,将采样提升2。
filterSize = 4;transsedconvupsample2x = transsedconv2dlayer (4,numFilters,“步”2,“种植”1);
“裁剪”参数设置为1,使输出大小等于输入大小的两倍。
堆叠转置卷积和relu层。这组层的输入被上采样4。
upsamplingLayers =[转置convupsample2x relu转置convupsample2x relu]
upsamplingLayers = 4x1 Layer array with layers: 1”转置卷积32个4x4转置卷积,步幅[2 2],输出裁剪[1 1]2”ReLU ReLU 3”转置卷积32个4x4转置卷积,步幅[2 2],输出裁剪[1 1]4”ReLU ReLU
创建一个像素分类层
最后一组层负责进行像素分类。这些最后的图层处理一个与输入图像具有相同空间尺寸(高度和宽度)的输入。然而,通道(第三维)的数量更大,等于最后一个转置卷积层中的滤波器数量。这第三个维度需要压缩到我们希望分割的类的数量。这可以使用1 × 1的卷积层来完成,其过滤器的数量等于类的数量,例如3。
创建一个卷积层,将输入特征映射的第三维组合到类的数量。
numClasses = 3;con1x1 = convolution2dLayer(1,numClasses);
在这个1 × 1的卷积层之后是softmax和像素分类层。这两层结合起来预测每个图像像素的分类标签。
finalllayers = [conv1x1 softmaxLayer() pixelClassificationLayer()]
finalLayers = 3x1层数组,包含层:1“卷积3个1x1卷积,stride[1 1]和padding[0 0 0 0] 2“Softmax Softmax 3”像素分类层交叉熵损失
堆叠所有图层
将各层叠加完成语义分割网络。
net = [imgLayer downsamplingLayers upsamplingLayers finalllayers]
net = 14x1层数组1”的形象输入32 x32x3图像zerocenter正常化2”卷积32 3 x3的隆起与步幅[1]和填充[1 1 1 1]3”ReLU ReLU 4”马克斯池2 x2马克斯池步(2 - 2)和填充[0 0 0 0]5“卷积32 3 x3的隆起与步幅[1]和填充1 1 1 1 6”ReLU ReLU 7”麦克斯池2 x2马克斯池步(2 - 2)和填充[0 0 0 0]8”转置卷积32 4 x4转置运算与步幅[2 2]和输出裁剪[1]9ReLU ReLU 10”转置卷积32个4x4转置卷积,步幅[2 2]和输出裁剪[1 1]11”ReLU ReLU 12”卷积3个1x1卷积,步幅[1 1]和填充[0 0 0 0]13”Softmax Softmax 14”像素分类层交叉熵损失
这个网络已经准备好进行训练了trainNetwork
来自深度学习工具箱™。
训练一个语义分割网络
加载训练数据。
dataSetDir = fullfile(toolboxdir =“愿景”),“visiondata”,“triangleImages”);imageDir = fullfile(dataSetDir,“trainingImages”);labelDir = fullfile(dataSetDir,“trainingLabels”);
为映像创建一个映像数据存储。
imds = imageDatastore(imagedr);
创建一个pixelLabelDatastore
对于地面真值像素标签。
classNames = [“三角形”,“背景”];labelIDs = [255 0];pxds = pixelLabelDatastore(labelDir,classNames,labelIDs);
可视化训练图像和地面真值像素标签。
I = read(imds);C = read(pxds);I = imresize(I,5);L = imresize(uint8(C bb0),5);imshowpair (L,我“蒙太奇”)
创建一个语义分割网络。该网络采用基于下采样和上采样设计的简单语义分割网络。
numFilters = 64;filterSize = 3;numClasses = 2;layers = [imageInputLayer([32 32 1])卷积2dlayer (filterSize,numFilters,“填充”,1) reluLayer() maxPooling2dLayer(2)“步”(2) convolution2dLayer filterSize numFilters,“填充”,1) reluLayer() transsedconv2dlayer (4,numFilters,“步”2,“种植”1);numClasses convolution2dLayer(1日);softmaxLayer() pixelClassificationLayer()];
设置培训选项。
opts = trainingOptions(“个”,…“InitialLearnRate”1 e - 3,…“MaxEpochs”, 100,…“MiniBatchSize”, 64);
结合图像和像素标签数据存储进行训练。
trainingData = combine(imds,pxds);
训练网络。
net = trainNetwork(trainingData,layers, options);
单CPU训练。初始化输入数据规范化。|========================================================================================| | 时代| |迭代时间| Mini-batch | Mini-batch |基地学习 | | | | ( hh: mm: ss) | | |丧失准确性 | |========================================================================================| | 1 | 1 |就是58.11% | | 1.3458 | 0.0010 | | 17 | 50 | 00:00:12 | 97.30% | 0.0924 | 0.0010 | | 100 | | 00:00:24 | 98.09% | 0.0575 | 0.0010 | | 150 | | 00:00:37 | 98.56% | 0.0424 |0.0010 | 67 | 200 | | 00:00:49 | 98.48% | 0.0435 | 0.0010 | 84 | 250 | | 00:01:02 | 98.66% | 0.0363 | 0.0010 | 100 | 300 | | 00:01:14 | | 0.0310 | 0.0010 98.90% | |========================================================================================| 培训完成:达到最终的迭代。
读取并显示测试图像。
testImage = imread(“triangleTest.jpg”);imshow (testImage)
分割测试图像并显示结果。
C = semanticseg(testImage,net);B = labeloverlay(testimate,C);imshow (B)
评估和检查语义分割的结果
导入测试数据集,运行预训练的语义分割网络,并评估和检查预测结果的语义分割质量度量。
导入数据集
的triangleImages
数据集有100个带有地面真值标签的测试图像。定义数据集的位置。
dataSetDir = fullfile(toolboxdir =“愿景”),“visiondata”,“triangleImages”);
定义测试图像的位置。
testImagesDir = fullfile(dataSetDir,“testImages”);
创建一个imageDatastore
保存测试图像的对象。
imds = imageDatastore(testImagesDir);
定义地面真值标签的位置。
testLabelsDir = fullfile(dataSetDir,“testLabels”);
定义类名及其关联的标签id。标签id是图像文件中用于表示每个类的像素值。
classNames = [“三角形”“背景”];labelIDs = [255 0];
创建一个pixelLabelDatastore
对象持有测试图像的地面真值像素标签。
pxdsTruth = pixelLabelDatastore(testLabelsDir,classNames,labelIDs);
运行语义分割分类器
加载一个在训练图像上训练过的语义分割网络triangleImages
.
净=负荷(“triangleSegmentationNetwork.mat”);Net = net.net;
在测试映像上运行网络。预测的标签被写入临时目录中的磁盘,并作为pixelLabelDatastore
对象。
pxdsResults = semanticseg(imds,net)“WriteLocation”, tempdir);
运行的语义分割网络 ------------------------------------- * 100张图片处理。
评估预测的质量
将预测标签与基础真值标签进行比较。在计算语义分割度量时,将进度打印到命令窗口。
metrics = evaluateSemanticSegmentation(pxdsResults,pxdsTruth);
评估语义分割结果---------------------------------------- *选择的指标:全局精度,类精度,IoU,加权IoU, BF评分。*处理100张图片。*完成……完成了。*数据集指标:GlobalAccuracy MeanAccuracy MeanIoU WeightedIoU MeanBFScore ______________ ____________ _______ ___________ ___________ 0.90624 0.95085 0.61588 0.87529 0.40652
检查类指标
显示数据集中每个类的分类精度、交集/联合(IoU)和边界F-1分数。
指标。ClassMetrics
ans =2×3表精度IoU MeanBFScore ________ _______ ___________三角形1 0.33005 0.028664背景0.9017 0.9017 0.78438
显示混淆矩阵
显示混淆矩阵。
指标。ConfusionMatrix
ans =2×2表三角形背景________ __________三角形4730 0背景9601 88069
将规范化的混淆矩阵可视化为图形窗口中的混淆图表。
cm = confusionchart(metrics.ConfusionMatrix.Variables)…一会,正常化=“row-normalized”);厘米。Title =归一化混淆矩阵(%);
检查图像度量
可视化每个图像的相交与联合(IoU)的直方图。
imageIoU = metrics. imageIoU . meaniou;图直方图(imageIoU)“形象意味着欠条”)
找到具有最低IoU的测试映像。
[minIoU, worstImageIndex] = min(imageIoU);minIoU = minIoU(1);worstImageIndex = worstImageIndex(1)
阅读带有最差借据的测试图像,它的真实标签和预测标签进行比较。
worstTestImage = readimage(imds,worstImageIndex);worstTrueLabels = readimage(pxdsTruth,worstImageIndex);worstPredictedLabels = readimage(pxdsResults,worstImageIndex);
将标签图像转换为可在图形窗口中显示的图像。
worstTrueLabelImage = im2uint8(worstTrueLabels == classNames(1));worstPredictedLabelImage = im2uint8(worstPredictedLabels == classNames(1));
显示最差的测试图像,地面真相,和预测。
worstMontage = cat(4,worstTestImage,worstTrueLabelImage,worstPredictedLabelImage);最糟糕的蒙太奇= imresize(最差蒙太奇,4;“最近的”);图蒙太奇(worstMontage,“大小”,[13]) title([“测试形象vs真相vs预测。借据= 'num2str (minIoU)))
同样,找到IoU最高的测试图像。
[maxIoU, bestImageIndex] = max(imageIoU);maxou = maxou (1);bestImageIndex = bestImageIndex(1);
重复上述步骤,读取、转换和显示具有最佳IoU的测试图像,其中包含其真实值和预测标签。
bestTestImage = readimage(imds,bestImageIndex);bestTrueLabels = readimage(pxdsTruth,bestImageIndex);bestPredictedLabels = readimage(pxdsResults,bestImageIndex);bestTrueLabelImage = im2uint8(bestTrueLabels == classNames(1));bestPredictedLabelImage = im2uint8(bestPredictedLabels == classNames(1));bestMontage = cat(4,bestTestImage,bestTrueLabelImage,bestPredictedLabelImage);最佳蒙太奇= imresize(最佳蒙太奇,4;“最近的”);图蒙太奇(bestMontage,“大小”,[13]) title([“测试形象vs真相vs预测。借据= 'num2str (maxIoU)))
指定要评估的指标
可选地,列出您想要使用的评估指标“指标”
参数。
定义要计算的指标。
evaluationMetrics = [“准确性”“借据”];
计算这些度量triangleImages
测试数据集。
metrics = evaluateSemanticSegmentation(pxdsResults,pxdsTruth,“指标”, evaluationMetrics);
评估语义分割结果 ---------------------------------------- * 选择的指标:类准确性,借据。*处理100张图片。*完成……完成了。*数据集指标:MeanAccuracy MeanIoU ____________ _______ 0.95085 0.61588
显示每个类的选定指标。
指标。ClassMetrics
ans =2×2表精度IoU ________ _______三角形1 0.33005背景0.9017 0.9017
导入像素标记数据集用于语义分割
这个例子向您展示了如何为语义分割网络导入一个像素标记的数据集。
像素标记数据集是用于训练语义分割网络的图像和相应的ground truth像素标签的集合。有许多公共数据集提供带有逐像素标签的带注释的图像。为了说明导入这些类型数据集的步骤,本例使用剑桥大学[1]的CamVid数据集。
CamVid数据集是在驾驶时获得的包含街道视图的图像集合。该数据集为32个语义类提供像素级标签,包括汽车、行人和道路。所示的导入CamVid的步骤可用于导入其他像素标记数据集。
下载CamVid数据集
从以下网址下载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”);imageDir = fullfile(输出文件夹,“图片”);labelDir = fullfile(输出文件夹,“标签”);如果~存在(outputFolder“dir”) disp (“正在下载557 MB CamVid数据集…”);解压缩(imageURL imageDir);解压缩(labelURL labelDir);结束
注:数据下载时间视乎你的网络连接而定。上面使用的命令将阻止MATLAB®,直到下载完成。或者,您可以使用web浏览器先将数据集下载到本地磁盘。要使用从web下载的文件,请更改outputFolder
变量设置为下载文件的位置。
CamVid像素标签
CamVid数据集将像素标签编码为RGB图像,其中每个类由RGB颜色表示。下面是数据集定义的类以及它们的RGB编码。
classNames = […“动物”,…“拱门”,…“自行车”,…“桥”,…“建筑”,…“汽车”,…“CartLuggagePram”,…“子”,…“Column_Pole”,…“篱笆”,…“LaneMkgsDriv”,…“LaneMkgsNonDriv”,…“Misc_Text”,…“MotorcycleScooter”,…“OtherMoving”,…“ParkingBlock”,…“行人”,…“路”,…“RoadShoulder”,…“人行道”,…“SignSymbol”,…“天空”,…“SUVPickupTruck”,…“TrafficCone”,…“TrafficLight”,…“训练”,…“树”,…“Truck_Bus”,…“隧道”,…“VegetationMisc”,…“墙”];
定义标签索引和类名之间的映射,以便类名(k)
对应于labelIDs (k,:)
.
labelIDs = […064 128 064;…%的“动物”192 000 128;…%的“拱门”000 128 192;…%“自行车”000 128 064;…%“桥”128 000 000;…%“建设”064 000 128;…%的“汽车”064 000 192;…%”CartLuggagePram”192 128 064;…%的“孩子”192 192 128;…%”Column_Pole”064 064 128;…%“栅栏”128 000 192;…%”LaneMkgsDriv”192 000 064;…%”LaneMkgsNonDriv”128 128 064;…%”Misc_Text”192 000 192;…%”MotorcycleScooter”128 064 064;…%”OtherMoving”064 192 128;…%”ParkingBlock”064 064 000;…%“行人”128 064 128;…%的“路”128 128 192;…%”RoadShoulder”000 000 192;…%“人行道”192 128 128;…%”SignSymbol”128 128 128;…%的“天空”064 128 192;…%”SUVPickupTruck”000 000 064;…%”TrafficCone”000 064 064;…%”TrafficLight”192 064 128;…%“训练”128 128 000;…%的“树”192 128 192;…%”Truck_Bus”064 000 064;…%的“隧道”192 192 000;…%”VegetationMisc”[64 192 000];%的“墙”
请注意,其他数据集具有不同的编码数据格式。例如,PASCAL VOC[2]数据集使用0到21之间的数字标签id来编码它们的类标签。
可视化一个CamVid图像的像素标签。
标签= imread(fullfile(labelDir,0001 tp_006690_l.png));图imshow(标签)添加颜色条以显示颜色映射的类。N = numel(classNames);ticks = 1/(N*2):1/N:1;colorbar (“TickLabels”cellstr(类名),“滴答”蜱虫,“TickLength”0,“TickLabelInterpreter”,“没有”);colormap (labelIDs. / 255)
加载摄像头数据
一个像素标记的数据集可以使用imageDatastore
和一个pixelLabelDatastore
.
创建一个imageDatastore
加载CamVid图像。
imds = imageDatastore(fullfile(imagedr,701 _stillsraw_full));
创建一个pixelLabelDatastore
加载CamVid像素标签。
pxds = pixelLabelDatastore(labelDir,classNames,labelIDs);
读取第10个图像和相应的像素标签图像。
I = readimage(imds,10);C = readimage(pxds,10);
像素标签图像作为分类数组返回,其中C (i, j)
分类标签是否分配给像素我(I, j)
.在图像的顶部显示像素标签图像。
B = labeloverlay(I,C,“Colormap”, labelIDs. / 255);图imshow (B)添加一个颜色条。N = numel(classNames);ticks = 1/(N*2):1/N:1;colorbar (“TickLabels”cellstr(类名),“滴答”蜱虫,“TickLength”0,“TickLabelInterpreter”,“没有”);colormap (labelIDs. / 255)
未定义或无效标签
像素标记的数据集通常包含“未定义”或“无效”标签。这些用于指定未标记的像素。例如,在CamVid中,标签ID[0 0 0]用于指定“void”类。训练算法和评估算法不期望在任何计算中包含这些标签。
使用时不需要显式地命名"void"类pixelLabelDatastore
.任何未映射到类名的标签ID都会自动标记为“未定义”,并被排除在计算之外。要查看未定义的像素,使用isundefined
创建一个蒙版,然后将其显示在图像的顶部。
undefinedPixels = isundefined(C);B = labeloverlay(I,undefinedPixels);(B)标题(“未定义的像素标签”)
结合类
在使用公共数据集时,您可能需要组合一些类以更好地适应您的应用程序。例如,您可能想要训练一个语义分割网络,将场景分为4类:道路,天空,车辆,行人和背景。要对CamVid数据集执行此操作,请将上面定义的标签id分组以适合新类。首先,定义新的类名。
newClassNames = [“路”,“天空”,“汽车”,“行人”,“背景”];
接下来,使用m × 3矩阵的单元格数组对标签id进行分组。
groupedLabelIDs = {%的道路[128 064 128;…%的“路”128 000 192;…%”LaneMkgsDriv”192 000 064;…%”LaneMkgsNonDriv”000 000 192;…%“人行道”064 192 128;…%”ParkingBlock”128 128 192;…%”RoadShoulder”]%的“天空”[128 128 128;…%的“天空”]%“车辆”[064 000 128;…%的“汽车”064 128 192;…%”SUVPickupTruck”192 128 192;…%”Truck_Bus”192 064 128;…%“训练”000 128 192;…%“自行车”192 000 192;…%”MotorcycleScooter”128 064 064;…%”OtherMoving”]%“行人”[064 064 000;…%“行人”192 128 064;…%的“孩子”064 000 192;…%”CartLuggagePram”064 128 064;…%的“动物”]%的“背景”[128 128 000;…%的“树”192 192 000;…%”VegetationMisc”192 128 128;…%”SignSymbol”128 128 064;…%”Misc_Text”000 064 064;…%”TrafficLight”064 064 128;…%“栅栏”192 192 128;…%”Column_Pole”000 000 064;…%”TrafficCone”000 128 064;…%“桥”128 000 000;…%“建设”064 192 000;…%的“墙”064 000 064;…%的“隧道”192 000 128;…%的“拱门”]};
创建一个pixelLabelDatastore
使用新的类和标签id。
pxds = pixelLabelDatastore(labelDir,newClassNames,groupedLabelIDs);
读取第10个像素标签图像并将其显示在图像的顶部。
C = readimage(pxds,10);cmap = jet(numel(newClassNames));B = labeloverlay(I,C,“Colormap”,提出);图imshow (B)添加颜色条N = numel(newClassNames);ticks = 1/(N*2):1/N:1;colorbar (“TickLabels”cellstr (newClassNames),“滴答”蜱虫,“TickLength”0,“TickLabelInterpreter”,“没有”);colormap城市规划机构(cmap)
的pixelLabelDatastore
有了新的类名,现在可以用来训练4个类的网络,而不必修改原始的CamVid像素标签。
参考文献
[1] Brostow, Gabriel J., Julien Fauqueur和Roberto Cipolla。视频中的语义对象类:一个高清地面真相数据库。模式识别字母30.2(2009): 88-97。
[b] M.埃弗林汉姆,等。“PASCAL可视化对象类挑战2012年的结果。”见http://www。pascal-network。org/challenges/VOC/voc2012/workshop/index。超文本标记语言.卷。5。2012.