チュートリアル: ML.NET で ONNX を使用してオブジェクトを検出するTutorial: Detect objects using ONNX in ML.NET

ML.NET の事前トレーニング済みの ONNX モデルを使用して画像内のオブジェクトを検出する方法について説明します。Learn how to use a pre-trained ONNX model in ML.NET to detect objects in images.

オブジェクト検出モデルを最初からトレーニングするには、数百万のパラメーター、大量のラベル付きトレーニング データ、膨大な量の計算リソース (数百時間の GPU) を設定する必要があります。Training an object detection model from scratch requires setting millions of parameters, a large amount of labeled training data and a vast amount of compute resources (hundreds of GPU hours). 事前トレーニング済みモデルを使用すると、トレーニング プロセスをショートカットできます。Using a pre-trained model allows you to shortcut the training process.

このチュートリアルでは、次の作業を行う方法について説明します。In this tutorial, you learn how to:

  • 問題を把握するUnderstand the problem
  • ONNX の概要と ML.NET でどのように動作するかについて説明します。Learn what ONNX is and how it works with ML.NET
  • モデルの概要Understand the model
  • 事前トレーニング済みモデルを再利用するReuse the pre-trained model
  • 読み込まれたモデルを使用してオブジェクトを検出するDetect objects with a loaded model

前提条件Pre-requisites

ONNX オブジェクト検出サンプルの概要ONNX object detection sample overview

このサンプルでは、事前トレーニング済みのディープ ラーニング ONNX モデルを使用して、画像内のオブジェクトを検出する .NET Core コンソール アプリケーションを作成します。This sample creates a .NET core console application that detects objects within an image using a pre-trained deep learning ONNX model. このサンプルのコードについては、GitHub の dotnet/machinelearning-samples リポジトリを参照してください。The code for this sample can be found on the dotnet/machinelearning-samples repository on GitHub.

オブジェクトの検出とはWhat is object detection?

オブジェクト検出はコンピューターのビジョンの問題です。Object detection is a computer vision problem. 画像の分類に密接に関連していますが、オブジェクト検出では、より詳細なスケールで画像分類が実行されます。While closely related to image classification, object detection performs image classification at a more granular scale. オブジェクト検出では、画像内のエンティティの特定 "" 分類の両方が行われます。Object detection both locates and categorizes entities within images. オブジェクト検出は、画像に異なる種類のオブジェクトが複数含まれる場合に使用します。Use object detection when images contain multiple objects of different types.

画像の分類とオブジェクトの分類を示すスクリーンショット。

オブジェクト検出のユース ケースには、次のようなものがあります。Some use cases for object detection include:

  • 自動運転車Self-Driving Cars
  • ロボティクスRobotics
  • 顔検出Face Detection
  • 職場の安全Workplace Safety
  • オブジェクトのカウントObject Counting
  • アクティビティ認識Activity Recognition

ディープ ラーニング モデルを選択するSelect a deep learning model

ディープ ラーニングは、機械学習のサブセットです。Deep learning is a subset of machine learning. ディープ ラーニング モデルをトレーニングするには、大量のデータが必要です。To train deep learning models, large quantities of data are required. データ内のパターンは、一連のレイヤーによって表されます。Patterns in the data are represented by a series of layers. データ内のリレーションシップは、重みを含むレイヤー間の接続としてエンコードされます。The relationships in the data are encoded as connections between the layers containing weights. 重みが高いほど、リレーションシップが強くなります。The higher the weight, the stronger the relationship. この一連のレイヤーと接続は、まとめて人工ニューラル ネットワークと呼ばれます。Collectively, this series of layers and connections are known as artificial neural networks. ネットワーク内のレイヤーが多くなるほど、"深く" なり、ディープ ニューラル ネットワークになります。The more layers in a network, the "deeper" it is, making it a deep neural network.

ニューラル ネットワークにはさまざまな種類があり、よく知られているものとして、マルチレイヤー パーセプトロン (MLP)、畳み込みニューラル ネットワーク (CNN)、および再帰型ニューラル ネットワーク (RNN) があります。There are different types of neural networks, the most common being Multi-Layered Perceptron (MLP), Convolutional Neural Network (CNN) and Recurrent Neural Network (RNN). 最も基本的なのは MLP であり、一連の入力が一連の出力にマップされます。The most basic is the MLP, which maps a set of inputs to a set of outputs. このニューラル ネットワークは、データに空間と時間のコンポーネントがない場合に適しています。This neural network is good when the data does not have a spatial or time component. CNN では、畳み込みレイヤーを利用し、データに含まれている空間情報を処理します。The CNN makes use of convolutional layers to process spatial information contained in the data. CNN の良いユース ケースは、画像の領域内に特徴が存在するかどうかを検出する画像処理です (画像の中央に鼻があるか、など)。A good use case for CNNs is image processing to detect the presence of a feature in a region of an image (for example, is there a nose in the center of an image?). 最後に、RNN では、状態またはメモリの永続化を入力として使用できます。Finally, RNNs allow for the persistence of state or memory to be used as input. RNN は、時系列分析に使用されます。この場合、イベントの順序付けとコンテキストが重要です。RNNs are used for time-series analysis, where the sequential ordering and context of events is important.

モデルの概要Understand the model

オブジェクト検出は、画像処理タスクです。Object detection is an image-processing task. そのため、この問題を解決するためにトレーニングされるほとんどのディープ ラーニング モデルは、CNN です。Therefore, most deep learning models trained to solve this problem are CNNs. このチュートリアルで使用するモデルは、次のドキュメントで説明されている YOLOv2 モデルのコンパクトなバージョンである Tiny YOLOv2 モデルです。「YOLO9000:より良く、より速く、より強く」 (Redmon、Farhadi 著)The model used in this tutorial is the Tiny YOLOv2 model, a more compact version of the YOLOv2 model described in the paper: "YOLO9000: Better, Faster, Stronger" by Redmon and Farhadi. Tiny YOLOv2 は Pascal VOC データセットでトレーニングされ、20 種類のクラスのオブジェクトを予測できる 15 個のレイヤーで構成されています。Tiny YOLOv2 is trained on the Pascal VOC dataset and is made up of 15 layers that can predict 20 different classes of objects. Tiny YOLOv2 は元の YOLOv2 モデルを凝縮したバージョンなので、速度と精度のトレードオフが生じます。Because Tiny YOLOv2 is a condensed version of the original YOLOv2 model, a tradeoff is made between speed and accuracy. Netron などのツールを使用して、モデルを構成するさまざまなレイヤーを視覚化できます。The different layers that make up the model can be visualized using tools like Netron. モデルを検査すると、ニューラル ネットワークを構成するすべてのレイヤー間の接続のマッピングが生成されます。各レイヤーには、レイヤーの名前と共にそれぞれの入力/出力の寸法が含まれます。Inspecting the model would yield a mapping of the connections between all the layers that make up the neural network, where each layer would contain the name of the layer along with the dimensions of the respective input / output. モデルの入力と出力を記述するために使用されるデータ構造は、テンソルと呼ばれます。The data structures used to describe the inputs and outputs of the model are known as tensors. テンソルは、データを N 次元に格納するコンテナーと考えることができます。Tensors can be thought of as containers that store data in N-dimensions. Tiny YOLOv2 の場合、入力レイヤーの名前は image であり、3 x 416 x 416 の寸法のテンソルが想定されています。In the case of Tiny YOLOv2, the name of the input layer is image and it expects a tensor of dimensions 3 x 416 x 416. 出力レイヤーの名前は grid であり、125 x 13 x 13 の寸法の出力テンソルが生成されます。The name of the output layer is grid and generates an output tensor of dimensions 125 x 13 x 13.

非表示レイヤーに分割される入力レイヤー、次に出力レイヤー

YOLO モデルは 3(RGB) x 416px x 416px の画像を受け取ります。The YOLO model takes an image 3(RGB) x 416px x 416px. この入力はモデルによって取得され、さまざまなレイヤーを経由して出力が生成されます。The model takes this input and passes it through the different layers to produce an output. 出力では入力画像が 13 x 13 グリッドに分割されます。グリッド内の各セルは、125 値で構成されます。The output divides the input image into a 13 x 13 grid, with each cell in the grid consisting of 125 values.

ONNX モデルとはWhat is an ONNX model?

Open Neural Network Exchange (ONNX) は、AI モデルのオープン ソース形式です。The Open Neural Network Exchange (ONNX) is an open source format for AI models. ONNX は、フレームワーク間の相互運用性をサポートしています。ONNX supports interoperability between frameworks. つまり、PyTorch などの多くの一般的な機械学習フレームワークのいずれかでモデルをトレーニングして ONNX 形式に変換し、ML.NET などの別のフレームワークで ONNX モデルを使用することができます。This means you can train a model in one of the many popular machine learning frameworks like PyTorch, convert it into ONNX format and consume the ONNX model in a different framework like ML.NET. 詳細については、ONNX の Web サイトを参照してください。To learn more, visit the ONNX website.

