LoginSignup
6
4

More than 1 year has passed since last update.

ONNX Runtime Webを使ってBlazor WebAssemblyで手書き数字認識

Last updated at Posted at 2021-12-02

今年はBlazor WebAssemblyで画像処理をするアプリを開発していました。そのアプリで手書き数字認識の機能を搭載したいと思っていたところ、ONNX Runtime Webを利用して比較的簡単に実現することができました。そのときの手順を参考にしてBlazor WebAssemblyのアプリに指定した画像の数字を認識する簡単なプログラムを作ってみます。

ONNX Runtime Web (ORT Web) とは

ONNX (Open Neural Network Exchange) は機械学習モデルのフォーマットで、PyTorchなどの機械学習フレームワークからONNX形式でモデルをエクスポートすることができます。これまでONNX形式のモデルをWebブラウザで動作させるONNX.jsがありましたが、2021年9月2日にONNX Runtime Web (ORT Web) が公開されました。

ORT Webの概要

ONNX Runtime WebではWebAssemblyとWebGLバックエンドを別々に使用して、CPUとGPUの両方でWebブラウザでのモデル推論を高速化します。

セットアップ

Blazor WebAssembly

Visual Studio 2022でBlazor WebAssemblyのプロジェクトを作成して、NuGetでSixLabors.ImageSharpをインストールします。

Pages/Index.razorを以下のように編集します。

Pages/Index.razor
@page "/"

@inject IJSRuntime JS

@using SixLabors.ImageSharp;
@using SixLabors.ImageSharp.PixelFormats;
@using SixLabors.ImageSharp.Processing;

<PageTitle>Index</PageTitle>

<p>
    <InputFile OnChange="@LoadImages" accept=".png,.jpg,.jpeg,.bmp,.gif" />
</p>

@if (@isLoading)
{
    <p>Loading...</p>
}
else if (@resizedImage != null)
{
    MemoryStream stream = new();
    resizedImage.SaveAsPng(stream);
    <p>
        <img src="data:image/png;base64,@Convert.ToBase64String(stream.ToArray())" style="width: 300px;" />
    </p>
    <p>@result</p>
}


@code {
    private long maxFileSize = 1024 * 1024 * 24;
    private int maxAllowedFiles = 1;
    private bool isLoading = false;
    private Image<Rgba32>? resizedImage;
    private int result;

    private async Task LoadImages(InputFileChangeEventArgs e)
    {
        isLoading = true;
        StateHasChanged();

        foreach (var file in e.GetMultipleFiles(maxAllowedFiles))
        {
            MemoryStream stream = new();
            await file.OpenReadStream(maxFileSize).CopyToAsync(stream);
            var image = Image.Load(stream.ToArray());
            // 指定された画像ファイルを28x28にリサイズ
            resizedImage = image.Clone(img => img.Resize(28, 28));

            // 画像のデータをONNXのために変換
            float[] data = new float[1 * 1 * 28 * 28];
            for (int y = 0; y < 28; y++)
            {
                for (int x = 0; x < 28; x++)
                {
                    data[28 * y + x] = (float)(255.0 - resizedImage[x, y].R) / 255.0f;
                }
            }
            result = await JS.InvokeAsync<int>("runOnnxRuntime", data);
        }

        isLoading = false;
    }
}

wwwroot/index.htmldist/bundle.min.jsを読み込むタグを追加します。

wwwroot/index.html
    <script src="_framework/blazor.webassembly.js"></script>
    <script src="dist/bundle.min.js"></script>
</body>

</html>

ONNX Runtime Web (ORT Web)

さきほどとは別のフォルダを作成して、以下の3つのファイルを作成します。

main.js
export const ort = require('onnxruntime-web');

let session;
window.runOnnxRuntime = async (methodParameter) => {
  if (session == null) {
    session = await ort.InferenceSession.create('/mnist-8.onnx');
  }
  const dataIn = Float32Array.from(methodParameter);
  const tensorIn = new ort.Tensor('float32', dataIn, [1, 1, 28, 28]);

  const results = await session.run({ Input3: tensorIn });
  const resultData = Array.from(results['Plus214_Output_0'].data);
  var currentMax = resultData[0];
  var resultValue = 0;
  resultData.forEach((v, i) => {
    if (v > currentMax) {
      currentMax = v;
      resultValue = i;
    }
  });
  return resultValue;
};
package.json
{
  "name": "web-bundler",
  "private": true,
  "version": "1.0.0",
  "description": "(based on https://github.com/microsoft/onnxruntime-inference-examples/blob/main/js/quick-start_onnxruntime-web-bundler/package.json)",
  "dependencies": {
    "onnxruntime-web": "^1.8.0"
  },
  "devDependencies": {
    "copy-webpack-plugin": "^8.1.1",
    "webpack": "^5.36.2",
    "webpack-cli": "^4.6.0"
  }
}
webpack.config.js
const path = require('path');
const CopyPlugin = require('copy-webpack-plugin');
module.exports = () => {
  return {
    target: ['web'],
    entry: path.resolve(__dirname, 'main.js'),
    output: {
      path: path.resolve(__dirname, 'wwwroot', 'dist'),
      filename: 'bundle.min.js',
      library: {
        type: 'umd'
      }
    },
    plugins: [new CopyPlugin({
      patterns: [{ from: 'node_modules/onnxruntime-web/dist/*.wasm', to: '[name][ext]' }]
    })],
    mode: 'production'
  }
};

npmで依存関係のあるパッケージをインストールします。

$ npm install

以下のコマンドを実行すると、wwwroot/dist以下にファイルが作成されます。

$ npx webpack

作成されたwwwroot/distを、Blazor WebAssemblyのwwwroot/distにコピーします。

手書き数字認識のモデルをダウンロード

ONNXのリポジトリから手書き数字認識のモデルのmnist-8.onnxをダウンロードして、Blazor WebAssemblyのアプリのwwwroot以下にコピーします。

実行

画像を指定してしばらくするとリサイズされた画像が拡大して表示され、その下に認識結果が表示されます。

result.png

まとめ

ONNX Runtime Webを使ってBlazor WebAssemblyで手書き数字認識をしました。本当はせっかくBlazor WebAssemblyを使っているのだからJavascriptを書かずにC#のみで書きたかったのですが、Blazor WebAssemblyからML.NETを使ってONNXのモデルをロードしようとしたらうまくいかなかったので、まずはONNX Runtime Webを利用することにしました。今回のようにJavascriptの資産と連携できることもBlazor WebAssemblyのよいところかと思います。

6
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
4