Main Content

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

YOLO v3 深層学習を使用したオブジェクト検出用のコード生成

この例では、カスタム層を使用した You Only Look Once (YOLO) v3 オブジェクト検出器用の CUDA® MEX を生成する方法を示します。この例では YOLO v3 オブジェクト検出を使用して以下を説明します。

  • カスタム層をもつ深層学習ネットワーク用の CUDA コードの生成。

  • 深層学習のdlnetwork (Deep Learning Toolbox)オブジェクトをコード生成用のDAGNetwork (Deep Learning Toolbox)オブジェクトに変換。

YOLO v3 は YOLO v2 を改良したもので、複数のスケールにおける検出を追加してより小さなオブジェクトを検出できるようになっています。さらに、学習で使用される損失関数は、境界ボックス回帰用の平均二乗誤差と、オブジェクト分類用のバイナリ交差エントロピーに分割されており、検出精度が向上しています。この例では、Computer Vision Toolbox (TM) の "YOLO v3 深層学習を使用したオブジェクトの検出" の例で学習させた YOLO v3 ネットワークを使用します。詳細は、YOLO v3 深層学習を使用したオブジェクトの検出 (Computer Vision Toolbox) を参照してください。

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

必須

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

オプション

スタティック ライブラリ、ダイナミック ライブラリ、または実行可能ファイルなどの MEX 以外のビルドについて、この例では以下の要件も適用されます。

GPU 環境の検証

この例を実行するためのコンパイラおよびライブラリが正しくセットアップされていることを確認するために、関数 coder.checkGpuInstall を使用します。

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

YOLO v3 ネットワーク

この例の YOLO v3 ネットワークは、squeezenet (Deep Learning Toolbox)がベースとなっています。このネットワークは、SqueezeNet の特徴抽出ネットワークを使用し、最後に 2 つの検出ヘッドが追加されています。2 番目の検出ヘッドのサイズは、最初の検出ヘッドの 2 倍となっているため、小さなオブジェクトをより的確に検出できます。検出するオブジェクトのサイズに基づいて、さまざまなサイズの検出ヘッドを任意の数で指定できます。YOLO v3 ネットワークは、学習データを使用して推定されたアンカー ボックスを使用します。これにより、データセットの種類に対応した初期の事前確率が改善され、ボックスを正確に予測できるようにネットワークに学習させることができます。アンカー ボックスの詳細については、アンカー ボックスによるオブジェクトの検出 (Computer Vision Toolbox)を参照してください。

この例の YOLO v3 ネットワークを次の図に示します。

各検出ヘッドは、境界ボックス座標 (x、y、幅、高さ)、オブジェクトの信頼度、および各アンカー ボックス マスクに対するクラスの確率を予測します。そのため、各検出ヘッドにおける最終畳み込み層の出力フィルターの数は、アンカー ボックス マスクの数と、アンカー ボックスあたりの予測要素の数を乗算したものになります。この検出ヘッドは、ネットワークの出力層を構成します。

事前学習済みの YOLO v3 ネットワーク

"YOLO v3 深層学習を使用したオブジェクトの検出" の例で学習させた YOLO v3 ネットワークをダウンロードします。自分でネットワークに学習させるには、YOLO v3 深層学習を使用したオブジェクトの検出 (Computer Vision Toolbox)を参照してください。

pretrained = load("yolov3SqueezeNetVehicleExample_20a.mat");
net = pretrained.net;

コード生成のための事前学習済みモデルの準備

事前学習済みの YOLO v3 モデルは、コード生成でサポートされない dlnetwork オブジェクトです。関数layerGraph (Deep Learning Toolbox)を使用して、dlnetwork オブジェクトの層グラフを抽出します。

lGraph = layerGraph(net);

関数 layerGraph によって出力された層グラフには、出力層が含まれません。関数addLayers (Deep Learning Toolbox)および関数connectLayers (Deep Learning Toolbox)を使用して、それぞれの出力で層グラフに回帰層を追加します。

outLayerIdx = 1:numel(lGraph.Layers);
isOutLayer = arrayfun(@(x) any(strcmp(x.Name, net.OutputNames)), lGraph.Layers);
outLayerIdx(~isOutLayer) = [];

