Main Content

딥러닝을 사용한 다중분광 영상의 의미론적 분할

이 예제에서는 U-Net을 사용하여 7가지 채널을 갖는 다중분광 영상에 대한 의미론적 분할을 수행하는 방법을 보여줍니다.

의미론적 분할은 영상의 각 픽셀에 클래스 레이블을 지정하는 작업을 수반합니다. 의미론적 분할의 응용 사례 중 하나가 삼림 파괴 추적입니다. 즉 시간의 경과에 따른 산림 피복(forest cover)의 변화를 추적하는 것입니다. 환경 보호 기관은 어떤 지역의 환경 및 생태 건강을 평가하고 정량화하기 위해 삼림 파괴를 추적합니다.

딥러닝 기반의 의미론적 분할을 통해 고해상도 항공 사진에서 식생 피복을 정확히 측정할 수 있습니다. 한 가지 문제는 시각적 특성이 비슷한 클래스를 구별하는 것입니다. 이를테면 녹색 픽셀을 풀, 관목 또는 나무로 분류해야 합니다. 분류의 정확도를 높이기 위해 일부 데이터 세트는 각 픽셀에 대한 추가 정보를 제공해 주는 다중분광 영상을 포함하고 있습니다. 예를 들어, Hamlin Beach State Park 데이터 세트는 이 클래스를 더 확실하게 구별하도록 3개의 근적외선 채널로 컬러 영상을 보완합니다.

이 예제에서는 먼저 사전 훈련된 U-Net을 사용하여 의미론적 분할을 수행한 다음 분할 결과를 사용하여 식생 피복의 규모를 계산하는 방법을 보여줍니다. 그런 다음 원한다면 패치 기반 훈련 방법론을 사용하여 Hamlin Beach State Park 데이터 세트에 대해 U-Net 신경망을 훈련시킬 수 있습니다.

사전 훈련된 U-Net 다운로드하기

dataDir을 훈련된 신경망 및 데이터 세트의 원하는 위치로 지정합니다.

dataDir = fullfile(tempdir,"rit18_data");

사전 훈련된 U-Net 신경망을 다운로드합니다.

trainedNet_url = "https://www.mathworks.com/supportfiles/"+ ...
    "vision/data/trainedMultispectralUnetModel.zip";
downloadTrainedNetwork(trainedNet_url,dataDir);
load(fullfile(dataDir,"trainedMultispectralUnetModel", ...
    "trainedMultispectralUnetModel.mat"));

데이터 세트 다운로드하기

이 예제에서는 신경망 훈련에 고해상도 다중분광 데이터 세트를 사용합니다 [1]. 이 영상 세트는 뉴욕 햄린 비치 주립 공원 위에서 드론으로 촬영한 것입니다. 이 데이터에는 레이블이 지정된 훈련 세트, 검증 세트, 테스트 세트와 18가지 사물 클래스 레이블이 포함되어 있습니다. 데이터 파일의 크기는 3.0GB입니다.

downloadHamlinBeachMSIData 헬퍼 함수를 사용하여 MAT 파일 버전의 데이터 세트를 다운로드합니다. 이 함수는 예제에 지원 파일로 첨부되어 있습니다.

downloadHamlinBeachMSIData(dataDir);

데이터 세트를 불러옵니다.

load(fullfile(dataDir,"rit18_data.mat"));
whos train_data val_data test_data
  Name            Size                         Bytes  Class     Attributes

  test_data       7x12446x7654            1333663576  uint16              
  train_data      7x9393x5642              741934284  uint16              
  val_data        7x8833x6918              855493716  uint16              

다중분광 영상 데이터는 채널 수×너비×높이 배열로 구성되어 있습니다. 그러나 MATLAB®에서는 다중채널 영상이 너비×높이×채널 수 배열로 구성됩니다. 데이터의 형태를 변경하여 채널을 세 번째 차원으로 만들려면 permute 함수를 사용하십시오.

train_data = permute(train_data,[2 3 1]);
val_data = permute(val_data,[2 3 1]);
test_data = permute(test_data,[2 3 1]);

