コンテンツにスキップ

C#でのResNet50v2による画像認識

このサンプルでは、Onnx Runtime C# APIを使用して、事前トレーニング済みのResNet50 v2 ONNXモデルを実行する方法を順を追って説明します。

このサンプルのソースコードはこちらで入手できます。

このサンプルを実行するには、次のものが必要です。

  1. お使いのOS(Mac、Windows、またはLinux)に.NET Core 3.1以上をインストールします。
  2. ResNet50 v2 ONNXモデルをローカルシステムにダウンロードします。
  3. モデルをテストするためにこの犬の写真をダウンロードします。お好きな画像を使用することもできます。

これで準備が整いましたので、画像上でモデルを実行するためのコードの追加を開始できます。簡単にするために、プログラムのmainメソッドでこれを行います。

まず、プログラムの引数を介して、モデルへのパスとテストしたい画像へのパスを読み取ります。

string modelFilePath = args[0];
string imageFilePath = args[1];

次に、クロスプラットフォームの画像ライブラリImageSharpを使用して画像を読み取ります。

using Image<Rgb24> image = Image.Load<Rgb24>(imageFilePath, out IImageFormat format);

後のステップで画像を効率的に前処理できるように、特にRgb24型を読み取っていることに注意してください。

次に、モデルが期待する適切なサイズ(224ピクセル×224ピクセル)に画像をリサイズします。

using Stream imageStream = new MemoryStream();
image.Mutate(x =>
{
x.Resize(new ResizeOptions
{
Size = new Size(224, 224),
Mode = ResizeMode.Crop
});
});
image.Save(imageStream, format);

アスペクト比を維持するために、中央クロップリサイズを行っていることに注意してください。

次に、モデルの要件に従って画像を前処理します。

// 画像データを入力するために多次元アクセスにDenseTensorを使用します
var mean = new[] { 0.485f, 0.456f, 0.406f };
var stddev = new[] { 0.229f, 0.224f, 0.225f };
DenseTensor<float> processedImage = new(new[] { 1, 3, 224, 224 });
image.ProcessPixelRows(accessor =>
{
for (int y = 0; y < accessor.Height; y++)
{
Span<Rgb24> pixelSpan = accessor.GetRowSpan(y);
for (int x = 0; x < accessor.Width; x++)
{
processedImage[0, 0, y, x] = ((pixelSpan[x].R / 255f) - mean[0]) / stddev[0];
processedImage[0, 1, y, x] = ((pixelSpan[x].G / 255f) - mean[1]) / stddev[1];
processedImage[0, 2, y, x] = ((pixelSpan[x].B / 255f) - mean[2]) / stddev[2];
}
}
});

ここでは、必要なサイズ(バッチサイズ、チャネル、高さ、幅)のテンソルを作成し、ピクセル値にアクセスして前処理し、最後に適切なインデックスでテンソルに割り当てています。

次に、モデルへの入力を作成します。

// テンソルバッファをピン留めし、
// DenseTensorバッファを直接利用するネイティブテンソルを持つOrtValueを作成します。
// これにより、OnnxRuntime内での余分なデータコピーが回避されます。
// ortValueの破棄時にピン留めが解除されます
using var inputOrtValue = OrtValue.CreateTensorValueFromMemory(OrtMemoryInfo.DefaultInstance,
processedImage.Buffer, new long[] { 1, 3, 224, 224 });
var inputs = new Dictionary<string, OrtValue>
{
{ "data", inputOrtValue }
}

ONNXモデルの入力ノード名を確認するには、Netronを使用してモデルを視覚化し、入力/出力名を確認できます。この場合、このモデルの入力ノード名はdataです。

次に、推論セッションを作成し、それに入力を通します。

using var session = new InferenceSession(modelFilePath);
using var runOptions = new RunOptions();
using IDisposableReadOnlyCollection<OrtValue> results = session.Run(runOptions, inputs, session.OutputNames);

次に、モデル自体では処理されないため、softmaxベクトルを取得するために出力を後処理する必要があります。

// アルゴリズムを適用するためにのみ結果を配列にコピーします。
// それ以外の場合、データはReadOnlySpan<T>またはSpan<T>を介してネイティブバッファから直接アクセスできます
var output = results[0].GetTensorDataAsSpan<float>().ToArray();
float sum = output.Sum(x => (float)Math.Exp(x));
IEnumerable<float> softmax = output.Select(x => (float)Math.Exp(x) / sum);

他のモデルでは、出力の前にSoftmaxノードを適用する場合があり、その場合はこのステップは不要です。繰り返しになりますが、Netronを使用してモデルの出力を確認できます。

次に、上位10件のクラス予測を抽出します。

IEnumerable<Prediction> top10 = softmax.Select((x, i) => new Prediction { Label = LabelMap.Labels[i], Confidence = x })
.OrderByDescending(x => x.Confidence)
.Take(10);

次に、上位10件の結果をコンソールに出力します。

Console.WriteLine("Top 10 predictions for ResNet50 v2...");
Console.WriteLine("--------------------------------------------------------------");
foreach (var t in top10)
{
Console.WriteLine($"Label: {t.Label}, Confidence: {t.Confidence}");
}

プログラムが作成されたので、次のコマンドで実行できます。

dotnet run [path-to-model] [path-to-image]

例:

dotnet run ~/Downloads/resnet50-v2-7.onnx ~/Downloads/dog.jpeg

これを次の画像で実行すると:

次の出力が得られます。

Top 10 predictions for ResNet50 v2...
--------------------------------------------------------------
Label: Golden Retriever, Confidence: 0.9212826
Label: Kuvasz, Confidence: 0.026514154
Label: Clumber Spaniel, Confidence: 0.012455719
Label: Labrador Retriever, Confidence: 0.004103844
Label: Saluki, Confidence: 0.0033182495
Label: Flat-Coated Retriever, Confidence: 0.0032045357
Label: English Setter, Confidence: 0.002513516
Label: Brittany, Confidence: 0.0023459378
Label: Cocker Spaniels, Confidence: 0.0019343802
Label: Sussex Spaniel, Confidence: 0.0019247672