前回の続き。

デモ (の続き)

前回はプロジェクトにONNXファイルを追加し、コードが自動生成されることを確認しました。
あと、前回記述を忘れましたが、アプリは .NET Core 3.0 です。

パッケージ追加

生成されたコードは様々なパッケージに依存しています。

これを解決するために、nugetで下記のパッケージを追加します。

  • Microsoft.Windows.SDK.Contracts
    • 10.0.17763.1000

これでビルドが通ります。

画像の読み込み処理の下準備

1
2
3
4
5
public sealed class Input
{
- public ImageFeatureValue image; // BitmapPixelFormat: Rgba8, BitmapAlphaMode: Premultiplied, width: 416, height: 416
+ public VideoFrame image; // BitmapPixelFormat: Rgba8, BitmapAlphaMode: Premultiplied, width: 416, height: 416
}

Parserの作成

先に、YOLOの出力をBoundingBoxに変換するパーサーを作ります。
幸いにして偉大なる先人が既に作成していらしたので、クラス名などを手直ししてプロジェクトに追加します。

上から下記の2つを移植します。

  • YoloBoundingBox.cs
  • YoloWinMLParser.cs

推論処理

モデルファイル、画像ファイルを読みこんで推論します。
コンソールなので引数にモデルファイルと画像ファイルのパスを渡します。
また、画像に対して結果を描画したいので、下記のパッケージを追加します。

  • System.Drawing.Common

そして出来上がったのが下記。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
using System;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Runtime.InteropServices.WindowsRuntime;
using Windows.Graphics.Imaging;
using Windows.Media;
using Windows.Storage;

namespace YoloTest
{
class Program
{
static void Main(string[] args)
{
// Create model
var modelPath = Path.GetFullPath(args[0]);
var modelFile = StorageFile.GetFileFromPathAsync(modelPath).GetAwaiter().GetResult();
var model = Model.CreateFromStreamAsync(modelFile).Result;

// Create VideoFrame from SoftwareBitmap
var imagePath = Path.GetFullPath(args[1]);
var file = StorageFile.GetFileFromPathAsync(imagePath).GetAwaiter().GetResult();
using var stream = file.OpenAsync(FileAccessMode.Read).GetAwaiter().GetResult();
var decoder = BitmapDecoder.CreateAsync(stream).GetAwaiter().GetResult();
var softwareBitmap = decoder.GetSoftwareBitmapAsync().GetAwaiter().GetResult();
var inputImage = VideoFrame.CreateWithSoftwareBitmap(softwareBitmap);

// Create input data
var input = new Input
{
image = inputImage
};

// Inference
var output = model.EvaluateAsync(input).Result;
var boxes = new YoloParser().ParseOutputs(output.grid.GetAsVectorView().ToArray(), 0.7f);

// Get raw byte array
var width = inputImage.SoftwareBitmap.PixelWidth;
var height = inputImage.SoftwareBitmap.PixelHeight;
var imageBytes = new byte[4 * width * height];
softwareBitmap.CopyToBuffer(imageBytes.AsBuffer());

// Draw source image to result image as background
using var result = new Bitmap(width, height);
using var g = Graphics.FromImage(result);
var data = result.LockBits(new Rectangle(0, 0, width, height), ImageLockMode.WriteOnly, PixelFormat.Format32bppArgb);
Marshal.Copy(imageBytes, 0, data.Scan0, imageBytes.Length);
result.UnlockBits(data);

// Draw bounding box
foreach (var box in boxes)
{
var x = width * box.X / 416;
var y = height * box.Y / 416;
var w = width * box.Width / 416;
var h = height * box.Height / 416;

g.DrawRectangle(Pens.Red, x, y, w, h);
g.DrawString(box.Label, SystemFonts.MessageBoxFont, Brushes.Red, x + 10, y + 10);
g.DrawString(box.Confidence.ToString(), SystemFonts.MessageBoxFont, Brushes.Red, x + 10, y + 30);
}

result.Save("result.jpg", ImageFormat.Jpeg);
}
}
}

結果の閾値は少し恣意的に選定しましたが、結果の画像は下記です。
なお、入力画像は、本家YOLOのリポジトリにあるサンプル画像です。

自転車が領域に対して矩形が大きすぎますが、他は概ね正しいです。
実際のYOLO v3は正しく認識できていた記憶があるので、Tiny故の精度の低さでしょうか?

まとめ

C#だけでYOLOを使うことができました。
モデルファイルの推論処理周りはだいぶ楽ですが、画像の読み書きは完全にUWPの世界で正直面倒….

Source