基於深度學習的語義分割


此示例顯示如何使用深度學習訓練語義分段網絡。

語義分割網絡對圖像中的每個像素進行分類,從而產生按類別分割的圖像。語義分割的應用包括用於自主駕駛的道路分割和用於醫學診斷的癌細胞分割。有關詳細信息,請參閱語義分段基礎知識(計算機視覺系統工具箱)。

為了說明訓練過程,本例訓練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像素標簽圖像數據。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 resizeCamVidImagesresizeCamVidPixelLabels並支持本示例末尾列出的功能。

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傳輸權重所需的網絡轉換,並添加語義分段所需的附加層。輸出segnetLayersLayerGraph表示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支持其他幾種類型的數據擴充。選擇它們需要經驗分析,並且是超參數調整的另一個層次。

開始訓練

使用以下方法組合訓練數據和數據增強選擇pixelLabelImageDatastorepixelLabelImageDatastore讀取的訓練數據的批次,施加數據增強,並且增強的數據發送給訓練算法。

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分數,而諸如行人和汽車等類別的分數較低。其他常見的分割度量包括dicebfscore輪廓匹配分數。

評估受過訓練的網絡

要測量多個測試圖像的准確度,請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 

雖然整體數據集的性能是相當高的,該類指標顯示,代表性不足類,例如PedestrianBicyclistCar不分段,以及類,如RoadSkyBuilding包含更多代表性不足的類的樣本的其他數據可能有助於改善結果。

支持功能

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學習最常見的問題,每天進步一點點,業精於勤荒於嬉

 打開微信掃一掃哦!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM