備忘録

備忘録

C#でONNXファイルを利用して手書き数字を認識する方法

Ⅰ. はじめに

タイトルの通り「C#でONNXファイルを利用して手書き数字を認識する方法」です。

Ⅱ. やり方

1. WPFとして新規プロジェクトを作成する

.NET Core / .NET Framework どちらでもOKです。

2. 必要なパッケージをNuGetからインストールする
Install-Package Microsoft.ML.OnnxRuntime
Install-Package SixLabors.ImageSharp
3. MNIST を学習済みの ONNX ファイルをダウンロードし、model.onx をC:\model.onx に保存する

https://github.com/onnx/models/tree/master/mnist

4. サンプルプログラムを書く

MainWindow.xaml

<Window Height="160" Width="210">
  <Grid>
    <InkCanvas x:Name="inkCanvas1" StrokeCollected="InkCanvas1_StrokeCollected" Width="100" Height="100" Margin="10,11,0,0" Background="#FFA0A0A0" HorizontalAlignment="Left" VerticalAlignment="Top" />
    <TextBlock x:Name="textBlock1" FontSize="50" Margin="115,11,0,0" TextWrapping="Wrap" Text="0" VerticalAlignment="Top" HorizontalAlignment="Left" Width="27"/>
    <Button Content="Clear" Click="Button_Click" HorizontalAlignment="Left" Margin="115,83,0,0" VerticalAlignment="Top" Width="73" Height="28"/>
  </Grid>
</Window>

MainWindow.xaml.cs

namespace MNISTTest
{
  public partial class MainWindow : Window
  {
    const int ImgWidth = 28;
    const int ImgHeight = 28;

    public MainWindow()
    {
      InitializeComponent();
    }

    static float[] GetFloatArrayFromImage(byte[] imgBytes)
    {
      var floats = new float[ImgWidth * ImgHeight];

      using (var img = SixLabors.ImageSharp.Image.Load(imgBytes))
      {
        // img.Save("out.png");
        img.Mutate(x =>
          x.Resize(ImgWidth, ImgHeight)
          .BinaryThreshold(0.5f)); // 2値化する
        
        for (var x = 0; x < img.Width; x++)
        {
          for (var y = 0; y < img.Height; y++)
          {
            floats[x + y * img.Width] = (img[x, y].R == 255) ? 0 : 1;
          }
        }
      }

      return floats;
    }

    private byte[] GetImageBytes()
    {
      var bounds = VisualTreeHelper.GetDescendantBounds(inkCanvas1);
      var width = (int)inkCanvas1.Width;
      var height = (int)inkCanvas1.Height;
      var rtb = new RenderTargetBitmap(width, height, 96d, 96d, PixelFormats.Pbgra32);
      var drawingVisual = new DrawingVisual();

      using (var ctx = drawingVisual.RenderOpen())
      {
        var vb = new VisualBrush(inkCanvas1);
        ctx.DrawRectangle(vb, null, new Rect(new Point(bounds.X, bounds.Y), new Point(width, height)));
      }

      rtb.Render(drawingVisual);
      var encoder = new BmpBitmapEncoder();
      encoder.Frames.Add(BitmapFrame.Create(rtb));
      using (var ms = new MemoryStream())
      {
        encoder.Save(ms);
        return ms.ToArray();
      }
    }

    private int Check(float[] dimensions)
    {
      using (var session = new InferenceSession("C:\\model.onnx"))
      {
        var inputNodeName = session.InputMetadata.First().Key;
        var innodedims = session.InputMetadata.First().Value.Dimensions;
        var inputTensor = new DenseTensor<float>(dimensions, innodedims);

        var namedOnnxValues = new List<NamedOnnxValue>
        {
          NamedOnnxValue.CreateFromTensor(inputNodeName, inputTensor)
        };

        using (var results = session.Run(namedOnnxValues))
        {
          var resultScores = results.First().AsTensor<float>().ToArray();
          return Array.IndexOf(resultScores, resultScores.Max());
        }
      }
    }

    private void InkCanvas1_StrokeCollected(object sender, InkCanvasStrokeCollectedEventArgs e)
    {
      var imgBytes = GetImageBytes();
      var dimensions = GetFloatArrayFromImage(imgBytes);
      var result = Check(dimensions);

      textBlock1.Text = result.ToString();
    }

    private void Button_Click(object sender, RoutedEventArgs e)
    {
      inkCanvas1.Strokes.Clear();
    }
  }
}

実行結果

f:id:kagasu:20190521161401g:plain

WPFのInkCanvasを画像ファイルとして保存する方法

Ⅰ. はじめに

タイトルの通り「InkCanvasを画像ファイルとして保存する方法」です。

Ⅱ. やり方

1. サンプルプログラムを書く

MainWindow.xaml

<Window Height="150" Width="150">
  <Grid>
    <InkCanvas
      x:Name="inkCanvas1"
      Width="100"
      Height="100"
      Background="#B2B2B2"
      StrokeCollected="InkCanvas_StrokeCollected" />
  </Grid>
</Window>

MainWindow.xaml.cs

private void SaveImg(UIElement element, string path, BitmapEncoder encoder)
{
  var bounds = VisualTreeHelper.GetDescendantBounds(element);
  var width = (int)bounds.Width;
  var height = (int)bounds.Height;

  var rtb = new RenderTargetBitmap(width, height, 96d, 96d, PixelFormats.Pbgra32);

  var drawingVisual = new DrawingVisual();
  using (var ctx = drawingVisual.RenderOpen())
  {
    var vb = new VisualBrush(element);
    ctx.DrawRectangle(vb, null, new Rect(new Point(bounds.X, bounds.Y), new Point(width, height)));
  }

  rtb.Render(drawingVisual);
  encoder.Frames.Add(BitmapFrame.Create(rtb));
  using (var fs = File.Open(path, FileMode.OpenOrCreate))
  {
    encoder.Save(fs);
  }
}

private void InkCanvas_StrokeCollected(object sender, InkCanvasStrokeCollectedEventArgs e)
{
  SaveImg(inkCanvas1, "out.jpg", new JpegBitmapEncoder());
  SaveImg(inkCanvas1, "out.png", new PngBitmapEncoder());
  SaveImg(inkCanvas1, "out.bmp", new BmpBitmapEncoder());
}

実行結果

f:id:kagasu:20190521150439p:plain

WindowsでC++をWebAssemblyとしてコンパイルして実行する方法

Ⅰ. はじめに

タイトルの通り「WindowsC++をWebAssemblyとしてコンパイルして実行する方法」です。

Ⅱ. やり方

1. Emscripten をインストールする
git clone https://github.com/juj/emsdk.git
cd emsdk

emsdk install latest
emsdk activate latest
2. C++をWebAssemblyとしてコンパイルする

main.cpp

#include <iostream>

int main() {
  std::cout << "hello world" << std::endl;
  return 0;
}
C:\emsdk\emsdk_env.bat
emcc main.cpp -o main.wasm
3. Wasmer をインストールする

WasmerInstaller-x.x.x.exe
https://github.com/wasmerio/wasmer/releases

4. WebAssembly を実行する
wasmer run main.wasm

実行結果

hello world

git clone recursive のエラー対策方法

Ⅰ. はじめに

タイトルの通り「git clone recursive のエラー対策方法」です。

エラー例は以下のとおりです。

$ git clone --recursive https://github.com/user001/project001
(略)
Cloning into 'C:/project001/dependency001'...
Host key verification failed.
fatal: Could not read from remote repository.

Please make sure you have the correct access rights
and the repository exists.
fatal: clone of 'git@github.com:user002/dependency001.git' into submodule path 'C:/project001/dependency001' failed
Failed to clone 'dependency001' a second time, aborting

Ⅱ. 対策方法1

1. git clone する
git clone --recursive https://github.com/user001/project001
2. .git/config を編集する

httpsから始まるURLに書き換える
※.gitディレクトリは隠しフォルダになっています。

[submodule "dependency001"]
#  url = git@github.com:user002/dependency001.git
   url = https://github.com/user002/dependency001
3. git submodule update する
git submodule update --recursive

Ⅲ. 対策方法2

SSH の公開鍵を登録する方法です。
https://stackoverflow.com/a/29475881/4771485

C#でDLLをInjectする方法

Ⅰ. はじめに

タイトルの通り「C#でDLLをInjectする方法」です。

2019/05/12時点で以下3つのインジェクト方法に対応してます。

  • CreateRemoteThread
  • ManualMap
  • ThreadHijack

Ⅱ. やり方

1. NuGetから Bleak をインストールする
Install-Package Bleak
2. サンプルプログラムを書く

TestDll.cpp

#include <Windows.h>

BOOL WINAPI DllMain(HINSTANCE hinstModule, DWORD dwReason, LPVOID lpvReserved)
{
  if (dwReason == DLL_PROCESS_ATTACH)
  {
    MessageBox(NULL, L"Hello world", L"Info", NULL);

    DisableThreadLibraryCalls(hinstModule);
  }
  return TRUE;
}

Program.cs

static void Main(string[] args)
{
  var injector = new Injector(InjectionMethod.ManualMap, "notepad", "TestDLL.dll");
  injector.InjectDll();
  injector.RandomiseDllHeaders();
}

実行結果

f:id:kagasu:20190512192339p:plain