Main Content

このページの翻訳は最新ではありません。ここをクリックして、英語の最新版を参照してください。

セマンティック セグメンテーション用の完全畳み込みネットワークの学習と展開

この例では、GPU Coder ™ を使用した NVIDIA® GPU での完全畳み込みセマンティック セグメンテーション ネットワークの学習および展開について説明します。

セマンティック セグメンテーション ネットワークはイメージ内のすべてのピクセルを分類して、クラスごとにセグメント化されたイメージを作成します。セマンティック セグメンテーションの応用例としては、自動運転のための道路セグメンテーションや医療診断のための癌細胞セグメンテーションなどがあります。詳細については、深層学習を使用したセマンティック セグメンテーション入門 (Computer Vision Toolbox)を参照してください。

学習手順を示すために、この例では、セマンティック イメージ セグメンテーション用に設計された 1 つのタイプの畳み込みニューラル ネットワーク (CNN) である FCN-8s [1] に学習させます。他のタイプのセマンティック セグメンテーション ネットワークには、SegNet、U-Net などの完全畳み込みネットワークがあります。この学習手順は、これらのネットワークにも適用することができます。

この例では、学習用に Cambridge University の CamVid データセット [2] を使用します。このデータセットは、運転中に得られた路上レベルでのビューが含まれるイメージ コレクションです。データセットは、車、歩行者、道路を含む 32 個のセマンティック クラスについてピクセルレベルのラベルを提供します。

サードパーティの必要条件

必須

  • CUDA® 対応 NVIDIA GPU および互換性のあるドライバー。

オプション

  • NVIDIA CUDA ツールキット。

  • NVIDIA cuDNN ライブラリ。

  • コンパイラおよびライブラリの環境変数。サポートされているコンパイラおよびライブラリのバージョンの詳細は、サードパーティ ハードウェアを参照してください。環境変数の設定は、前提条件となる製品の設定を参照してください。

GPU 環境の検証

関数coder.checkGpuInstallを使用して、この例を実行するのに必要なコンパイラおよびライブラリが正しく設定されていることを検証します。

envCfg = coder.gpuEnvConfig('host');
envCfg.DeepLibTarget = 'cudnn';
envCfg.DeepCodegen = 1;
envCfg.Quiet = 1;
coder.checkGpuInstall(envCfg);

設定

この例では、VGG-16 ネットワークから初期化された重みを使用して完全畳み込みセマンティック セグメンテーション ネットワークを作成します。関数 vgg16 は、Deep Learning Toolbox Model for VGG-16 Network サポート パッケージの有無をチェックし、事前学習済みの VGG-16 モデルを返します。

vgg16();

事前学習済みのバージョンの FCN をダウンロードします。この事前学習済みのモデルを使用することで、学習の完了を待つことなく例全体を実行できます。doTraining フラグは、この例において、学習済みネットワークの例または事前学習済みの FCN ネットワークをコード生成に使用するかどうかを制御します。

doTraining = false;
if ~doTraining
    pretrainedURL = 'https://www.mathworks.com/supportfiles/gpucoder/cnn_models/fcn/FCN8sCamVid.mat';
    disp('Downloading pretrained FCN (448 MB)...');
    websave('FCN8sCamVid.mat',pretrainedURL);
end
Downloading pretrained FCN (448 MB)...

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(pwd,'CamVid');

if ~exist(outputFolder, 'dir')
   
    mkdir(outputFolder)
    labelsZip = fullfile(outputFolder,'labels.zip');
    imagesZip = fullfile(outputFolder,'images.zip');   
    
    disp('Downloading 16 MB CamVid dataset labels...'); 
    websave(labelsZip, labelURL);
    unzip(labelsZip, fullfile(outputFolder,'labels'));
    
    disp('Downloading 557 MB CamVid dataset images...');  
    websave(imagesZip, imageURL);       
    unzip(imagesZip, fullfile(outputFolder,'images'));    
end

データのダウンロード時間はお使いのインターネット接続によって異なります。この例の実行は、ダウンロード操作が完了するまで進行しません。または、Web ブラウザーを使用して、このデータセットをローカル ディスクにまずダウンロードします。次に、変数 outputFolder を使用してダウンロード済みのファイルの場所を指します。

CamVid イメージの読み込み

imageDatastore を使用して CamVid イメージを読み込みます。imageDatastore によって、大規模なイメージ コレクションを効率的にディスクに読み込むことができます。

imgDir = fullfile(outputFolder,'images','701_StillsRaw_full');
imds = imageDatastore(imgDir);

イメージのうちの 1 つを表示します。