for iOut = 1:numel(outLayerIdx)
    outLayer = lGraph.Layers(outLayerIdx(iOut));
    newLayer = regressionLayer('Name', [outLayer.Name '_output_' num2str(iOut)]);
    lGraph = addLayers(lGraph, newLayer);
    lGraph = connectLayers(lGraph, outLayer.Name, newLayer.Name);
end

ネットワークの出力層の数は、ネットワーク内の検出ヘッドの数と同じです。

カスタム アップサンプリング層

YOLO v3 ネットワークは、入力イメージ データのアップサンプリングにカスタム アップサンプリング層を使用します。ここでは、YOLO v3 深層学習を使用したオブジェクトの検出 (Computer Vision Toolbox)の例で説明されている upsampleLayer のコード生成互換バージョンを使用します。upsampleBy2Layer は、関数repelemを使用して、隣接するピクセル値を係数 2 で複製することによって、入力イメージをアップサンプリングします。関数 repelem のコード生成はベクトルまたは 2 次元行列の入力に対してのみサポートされます。したがって、関数 upsampleBy2Layer の実装では、関数coder.targetを使用してパラメーター化する必要があります。MATLAB 上でシミュレーションを実行中は、入力を N 次元行列にできるため、repelem の呼び出しは 1 回で十分です。コード生成のために、この例では入れ子の for ループを使用して、生成コード内の repelem に対する各呼び出しで関数 repelem に対する各呼び出しの 2 次元行列になるようにします。さらに、コード生成層の出力サイズはコード生成時に一定である必要があります。したがって、層の UpsampleFactor プロパティは Constant に変更されます。

type('upsampleBy2Layer.m')
% Upsample by replicating neighbouring pixel values.

% Copyright 2020 The MathWorks, Inc.

classdef upsampleBy2Layer < nnet.layer.Layer
    properties (Constant)
        % factor to upsample the input.
        UpSampleFactor = 2
    end
    
    methods
        function layer = upsampleBy2Layer(name)
            
            % Set layer name.
            layer.Name = name;
            
            % Set layer description.
            layer.Description = "upSamplingLayer with factor " + layer.UpSampleFactor;
            
        end
        
        function Z = predict(layer, X)
            % Z = predict(layer, X) forwards the input data X through the
            % layer and outputs the result Z.
            if coder.target('MATLAB')
                Z = repelem(X,layer.UpSampleFactor,layer.UpSampleFactor);
            else
                numChannels = size(X, 3);
                numBatches = size(X, 4);
                Zsize = coder.const([size(X, 1) size(X, 2) numChannels numBatches] .* [layer.UpSampleFactor layer.UpSampleFactor 1 1]);
                Z = coder.nullcopy(zeros(Zsize, 'like', X));
                
                coder.gpu.kernel(-1, -1);
                for iBatch = 1:numBatches
                    for iChannel = 1:numChannels
                        Z(:, :, iChannel, iBatch) = repelem(X(:, :, iChannel, iBatch), layer.UpSampleFactor, layer.UpSampleFactor);
                    end
                end
            end
        end
    end
end

関数replaceLayer (Deep Learning Toolbox)を使用して、事前学習済みネットワークに存在する upsampleLayer をそのコード生成互換バージョン upsampleBy2Layer に置き換えます。

isUpsampleLayer = arrayfun(@(x) isa(x, 'upsampleLayer'), lGraph.Layers);
layerNameToReplace = lGraph.Layers(isUpsampleLayer).Name;
lGraph = replaceLayer(lGraph, layerNameToReplace, upsampleBy2Layer(layerNameToReplace));

DAGNetwork への layerGraph の組み立て

関数assembleNetwork (Deep Learning Toolbox)を使用して、layerGraph をコード生成に使用可能な DAGNetwork オブジェクトに組み立てます。

dagNet = assembleNetwork(lGraph)
dagNet = 
  DAGNetwork with properties:

         Layers: [72×1 nnet.cnn.layer.Layer]
    Connections: [80×2 table]
     InputNames: {'data'}
    OutputNames: {'conv2Detection1_output_1'  'conv2Detection2_output_2'}

ネットワークを MAT ファイルに保存します。

matFile = 'yolov3DAGNetwork.mat';
save(matFile, 'dagNet');

エントリポイント関数 yolov3Detect

