Raspberry Pi で .NET Core + OpenCV を始める

# はじめに

.NET Coreがクロスプラットフォーム対応になって、
LinuxだろうがMacだろうが、x86だろうがarmhfだろうが、
動くようになったこのご時世だというのに、全然試していなかったので試しました。

## .NET Core の導入

まずは .NET Core の導入です。
ARMでは ARM32 と ARM64 のバージョンがあるので、ターゲットに合わせて選びます。

今回はTinkerBoardというRaspberry Pi みたいな armhf （armv7l）なシングルボードコンピュータに導入しようと思いますので、
「`SDK 3.1.402`」の「`ARM32`」を選びました。

https://dotnet.microsoft.com/download/dotnet-core/3.1



## OpenCV 4.4.0 の導入

[OpenCV 4.4.0のリリースノート](https://github.com/opencv/opencv/wiki/ChangeLog#version440)

関係ないですが、OpenCV 4.4.0 では晴れて自由に使えるようになった `SIFT特徴量` がメインのモジュールに含まれるようになりました。（`opencv_features2d`に入ってます。）

※今回はソースコードからビルドしてみますが、他のバージョンで良ければいろんなところで配布されているのでそれを使用した方が早いです。

### OpenCV 4.4.0 自体のビルド

いつもと同じように、CMakeを使って構成、Makeでビルド／インストールします。

OpenCV本体のダウンロード

```bash
mkdir src
cd src
wget https://github.com/opencv/opencv/archive/4.4.0.zip
unzip 4.4.0.zip
rm 4.4.0.zip
```

OpenCV contribのダウンロード

```bash
wget https://github.com/opencv/opencv_contrib/archive/4.4.0.zip
unzip 4.4.0.zip
rm 4.4.0.zip
```

ダウンロードし展開すると、このように `opencv-4.4.0` と `opencv_contrib-4.4.0` フォルダができあがります。（両方 `4.4.0.zip` というzipファイルなのでダウンロード時に上書きしないように注意。）

```bash
~/src$ ls
4.4.0.zip  opencv-4.4.0  opencv_contrib-4.4.0
```

そしてCMakeを使ってビルドする準備をします。

```bash
cd opencv-4.4.0
mkdir build
cd build
cmake -DOPENCV_EXTRA_MODULES_PATH=/home/XXX/src/opencv_contrib-4.4.0/modules -D WITH_LIBV4L=ON -D CMAKE_BUILD_TYPE=RELEASE -D WITH_TBB=ON -D ENABLE_NEON=ON ..
```

`-DOPENCV_EXTRA_MODULES_PATH` は 先程の `opencv_contrib-4.4.0` フォルダ配下の `modules` を指定します。（オプションは一例です。この例ではあんまり真面目に考えてませんが、Python用のモジュールをビルドしないなど最適化する余地はあります。）

次に、ビルド／インストールをします。

```bash
make
sudo make install
sudo ldconfig
```

さすがに実機でやると時間がめっちゃかかるので `screen` コマンドで裏で走らせつつ寝ました。

### OpenCvSharpのビルド

さて、ここまでで opencv の導入はできたので、それを .NET Core から使うべくOpenCVをC#ラッパーである `OpenCvSharp` を導入していきます。

[shimat/opencvsharp: OpenCV wrapper for .NET](https://github.com/shimat/opencvsharp)

OpenCvSharpには OpenCV を軽くラップした薄いラッパーが同梱している（`OpenCvSharp4.runtime.win` とかが多分それ。）のですが、Ubuntu用は x64用しか無いのでARM版を手動でビルドしていきます。

ビルド手順はまんまこれ　→ [Build on ARM · Issue #388 · shimat/opencvsharp](https://github.com/shimat/opencvsharp/issues/388)

```bash
#download OpenCvSharp
cd ~/src
git clone https://github.com/shimat/opencvsharp.git

#install the Extern lib.
cd opencvsharp/src
sed -i.bak '5i\
include_directories("/usr/local/include/")\
set (CMAKE_CXX_STANDARD 11)\
' CMakeLists.txt
mkdir build
cd build
cmake ..
make
sudo make install
sudo ldconfig
```

`sudo ldconfig` までやると NuGet で OpenCvSharp落としてくるだけで使えるようになる。めっちゃすごい。

## クロスプラットフォームサンプル

というわけで、上記手順に従うと .NET Core + OpenCV をやる最低限の準備は整うことがわかりました。ただ、やっぱりサンプルがないと寂しいので書いてみました。
一応 Windows 10 on ThinkPad X220 (x64) と Debian 9 on TinkerBoard (armv7l) で動作確認を取ったサンプルです。

https://github.com/yoh1496/dotnetcore-opencvsharp

このサンプルはカレントディレクトリの example.jpg というファイルを読み込んでキャニーのエッジ抽出をして結果を example_canny.jpg として保存するものです。

```csharp
static void Main(string[] args) {
    using var src = new Mat("example.jpg", ImreadModes.Grayscale);
    using var dst = new Mat();

    Cv2.Canny(src, dst, 50, 200);
    Cv2.ImWrite("example_canny.jpg", dst);
    Console.WriteLine("Example done!");
}
```

### サンプルのキモ

極力共通のコマンドを叩くと環境が揃ってビルドできるようになるようにしたかったので `csproj` ファイルの中でプラットフォーム依存の部分を記述しています。

https://github.com/yoh1496/dotnetcore-opencvsharp/blob/master/dotnetcore-opencvsharp.csproj

```xml
  <ItemGroup Condition="$(OS.StartsWith('Windows'))">
    <PackageReference Include="OpenCVSharp4.Windows" Version="4.4.0.20200915" />
  </ItemGroup>
```

といってもこれだけですが。

ここでやっているのは、Windowsでビルドしたときは OpenCvSharp の「All-in-one package」への参照をもたせて先程のようなOpenCVをWindowsでビルドする必要をなくする処理です。先程紹介したOpenCVのビルド手順をなぞればここの記述は必要ありませんが、正直気力が足りないのと素直にビルド済みの配布物を使用するほうが後々トラブルが少ないという考えからです。

### TinkerBoardでビルドしてみる

先程のリポジトリをTinkerBoardにクローンしてビルドしてみます。

```
yoh@tinkerboard:~/dev$ git clone https://github.com/yoh1496/dotnetcore-opencvsharp
Cloning into 'dotnetcore-opencvsharp'...
remote: Enumerating objects: 12, done.
remote: Counting objects: 100% (12/12), done.
remote: Compressing objects: 100% (12/12), done.
remote: Total 12 (delta 1), reused 11 (delta 0), pack-reused 0
Unpacking objects: 100% (12/12), done.
yoh@tinkerboard:~/dev$ cd dotnetcore-opencvsharp/
yoh@tinkerboard:~/dev/dotnetcore-opencvsharp$ dotnet build .
Microsoft (R) Build Engine version 16.7.0+7fb82e5b2 for .NET
Copyright (C) Microsoft Corporation. All rights reserved.

  Determining projects to restore...
  Restored /home/yoh/dev/dotnetcore-opencvsharp/dotnetcore-opencvsharp.csproj (in 960 ms).
  dotnetcore-opencvsharp -> /home/yoh/dev/dotnetcore-opencvsharp/bin/Debug/netcoreapp3.1/dotnetcore-opencvsharp.dll

Build succeeded.
    0 Warning(s)
    0 Error(s)

Time Elapsed 00:00:08.28
```

ビルドできますね。最高。

ここで、入力画像を `dotnetcore-opencvsharp` フォルダ直下に配置します。`scp` で渡しました。
画像は何でもいいので適当に旅行で撮った写真を使ってみました。（掲載に当たり縮小してます）

![a.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/328319/81bb6ac9-8c4e-48b4-f208-b183149188da.jpeg)


```
yoh@tinkerboard:~/dev/dotnetcore-opencvsharp$ ls
Program.cs  bin  dotnetcore-opencvsharp.csproj  example.jpg  obj
```

では実行！

```
yoh@tinkerboard:~/dev/dotnetcore-opencvsharp$ dotnet run .
Example done!
```

```
yoh@tinkerboard:~/dev/dotnetcore-opencvsharp$ ls
Program.cs  bin  dotnetcore-opencvsharp.csproj  example.jpg  example_canny.jpg  obj
```

**`example_canny.jpg` が出力されてる！**
出力結果を確認してみます。 `scp` で取ってきてもいいんですが、面倒くさかったので Visual Studio Code の Remote-SSH 拡張機能で開きました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/328319/d3e2c973-9e28-4c4a-bfdc-6ad15dd67f13.png)

**ちゃんとエッジ抽出できてる！**

### Windows で実行してみる

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/328319/ad3e97d1-6b41-198f-7f27-6b7b6881f5e4.png)

**できました！**

SSH-Remote で armhf の Linux につなぐとデバッガが使用できないんですが、Windows環境だとちゃんと使用できて便利です。開発はWindowsでやって、実際に動かすのはARMみたいな使い分けをしていこうかなと思います。

ARM でのみ発生する問題もあると思うので、そういった場合はリモートデバッグを使うとよいかもしれません。まだ試せてませんが、、、

[Remote Debugging On Linux Arm · OmniSharp/omnisharp-vscode Wiki](https://github.com/OmniSharp/omnisharp-vscode/wiki/Remote-Debugging-On-Linux-Arm)

## 終わりに

Windowsで書いたコードがそのまま Linux on ARM でも動作するのは便利ですね。試してませんがまぁMacでも動くんでしょう。
.NET Coreが動くのが便利なのもそうなんですが、vscodeがARMのLinuxマシン上でも問題なく繋げるのが最高に便利ですね。