I = readimage(imds,25);
I = histeq(I);
imshow(I)

CamVid のピクセル ラベル付きイメージの読み込み

pixelLabelDatastore (Computer Vision Toolbox) を使用して、CamVid のピクセル ラベル イメージ データを読み込みます。pixelLabelDatastore は、ピクセル ラベル データとラベル ID をクラス名のマッピングにカプセル化します。

SegNet ペーパー [3] で説明されている学習方法に従って、CamVid の元の 32 個のクラスを 11 個のクラスにグループ化します。次のクラスを指定します。

classes = [
    "Sky"
    "Building"
    "Pole"
    "Road"
    "Pavement"
    "Tree"
    "SignSymbol"
    "Fence"
    "Car"
    "Pedestrian"
    "Bicyclist"
    ];

32 個のクラスを 11 個のクラスに減らすには、元のデータセットの複数のクラスをグループとしてまとめます。たとえば、"Car"、"SUVPickupTruck"、"Truck_Bus"、"Train"、および "OtherMoving" を組み合わせたものを "Car" とします。サポート関数 camvidPixelLabelIDs を使用することで、グループ化されたラベル ID が返されます。

labelIDs = camvidPixelLabelIDs();

クラスおよびラベル ID を使用して、pixelLabelDatastore を作成します。

labelDir = fullfile(outputFolder,'labels');
pxds = pixelLabelDatastore(labelDir,classes,labelIDs);

イメージの上に重ね合わせることで、ピクセル ラベル付きイメージのうちの 1 つを読み取って表示します。

C = readimage(pxds,25);
cmap = camvidColorMap;
B = labeloverlay(I,C,'ColorMap',cmap);
imshow(B)
pixelLabelColorbar(cmap,classes);

色の重ね合わせが存在しない領域にはピクセル ラベルはなく、学習中は使用されません。

データセット統計の解析

CamVid データセット内のクラス ラベルの分布を表示するには、countEachLabel (Computer Vision Toolbox) を使用します。この関数は、クラス ラベル別にピクセルの数をカウントします。

tbl = countEachLabel(pxds)
tbl=11×3 table
         Name         PixelCount    ImagePixelCount
    ______________    __________    _______________

    {'Sky'       }    7.6801e+07      4.8315e+08   
    {'Building'  }    1.1737e+08      4.8315e+08   
    {'Pole'      }    4.7987e+06      4.8315e+08   
    {'Road'      }    1.4054e+08      4.8453e+08   
    {'Pavement'  }    3.3614e+07      4.7209e+08   
    {'Tree'      }    5.4259e+07       4.479e+08   
    {'SignSymbol'}    5.2242e+06      4.6863e+08   
    {'Fence'     }    6.9211e+06       2.516e+08   
    {'Car'       }    2.4437e+07      4.8315e+08   
    {'Pedestrian'}    3.4029e+06      4.4444e+08   
    {'Bicyclist' }    2.5912e+06      2.6196e+08   

ピクセル数をクラス別に可視化します。

frequency = tbl.PixelCount/sum(tbl.PixelCount);

bar(1:numel(classes),frequency)
xticks(1:numel(classes)) 
xticklabels(tbl.Name)
xtickangle(45)
ylabel('Frequency')

観測値の数がすべてのクラスで等しいことが理想的です。CamVid 内のクラスは不均衡です。これは、路上シーンの自動車データセットに共通する問題です。こうしたシーンには、歩行者や自転車運転者のピクセルよりも多くの空、建物、および道路のピクセルが含まれます。これは、空、建物、および道路がイメージ内でより広い領域を占めているためです。学習では上位クラスを優先してバイアスがかけられるため、正しく処理されていない場合は、こうした不均衡が学習プロセスに悪影響を及ぼす可能性があります。この例の後半では、クラスの重み付けを使用してこの問題に対処します。

CamVid データのリサイズ

CamVid データセット内のイメージは 720 × 960 です。学習時間を短縮し、メモリ使用量を削減するには、サポート関数 resizeCamVidImages および resizeCamVidPixelLabels を使用して、イメージおよびピクセル ラベル イメージを 360 × 480 にリサイズします。

imageFolder = fullfile(outputFolder,'imagesResized',filesep);
imds = resizeCamVidImages(imds,imageFolder);

labelFolder = fullfile(outputFolder,'labelsResized',filesep);
pxds = resizeCamVidPixelLabels(pxds,labelFolder);

学習セットとテスト セットの準備

SegNet は、データセットのイメージの 60% を使用して学習されます。残りのイメージはテスト用に使用されます。次のコードでは、イメージとピクセル ラベル データを学習セットとテスト セットに無作為に分割します。