エントリポイント関数 yolov3Detect は、入力イメージを受け取り、それを関数 yolov3Predict を介して予測用の学習済みネットワークに渡します。関数 yolov3Predict は、ネットワーク オブジェクトを MAT ファイルから永続変数に読み込み、以降の予測呼び出しでその永続オブジェクトを再利用します。具体的には、この関数は、YOLO v3 深層学習を使用したオブジェクトの検出 (Computer Vision Toolbox)の例で学習させたネットワークの DAGNetwork 表現を使用します。次に、yolov3Predict 呼び出しから取得した YOLO v3 グリッド セル座標からの予測が、サポート関数 generateTiledAnchors および applyAnchorBoxOffsets を使用して境界ボックス座標に変換されます。

type('yolov3Detect.m')
function [bboxes,scores,labels] = yolov3Detect(matFile, im, networkInputSize, networkOutputs, confidenceThreshold, overlapThreshold, classes)
% The yolov3Detect function detects the bounding boxes, scores, and labels in an image.
coder.extrinsic('generateYOLOv3Detections');

%% Preprcess Data
% This example applies all the preprocessing transforms to the data set
% applied during training, except data augmentation. Because the example
% uses a pretrained YOLO v3 network, the input data must be representative
% of the original data and left unmodified for unbiased evaluation.

% Specifically the following preprocessing operations are applied to the
% input data. 
%     1. Resize the images to the network input size, as the images are bigger than networkInputSize. 
%     2. Scale the image pixels in the range [0 1].

im = preprocessData(im, networkInputSize);
imageSize = size(im,[1,2]);

%% Define Anchor Boxes
% Specify the anchor boxes estimated on the basis of the preprocessed
% training data used when training the YOLO v3 network. These anchor box
% values are same as mentioned in
% <docid:vision_ug#mw_47d9a223-5ec7-4d36-a020-4f9d147ecdec Object Detection
% Using YOLO v3 Deep Learning> example. For details on estimating anchor
% boxes, see <docid:vision_ug#mw_f9f22f48-0ad0-4f37-8bc1-22a2046637f2
% Anchor Boxes for Object Detection>.

anchors = [150   127;
    97    90;
    68    67;
    38    42;
    41    29;
    31    23];

% Specify anchorBoxMasks to select anchor boxes to use in both the
% detection heads of the YOLO v3 network. anchorBoxMasks is a cell array of
% size M-by-1, where M denotes the number of detection heads. Each
% detection head consists of a 1-by-N array of row index of anchors in
% anchorBoxes, where N is the number of anchor boxes to use. Select anchor
% boxes for each detection head based on size-use larger anchor boxes at
% lower scale and smaller anchor boxes at higher scale. To do so, sort the
% anchor boxes with the larger anchor boxes first and assign the first
% three to the first detection head and the next three to the second
% detection head.

area = anchors(:, 1).*anchors(:, 2);
[~, idx] = sort(area, 'descend');
anchors = anchors(idx, :);
anchorBoxMasks = {[1,2,3]
    [4,5,6]
    };

%% Predict on Yolov3
% Predict and filter the detections based on confidence threshold.
predictions = yolov3Predict(matFile,im,networkOutputs,anchorBoxMasks);

%% Generate Detections
anchorIndex = 2:5; % indices corresponding to x,y,w,h predictions for bounding boxes
tiledAnchors = generateTiledAnchors(predictions,anchors,anchorBoxMasks,anchorIndex);
predictions = applyAnchorBoxOffsets(tiledAnchors, predictions, networkInputSize, anchorIndex);
[bboxes,scores,labels] = generateYOLOv3Detections(predictions, confidenceThreshold, overlapThreshold, imageSize, classes);

% Apply suppression to the detections to filter out multiple overlapping
% detections.
if ~isempty(scores)
    [bboxes, scores, labels] = selectStrongestBboxMulticlass(bboxes, scores, labels ,...
        'RatioType', 'Union', 'OverlapThreshold', overlapThreshold);
end
end

function YPredCell = yolov3Predict(matFile,im,networkOutputs,anchorBoxMask)
% Predict the output of network and extract the confidence, x, y,
% width, height, and class.

% load the deep learning network for prediction
persistent net;

if isempty(net)
    net = coder.loadDeepLearningNetwork(matFile);
end

