Main Content

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

YOLO v2 深層学習を使用したマルチクラス オブジェクト検出

この例では、マルチクラス オブジェクト検出器に学習させる方法を示します。

概要

深層学習は、YOLO v2、YOLO v4、SSD、Faster R-CNN などのロバストなマルチクラス オブジェクト検出器の学習に使用できる強力な機械学習手法です。この例では、関数 trainYOLOv2ObjectDetector を使用して、YOLO v2 マルチクラス屋内オブジェクト検出器に学習させます。学習済みオブジェクト検出器は、複数の異なる屋内オブジェクトの検出と識別を行うことができます。YOLO v4、SSD、または Faster R-CNN など他のマルチクラス オブジェクト検出器の学習に関する詳細については、深層学習を使用したオブジェクト検出入門を参照してください。

事前学習済み検出器を使用したオブジェクト検出の実行

ターゲット クラスのオブジェクトを含むテスト イメージを読み取り、表示します。

I = imread('indoorTest.jpg');
imshow(I)

事前学習済みの YOLO v2 オブジェクト検出器をダウンロードして読み込みます。

pretrainedURL = "https://www.mathworks.com/supportfiles/vision/data/yolov2IndoorObjectDetector.zip";
pretrainedFolder = fullfile(tempdir,"pretrainedNetwork");
pretrainedNetworkZip = fullfile(pretrainedFolder, "yolov2IndoorObjectDetector.zip"); 

if ~exist(pretrainedNetworkZip,"file")
    mkdir(pretrainedFolder);
    disp("Downloading pretrained network (98 MB)...");
    websave(pretrainedNetworkZip, pretrainedURL);
end

unzip(pretrainedNetworkZip, pretrainedFolder)

pretrainedNetwork = fullfile(pretrainedFolder, "yolov2IndoorObjectDetector.mat");
pretrained = load(pretrainedNetwork);
detector = pretrained.detector;

関数 detect を使用して、イメージ内のオブジェクトとそのラベルを検出します。

[bbox, score, label]  = detect(detector, I);

関数 insertObjectAnnotation を使用し、検出された境界ボックスをイメージに重ね合わせて、予測を可視化します。

imshow(I)
showShape("rectangle", bbox, Label=label);

データセットの読み込み

この例では、Bishwo Adhikari [1] によって作成された屋内オブジェクト検出のデータセットを使用します。データセットは、7 つのクラス (消火器、椅子、時計、ゴミ箱、画面、およびプリンター) を含む屋内シーンから収集された 2213 個のラベル付けされたイメージで構成されています。各イメージには、前述のカテゴリのラベル付けされたインスタンスが 1 つ以上含まれています。

データセットをダウンロードします。

dsURL = "https://zenodo.org/record/2654485/files/Indoor%20Object%20Detection%20Dataset.zip?download=1";
 
outputFolder = fullfile(tempdir,"indoorObjectDetection"); 
imagesZip = fullfile(outputFolder,"indoor.zip");

if ~exist(imagesZip,"file")   
    mkdir(outputFolder)       
    disp("Downloading 401 MB Indoor Objects dataset images..."); 
    websave(imagesZip, dsURL);
    unzip(imagesZip, fullfile(outputFolder));  
end

datapath = fullfile(outputFolder, "Indoor Object Detection Dataset");

イメージは、さまざまなシーケンスから構成される 6 つのフォルダーにまとめられています。さまざまなフォルダー パスを指定して、imageDatastore を作成します。

numSequences = 6;
imds = imageDatastore(datapath, IncludeSubfolders=true, FileExtensions=".jpg");

注釈とデータセットの分割は、ファイル annotationsIndoor.mat で提供されています。学習、検証、およびテストの分割に対応する注釈とインデックスを読み込みます。6 個のイメージにはラベルが関連付けられていないため、分割に含まれるのは 2213 個のイメージではなく合計 2207 個のイメージであることに注意してください。ラベルを含むイメージのインデックスを cleanIdx に保存します。

data = load("annotationsIndoor.mat");
bbStore = data.BBstore;
trainingIdx = data.trainingIdx;
validationIdx = data.validationIdx;
testIdx = data.testIdx;
cleanIdx = data.idxs;

最後に、imageDatastoreboxLabelDatastore を統合します。subset コマンドを使用して、プリロードされたインデックスを指定し、統合されたデータストアを、学習、検証、およびテストのデータストアに分割します。

ds = combine(imds,bbStore);
% Remove the 6 images with no labels.
ds = subset(ds,cleanIdx);

% Set random seed.
rng(0);

% Shuffle the dataset before the split to ensure good class distibution.
ds = shuffle(ds);
dsTrain = subset(ds,trainingIdx);
dsVal = subset(ds,validationIdx);
dsTest = subset(ds,testIdx);

データの解析

まず、データセットを使用してデータセットのサンプル イメージを可視化します。

data = read(dsTrain);
I = data{1,1};
box = data{1,2};
label = data{1,3};
imshow(I)
showShape("rectangle", box, Label=label)