데이터가 올바른 구조인지 확인합니다.

whos train_data val_data test_data
  Name                Size                     Bytes  Class     Attributes

  test_data       12446x7654x7            1333663576  uint16              
  train_data       9393x5642x7             741934284  uint16              
  val_data         8833x6918x7             855493716  uint16              

다중분광 데이터 시각화하기

각 스펙트럼 대역의 중심을 나노미터 단위로 표시합니다.

disp(band_centers)
   490   550   680   720   800   900

이 데이터 세트에서 RGB 색 채널은 각각 3번째, 2번째, 1번째 영상 채널입니다. 훈련 영상, 검증 영상, 테스트 영상의 RGB 성분을 몽타주로 표시합니다. 영상이 화면에 더 밝게 표시되게 하려면 histeq 함수를 사용하여 히스토그램을 평활화하십시오.

rgbTrain = histeq(train_data(:,:,[3 2 1]));
rgbVal = histeq(val_data(:,:,[3 2 1]));
rgbTest = histeq(test_data(:,:,[3 2 1]));

montage({rgbTrain,rgbVal,rgbTest},BorderSize=10,BackgroundColor="white")
title("RGB Component of Training, Validation, and Test Image (Left to Right)")

데이터의 4번째, 5번째, 6번째 채널은 근적외선 대역에 해당합니다. 훈련 영상에 대한 이러한 3개 채널의 히스토그램을 평활화한 다음 채널을 몽타주로 표시합니다. 채널은 각각의 열 신호에 따라 영상의 각기 다른 성분을 강조 표시합니다. 예를 들어, 나무는 나머지 두 적외선 채널보다 4번째 채널에서 더 어둡습니다.

ir4Train = histeq(train_data(:,:,4));
ir5Train = histeq(train_data(:,:,5));
ir6Train = histeq(train_data(:,:,6));

montage({ir4Train,ir5Train,ir6Train},BorderSize=10,BackgroundColor="white")
title("Infrared Channels 4, 5, and 6 (Left to Right) of Training Image ")

데이터의 7번째 채널은 유효한 분할 지역을 나타내는 이진 마스크입니다. 훈련 영상, 검증 영상, 테스트 영상을 위한 마스크를 표시합니다.

maskTrain = train_data(:,:,7);
maskVal = val_data(:,:,7);
maskTest = test_data(:,:,7);

montage({maskTrain,maskVal,maskTest},BorderSize=10,BackgroundColor="white")
title("Mask of Training, Validation, and Test Image (Left to Right)")

실측 레이블 시각화하기

레이블이 지정된 영상에는 분할을 위한 실측 데이터가 들어 있으며, 각 픽셀에 18개 클래스 중 하나가 지정되어 있습니다. 클래스 및 그 ID의 목록을 얻습니다.

disp(classes)
0. Other Class/Image Border      
1. Road Markings                 
2. Tree                          
3. Building                      
4. Vehicle (Car, Truck, or Bus)  
5. Person                        
6. Lifeguard Chair               
7. Picnic Table                  
8. Black Wood Panel              
9. White Wood Panel              
10. Orange Landing Pad           
11. Water Buoy                   
12. Rocks                        
13. Other Vegetation             
14. Grass                        
15. Sand                         
16. Water (Lake)                 
17. Water (Pond)                 
18. Asphalt (Parking Lot/Walkway)

이 예제의 목적은 영상을 식생과 비식생의 두 가지 클래스로 분할하는 것입니다. 목표 클래스 이름을 정의합니다.

classNames = ["NotVegetation" "Vegetation"];

18개의 원래 클래스를 훈련 데이터와 검증 데이터에 대한 두 개의 목표 클래스로 그룹화합니다. "Vegetation"은 클래스 ID 2, 13, 14를 갖는 원래 클래스 "Tree", "Other Vegetation", "Grass"를 조합한 것입니다. 클래스 ID가 0인 원래 클래스 "Other Class/Image Border"는 배경 클래스에 속합니다. 그 외 모든 원래 클래스는 목표 레이블 "NotVegetation"에 속합니다.