YPredictions = cell(size(networkOutputs));
[YPredictions{:}] = predict(net, im);
YPredCell = extractPredictions(YPredictions, anchorBoxMask);

% Apply activation to the predicted cell array.
YPredCell = applyActivations(YPredCell);
end

オブジェクト検出用のエントリポイント関数の評価

次の手順に従って、テスト データからのイメージに対するエントリポイント関数を評価します。

  • 信頼度しきい値に 0.5 を指定し、信頼度スコアがこの値より高い検出のみ保持します。

  • オーバーラップしきい値に 0.5 を指定し、オーバーラップしている検出を削除します。

  • 入力データからイメージを読み取ります。

  • エントリポイント関数 yolov3Detect を使用して、予測された境界ボックス、信頼度スコア、およびクラス ラベルを取得します。

  • 境界ボックスと信頼度スコアと共にイメージを表示します。

必要なしきい値を定義します。

confidenceThreshold = 0.5;
overlapThreshold = 0.5;

学習済みネットワークの入力層からネットワーク入力サイズを取得し、ネットワーク出力の数を取得します。

networkInputIdx = arrayfun(@(x)isa(x,'nnet.cnn.layer.ImageInputLayer'),net.Layers);
networkInputSize = net.Layers(networkInputIdx).InputSize;
networkOutputs = numel(dagNet.OutputNames);

YOLO v3 深層学習を使用したオブジェクトの検出 (Computer Vision Toolbox)の例のラベル付きデータ セットから取得したサンプル イメージ データを読み取ります。このイメージには、車両タイプのオブジェクトのインスタンスが 1 つ含まれています。

I = imread('vehicleImage.jpg');

クラス名を指定します。

classNames = {'vehicle'};

YOLO v3 ネットワークで検出メソッドを呼び出し、結果を表示します。

[bboxes,scores,~] = yolov3Detect(matFile, I, networkInputSize, networkOutputs, confidenceThreshold, overlapThreshold, classNames);

% Display the detections on the image
IAnnotated = insertObjectAnnotation(I, 'rectangle', bboxes, scores);
figure
imshow(IAnnotated)

CUDA MEX の生成

エントリポイント関数 yolov3Detect 用の CUDA® コードを生成するには、MEX ターゲットの GPU コード構成オブジェクトを作成し、ターゲット言語を C++ に設定します。関数coder.DeepLearningConfigを使用して CuDNN 深層学習構成オブジェクトを作成し、それを GPU コード構成オブジェクトの DeepLearningConfig プロパティに割り当てます。

cfg = coder.gpuConfig('mex');
cfg.TargetLang = 'C++';
cfg.DeepLearningConfig = coder.DeepLearningConfig('cudnn');

args = {coder.Constant(matFile), I, coder.Constant(networkInputSize), networkOutputs, ...
    confidenceThreshold, overlapThreshold, classNames};

codegen -config cfg yolov3Detect -args args
Code generation successful: View report

TensorRT ターゲット用の CUDA® コードを生成するには、CuDNN 構成オブジェクトではなく、TensorRT 深層学習構成オブジェクトを使用します。同様に、MKLDNN ターゲット用のコードを生成するには、CPU コード構成オブジェクトを作成し、その DeepLearningConfig プロパティとして MKLDNN 深層学習構成オブジェクトを使用します。

生成された MEX の実行

前回と同じイメージ入力 I を使用して生成された CUDA MEX を呼び出し、結果を表示します。

[bboxes, scores, labels] = yolov3Detect_mex(matFile, I, networkInputSize, networkOutputs, ...
    confidenceThreshold, overlapThreshold, classNames);
figure;
IAnnotated = insertObjectAnnotation(I, 'rectangle', bboxes, scores);
imshow(IAnnotated);

ユーティリティ関数

以下のユーティリティ関数は、関数 preProcessData を除いて、YOLO v3 深層学習を使用したオブジェクトの検出 (Computer Vision Toolbox)の例で使用されたものと同様です。この例では、境界ボックスも処理されるYOLO v3 深層学習を使用したオブジェクトの検出 (Computer Vision Toolbox)の例とは異なり、イメージ データの前処理のみを行います。

type('applyActivations.m')
function YPredCell = applyActivations(YPredCell)
for idx = 1:3
    YPredCell{:, idx} = sigmoidActivation(YPredCell{:,idx});