[imdsTrain,imdsTest,pxdsTrain,pxdsTest] = partitionCamVidData(imds,pxds);

60 対 40 に分割すると、トレーニング イメージとテスト イメージの数が次のようになります。

numTrainingImages = numel(imdsTrain.Files)
numTrainingImages = 421
numTestingImages = numel(imdsTest.Files)
numTestingImages = 280

ネットワークの作成

fcnLayers (Computer Vision Toolbox) を使用して、VGG-16 の重みを使用して初期化される完全な畳み込みネットワーク層を作成します。関数 fcnLayers はネットワーク変換を実行して VGG-16 からの重みを転送し、セマンティック セグメンテーションに必要な層を新たに追加します。関数 fcnLayers の出力は FCN を表す LayerGraph オブジェクトです。LayerGraph オブジェクトは、ネットワーク層および層間の接続をカプセル化します。

imageSize = [360 480];
numClasses = numel(classes);
lgraph = fcnLayers(imageSize,numClasses);

イメージ サイズは、データセット内のイメージのサイズに基づいて選択されます。クラスの数は CamVid 内のクラスに基づいて選択されます。

クラスの重み付けを使用したクラスのバランス調整

CamVid 内のクラスはバランスがとれていません。学習を改善するために、あらかじめ関数countEachLabel (Computer Vision Toolbox)によって計算したピクセル ラベルのカウントを使用して、中央頻度クラスの重みを計算できます [3]。

imageFreq = tbl.PixelCount ./ tbl.ImagePixelCount;
classWeights = median(imageFreq) ./ imageFreq;

pixelClassificationLayer (Computer Vision Toolbox) を使用してクラスの重みを指定します。

pxLayer = pixelClassificationLayer('Name','labels','Classes',tbl.Name,'ClassWeights',classWeights)
pxLayer = 
  PixelClassificationLayer with properties:

            Name: 'labels'
         Classes: [11×1 categorical]
    ClassWeights: [11×1 double]
      OutputSize: 'auto'

   Hyperparameters
    LossFunction: 'crossentropyex'

現在の pixelClassificationLayer を削除して新しい層を追加することで、新しい pixelClassificationLayer をもつ SegNet ネットワークを更新します。現在の pixelClassificationLayer には 'pixelLabels' という名前が付けられています。関数removeLayers (Deep Learning Toolbox)を使用してこれを削除し、関数addLayers (Deep Learning Toolbox)を使用して新しい層を追加し、関数connectLayers (Deep Learning Toolbox)を使用して新しい層を残りのネットワークに接続します。

lgraph = removeLayers(lgraph,'pixelLabels');
lgraph = addLayers(lgraph, pxLayer);
lgraph = connectLayers(lgraph,'softmax','labels');

学習オプションの選択

学習の最適化アルゴリズムは Adam です。これは "適応モーメント推定" (adaptive moment estimation) に由来します。関数trainingOptions (Deep Learning Toolbox)を使用して、Adam に使用されるハイパーパラメーターを指定します。

options = trainingOptions('adam', ...
    'InitialLearnRate',1e-3, ...
    'MaxEpochs',100, ...  
    'MiniBatchSize',4, ...
    'Shuffle','every-epoch', ...
    'CheckpointPath', tempdir, ...
    'VerboseFrequency',2);

'MiniBatchSize' 4 により学習中のメモリ使用量を削減します。この値は、使用しているシステムの GPU メモリの量に応じて増減させることができます。

'CheckpointPath' は一時的な場所に設定されています。この名前と値のペアを設定すると、各学習エポックの終わりにネットワーク チェックポイントを保存できます。システム障害や停電で学習が中断された場合に、保存したチェックポイントから学習を再開できます。'CheckpointPath' で指定された場所に、ネットワーク チェックポイントを保存するのに十分なスペースがあることを確認します。

データ拡張

データ拡張はネットワークの精度を高めるのに役立つため、ネットワークに対してより多くの例を提供します。ここでは、データ拡張に対して +/- 10 ピクセルのランダムな左/右反射とランダムな X/Y 平行移動が使用されます。これらのデータ拡張パラメーターを指定するには、関数imageDataAugmenter (Deep Learning Toolbox)を使用します。

augmenter = imageDataAugmenter('RandXReflection',true,...
    'RandXTranslation',[-10 10],'RandYTranslation',[-10 10]);

関数 The imageDataAugmenter は、この他にいくつかのタイプのデータ拡張をサポートします。それらの中から選択することは、経験的解析が必要であり、別のレベルのハイパーパラメーター調整です。