vegetationClassIDs = [2 13 14];
nonvegetationClassIDs = setdiff(1:length(classes),vegetationClassIDs);

labelsTrain = zeros(size(train_labels),"uint8");
labelsTrain(ismember(train_labels,nonvegetationClassIDs)) = 1;
labelsTrain(ismember(train_labels,vegetationClassIDs)) = 2;

labelsVal = zeros(size(val_labels),"uint8");
labelsVal(ismember(val_labels,nonvegetationClassIDs)) = 1;
labelsVal(ismember(val_labels,vegetationClassIDs)) = 2;

실측 검증 레이블을 PNG 파일로 저장합니다. 이 예제에서는 이 파일을 사용하여 정확도 메트릭을 계산합니다.

imwrite(labelsVal,"gtruth.png");

히스토그램을 평활화한 RGB 훈련 영상 위에 레이블을 겹쳐 놓습니다. 영상에 컬러바를 추가합니다.

cmap = [1 0 1;0 1 0];
B = labeloverlay(rgbTrain,labelsTrain,Transparency=0.8,Colormap=cmap);
imshow(B,cmap)
title("Training Labels")
numClasses = numel(classNames);
ticks = 1/(numClasses*2):1/numClasses:1;
colorbar(TickLabels=cellstr(classNames),Ticks=ticks,TickLength=0,TickLabelInterpreter="none");

테스트 영상에 의미론적 분할 수행하기

영상의 크기로 인해 전체 영상을 한 번에 분할할 수 없습니다. 대신 블록 형식 영상 방식을 사용하여 영상을 분할합니다. 이 방식은 한 번에 하나의 데이터 블록을 불러와 처리하므로 매우 큰 파일로 스케일링할 수 있습니다.

blockedImage 함수를 사용하여 테스트 데이터의 스펙트럼 채널을 6개 포함하는 블록 형식 영상을 만듭니다.

patchSize = [1024 1024];
bimTest = blockedImage(test_data(:,:,1:6),BlockSize=patchSize);

semanticseg 함수를 사용하여 데이터 블록을 분할합니다. apply 함수를 사용하여 블록 형식 영상의 모든 블록에 대해 sematicseg 함수를 호출합니다.

bimSeg = apply(bimTest,@(bs)semanticseg(bs.Data,net,Outputtype="uint8"),...
    PadPartialBlocks=true,PadMethod=0);

gather 함수를 사용하여, 분할된 모든 블록을 작업 공간에 단일 영상으로 조합합니다.

segmentedImage = gather(bimSeg);

분할에서 유효한 부분만 추출하려면 분할된 영상에 검증 데이터의 마스크 채널을 곱하십시오.

segmentedImage = segmentedImage .* uint8(maskTest~=0);
imshow(segmentedImage,[])
title("Segmented Image")

의미론적 분할의 출력에 잡음이 있습니다. 잡음 및 고립된 픽셀을 제거하려면 영상 후처리를 수행하십시오. medfilt2 함수를 사용하여 분할에 섞여 있는 점잡음을 제거하십시오. 잡음을 제거한 분할된 영상을 표시합니다.

segmentedImage = medfilt2(segmentedImage,[7 7]);
imshow(segmentedImage,[]);
title("Segmented Image with Noise Removed")

히스토그램을 평활화한 RGB 검증 영상 위에 분할된 영상을 겹쳐 놓습니다.

B = labeloverlay(rgbTest,segmentedImage,Transparency=0.8,Colormap=cmap);
imshow(B,cmap)
title("Labeled Segmented Image")
colorbar(TickLabels=cellstr(classNames),Ticks=ticks,TickLength=0,TickLabelInterpreter="none");

식생 피복의 규모 계산하기