end
for idx = 4:5
    YPredCell{:, idx} = exp(YPredCell{:, idx});
end
YPredCell{:, 6} = sigmoidActivation(YPredCell{:, 6});
end

function out = sigmoidActivation(x)
out = 1./(1+exp(-x));
end
type('extractPredictions.m')
function predictions = extractPredictions(YPredictions, anchorBoxMask)
predictions = cell(size(YPredictions, 1),6);
for ii = 1:size(YPredictions, 1)
    numAnchors = size(anchorBoxMask{ii},2);
    % Confidence scores.
    startIdx = 1;
    endIdx = numAnchors;
    predictions{ii,1} = YPredictions{ii}(:,:,startIdx:endIdx,:);
    
    % X positions.
    startIdx = startIdx + numAnchors;
    endIdx = endIdx+numAnchors;
    predictions{ii,2} = YPredictions{ii}(:,:,startIdx:endIdx,:);
    
    % Y positions.
    startIdx = startIdx + numAnchors;
    endIdx = endIdx+numAnchors;
    predictions{ii,3} = YPredictions{ii}(:,:,startIdx:endIdx,:);
    
    % Width.
    startIdx = startIdx + numAnchors;
    endIdx = endIdx+numAnchors;
    predictions{ii,4} = YPredictions{ii}(:,:,startIdx:endIdx,:);
    
    % Height.
    startIdx = startIdx + numAnchors;
    endIdx = endIdx+numAnchors;
    predictions{ii,5} = YPredictions{ii}(:,:,startIdx:endIdx,:);
    
    % Class probabilities.
    startIdx = startIdx + numAnchors;
    predictions{ii,6} = YPredictions{ii}(:,:,startIdx:end,:);
end
end
type('generateTiledAnchors.m')
function tiledAnchors = generateTiledAnchors(YPredCell,anchorBoxes,anchorBoxMask,anchorIndex)
% Generate tiled anchor offset for converting the predictions from the YOLO 
% v3 grid cell coordinates to bounding box coordinates

tiledAnchors = cell(size(anchorIndex));
for i=1:size(YPredCell,1)
    anchors = anchorBoxes(anchorBoxMask{i}, :);
    [h,w,~,n] = size(YPredCell{i,1});
    [tiledAnchors{i,2}, tiledAnchors{i,1}] = ndgrid(0:h-1,0:w-1,1:size(anchors,1),1:n);
    [~,~,tiledAnchors{i,3}] = ndgrid(0:h-1,0:w-1,anchors(:,2),1:n);
    [~,~,tiledAnchors{i,4}] = ndgrid(0:h-1,0:w-1,anchors(:,1),1:n);
end
end
type('applyAnchorBoxOffsets.m')
function YPredCell = applyAnchorBoxOffsets(tiledAnchors,YPredCell,inputImageSize,anchorIndex)
% Convert the predictions from the YOLO v3 grid cell coordinates to bounding box coordinates
for i=1:size(YPredCell,1)
    [h,w,~,~] = size(YPredCell{i,1});  
    YPredCell{i,anchorIndex(1)} = (tiledAnchors{i,1}+YPredCell{i,anchorIndex(1)})./w;
    YPredCell{i,anchorIndex(2)} = (tiledAnchors{i,2}+YPredCell{i,anchorIndex(2)})./h;
    YPredCell{i,anchorIndex(3)} = (tiledAnchors{i,3}.*YPredCell{i,anchorIndex(3)})./inputImageSize(2);
    YPredCell{i,anchorIndex(4)} = (tiledAnchors{i,4}.*YPredCell{i,anchorIndex(4)})./inputImageSize(1);
end
end
type('preprocessData.m')
function image = preprocessData(image, targetSize)
% Resize the images and scale the pixels to between 0 and 1.

imgSize = size(image);

% Convert an input image with single channel to 3 channels.
if numel(imgSize) == 1
    image = repmat(image,1,1,3);
end
image = im2single(imresize(image, coder.const(targetSize(1:2))));

end

参考文献

1.Redmon, Joseph, and Ali Farhadi. "YOLOv3: An Incremental Improvement." Preprint, submitted April 8, 2018. https://arxiv.org/abs/1804.02767.