データセット内のクラス ラベルの分布を測定するには、countEachLabel を使用してクラス ラベルごとにオブジェクトの数をカウントします。

bbStore = ds.UnderlyingDatastores{2};
tbl = countEachLabel(bbStore)

カウントをクラスごとに可視化します。

bar(tbl.Label,tbl.Count)
ylabel("Frequency")

このデータセット内のクラスはバランスがとれていません。学習では上位クラスを優先してバイアスがかけられるため、正しく処理されていない場合は、こうした不均衡が学習プロセスに悪影響を及ぼす可能性があります。この問題への対処に使用される手法は複数 (少数しか存在しないクラスのオーバーサンプリング、損失関数の変更、およびデータ拡張) あります。後の節で、学習データにデータ拡張を適用します。

Yolo v2 オブジェクト検出ネットワークの作成

この例では、YOLO v2 オブジェクト検出ネットワークを作成します。YOLO v2 オブジェクトの検出ネットワークは 2 つのサブネットワークで構成されます。特徴抽出ネットワークに検出ネットワークが続きます。通常、特徴抽出ネットワークは事前学習済みの CNN です。この例では特徴抽出に ResNet-50 を使用します。

最初に、ネットワーク入力サイズとクラス数を指定します。ネットワーク入力サイズを選択する際には、ネットワーク自体に必要な最小サイズ、学習イメージのサイズ、選択したサイズでのデータの処理によって発生する計算コストを考慮します。可能な場合、学習イメージのサイズに近く、ネットワークに必要な入力サイズより大きいネットワーク入力サイズを選択します。ただし、イメージの解像度を下げると、オブジェクト検出器が小さなオブジェクトを検出しにくくなる可能性があります。例を実行する際の精度と計算コストのバランスを維持するため、ネットワーク入力サイズを [450 450 3] に指定します。

inputSize = [450 450 3];

検出するオブジェクト クラスの数を定義します。

numClasses = 7;

ベース ネットワークと特徴抽出層を選択します。特徴抽出層として 'activation_40_relu' を選択し、'activation_40_relu' の後の層を検出サブネットワークに置き換えます。この特徴抽出層は、係数 16 でダウンサンプリングされる特徴マップを出力します。このダウンサンプリングの量は、空間分解能と抽出される特徴の強度との適切なトレードオフです (ネットワークでさらに抽出された特徴により、より強力なイメージの特徴が符号化されますが、空間分解能は低下します)。最適な特徴抽出層を選択するには経験的解析が必要です。

network = resnet50();
featureLayer = "activation_40_relu";

学習データを前処理して学習用のデータを準備します。前処理関数は、イメージと境界ボックスのサイズを変更します。さらに、境界ボックスをサニタイズして有効な形状に変換します。

preprocessedTrainingData = transform(dsTrain,@(data)resizeImageAndLabel(data, inputSize));

次に、estimateAnchorBoxes を使用して、学習データ内のオブジェクトのサイズに基づいて 2 つのアンカー ボックスを推定します。アンカー ボックスの最適な数を選択するには、経験的解析が必要です。

numAnchors = 2;
aboxes = estimateAnchorBoxes(preprocessedTrainingData, numAnchors);

関数yolov2Layersを使用して、YOLO v2 オブジェクト検出ネットワークを作成します。

lgraph = yolov2Layers(inputSize, numClasses, aboxes, network, featureLayer);   

Deep Learning Toolbox から analyzeNetwork または DeepNetworkDesigner を使用してネットワークを可視化できます。

データ拡張

データ拡張は、学習中に元のデータをランダムに変換してネットワークの精度を高めるために使用されます。データ拡張を使用すると、ラベル付き学習サンプルの数を実際に増やさずに、学習データをさらに多様化させることができます。transform を使用して、以下のように学習データを拡張します。

  • イメージおよび関連するボックス ラベルを水平方向にランダムに反転。

  • イメージおよび関連するボックス ラベルをランダムにスケーリング。

  • イメージの色にジッターを付加。

augmentedTrainingData = transform(preprocessedTrainingData, @augmentData);

学習イメージとボックス ラベルのうちの 1 つを表示します。

data = read(augmentedTrainingData);
I = data{1};
bbox = data{2};
label = data{3};
imshow(I)
showShape("rectangle", bbox, Label=label)

YOLO v2 オブジェクト検出器の学習

trainingOptions を使用してネットワーク学習オプションを指定します。

opts = trainingOptions("rmsprop",...
        InitialLearnRate=0.001,...
        MiniBatchSize=4,...
        MaxEpochs=10,...
        LearnRateSchedule="piecewise",...
        LearnRateDropPeriod=3,...
        VerboseFrequency=30, ...
        L2Regularization=0.001,...
        ValidationData=dsVal,...
        ValidationFrequency=50);

doTraining が true の場合、関数trainYOLOv2ObjectDetectorを使用して YOLO v2 オブジェクト検出器に学習させます。

doTraining = false;
if doTraining
    % Train the YOLO v2 detector.
    [detector, info] = trainYOLOv2ObjectDetector(augmentedTrainingData,lgraph, opts);