의미론적 분할 결과를 활용하여 관련 생태학적 질문에 답할 수 있습니다. 예를 들어 '육지 영역의 몇 퍼센트가 식생으로 덮여 있습니까?'라는 질문에 답해야 한다고 가정합니다. 이 질문에 답하려면 분할된 테스트 영상에서 식생으로 레이블이 지정된 픽셀의 수를 확인해야 합니다. 또한 분할된 영상에 있는 0이 아닌 픽셀의 수를 세어 ROI에 있는 총 픽셀 수를 구합니다.

vegetationPixels = ismember(segmentedImage(:),vegetationClassIDs);
numVegetationPixels = sum(vegetationPixels(:));
numROIPixels = nnz(segmentedImage);

식생 픽셀의 수를 ROI에 있는 픽셀 수로 나누어 식생 피복의 비율을 계산합니다.

percentVegetationCover = (numVegetationPixels/numROIPixels)*100;
disp("The percentage of vegetation cover is "+percentVegetationCover+"%");
The percentage of vegetation cover is 65.8067%

이 예제의 나머지 부분에서는 Hamlin Beach 데이터 세트에서 U-Net을 훈련시키는 방법을 보여줍니다.

훈련용 블록 형식 영상 데이터저장소 만들기

블록 형식 영상 데이터저장소를 사용하여 훈련 데이터를 신경망에 입력합니다. 이 데이터저장소는 실측 영상이 있는 영상 데이터저장소와 픽셀 레이블 데이터가 있는 픽셀 레이블 데이터저장소로부터 대응하는 여러 패치를 추출합니다.

훈련 영상, 훈련 레이블 및 마스크를 블록 형식 영상으로 읽어 들입니다.

inputTileSize = [256 256];
bim = blockedImage(train_data(:,:,1:6),BlockSize=inputTileSize);
bLabels = blockedImage(labelsTrain,BlockSize=inputTileSize);
bmask = blockedImage(maskTrain,BlockSize=inputTileSize);

마스크와 겹치는 영상 데이터 블록을 선택합니다.

overlapPct = 0.185;
blockOffsets = round(inputTileSize.*overlapPct);
bls = selectBlockLocations(bLabels, ...
    BlockSize=inputTileSize,BlockOffsets=blockOffsets, ...
    Masks=bmask,InclusionThreshold=0.95);

레이블을 one-hot 형식으로 인코딩합니다.

labelsTrain1hot = onehotencode(labelsTrain,3,ClassNames=1:2);
labelsTrain1hot(isnan(labelsTrain1hot)) = 0;
bLabels = blockedImage(labelsTrain1hot,BlockSize=inputTileSize);

blockedImageDatastore 함수를 사용하여 데이터를 블록 형식 영상 데이터저장소에 씁니다.

bimds = blockedImageDatastore(bim,BlockLocationSet=bls,PadMethod=0);
bimdsLabels = blockedImageDatastore(bLabels,BlockLocationSet=bls,PadMethod=0);

두 개의 블록 형식 영상 데이터저장소에서 CombinedDatastore를 만듭니다.

dsTrain = combine(bimds,bimdsLabels);

블록 형식 영상 데이터저장소 dsTrain은 Epoch가 반복될 때마다 신경망으로 데이터의 미니 배치를 제공합니다. 데이터를 살펴보려면 데이터저장소를 미리 보십시오.

preview(dsTrain)
ans=1×2 cell array
    {256×256×6 uint16}    {256×256×2 double}

U-Net 신경망 계층 만들기

이 예제에서는 변형된 U-Net 신경망을 사용합니다. U-Net에서는 초기 일련의 컨벌루션 계층의 사이에 일정 간격으로 최댓값 풀링 계층이 배치되어 입력 영상의 해상도를 연이어 낮춥니다. 그다음에 오는 일련의 컨벌루션 계층 사이에는 업샘플링 연산자가 여기저기 배치되어 입력 영상의 해상도를 연속적으로 높입니다 [2]. U-Net이라는 이름은 이 신경망을 문자 U와 같은 대칭 형태로 그릴 수 있다는 사실에서 유래했습니다.