使用されている、ONNX でサポートされる形式の図。

事前トレーニング済みの Tiny YOLOv2 モデルは ONNX 形式で格納されます。これはレイヤーのシリアル化された表現であり、それらのレイヤーの学習済みパターンです。The pre-trained Tiny YOLOv2 model is stored in ONNX format, a serialized representation of the layers and learned patterns of those layers. ML.NET では、ONNX との相互運用性は ImageAnalytics および OnnxTransformer NuGet パッケージを使用して実現されます。In ML.NET, interoperability with ONNX is achieved with the ImageAnalytics and OnnxTransformer NuGet packages. ImageAnalytics パッケージには、画像を受け取り、予測またはトレーニング パイプラインへの入力として使用できる数値にエンコードする一連の変換が含まれています。The ImageAnalytics package contains a series of transforms that take an image and encode it into numerical values that can be used as input into a prediction or training pipeline. OnnxTransformer パッケージでは、ONNX ランタイムを利用して ONNX モデルを読み込み、それを使用して、指定された入力に基づいて予測を行います。The OnnxTransformer package leverages the ONNX Runtime to load an ONNX model and use it to make predictions based on input provided.

ONNX ランタイムへの ONNX ファイルのデータ フロー。

.NET Core プロジェクトを設定するSet up the .NET Core project

ONNX の概要と Tiny YOLOv2 のしくみについて全般的な知識が得られたので、次はアプリケーションをビルドします。Now that you have a general understanding of what ONNX is and how Tiny YOLOv2 works, it's time to build the application.

コンソール アプリケーションを作成するCreate a console application

  1. "ObjectDetection" という名前の .NET Core コンソール アプリケーションを作成します。Create a .NET Core Console Application called "ObjectDetection".

  2. Microsoft.ML NuGet パッケージをインストールします。Install the Microsoft.ML NuGet Package:

    注意

    このサンプルでは、特に明記されていない限り、記載されている最新の安定バージョンの NuGet パッケージを使用します。This sample uses the latest stable version of the NuGet packages mentioned unless otherwise stated.

    • ソリューション エクスプローラーで、プロジェクトを右クリックし、 [NuGet パッケージの管理] を選択します。In Solution Explorer, right-click on your project and select Manage NuGet Packages.
    • [パッケージ ソース] として "nuget.org" を選択し、[参照] タブを選択し、"Microsoft.ML" を検索します。Choose "nuget.org" as the Package source, select the Browse tab, search for Microsoft.ML.
    • [インストール] ボタンを選択します。Select the Install button.
    • [変更のプレビュー] ダイアログの [OK] を選択します。表示されているパッケージのライセンス条項に同意する場合は、 [ライセンスの同意] ダイアログの [同意する] を選択します。Select the OK button on the Preview Changes dialog and then select the I Accept button on the License Acceptance dialog if you agree with the license terms for the packages listed.
    • Microsoft.ML.ImageAnalyticsMicrosoft.ML.OnnxTransformerMicrosoft.ML.OnnxRuntime に対してこれらの手順を繰り返します。Repeat these steps for Microsoft.ML.ImageAnalytics, Microsoft.ML.OnnxTransformer and Microsoft.ML.OnnxRuntime.