else
    % Load pretrained detector for the example.
    pretrained = load(pretrainedNetwork);
    detector = pretrained.detector;
end

この例は、12 GB メモリ搭載の NVIDIA™ Titan X GPU で検証済みです。GPU のメモリがこれより少ない場合、メモリ不足が発生する可能性があります。これが発生した場合は、関数 trainingOptions を使用して MiniBatchSize を減らします。この設定を使用してこのネットワークに学習させるのに約 2 時間かかりました。学習所要時間は使用するハードウェアによって異なります。

テスト セットを使用した検出器の評価

テスト イメージで学習させたオブジェクト検出器を評価し、パフォーマンスを測定します。Computer Vision Toolbox™ には、平均適合率 (evaluateDetectionPrecision) や対数平均ミス率 (evaluateDetectionMissRate) などの一般的なメトリクスを測定するオブジェクト検出器の評価関数が用意されています。この例では、平均適合率メトリクスを使用してパフォーマンスを評価します。平均適合率は、検出器が正しい分類を実行できること (precision) と検出器がすべての関連オブジェクトを検出できること (recall) を示す単一の数値です。

学習データと同じ前処理変換をテスト データに適用します。データ拡張はテスト データには適用されないことに注意してください。テスト データは元のデータを代表するもので、バイアスのない評価を行うために変更なしで使用されなければなりません。

preprocessedTestData = transform(dsTest, @(data)resizeImageAndLabel(data, inputSize));
results = detect(detector,preprocessedTestData, MiniBatchSize=4, Threshold=0.5);
[ap, precision, recall] = evaluateDetectionPrecision(results, preprocessedTestData);

適合率/再現率 (PR) の曲線は、さまざまなレベルの再現率における検出器の適合率を示しています。すべてのレベルの再現率で適合率が 1 になるのが理想的です。より多くのデータを使用すると平均適合率を向上できますが、学習に必要な時間が長くなる場合があります。選択したクラスの PR 曲線をプロットします。

classID = 1;
figure
plot(recall{classID},precision{classID})
xlabel("Recall")
ylabel("Precision")
grid on
title(sprintf("Average Precision = %.2f",ap(classID)))

コード生成

検出器に学習させて評価したら、GPU Coder™ を使用して yolov2ObjectDetector のコードを生成できます。詳細については、YOLO v2 を使用したオブジェクト検出のコードの生成 (GPU Coder)の例を参照してください。

サポート関数

function B = augmentData(A)
% Apply random horizontal flipping, and random X/Y scaling. Boxes that get
% scaled outside the bounds are clipped if the overlap is above 0.25. Also,
% jitter image color.
B = cell(size(A));

I = A{1};
sz = size(I);
if numel(sz)==3 && sz(3) == 3
    I = jitterColorHSV(I,...
        Contrast=0.2,...
        Hue=0,...
        Saturation=0.1,...
        Brightness=0.2);
end

% Randomly flip and scale image.
tform = randomAffine2d(XReflection=true, Scale=[1 1.1]);  
rout = affineOutputView(sz, tform, BoundsStyle="CenterOutput");    
B{1} = imwarp(I, tform, OutputView=rout);

% Sanitize boxes, if needed.
A{2} = helperSanitizeBoxes(A{2}, sz);
    
% Apply same transform to boxes.
[B{2},indices] = bboxwarp(A{2}, tform, rout, OverlapThreshold=0.25);    
B{3} = A{3}(indices);
    
% Return original data only when all boxes are removed by warping.
if isempty(indices)
    B = A;
end
end
% helperSanitizeBoxes Sanitize box data.
% If none of the boxes are valid, this function passes the data through to
% enable downstream processing to issue proper errors.
function boxes = helperSanitizeBoxes(boxes, ~)
persistent hasInvalidBoxes
valid = all(boxes > 0, 2);
if any(valid)
    if ~all(valid) && isempty(hasInvalidBoxes)
        % Issue one-time warning about removing invalid boxes.
        hasInvalidBoxes = true;
        warning('Removing ground truth bouding box data with values <= 0.')
    end
    boxes = boxes(valid,:); 
end
end
function data = resizeImageAndLabel(data,targetSize)
% Resize the images and scale the corresponding bounding boxes.

    scale = (targetSize(1:2))./size(data{1},[1 2]);
    data{1} = imresize(data{1},targetSize(1:2));
    data{2} = bboxresize(data{2},scale);

    data{2} = floor(data{2});
    imageSize = targetSize(1:2);
    boxes = data{2};
    % Set boxes with negative values to have value 1.
    boxes(boxes<=0) = 1;
    
    % Validate if bounding box in within image boundary.
    boxes(:,3) = min(boxes(:,3),imageSize(2) - boxes(:,1)-1);
    boxes(:,4) = min(boxes(:,4),imageSize(1) - boxes(:,2)-1);
    
    data{2} = boxes; 

end

参考文献

[1] Adhikari, Bishwo; Peltomaki, Jukka; Huttunen, Heikki. (2019).Indoor Object Detection Dataset [Data set]. 7th European Workshop on Visual Information Processing 2018 (EUVIP), Tampere, Finland.