U-Net의 하이퍼파라미터를 지정합니다. 입력 깊이는 초분광 채널 개수인 6입니다.

inputDepth = 6;
encoderDepth = 4;
convFilterSize = 3;
upconvFilterSize = 2;

blockedNetwork 함수를 사용하여, 반복되는 계층 블록으로 구성된 인코더 모듈을 만듭니다. encoderBlockMultispectralUNet 헬퍼 함수는 인코더에 대한 계층 블록을 만들고 예제에 지원 파일로 첨부되어 있습니다.

encoderBlockFcn = @(block) ...
    encoderBlockMultispectralUNet(block,inputDepth,convFilterSize,encoderDepth);
encoder = blockedNetwork(encoderBlockFcn,encoderDepth,NamePrefix="encoder_");

blockedNetwork 함수를 사용하여, 반복되는 계층 블록으로 구성된 디코더 모듈을 만듭니다. decoderBlockMultispectralUNet 헬퍼 함수는 디코더에 대한 계층 블록을 만들고 예제에 지원 파일로 첨부되어 있습니다.

decoderBlockFcn = @(block) ...
    decoderBlockMultispectralUNet(block,convFilterSize,upconvFilterSize);
decoder = blockedNetwork(decoderBlockFcn,encoderDepth,NamePrefix="decoder_");

예제에 지원 파일로 첨부되어 있는 bridgeBlockMultispectralUNet 헬퍼 함수를 사용하여 브리지 계층을 정의합니다.

bridge = bridgeBlockMultispectralUNet(convFilterSize,encoderDepth);

출력 계층을 정의합니다.

final = [
    convolution2dLayer(1,numClasses,Padding="same")
    softmaxLayer];

encoderDecoderNetwork 함수를 사용하여 인코더 모듈, 브리지, 디코더 모듈, 마지막 계층을 연결합니다. 건너뛰기 연결을 추가합니다.

skipConnectionNames = [
    "encoder_Block1Layer5","decoder_Block4Layer2";
    "encoder_Block2Layer5","decoder_Block3Layer2";
    "encoder_Block3Layer5","decoder_Block2Layer2";
    "encoder_Block4Layer5","decoder_Block1Layer2"];
unet = encoderDecoderNetwork([inputTileSize inputDepth],encoder,decoder, ...
    OutputChannels=numClasses, ...
    SkipConnectionNames=skipConnectionNames, ...
    SkipConnections="concatenate", ...
    LatentNetwork=bridge, ...
    FinalNetwork=final);

훈련 옵션 선택하기

SGDM(Stochastic Gradient Descent with Momentum: 모멘텀을 사용한 확률적 경사하강법)을 사용하여 신경망을 훈련시킵니다. trainingOptions (Deep Learning Toolbox) 함수를 사용하여 SGDM의 하이퍼파라미터 설정을 지정합니다. 기울기 제한을 활성화하려면 GradientThreshold 이름-값 인수를 0.05로 지정하고 GradientThresholdMethod가 기울기의 L2-노름을 사용하도록 지정합니다.

maxEpochs = 150;
minibatchSize = 16;

options = trainingOptions("sgdm", ...
    InitialLearnRate=0.05, ...
    Momentum=0.9, ...
    L2Regularization=0.001, ...
    MaxEpochs=maxEpochs, ...
    MiniBatchSize=minibatchSize, ...
    LearnRateSchedule="piecewise", ...    
    Shuffle="every-epoch", ...
    GradientThresholdMethod="l2norm", ...
    GradientThreshold=0.05, ...
    Plots="training-progress", ...
    VerboseFrequency=20);

신경망 훈련시키기

신경망을 훈련시키려면 다음 코드에서 doTraining 변수를 true로 설정하십시오. trainnet (Deep Learning Toolbox) 함수를 사용하여 모델을 훈련시킵니다. 마스크 처리되지 않은 픽셀에 대해서만 교차 엔트로피 손실을 계산하는 사용자 지정 손실 함수 modelLoss를 지정합니다. 이 사용자 지정 손실 함수는 예제의 끝부분에 정의되어 있습니다.