データと事前トレーニング済みモデルを準備するPrepare your data and pre-trained model

  1. プロジェクト資産ディレクトリの zip ファイルをダウンロードし、解凍します。Download The project assets directory zip file and unzip.

  2. assets ディレクトリを "ObjectDetection" プロジェクト ディレクトリにコピーします。Copy the assets directory into your ObjectDetection project directory. このディレクトリとそのサブディレクトリには、このチュートリアルに必要な画像ファイル (次の手順でダウンロードして追加する Tiny YOLOv2 モデルを除く) が含まれています。This directory and its subdirectories contain the image files (except for the Tiny YOLOv2 model, which you'll download and add in the next step) needed for this tutorial.

  3. ONNX Model Zoo から Tiny YOLOv2 モデルをダウンロードし、解凍します。Download the Tiny YOLOv2 model from the ONNX Model Zoo, and unzip.

    コマンド プロンプトを開き、次のコマンドを入力します。Open the command prompt and enter the following command:

    tar -xvzf tiny_yolov2.tar.gz
    
  4. 解凍したディレクトリから抽出した model.onnx ファイルを "ObjectDetection" プロジェクトの assets\Model ディレクトリにコピーし、名前を TinyYolo2_model.onnx に変更します。Copy the extracted model.onnx file from the directory just unzipped into your ObjectDetection project assets\Model directory and rename it to TinyYolo2_model.onnx. このディレクトリには、このチュートリアルに必要なモデルが含まれています。This directory contains the model needed for this tutorial.

  5. ソリューション エクスプローラーで、資産ディレクトリとサブディレクトリ内の各ファイルを右クリックし、 [プロパティ] を選択します。In Solution Explorer, right-click each of the files in the asset directory and subdirectories and select Properties. [詳細設定] で、 [出力ディレクトリにコピー] の値を [新しい場合はコピーする] に変更します。Under Advanced, change the value of Copy to Output Directory to Copy if newer.

クラスを作成してパスを定義するCreate classes and define paths

"Program.cs" ファイルを開き、ファイルの先頭に次の追加の using ステートメントを追加します。Open the Program.cs file and add the following additional using statements to the top of the file:

using System;
using System.IO;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Linq;
using Microsoft.ML;

次に、さまざまな資産のパスを定義します。Next, define the paths of the various assets.

  1. 最初に、GetAbsolutePath メソッドを Program クラスの Main メソッドの下に追加します。First, add the GetAbsolutePath method below the Main method in the Program class.

    public static string GetAbsolutePath(string relativePath)
    {
        FileInfo _dataRoot = new FileInfo(typeof(Program).Assembly.Location);
        string assemblyFolderPath = _dataRoot.Directory.FullName;
    
        string fullPath = Path.Combine(assemblyFolderPath, relativePath);
    
        return fullPath;
    }
    
  2. 次に、Main メソッド内にアセットの場所を格納するフィールドを作成します。Then, inside the Main method, create fields to store the location of your assets.

    var assetsRelativePath = @"../../../assets";
    string assetsPath = GetAbsolutePath(assetsRelativePath);
    var modelFilePath = Path.Combine(assetsPath, "Model", "TinyYolo2_model.onnx");
    var imagesFolder = Path.Combine(assetsPath, "images");
    var outputFolder = Path.Combine(assetsPath, "images", "output");
    

入力データと予測クラスを格納する新しいディレクトリをプロジェクトに追加します。Add a new directory to your project to store your input data and prediction classes.

ソリューション エクスプローラーで、プロジェクトを右クリックし、 [追加] > [新しいフォルダー] を選択します。In Solution Explorer, right-click the project, and then select Add > New Folder. ソリューション エクスプローラーに新しいフォルダーが表示されたら、"DataStructures" と名前を付けます。When the new folder appears in the Solution Explorer, name it "DataStructures".

新しく作成した "DataStructures" ディレクトリに入力データ クラスを作成します。Create your input data class in the newly created DataStructures directory.

  1. ソリューション エクスプローラーで "DataStructures" ディレクトリを右クリックし、 [追加] > [新しい項目] の順に選択します。In Solution Explorer, right-click the DataStructures directory, and then select Add > New Item.

  2. [新しい項目の追加] ダイアログ ボックスで、 [クラス] を選択し、 [名前] フィールドを "ImageNetData.cs" に変更します。In the Add New Item dialog box, select Class and change the Name field to ImageNetData.cs. 次に [追加] を選択します。Then, select the Add button.

    コード エディターで "ImageNetData.cs" ファイルが開きます。The ImageNetData.cs file opens in the code editor. 次の using ステートメントを "ImageNetData.cs" の先頭に追加します。Add the following using statement to the top of ImageNetData.cs:

    using System.Collections.Generic;
    using System.IO;
    using System.Linq;
    using Microsoft.ML.Data;
    

    既存のクラス定義を削除し、ImageNetData クラスの次のコードを "ImageNetData.cs" ファイルに追加します。Remove the existing class definition and add the following code for the ImageNetData class to the ImageNetData.cs file:

    public class ImageNetData
    {
        [LoadColumn(0)]
        public string ImagePath;
    
        [LoadColumn(1)]
        public string Label;
    
        public static IEnumerable<ImageNetData> ReadFromFile(string imageFolder)
        {
            return Directory
                .GetFiles(imageFolder)
                .Where(filePath => Path.GetExtension(filePath) != ".md")
                .Select(filePath => new ImageNetData { ImagePath = filePath, Label = Path.GetFileName(filePath) });
        }
    }
    

    ImageNetData は、入力画像データ クラスであり、次の String フィールドがあります。ImageNetData is the input image data class and has the following String fields:

    • ImagePath には、画像が保存されているパスが格納されます。ImagePath contains the path where the image is stored.
    • Label には、ファイルの名前が格納されます。Label contains the name of the file.

    また、ImageNetData には、指定された imageFolder パスに格納されている複数の画像ファイルを読み込み、それらを ImageNetData オブジェクトのコレクションとして返すメソッド ReadFromFile が含まれています。Additionally, ImageNetData contains a method ReadFromFile that loads multiple image files stored in the imageFolder path specified and returns them as a collection of ImageNetData objects.

"DataStructures" ディレクトリに予測クラスを作成します。Create your prediction class in the DataStructures directory.

  1. ソリューション エクスプローラーで "DataStructures" ディレクトリを右クリックし、 [追加] > [新しい項目] の順に選択します。In Solution Explorer, right-click the DataStructures directory, and then select Add > New Item.

  2. [新しい項目の追加] ダイアログ ボックスで、 [クラス] を選択し、 [名前] フィールドを "ImageNetPrediction.cs "に変更します。In the Add New Item dialog box, select Class and change the Name field to ImageNetPrediction.cs. 次に [追加] を選択します。Then, select the Add button.

    コード エディターで "ImageNetPrediction.cs" ファイルが開きます。The ImageNetPrediction.cs file opens in the code editor. "ImageNetPrediction.cs" の先頭に次の using ステートメントを追加します。Add the following using statement to the top of ImageNetPrediction.cs:

    using Microsoft.ML.Data;
    

    既存のクラス定義を削除し、ImageNetPrediction クラスの次のコードを "ImageNetPrediction.cs" ファイルに追加します。Remove the existing class definition and add the following code for the ImageNetPrediction class to the ImageNetPrediction.cs file:

    public class ImageNetPrediction
    {
        [ColumnName("grid")]
        public float[] PredictedLabels;
    }
    

    ImageNetPrediction は予測データ クラスであり、次の float[] フィールドがあります。ImageNetPrediction is the prediction data class and has the following float[] field:

    • PredictedLabel には、画像で検出された各境界ボックスの寸法、物体らしさのスコア、およびクラスの確率が含まれます。PredictedLabel contains the dimensions, objectness score, and class probabilities for each of the bounding boxes detected in an image.

Main で変数を初期化するInitialize variables in Main

MLContext クラスは、すべての ML.NET 操作の開始点で、mlContext を初期化することで、モデル作成ワークフローのオブジェクト間で共有できる新しい ML.NET 環境が作成されます。The MLContext class is a starting point for all ML.NET operations, and initializing mlContext creates a new ML.NET environment that can be shared across the model creation workflow objects. これは Entity Framework における DBContext と概念的には同じです。It's similar, conceptually, to DBContext in Entity Framework.

"Program.cs" の Main メソッドで outputFolder フィールドの下に次の行を追加して、MLContext の新しいインスタンスで mlContext 変数を初期化します。Initialize the mlContext variable with a new instance of MLContext by adding the following line to the Main method of Program.cs below the outputFolder field.

MLContext mlContext = new MLContext();

モデル出力を後処理するパーサーを作成するCreate a parser to post-process model outputs

このモデルでは、画像を 13 x 13 グリッドに分割します。各グリッド セルは 32px x 32px です。The model segments an image into a 13 x 13 grid, where each grid cell is 32px x 32px. 各グリッド セルには、5 個の潜在的なオブジェクト境界ボックスが含まれます。Each grid cell contains 5 potential object bounding boxes. 境界ボックスには 25 個の要素があります。A bounding box has 25 elements:

左側にグリッドのサンプル、右側に境界ボックスのサンプル

  • x 関連付けられているグリッド セルを基準にした、境界ボックスの中心の x 位置。x the x position of the bounding box center relative to the grid cell it's associated with.
  • y 関連付けられているグリッド セルを基準にした、境界ボックスの中心の y 位置。y the y position of the bounding box center relative to the grid cell it's associated with.
  • w 境界ボックスの幅。w the width of the bounding box.
  • h 境界ボックスの高さ。h the height of the bounding box.
  • o オブジェクトが境界ボックス内に存在する信頼度の値。これは物体らしさのスコアとも呼ばれます。o the confidence value that an object exists within the bounding box, also known as objectness score.
  • p1-p20 モデルによって予測される 20 個のクラスそれぞれのクラスの確率。p1-p20 class probabilities for each of the 20 classes predicted by the model.

5 個の境界ボックスそれぞれを 25 個の要素で記述し、合計すると、各グリッド セルに 125 個の要素が含まれます。In total, the 25 elements describing each of the 5 bounding boxes make up the 125 elements contained in each grid cell.

事前トレーニング済みの ONNX モデルによって生成される出力は、長さ 21125 の float 型の配列であり、寸法が 125 x 13 x 13 のテンソルの要素を表します。The output generated by the pre-trained ONNX model is a float array of length 21125, representing the elements of a tensor with dimensions 125 x 13 x 13. モデルによって生成される予測をテンソルに変換するには、処理後の作業が必要です。In order to transform the predictions generated by the model into a tensor, some post-processing work is required. これを行うには、出力を解析する一連のクラスを作成します。To do so, create a set of classes to help parse the output.

新しいディレクトリをプロジェクトに追加し、一連のパーサー クラスを編成します。Add a new directory to your project to organize the set of parser classes.

  1. ソリューション エクスプローラーで、プロジェクトを右クリックし、 [追加] > [新しいフォルダー] を選択します。In Solution Explorer, right-click the project, and then select Add > New Folder. ソリューション エクスプローラーに新しいフォルダーが表示されたら、"YoloParser" と名前を付けます。When the new folder appears in the Solution Explorer, name it "YoloParser".

境界ボックスと寸法を作成するCreate bounding boxes and dimensions

モデルから出力されるデータには、画像内のオブジェクトの境界ボックスの座標と寸法が含まれています。The data output by the model contains coordinates and dimensions of the bounding boxes of objects within the image. 寸法の基底クラスを作成します。Create a base class for dimensions.

  1. ソリューション エクスプローラーで、"YoloParser" ディレクトリを右クリックし、 [追加] > [新しい項目] の順に選択します。In Solution Explorer, right-click the YoloParser directory, and then select Add > New Item.

  2. [新しい項目の追加] ダイアログボックスで [クラス] を選択し、 [名前] フィールドを "DimensionsBase.cs" に変更します。In the Add New Item dialog box, select Class and change the Name field to DimensionsBase.cs. 次に [追加] を選択します。Then, select the Add button.

    コード エディターで "DimensionsBase.cs" ファイルが開きます。The DimensionsBase.cs file opens in the code editor. すべての using ステートメントと既存のクラス定義を削除します。Remove all using statements and existing class definition.

    DimensionsBase クラスの次のコードを "DimensionsBase.cs" ファイルに追加します。Add the following code for the DimensionsBase class to the DimensionsBase.cs file:

    public class DimensionsBase
    {
        public float X { get; set; }
        public float Y { get; set; }
        public float Height { get; set; }
        public float Width { get; set; }
    }
    

    DimensionsBase には、次の float プロパティがあります。DimensionsBase has the following float properties:

    • X には、x 軸に沿ったオブジェクトの位置が格納されます。X contains the position of the object along the x-axis.
    • Y には、y 軸に沿ったオブジェクトの位置が格納されます。Y contains the position of the object along the y-axis.
    • Height には、オブジェクトの高さが格納されます。Height contains the height of the object.
    • Width には、オブジェクトの幅が格納されます。Width contains the width of the object.

次に、境界ボックスのクラスを作成します。Next, create a class for your bounding boxes.

  1. ソリューション エクスプローラーで、"YoloParser" ディレクトリを右クリックし、 [追加] > [新しい項目] の順に選択します。In Solution Explorer, right-click the YoloParser directory, and then select Add > New Item.

  2. [新しい項目の追加] ダイアログボックスで [クラス] を選択し、 [名前] フィールドを "YoloBoundingBox.cs" に変更します。In the Add New Item dialog box, select Class and change the Name field to YoloBoundingBox.cs. 次に [追加] を選択します。Then, select the Add button.

    コード エディターで "YoloBoundingBox.cs" ファイルが開きます。The YoloBoundingBox.cs file opens in the code editor. "YoloBoundingBox.cs" の先頭に次の using ステートメントを追加します。Add the following using statement to the top of YoloBoundingBox.cs:

    using System.Drawing;
    

    既存のクラス定義のすぐ上に、BoundingBoxDimensions という新しいクラス定義を追加します。これは DimensionsBase クラスから継承して、それぞれの境界ボックスの寸法を格納します。Just above the existing class definition, add a new class definition called BoundingBoxDimensions that inherits from the DimensionsBase class to contain the dimensions of the respective bounding box.

    public class BoundingBoxDimensions : DimensionsBase { }
    

    既存の YoloBoundingBox クラス定義を削除し、YoloBoundingBox クラスの次のコードを "YoloBoundingBox.cs" ファイルに追加します。Remove the existing YoloBoundingBox class definition and add the following code for the YoloBoundingBox class to the YoloBoundingBox.cs file:

    public class YoloBoundingBox
    {
        public BoundingBoxDimensions Dimensions { get; set; }
    
        public string Label { get; set; }
    
        public float Confidence { get; set; }
    
        public RectangleF Rect
        {
            get { return new RectangleF(Dimensions.X, Dimensions.Y, Dimensions.Width, Dimensions.Height); }
        }
    
        public Color BoxColor { get; set; }
    }
    

    YoloBoundingBox には、次のプロパティがあります。YoloBoundingBox has the following properties:

    • Dimensions には、境界ボックスの寸法が格納されます。Dimensions contains dimensions of the bounding box.
    • Label には、境界ボックス内で検出されるオブジェクトのクラスが格納されます。Label contains the class of object detected within the bounding box.
    • Confidence には、クラスの信頼度が格納されます。Confidence contains the confidence of the class.
    • Rect には、境界ボックスの寸法の四角形の表現が格納されます。Rect contains the rectangle representation of the bounding box's dimensions.
    • BoxColor には、画像への描画に使用される各クラスに関連付けられた色が格納されます。BoxColor contains the color associated with the respective class used to draw on the image.

パーサーを作成するCreate the parser

寸法と境界ボックスのクラスが作成されたので、次はパーサーを作成します。Now that the classes for dimensions and bounding boxes are created, it's time to create the parser.

  1. ソリューション エクスプローラーで、"YoloParser" ディレクトリを右クリックし、 [追加] > [新しい項目] の順に選択します。In Solution Explorer, right-click the YoloParser directory, and then select Add > New Item.

  2. [新しい項目の追加] ダイアログボックスで [クラス] を選択し、 [名前] フィールドを "YoloOutputParser.cs" に変更します。In the Add New Item dialog box, select Class and change the Name field to YoloOutputParser.cs. 次に [追加] を選択します。Then, select the Add button.

    コード エディターで "YoloOutputParser.cs" ファイルが開きます。The YoloOutputParser.cs file opens in the code editor. "YoloOutputParser.cs" の先頭に次の using ステートメントを追加します。Add the following using statement to the top of YoloOutputParser.cs:

    using System;
    using System.Collections.Generic;
    using System.Drawing;
    using System.Linq;
    

    既存の YoloOutputParser クラス定義内に、画像の各セルの寸法を格納する入れ子になったクラスを追加します。Inside the existing YoloOutputParser class definition, add a nested class that contains the dimensions of each of the cells in the image. YoloOutputParser クラス定義の先頭で DimensionsBase クラスから継承する CellDimensions クラスに次のコードを追加します。Add the following code for the CellDimensions class that inherits from the DimensionsBase class at the top of the YoloOutputParser class definition.

    class CellDimensions : DimensionsBase { }
    
  3. YoloOutputParser クラス定義内に、次の定数とフィールドを追加します。Inside the YoloOutputParser class definition, add the following constants and fields.

    public const int ROW_COUNT = 13;
    public const int COL_COUNT = 13;
    public const int CHANNEL_COUNT = 125;
    public const int BOXES_PER_CELL = 5;
    public const int BOX_INFO_FEATURE_COUNT = 5;
    public const int CLASS_COUNT = 20;
    public const float CELL_WIDTH = 32;
    public const float CELL_HEIGHT = 32;
    
    private int channelStride = ROW_COUNT * COL_COUNT;
    
    • ROW_COUNT は、画像が分割されるグリッドの行数です。ROW_COUNT is the number of rows in the grid the image is divided into.
    • COL_COUNT は、画像が分割されるグリッドの列数です。COL_COUNT is the number of columns in the grid the image is divided into.
    • CHANNEL_COUNT は、グリッドの 1 つのセルに含まれる値の合計数です。CHANNEL_COUNT is the total number of values contained in one cell of the grid.
    • BOXES_PER_CELL は、セル内の境界ボックスの数です。BOXES_PER_CELL is the number of bounding boxes in a cell,
    • BOX_INFO_FEATURE_COUNT は、ボックス内に含まれる特徴の数です (x、y、高さ、幅、信頼度)。BOX_INFO_FEATURE_COUNT is the number of features contained within a box (x,y,height,width,confidence).
    • CLASS_COUNT は、各境界ボックスに含まれるクラス予測の数です。CLASS_COUNT is the number of class predictions contained in each bounding box.
    • CELL_WIDTH は、画像グリッド内の 1 つのセルの幅です。CELL_WIDTH is the width of one cell in the image grid.
    • CELL_HEIGHT は、画像グリッド内の 1 つのセルの高さです。CELL_HEIGHT is the height of one cell in the image grid.
    • channelStride は、グリッド内の現在のセルの開始位置です。channelStride is the starting position of the current cell in the grid.

    モデルで予測 (スコアリングとも呼ばれる) が行われると、416px x 416px の入力画像が 13 x 13 のサイズのセルから成るグリッドに分割されます。When the model makes a prediction, also known as scoring, it divides the 416px x 416px input image into a grid of cells the size of 13 x 13. 各セルには 32px x 32px が含まれます。Each cell contains is 32px x 32px. 各セルには 5 個の境界ボックスがあり、それぞれに 5 つの特徴 (x、y、幅、高さ、信頼度) があります。Within each cell, there are 5 bounding boxes each containing 5 features (x, y, width, height, confidence). また、各境界ボックスには、各クラスの確率 (この例では 20) が格納されます。In addition, each bounding box contains the probability of each of the classes, which in this case is 20. したがって、各セルには 125 個の情報が含まれます (5 つの特徴 + 20 個のクラスの確率)。Therefore, each cell contains 125 pieces of information (5 features + 20 class probabilities).

5 個の境界ボックスすべてについて、channelStride の下にアンカーのリストを作成します。Create a list of anchors below channelStride for all 5 bounding boxes:

private float[] anchors = new float[]
{
    1.08F, 1.19F, 3.42F, 4.41F, 6.63F, 11.38F, 9.42F, 5.11F, 16.62F, 10.52F
};

アンカーは、境界ボックスの定義済みの高さと幅の比率です。Anchors are pre-defined height and width ratios of bounding boxes. モデルによって検出されるほとんどのオブジェクトまたはクラスには、同様の比率があります。Most object or classes detected by a model have similar ratios. これは、境界ボックスを作成するときに役立ちます。This is valuable when it comes to creating bounding boxes. 境界ボックスの予測ではなく、事前に定義された寸法からのオフセットが計算されるため、境界ボックスを予測するために必要な計算が少なくなります。Instead of predicting the bounding boxes, the offset from the pre-defined dimensions is calculated therefore reducing the computation required to predict the bounding box. 通常、これらのアンカーの比率は、使用されるデータセットに基づいて計算されます。Typically these anchor ratios are calculated based on the dataset used. この場合は、データセットは既知であり、値が事前に計算されているため、アンカーをハードコーディングすることができます。In this case, because the dataset is known and the values have been pre-computed, the anchors can be hard-coded.

次に、モデルで予測するラベルまたはクラスを定義します。Next, define the labels or classes that the model will predict. このモデルでは、元の YOLOv2 モデルによって予測されるクラスの合計数のサブセットである 20 個のクラスを予測します。This model predicts 20 classes, which is a subset of the total number of classes predicted by the original YOLOv2 model.

anchors の下にラベルのリストを追加します。Add your list of labels below the anchors.

private string[] labels = new string[]
{
    "aeroplane", "bicycle", "bird", "boat", "bottle",
    "bus", "car", "cat", "chair", "cow",
    "diningtable", "dog", "horse", "motorbike", "person",
    "pottedplant", "sheep", "sofa", "train", "tvmonitor"
};

各クラスには色が関連付けられています。There are colors associated with each of the classes. labels の下にクラスの色を割り当てます。Assign your class colors below your labels:

private static Color[] classColors = new Color[]
{
    Color.Khaki,
    Color.Fuchsia,
    Color.Silver,
    Color.RoyalBlue,
    Color.Green,
    Color.DarkOrange,
    Color.Purple,
    Color.Gold,
    Color.Red,
    Color.Aquamarine,
    Color.Lime,
    Color.AliceBlue,
    Color.Sienna,
    Color.Orchid,
    Color.Tan,
    Color.LightPink,
    Color.Yellow,
    Color.HotPink,
    Color.OliveDrab,
    Color.SandyBrown,
    Color.DarkTurquoise
};

ヘルパー関数を作成するCreate helper functions

後処理フェーズには、関係する一連の手順が含まれています。There are a series of steps involved in the post-processing phase. このためには、いくつかのヘルパー メソッドを使用できます。To help with that, several helper methods can be employed.

パーサーによって使用されるヘルパー メソッドは次のとおりです。The helper methods used in by the parser are:

  • Sigmoid では、0 から 1 までの数値を出力するシグモイド関数が適用されます。Sigmoid applies the sigmoid function that outputs a number between 0 and 1.
  • Softmax では、入力ベクトルが確率分布に正規化されます。Softmax normalizes an input vector into a probability distribution.
  • GetOffset では、1 次元モデルの出力の要素が、125 x 13 x 13 テンソルの対応する位置にマップされます。GetOffset maps elements in the one-dimensional model output to the corresponding position in a 125 x 13 x 13 tensor.
  • ExtractBoundingBoxes では、モデル出力から GetOffset メソッドを使用して、境界ボックスの寸法が抽出されます。ExtractBoundingBoxes extracts the bounding box dimensions using the GetOffset method from the model output.
  • GetConfidence では、モデルで、オブジェクトが検出されたことをどのくらい確信しているかを示す信頼度が抽出され、Sigmoid 関数を使用してパーセンテージ値に変換されます。GetConfidence extracts the confidence value that states how sure the model is that it has detected an object and uses the Sigmoid function to turn it into a percentage.
  • MapBoundingBoxToCell では、境界ボックスの寸法を使用して、画像内の各セルにマップされます。MapBoundingBoxToCell uses the bounding box dimensions and maps them onto its respective cell within the image.
  • ExtractClasses では、GetOffset メソッドを使用してモデルの出力から境界ボックスのクラス予測が抽出され、Softmax メソッドを使用して確率分布に変換されます。ExtractClasses extracts the class predictions for the bounding box from the model output using the GetOffset method and turns them into a probability distribution using the Softmax method.
  • GetTopResult では、確率が最も高い予測クラスのリストからクラスが選択されます。GetTopResult selects the class from the list of predicted classes with the highest probability.
  • IntersectionOverUnion では、確率が低い境界ボックスの重なりがフィルター処理されます。IntersectionOverUnion filters overlapping bounding boxes with lower probabilities.

すべてのヘルパー メソッドのコードを classColors のリストの下に追加します。Add the code for all the helper methods below your list of classColors.

private float Sigmoid(float value)
{
    var k = (float)Math.Exp(value);
    return k / (1.0f + k);
}

private float[] Softmax(float[] values)
{
    var maxVal = values.Max();
    var exp = values.Select(v => Math.Exp(v - maxVal));
    var sumExp = exp.Sum();

    return exp.Select(v => (float)(v / sumExp)).ToArray();
}

private int GetOffset(int x, int y, int channel)
{
    // YOLO outputs a tensor that has a shape of 125x13x13, which 
    // WinML flattens into a 1D array.  To access a specific channel 
    // for a given (x,y) cell position, we need to calculate an offset
    // into the array
    return (channel * this.channelStride) + (y * COL_COUNT) + x;
}

private BoundingBoxDimensions ExtractBoundingBoxDimensions(float[] modelOutput, int x, int y, int channel)
{
    return new BoundingBoxDimensions
    {
        X = modelOutput[GetOffset(x, y, channel)],
        Y = modelOutput[GetOffset(x, y, channel + 1)],
        Width = modelOutput[GetOffset(x, y, channel + 2)],
        Height = modelOutput[GetOffset(x, y, channel + 3)]
    };
}

private float GetConfidence(float[] modelOutput, int x, int y, int channel)
{
    return Sigmoid(modelOutput[GetOffset(x, y, channel + 4)]);
}

private CellDimensions MapBoundingBoxToCell(int x, int y, int box, BoundingBoxDimensions boxDimensions)
{
    return new CellDimensions
    {
        X = ((float)x + Sigmoid(boxDimensions.X)) * CELL_WIDTH,
        Y = ((float)y + Sigmoid(boxDimensions.Y)) * CELL_HEIGHT,
        Width = (float)Math.Exp(boxDimensions.Width) * CELL_WIDTH * anchors[box * 2],
        Height = (float)Math.Exp(boxDimensions.Height) * CELL_HEIGHT * anchors[box * 2 + 1],
    };
}

public float[] ExtractClasses(float[] modelOutput, int x, int y, int channel)
{
    float[] predictedClasses = new float[CLASS_COUNT];
    int predictedClassOffset = channel + BOX_INFO_FEATURE_COUNT;
    for (int predictedClass = 0; predictedClass < CLASS_COUNT; predictedClass++)
    {
        predictedClasses[predictedClass] = modelOutput[GetOffset(x, y, predictedClass + predictedClassOffset)];
    }
    return Softmax(predictedClasses);
}

private ValueTuple<int, float> GetTopResult(float[] predictedClasses)
{
    return predictedClasses
        .Select((predictedClass, index) => (Index: index, Value: predictedClass))
        .OrderByDescending(result => result.Value)
        .First();
}

private float IntersectionOverUnion(RectangleF boundingBoxA, RectangleF boundingBoxB)
{
    var areaA = boundingBoxA.Width * boundingBoxA.Height;

    if (areaA <= 0)
        return 0;

    var areaB = boundingBoxB.Width * boundingBoxB.Height;

    if (areaB <= 0)
        return 0;

    var minX = Math.Max(boundingBoxA.Left, boundingBoxB.Left);
    var minY = Math.Max(boundingBoxA.Top, boundingBoxB.Top);
    var maxX = Math.Min(boundingBoxA.Right, boundingBoxB.Right);
    var maxY = Math.Min(boundingBoxA.Bottom, boundingBoxB.Bottom);

    var intersectionArea = Math.Max(maxY - minY, 0) * Math.Max(maxX - minX, 0);

    return intersectionArea / (areaA + areaB - intersectionArea);
}

すべてのヘルパー メソッドを定義したら、次はそれらを使用してモデルの出力を処理します。Once you have defined all of the helper methods, it's time to use them to process the model output.

IntersectionOverUnion メソッドの下に、モデルによって生成される出力を処理する ParseOutputs メソッドを作成します。Below the IntersectionOverUnion method, create the ParseOutputs method to process the output generated by the model.

public IList<YoloBoundingBox> ParseOutputs(float[] yoloModelOutputs, float threshold = .3F)
{

}

境界ボックスを格納するリストを作成し、ParseOutputs メソッド内に変数を定義します。Create a list to store your bounding boxes and define variables inside the ParseOutputs method.

var boxes = new List<YoloBoundingBox>();

各画像は、13 x 13 セルのグリッドに分割されます。Each image is divided into a grid of 13 x 13 cells. 各セルには 5 個の境界ボックスがあります。Each cell contains five bounding boxes. boxes 変数の下に、各セルのすべてのボックスを処理するコードを追加します。Below the boxes variable, add code to process all of the boxes in each of the cells.

for (int row = 0; row < ROW_COUNT; row++)
{
    for (int column = 0; column < COL_COUNT; column++)
    {
        for (int box = 0; box < BOXES_PER_CELL; box++)
        {

        }
    }
}

最も内側のループ内で、1 次元モデルの出力に含まれる現在のボックスの開始位置を計算します。Inside the inner-most loop, calculate the starting position of the current box within the one-dimensional model output.

var channel = (box * (CLASS_COUNT + BOX_INFO_FEATURE_COUNT));

その直下で、ExtractBoundingBoxDimensions メソッドを使用して、現在の境界ボックスの寸法を取得します。Directly below that, use the ExtractBoundingBoxDimensions method to get the dimensions of the current bounding box.

BoundingBoxDimensions boundingBoxDimensions = ExtractBoundingBoxDimensions(yoloModelOutputs, row, column, channel);

次に、GetConfidence メソッドを使用して、現在の境界ボックスの信頼度を取得します。Then, use the GetConfidence method to get the confidence for the current bounding box.

float confidence = GetConfidence(yoloModelOutputs, row, column, channel);

その後、MapBoundingBoxToCell メソッドを使用して、現在の境界ボックスを現在処理中のセルにマップします。After that, use the MapBoundingBoxToCell method to map the current bounding box to the current cell being processed.

CellDimensions mappedBoundingBox = MapBoundingBoxToCell(row, column, box, boundingBoxDimensions);

さらに処理を進める前に、信頼度の値が、指定されたしきい値より大きいかどうかを確認します。Before doing any further processing, check whether your confidence value is greater than the threshold provided. そうでない場合は、次の境界ボックスを処理します。If not, process the next bounding box.

if (confidence < threshold)
    continue;

それ以外の場合は、出力の処理を続行します。Otherwise, continue processing the output. 次の手順では、ExtractClasses メソッドを使用して、現在の境界ボックスの予測クラスの確率分布を取得します。The next step is to get the probability distribution of the predicted classes for the current bounding box using the ExtractClasses method.

float[] predictedClasses = ExtractClasses(yoloModelOutputs, row, column, channel);

次に、GetTopResult メソッドを使用して、現在のボックスの確率が最も高いクラスの値とインデックスを取得し、スコアを計算します。Then, use the GetTopResult method to get the value and index of the class with the highest probability for the current box and compute its score.

var (topResultIndex, topResultScore) = GetTopResult(predictedClasses);
var topScore = topResultScore * confidence;

ここでも topScore を使用して、指定されたしきい値を超える境界ボックスのみを保持します。Use the topScore to once again keep only those bounding boxes that are above the specified threshold.

if (topScore < threshold)
    continue;

最後に、現在の境界ボックスがしきい値を超えている場合は、新しい BoundingBox オブジェクトを作成して boxes リストに追加します。Finally, if the current bounding box exceeds the threshold, create a new BoundingBox object and add it to the boxes list.

boxes.Add(new YoloBoundingBox()
{
    Dimensions = new BoundingBoxDimensions
    {
        X = (mappedBoundingBox.X - mappedBoundingBox.Width / 2),
        Y = (mappedBoundingBox.Y - mappedBoundingBox.Height / 2),
        Width = mappedBoundingBox.Width,
        Height = mappedBoundingBox.Height,
    },
    Confidence = topScore,
    Label = labels[topResultIndex],
    BoxColor = classColors[topResultIndex]
});

画像内のすべてのセルが処理されたら、boxes リストを返します。Once all cells in the image have been processed, return the boxes list. ParseOutputs メソッドの最も外側の for-loop ループの下に、次の return ステートメントを追加します。Add the following return statement below the outer-most for-loop in the ParseOutputs method.

return boxes;

重複するボックスをフィルター処理するFilter overlapping boxes

これで、信頼度の高いすべての境界ボックスがモデルの出力から抽出されたので、重複している画像を削除するために追加のフィルター処理を行う必要があります。Now that all of the highly confident bounding boxes have been extracted from the model output, additional filtering needs to be done to remove overlapping images. ParseOutputs メソッドの下に FilterBoundingBoxes というメソッドを追加します。Add a method called FilterBoundingBoxes below the ParseOutputs method:

public IList<YoloBoundingBox> FilterBoundingBoxes(IList<YoloBoundingBox> boxes, int limit, float threshold)
{

}

まずは、FilterBoundingBoxes メソッド内に、検出されたボックスのサイズと等しい配列を作成し、すべてのスロットをアクティブまたは処理の準備完了にマークします。Inside the FilterBoundingBoxes method, start off by creating an array equal to the size of detected boxes and marking all slots as active or ready for processing.

var activeCount = boxes.Count;
var isActiveBoxes = new bool[boxes.Count];

for (int i = 0; i < isActiveBoxes.Length; i++)
    isActiveBoxes[i] = true;

次に、境界ボックスが格納されたリストを信頼度に基づいて降順に並べ替えます。Then, sort the list containing your bounding boxes in descending order based on confidence.

var sortedBoxes = boxes.Select((b, i) => new { Box = b, Index = i })
                    .OrderByDescending(b => b.Box.Confidence)
                    .ToList();

その後、フィルター処理された結果を保持するリストを作成します。After that, create a list to hold the filtered results.

var results = new List<YoloBoundingBox>();

各境界ボックスを反復処理して、各境界ボックスの処理を開始します。Begin processing each bounding box by iterating over each of the bounding boxes.

for (int i = 0; i < boxes.Count; i++)
{

}

この for ループ内で、現在の境界ボックスを処理できるかどうかを確認します。Inside of this for-loop, check whether the current bounding box can be processed.

if (isActiveBoxes[i])
{

}

その場合は、境界ボックスを結果のリストに追加します。If so, add the bounding box to the list of results. 結果が、抽出されるボックスの指定された制限を超えると、ループから抜け出します。If the results exceed the specified limit of boxes to be extracted, break out of the loop. if ステートメント内に次のコードを追加します。Add the following code inside the if-statement.

var boxA = sortedBoxes[i].Box;
results.Add(boxA);

if (results.Count >= limit)
    break;

それ以外の場合は、隣接する境界ボックスを見ます。Otherwise, look at the adjacent bounding boxes. ボックスの制限チェックの下に次のコードを追加します。Add the following code below the box limit check.

for (var j = i + 1; j < boxes.Count; j++)
{

}

最初のボックスと同様に、隣接するボックスがアクティブまたは処理の準備ができている場合は、IntersectionOverUnion メソッドを使用して、最初のボックスと 2 番目のボックスが、指定されたしきい値を超えているかどうかを確認します。Like the first box, if the adjacent box is active or ready to be processed, use the IntersectionOverUnion method to check whether the first box and the second box exceed the specified threshold. 次のコードを、最も内側の for ループに追加します。Add the following code to your innermost for-loop.

if (isActiveBoxes[j])
{
    var boxB = sortedBoxes[j].Box;

    if (IntersectionOverUnion(boxA.Rect, boxB.Rect) > threshold)
    {
        isActiveBoxes[j] = false;
        activeCount--;

        if (activeCount <= 0)
            break;
    }
}

隣接する境界ボックスを確認する最も内側の for ループの外側で、処理する残りの境界ボックスがあるかどうかを確認します。Outside of the inner-most for-loop that checks adjacent bounding boxes, see whether there are any remaining bounding boxes to be processed. そうでない場合は、外側の for ループから抜け出します。If not, break out of the outer for-loop.

if (activeCount <= 0)
    break;

最後に、FilterBoundingBoxes メソッドの最初の for ループの外側で、次の結果を返します。Finally, outside of the initial for-loop of the FilterBoundingBoxes method, return the results:

return results;

これでセットアップは終了です。Great! 次は、このコードをモデルと共に使用して、スコアリングを行います。Now it's time to use this code along with the model for scoring.

スコアリングにモデルを使用するUse the model for scoring

後処理の場合と同様に、スコアリングの手順にはいくつかの手順があります。Just like with post-processing, there are a few steps in the scoring steps. このために、スコアリング ロジックを格納するクラスをプロジェクトに追加します。To help with this, add a class that will contain the scoring logic to your project.

  1. ソリューション エクスプローラーで、プロジェクトを右クリックし、 [追加] > [新しい項目] を選択します。In Solution Explorer, right-click the project, and then select Add > New Item.

  2. [新しい項目の追加] ダイアログボックスで [クラス] を選択し、 [名前] フィールドを "OnnxModelScorer.cs" に変更します。In the Add New Item dialog box, select Class and change the Name field to OnnxModelScorer.cs. 次に [追加] を選択します。Then, select the Add button.

    コード エディターで "OnnxModelScorer.cs" ファイルが開きます。The OnnxModelScorer.cs file opens in the code editor. "OnnxModelScorer.cs" の先頭に次の using ステートメントを追加します。Add the following using statement to the top of OnnxModelScorer.cs:

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using Microsoft.ML;
    using Microsoft.ML.Data;
    using ObjectDetection.DataStructures;
    using ObjectDetection.YoloParser;
    

    OnnxModelScorer クラス定義内に、次の変数を追加します。Inside the OnnxModelScorer class definition, add the following variables.

    private readonly string imagesFolder;
    private readonly string modelLocation;
    private readonly MLContext mlContext;
    
    private IList<YoloBoundingBox> _boundingBoxes = new List<YoloBoundingBox>();
    

    その直下で、以前に定義された変数を初期化する OnnxModelScorer クラスのコンストラクターを作成します。Directly below that, create a constructor for the OnnxModelScorer class that will initialize the previously defined variables.

    public OnnxModelScorer(string imagesFolder, string modelLocation, MLContext mlContext)
    {
        this.imagesFolder = imagesFolder;
        this.modelLocation = modelLocation;
        this.mlContext = mlContext;
    }
    

    コンストラクターを作成したら、画像とモデルの設定に関連する変数を格納する 2 つの構造体を定義します。Once you have created the constructor, define a couple of structs that contain variables related to the image and model settings. モデルの入力として想定される高さと幅を格納するために、ImageNetSettings という名前の構造体を作成します。Create a struct called ImageNetSettings to contain the height and width expected as input for the model.

    public struct ImageNetSettings
    {
        public const int imageHeight = 416;
        public const int imageWidth = 416;
    }
    

    その後、TinyYoloModelSettings という別の構造体を作成します。これには、モデルの入力レイヤーと出力レイヤーの名前を格納します。After that, create another struct called TinyYoloModelSettings that contains the names of the input and output layers of the model. モデルの入力レイヤーと出力レイヤーの名前を視覚化するには、Netron のようなツールを使用できます。To visualize the name of the input and output layers of the model, you can use a tool like Netron.

    public struct TinyYoloModelSettings
    {
        // for checking Tiny yolo2 Model input and  output  parameter names,
        //you can use tools like Netron, 
        // which is installed by Visual Studio AI Tools
    
        // input tensor name
        public const string ModelInput = "image";
    
        // output tensor name
        public const string ModelOutput = "grid";
    }
    

    次に、スコアリングに使用するメソッドの最初のセットを作成します。Next, create the first set of methods use for scoring. OnnxModelScorer クラス内に LoadModel メソッドを作成します。Create the LoadModel method inside of your OnnxModelScorer class.

    private ITransformer LoadModel(string modelLocation)
    {
    
    }
    

    LoadModel メソッド内に、ログ記録用に次のコードを追加します。Inside the LoadModel method, add the following code for logging.

    Console.WriteLine("Read model");
    Console.WriteLine($"Model location: {modelLocation}");
    Console.WriteLine($"Default parameters: image size=({ImageNetSettings.imageWidth},{ImageNetSettings.imageHeight})");
    

    Fit メソッドが呼び出されたときに操作されるデータ スキーマが ML.NET パイプラインで認識されている必要があります。ML.NET pipelines need to know the data schema to operate on when the Fit method is called. この場合、トレーニングに似たプロセスが使用されます。In this case, a process similar to training will be used. ただし、実際のトレーニングは行われないため、空の IDataView を使用することができます。However, because no actual training is happening, it is acceptable to use an empty IDataView. 空のリストから、パイプラインの新しい IDataView を作成します。Create a new IDataView for the pipeline from an empty list.

    var data = mlContext.Data.LoadFromEnumerable(new List<ImageNetData>());
    

    その下にパイプラインを定義します。Below that, define the pipeline. パイプラインは、4 つの変換で構成されます。The pipeline will consist of four transforms.

    • LoadImages で、画像がビットマップとして読み込まれます。LoadImages loads the image as a Bitmap.
    • ResizeImages で、指定されたサイズ (この場合は 416 x 416) に画像の再スケーリングが行われます。ResizeImages rescales the image to the size specified (in this case, 416 x 416).
    • ExtractPixels で、画像のピクセル表現がビットマップから数値ベクターに変更されます。ExtractPixels changes the pixel representation of the image from a Bitmap to a numerical vector.
    • ApplyOnnxModel で、ONNX モデルが読み込まれ、それを使用して、指定されたデータでスコアリングされます。ApplyOnnxModel loads the ONNX model and uses it to score on the data provided.

    LoadModel メソッドで、data 変数の下にパイプラインを定義します。Define your pipeline in the LoadModel method below the data variable.

    var pipeline = mlContext.Transforms.LoadImages(outputColumnName: "image", imageFolder: "", inputColumnName: nameof(ImageNetData.ImagePath))
                    .Append(mlContext.Transforms.ResizeImages(outputColumnName: "image", imageWidth: ImageNetSettings.imageWidth, imageHeight: ImageNetSettings.imageHeight, inputColumnName: "image"))
                    .Append(mlContext.Transforms.ExtractPixels(outputColumnName: "image"))
                    .Append(mlContext.Transforms.ApplyOnnxModel(modelFile: modelLocation, outputColumnNames: new[] { TinyYoloModelSettings.ModelOutput }, inputColumnNames: new[] { TinyYoloModelSettings.ModelInput }));
    

    次に、スコアリングできるようにモデルをインスタンス化します。Now it's time to instantiate the model for scoring. パイプラインの Fit メソッドを呼び出し、後続の処理のためにそれを返します。Call the Fit method on the pipeline and return it for further processing.

    var model = pipeline.Fit(data);
    
    return model;
    

モデルが読み込まれたら、それを使用して予測を行うことができます。Once the model is loaded, it can then be used to make predictions. このプロセスを容易にするために、LoadModel メソッドの下に PredictDataUsingModel というメソッドを作成します。To facilitate that process, create a method called PredictDataUsingModel below the LoadModel method.

private IEnumerable<float[]> PredictDataUsingModel(IDataView testData, ITransformer model)
{

}

PredictDataUsingModel 内に、ログを記録できるように、次のコードを追加します。Inside the PredictDataUsingModel, add the following code for logging.

Console.WriteLine($"Images location: {imagesFolder}");
Console.WriteLine("");
Console.WriteLine("=====Identify the objects in the images=====");
Console.WriteLine("");

次に、Transform メソッドを使用してデータをスコアリングします。Then, use the Transform method to score the data.

IDataView scoredData = model.Transform(testData);

予測される確率を抽出し、追加の処理が行えるように確率を返します。Extract the predicted probabilities and return them for additional processing.

IEnumerable<float[]> probabilities = scoredData.GetColumn<float[]>(TinyYoloModelSettings.ModelOutput);

return probabilities;

これで両方の手順が設定されたので、結合して 1 つのメソッドにします。Now that both steps are set up, combine them into a single method. PredictDataUsingModel メソッドの下に、Score という新しいメソッドを追加します。Below the PredictDataUsingModel method, add a new method called Score.

public IEnumerable<float[]> Score(IDataView data)
{
    var model = LoadModel(modelLocation);

    return PredictDataUsingModel(data, model);
}

もう一息です!Almost there! これで、すべてを使用できるようになりました。Now it's time to put it all to use.

オブジェクトを検出するDetect objects

すべての設定が完了したので、次はいくつかのオブジェクトを検出します。Now that all of the setup is complete, it's time to detect some objects. まず、Program.cs クラスでスコアラーとパーサーの参照を追加します。Start off by adding references to the scorer and parser in your Program.cs class.

using ObjectDetection.YoloParser;
using ObjectDetection.DataStructures;

モデル出力のスコア付けと解析Score and parse model outputs

"Program.cs" クラスの Main メソッド内に、try-catch ステートメントを追加します。Inside the Main method of your Program.cs class, add a try-catch statement.

try
{

}
catch (Exception ex)
{
    Console.WriteLine(ex.ToString());
}

try ブロック内で、オブジェクト検出ロジックの実装を開始します。Inside of the try block, start implementing the object detection logic. まず、データを IDataView に読み込みます。First, load the data into an IDataView.

IEnumerable<ImageNetData> images = ImageNetData.ReadFromFile(imagesFolder);
IDataView imageDataView = mlContext.Data.LoadFromEnumerable(images);

次に、OnnxModelScorer のインスタンスを作成し、読み込まれるデータのスコアリングに使用します。Then, create an instance of OnnxModelScorer and use it to score the loaded data.

var modelScorer = new OnnxModelScorer(imagesFolder, modelFilePath, mlContext);

// Use model to score data
IEnumerable<float[]> probabilities = modelScorer.Score(imageDataView);

次は、後処理の手順です。Now it's time for the post-processing step. YoloOutputParser のインスタンスを作成し、モデル出力の処理に使用します。Create an instance of YoloOutputParser and use it to process the model output.

YoloOutputParser parser = new YoloOutputParser();

var boundingBoxes =
    probabilities
    .Select(probability => parser.ParseOutputs(probability))
    .Select(boxes => parser.FilterBoundingBoxes(boxes, 5, .5F));

モデル出力が処理されたら、次は画像上に境界ボックスを描画します。Once the model output has been processed, it's time to draw the bounding boxes on the images.

予測の視覚化Visualize predictions

モデルによって画像にスコアが付けられ、出力が処理された後は、境界ボックスが画像の上に描画される必要があります。After the model has scored the images and the outputs have been processed, the bounding boxes have to be drawn on the image. これを行うには、Program.cs の内部にある GetAbsolutePath メソッドの下に DrawBoundingBox というメソッドを追加します。To do so, add a method called DrawBoundingBox below the GetAbsolutePath method inside of Program.cs.

private static void DrawBoundingBox(string inputImageLocation, string outputImageLocation, string imageName, IList<YoloBoundingBox> filteredBoundingBoxes)
{

}

まず、DrawBoundingBox メソッドで画像を読み込み、高さと幅の寸法を取得します。First, load the image and get the height and width dimensions in the DrawBoundingBox method.

Image image = Image.FromFile(Path.Combine(inputImageLocation, imageName));

var originalImageHeight = image.Height;
var originalImageWidth = image.Width;

次に、for-each ループを作成し、モデルにより検出される各境界ボックスに反復処理を行います。Then, create a for-each loop to iterate over each of the bounding boxes detected by the model.

foreach (var box in filteredBoundingBoxes)
{

}

for-each ループ内で、境界ボックスの寸法を取得します。Inside of the for-each loop, get the dimensions of the bounding box.

var x = (uint)Math.Max(box.Dimensions.X, 0);
var y = (uint)Math.Max(box.Dimensions.Y, 0);
var width = (uint)Math.Min(originalImageWidth - x, box.Dimensions.Width);
var height = (uint)Math.Min(originalImageHeight - y, box.Dimensions.Height);

境界ボックスの寸法は 416 x 416 のモデル入力に対応しているため、画像の実際のサイズに合わせて境界ボックスの寸法を拡大縮小します。Because the dimensions of the bounding box correspond to the model input of 416 x 416, scale the bounding box dimensions to match the actual size of the image.

x = (uint)originalImageWidth * x / OnnxModelScorer.ImageNetSettings.imageWidth;
y = (uint)originalImageHeight * y / OnnxModelScorer.ImageNetSettings.imageHeight;
width = (uint)originalImageWidth * width / OnnxModelScorer.ImageNetSettings.imageWidth;
height = (uint)originalImageHeight * height / OnnxModelScorer.ImageNetSettings.imageHeight;

次に、各境界ボックスの上に表示されるテキストのテンプレートを定義します。Then, define a template for text that will appear above each bounding box. テキストには、対応する境界ボックス内のオブジェクトのクラスのほか、信頼度を含めます。The text will contain the class of the object inside of the respective bounding box as well as the confidence.

string text = $"{box.Label} ({(box.Confidence * 100).ToString("0")}%)";

画像を描画するために、Graphics オブジェクトに変換します。In order to draw on the image, convert it to a Graphics object.

using (Graphics thumbnailGraphic = Graphics.FromImage(image))
{

}

using コード ブロック内で、グラフィックの Graphics オブジェクト設定を調整します。Inside the using code block, tune the graphic's Graphics object settings.

thumbnailGraphic.CompositingQuality = CompositingQuality.HighQuality;
thumbnailGraphic.SmoothingMode = SmoothingMode.HighQuality;
thumbnailGraphic.InterpolationMode = InterpolationMode.HighQualityBicubic;

その下で、テキストと境界ボックスのフォントと色のオプションを設定します。Below that, set the font and color options for the text and bounding box.

// Define Text Options
Font drawFont = new Font("Arial", 12, FontStyle.Bold);
SizeF size = thumbnailGraphic.MeasureString(text, drawFont);
SolidBrush fontBrush = new SolidBrush(Color.Black);
Point atPoint = new Point((int)x, (int)y - (int)size.Height - 1);

// Define BoundingBox options
Pen pen = new Pen(box.BoxColor, 3.2f);
SolidBrush colorBrush = new SolidBrush(box.BoxColor);

FillRectangle メソッドを使用してテキストを含めるために、境界ボックスの上に四角形を作成して塗りつぶします。Create and fill a rectangle above the bounding box to contain the text using the FillRectangle method. これにより、テキストにコントラストを付け、読みやすさを向上させることができます。This will help contrast the text and improve readability.

thumbnailGraphic.FillRectangle(colorBrush, (int)x, (int)(y - size.Height - 1), (int)size.Width, (int)size.Height);

次に、DrawString メソッドと DrawRectangle メソッドを使用して、画像の上にテキストと境界ボックスを描画します。Then, Draw the text and bounding box on the image using the DrawString and DrawRectangle methods.

thumbnailGraphic.DrawString(text, drawFont, fontBrush, atPoint);

// Draw bounding box on image
thumbnailGraphic.DrawRectangle(pen, x, y, width, height);

for-each ループの外側に、画像を outputDirectory に保存するコードを追加します。Outside of the for-each loop, add code to save the images in the outputDirectory.

if (!Directory.Exists(outputImageLocation))
{
    Directory.CreateDirectory(outputImageLocation);
}

image.Save(Path.Combine(outputImageLocation, imageName));

アプリケーションが実行時に想定どおりに予測を行っているという追加のフィードバックを取得するには、Program.cs ファイル内にある DrawBoundingBox メソッドの下に LogDetectedObjects というメソッドを追加して、検出されたオブジェクトをコンソールに出力します。For additional feedback that the application is making predictions as expected at runtime, add a method called LogDetectedObjects below the DrawBoundingBox method in the Program.cs file to output the detected objects to the console.

private static void LogDetectedObjects(string imageName, IList<YoloBoundingBox> boundingBoxes)
{
    Console.WriteLine($".....The objects in the image {imageName} are detected as below....");

    foreach (var box in boundingBoxes)
    {
        Console.WriteLine($"{box.Label} and its Confidence score: {box.Confidence}");
    }

    Console.WriteLine("");
}

これで予測から視覚的なフィードバックを作成するヘルパー メソッドが用意できたので、スコア付けされた画像を反復処理する for ループを追加します。Now that you have helper methods to create visual feedback from the predictions, add a for-loop to iterate over each of the scored images.

for (var i = 0; i < images.Count(); i++)
{

}

for ループ内で、画像ファイルの名前と、画像に関連付けられている境界ボックスを取得します。Inside of the for-loop, get the name of the image file and the bounding boxes associated with it.

string imageFileName = images.ElementAt(i).Label;
IList<YoloBoundingBox> detectedObjects = boundingBoxes.ElementAt(i);

その後、DrawBoundingBox メソッドを使用して、画像の上に境界ボックスを描画します。Below that, use the DrawBoundingBox method to draw the bounding boxes on the image.

DrawBoundingBox(imagesFolder, outputFolder, imageFileName, detectedObjects);

最後に、LogDetectedObjects メソッドを使用し、予測をコンソールに出力します。Lastly, use the LogDetectedObjects method to output predictions to the console.

LogDetectedObjects(imageFileName, detectedObjects);

try-catch ステートメントの後に、プロセスの実行が完了したことを示すロジックを追加します。After the try-catch statement, add additional logic to indicate the process is done running.

Console.WriteLine("========= End of Process..Hit any Key ========");
Console.ReadLine();

これで完了です。That's it!

結果Results

上記の手順を実行した後、コンソール アプリを実行します (Ctrl + F5 キー)。After following the previous steps, run your console app (Ctrl + F5). 結果は以下の出力のようになるはずです。Your results should be similar to the following output. 警告メッセージまたは処理中のメッセージが表示される場合がありますが、わかりやすくするため、これらのメッセージは結果から削除してあります。You may see warnings or processing messages, but these messages have been removed from the following results for clarity.

=====Identify the objects in the images=====

.....The objects in the image image1.jpg are detected as below....
car and its Confidence score: 0.9697262
car and its Confidence score: 0.6674225
person and its Confidence score: 0.5226039
car and its Confidence score: 0.5224892
car and its Confidence score: 0.4675332

.....The objects in the image image2.jpg are detected as below....
cat and its Confidence score: 0.6461141
cat and its Confidence score: 0.6400049

.....The objects in the image image3.jpg are detected as below....
chair and its Confidence score: 0.840578
chair and its Confidence score: 0.796363
diningtable and its Confidence score: 0.6056048
diningtable and its Confidence score: 0.3737402

.....The objects in the image image4.jpg are detected as below....
dog and its Confidence score: 0.7608147
person and its Confidence score: 0.6321323
dog and its Confidence score: 0.5967442
person and its Confidence score: 0.5730394
person and its Confidence score: 0.5551759

========= End of Process..Hit any Key ========

画像を境界ボックスと共に表示するには、assets/images/output/ ディレクトリに移動します。To see the images with bounding boxes, navigate to the assets/images/output/ directory. 処理された画像の 1 つのサンプルを次に示します。Below is a sample from one of the processed images.

ダイニング ルームの処理済み画像のサンプル

おめでとうございます!Congratulations! これで、ML.NET でトレーニング済みの ONNX モデルを再利用してオブジェクト検出用の機械学習モデルを構築できました。You've now successfully built a machine learning model for object detection by reusing a pre-trained ONNX model in ML.NET.

このチュートリアルのソース コードは、dotnet/machinelearning-samples リポジトリにあります。You can find the source code for this tutorial at the dotnet/machinelearning-samples repository.

このチュートリアルでは、次の作業を行う方法を学びました。In this tutorial, you learned how to:

  • 問題を把握するUnderstand the problem
  • ONNX の概要と ML.NET でどのように動作するかについて説明します。Learn what ONNX is and how it works with ML.NET
  • モデルの概要Understand the model
  • 事前トレーニング済みモデルを再利用するReuse the pre-trained model
  • 読み込まれたモデルを使用してオブジェクトを検出するDetect objects with a loaded model

Machine Learning サンプルの GitHub リポジトリを確認し、拡張されたオブジェクト検出サンプルを探索してください。Check out the Machine Learning samples GitHub repository to explore an expanded object detection sample.