学習の開始

関数pixelLabelImageDatastore (Computer Vision Toolbox)を使用して学習データとデータ拡張の選択を組み合わせます。関数 pixelLabelImageDatastore は学習データのバッチを読み取り、データ拡張を適用し、拡張されたデータを学習アルゴリズムに送信します。

pximds = pixelLabelImageDatastore(imdsTrain,pxdsTrain, ...
    'DataAugmentation',augmenter);

doTraining フラグが true の場合、関数trainNetwork (Deep Learning Toolbox)を使用して学習を開始します。

この学習は、12 GB の GPU メモリ搭載の NVIDIA™ Titan Xp で検証済みです。GPU のメモリがこれより少ない場合、メモリ不足が発生する可能性があります。システムに十分なメモリがない場合は、trainingOptionsMiniBatchSize プロパティを 1 に下げてみます。このネットワークの学習は、お使いの GPU ハードウェアによっては約 5 時間またはそれ以上かかります。

if doTraining    
    [net, info] = trainNetwork(pximds,lgraph,options);
    save('FCN8sCamVid.mat','net');
end

DAG ネットワーク オブジェクトを FCN8sCamVid.mat という名前の MAT ファイルとして保存します。この MAT ファイルはコード生成時に使用されます。

MEX コード生成の実行

関数 fcn_predict.m は、イメージ入力を受け取り、FCN8sCamVid.mat ファイルに保存されている深層学習ネットワークを使用して、イメージについて予測を実行します。この関数は、ネットワーク オブジェクトを FCN8sCamVid.mat から永続変数 mynet に読み込み、以降の予測呼び出しではその永続オブジェクトを再利用します。

type('fcn_predict.m')
function out = fcn_predict(in)
%#codegen
% Copyright 2018-2019 The MathWorks, Inc.

persistent mynet;

if isempty(mynet)
    mynet = coder.loadDeepLearningNetwork('FCN8sCamVid.mat');
end

% pass in input
out = predict(mynet,in);

ターゲット言語を C++ に設定して MEX ターゲットの GPU 構成オブジェクトを生成します。関数 coder.DeepLearningConfig を使用して cuDNN 深層学習構成オブジェクトを作成し、それを GPU コード構成オブジェクトの DeepLearningConfig プロパティに割り当てます。サイズが [360, 480, 3] の入力を指定して codegen コマンドを実行します。このサイズは FCN の入力層に対応します。

cfg = coder.gpuConfig('mex');
cfg.TargetLang = 'C++';
cfg.DeepLearningConfig = coder.DeepLearningConfig('cudnn');
codegen -config cfg fcn_predict -args {ones(360,480,3,'uint8')} -report
Code generation successful: View report

生成された MEX の実行

入力イメージを読み込んで表示します。

im = imread('testImage.png');
imshow(im);

入力イメージに対して fcn_predict_mex を呼び出して予測を実行します。

predict_scores = fcn_predict_mex(im);

変数 predict_scores は、各クラスのピクセル単位の予測スコアに対応する 11 個のチャネルを持つ 3 次元行列です。最大予測スコアを使用してチャネルを計算し、ピクセル単位のラベルを取得します。

[~,argmax] = max(predict_scores,[],3);

セグメント化されたラベルを入力イメージに重ね合わせ、セグメント化された領域を表示します。

classes = [
    "Sky"
    "Building"
    "Pole"
    "Road"
    "Pavement"
    "Tree"
    "SignSymbol"
    "Fence"
    "Car"
    "Pedestrian"
    "Bicyclist"
    ];

cmap = camvidColorMap();
SegmentedImage = labeloverlay(im,argmax,'ColorMap',cmap);
figure
imshow(SegmentedImage);
pixelLabelColorbar(cmap,classes);

クリーンアップ

メモリに読み込まれた静的ネットワーク オブジェクトをクリアします。

clear mex;

参考文献

[1] Long, J., E. Shelhamer, and T. Darrell."Fully Convolutional Networks for Semantic Segmentation."Proceedings of the IEEE Conference on Computer Vision and Pattern Recognition, 2015, pp. 3431–3440.

[2] Brostow, G. J., J. Fauqueur, and R. Cipolla. "Semantic object classes in video: A high-definition ground truth database." Pattern Recognition Letters. Vol. 30, Issue 2, 2009, pp 88-97.

[3] Badrinarayanan, V., A. Kendall, and R. Cipolla. "SegNet: A Deep Convolutional Encoder-Decoder Architecture for Image Segmentation." arXiv preprint arXiv:1511.00561, 2015.

参考

関数

オブジェクト

関連するトピック