사용 가능한 GPU가 있으면 GPU에서 훈련시킵니다. GPU를 사용하려면 Parallel Computing Toolbox™와 CUDA® 지원 NVIDIA® GPU가 필요합니다. 자세한 내용은 GPU 연산 요구 사항 (Parallel Computing Toolbox) 항목을 참조하십시오.

doTraining = false; 
if doTraining
    net = trainnet(dsTrain,unet,@modelLoss,options);
    modelDateTime = string(datetime("now",Format="yyyy-MM-dd-HH-mm-ss"));
    save(fullfile(dataDir,"multispectralUnet-"+modelDateTime+".mat"),"net");
end

분할 정확도 평가하기

검증 데이터를 분할합니다.

blockedImage 함수를 사용하여 검증 데이터의 스펙트럼 채널을 6개 포함하는 블록 형식 영상을 만듭니다.

bimVal = blockedImage(val_data(:,:,1:6),BlockSize=patchSize);

semanticseg 함수를 사용하여 데이터 블록을 분할합니다. apply 함수를 사용하여 블록 형식 영상의 모든 블록에 대해 sematicseg 함수를 호출합니다.

bimSeg = apply(bimVal,@(bs)semanticseg(bs.Data,net,Outputtype="uint8"),...
    PadPartialBlocks=true,PadMethod=0);

gather 함수를 사용하여, 분할된 모든 블록을 작업 공간에 단일 영상으로 조합합니다.

segmentedImage = gather(bimSeg);

분할된 영상을 PNG 파일로 저장합니다.

imwrite(segmentedImage,"results.png");

pixelLabelDatastore 함수를 사용하여 분할 결과와 실측 레이블을 불러옵니다.

pixelLabelIDs = [1 2];
pxdsResults = pixelLabelDatastore("results.png",classNames,pixelLabelIDs);
pxdsTruth = pixelLabelDatastore("gtruth.png",classNames,pixelLabelIDs);

evaluateSemanticSegmentation 함수를 사용하여 의미론적 분할의 정확도를 측정합니다. 전역 정확도 점수에 따르면, 픽셀의 96% 이상이 올바르게 분류되었습니다.

ssm = evaluateSemanticSegmentation(pxdsResults,pxdsTruth);
Evaluating semantic segmentation results
----------------------------------------
* Selected metrics: global accuracy, class accuracy, IoU, weighted IoU, BF score.
* Processed 1 images.
* Finalizing... Done.
* Data set metrics:

    GlobalAccuracy    MeanAccuracy    MeanIoU    WeightedIoU    MeanBFScore
    ______________    ____________    _______    ___________    ___________

       0.96875          0.96762       0.93914      0.93931        0.79113  

헬퍼 함수

modelLoss 함수는 영상에서 마스크 처리되지 않은 모든 픽셀에 대한 교차 엔트로피 손실을 계산합니다.

function loss = modelLoss(y,targets)
    mask = ~isnan(targets);
    targets(isnan(targets)) = 0;
    loss = crossentropy(y,targets,Mask=mask);
end

참고 문헌

[1] Kemker, R., C. Salvaggio, C. Kanan. "High-Resolution Multispectral Dataset for Semantic Segmentation." CoRR, abs/1703.01918. 2017.

[2] Ronneberger, O., P. Fischer, T. Brox. "U-Net: Convolutional Networks for Biomedical Image Segmentation." CoRR, abs/1505.04597. 2015.

[3] Kemker, Ronald, Carl Salvaggio, and Christopher Kanan. "Algorithms for Semantic Segmentation of Multispectral Remote Sensing Imagery Using Deep Learning." ISPRS Journal of Photogrammetry and Remote Sensing, Deep Learning RS Data, 145 (November 1, 2018): 60-77. https://doi.org/10.1016/j.isprsjprs.2018.04.014.

참고 항목

| | (Deep Learning Toolbox) | (Deep Learning Toolbox) | | | | | | |

관련 항목

외부 웹사이트