ちょっと進んで OpenCV を使う

OpenCV は物体認識などのライブラリが含まれているので、Windows IoT Core で使えると利用できる幅が広がりそうですよね。Linux 系の Raspbian の場合はソースコードをダウンロードしてビルドして導入という手順になりますが、Windows IoT Core の場合は UWP アプリにしないといけない難点があってちょっとハードルが高くなっています。が、できないことはないです。

[Windows 10ユニバーサルアプリ(Universal Windows Application)でOpenCVを使う（その2） - embeddedなブログ](http://blog.goo.ne.jp/yoossh/e/63bc59daa6f6cab4d09104989ccf9cd6 "Windows 10ユニバーサルアプリ(Universal Windows Application)でOpenCVを使う（その2） - embeddedなブログ")

なところに、導入＆プロジェクト作成手順が書いてあるので、それに従うとうまくいきます。

いくつかのポイントがあって、

- github の Microsoft/opencv の vs2015-samples というブランチを使う。
- このブランチが OpenCV 3.0 ベースになっている（現時点で最新が 3.2）
- コードは C++/CLI で記述する。

となっています。Microsoft/opencv は OpenCV/opencv のフォークになっているのですが、どちらも Microsoft のほうは1年位更新されていません。ただし、x86/x64版（Intel版）のほうは、本家の OpenCV/opencv で動作させることができました。ARM のほうがうまくビルドできないのは、ippicv 絡みなので、cmake するときに外してやるとライブラリはうまくビルドできるようにになります。が、UWP on RPi への組み込みは失敗してしまいます。この経緯は後で自前のブログにでも記録として残しておきます。

# Microsoft/opencv の vs2015-samples をダウンロードする

<a href="https://github.com/microsoft/opencv/tree/vs2015-samples">Microsoft/opencv at vs2015-samples</a> で ブランチを「vs2015-samples」に変更して zip でダウンロードします。

![001.jpg](https://qiita-image-store.s3.amazonaws.com/0/32488/1191a6a9-fdbb-599b-9a6c-5a109a308653.jpeg)

zip を解凍します。

# 環境変数 OCV2015_ROOT を設定する

ARM 版の OpenCV をビルドすることになるのですが、インクルードパスが「$(OCV2015_ROOT)\vs2015\WS\10.0\ARM;...」のように設定されいるので、環境変数 OCV2015_ROOT を解凍先の zip に合わせて設定します。

![002.jpg](https://qiita-image-store.s3.amazonaws.com/0/32488/d5917c07-b978-34e3-d9b5-abcd974ea32b.jpeg)

ユーザー環境変数に設定したら、一度ログアウトしてログインし直すと、環境変数が設定されます。

# ARM 版の OpenCV をビルドする

本来であれば、cmake を使って make を作って、という手順になるのですが、vs2015-samples は cmake まで済ませてあります。なので、$OCV2015_ROOT\vs2015\WS\10.0\ARM\OpenCV.sln を開いて、ALL_BUILD をビルドします。

![003.jpg](https://qiita-image-store.s3.amazonaws.com/0/32488/cb13569a-d8e7-1ec7-d46c-ba4ddfbb7032.jpeg)


無事ビルドが終わると、$OCV2015_ROOT\vs2015\WS\10.0\ARM\bin\Debug\ にアセンブリ（*.dll）が、$OCV2015_ROOT\vs2015\WS\10.0\ARM\lib\Debug\ にエクスポートされたライブラリ（*.lib）が作成されます。デバッグの場合は Debug、リリース版は Release を使えば ok です。

# OpenCVExample をビルドしてみる

ms-iot のサンプル <a href="https://github.com/ms-iot/samples/tree/develop/OpenCVExample">samples/OpenCVExample at develop · ms-iot/samples</a> をビルドしてみましょう。ちなみに、<a href="https://developer.microsoft.com/en-us/windows/iot/samples/OpenCV">OpenCV | Windows IoT</a> に OpenCV をビルドするドキュメントがあるのですが、肝心の opencv をダウンロードするリンクが切れている（間違っている？）ので、頓挫してしまいます。このリンク先は、先の vs2015-samples ブランチに変更されています。

OpenCV の DLL をプロジェクトにごっそり追加します。本来は選んだほうがサイズが小さくなるのですが、面倒なので一気に追加します。

![004.jpg](https://qiita-image-store.s3.amazonaws.com/0/32488/1bf3cc85-f437-bd54-b2c9-55a4e57cdb37.jpeg)

DLL のプロパティでコンテンツを「True」に変更しておきます。こうすると、DLL がごっそり *.exe と同じところにコピーされてアプリに追加されます。

![005.jpg](https://qiita-image-store.s3.amazonaws.com/0/32488/f5700a12-2ac9-195d-5661-5738a1249469.jpeg)

あとは、ソリューションプラットフォームを「ARM」に変更してビルドをします。いくつかの警告がでますが、無事ビルドができます。

ちなみに、DLL は ARM 版を追加するので、x64 にしたときは動作しません。普通の PC の UWP アプリを使いたい時は、x64 のほうにある opencv の DLL をコピーします。ここは面倒なので2つプロジェクトを作ってしまったほうがいいでしょうね。あるいは、ビルド前プロセスなどで自動化できると思います。

# 実機で動かしてみる。

いままでと同じように「リモートコンピュータ」の先を minwinpc にして動かすと、こんな感じで動作します。

![006.jpg](https://qiita-image-store.s3.amazonaws.com/0/32488/4ce5f5a2-dbbf-cc68-3cbe-92315005251a.jpeg)

ちなみにアプリケーションやDLLは、Uドライブのほうに保存されています。

![007.jpg](https://qiita-image-store.s3.amazonaws.com/0/32488/2663c5ef-57ba-07bb-e1fa-8c882753c2f8.jpeg)

試しに、x64 でビルドして PC で動かしたものはこんな感じです。

![008.jpg](https://qiita-image-store.s3.amazonaws.com/0/32488/dd070409-0a15-abc1-d89f-42ddbbdd5dfc.jpeg)

# OpenCV on Windows IoT Core の使いどころ

多少バージョンが古くなってしまいますが、これで Windows IoT Core でも OpenCV が使えることが分かります。まあ、本格的な画像処理を RPi でやるには非力なので、写真の前処理とかちょっとした色の加工とか、物体認識とか簡単なテンプレートマッチングとかに使うといいんじゃないでしょうか。

多くの方にとっての難点は C++/CLI で記述するというところです。

```cpp
// takes an image (inputImg), runs face and body classifiers on it, and stores the results in objectVector and objectVectorBodies, respectively
void InternalDetectObjects(cv::Mat& inputImg, std::vector<cv::Rect> & objectVector, std::vector<cv::Rect> & objectVectorBodies, cv::CascadeClassifier& face_cascade, cv::CascadeClassifier& body_cascade)
{
	cv::Mat frame_gray;

	cvtColor(inputImg, frame_gray, CV_BGR2GRAY);
	cv::equalizeHist(frame_gray, frame_gray);

	// Detect faces
	face_cascade.detectMultiScale(frame_gray, objectVector, 1.1, 2, 0 | CV_HAAR_SCALE_IMAGE, cv::Size(30, 30));
	//detect bodies
	body_cascade.detectMultiScale(frame_gray, objectVectorBodies, 1.1, 2, 0 | CV_HAAR_SCALE_IMAGE, cv::Size(30, 300));
}
```

このあたりを適当に C# でラップするだけでも使い勝手はよくなるかなと（本格的なものは 2.4 のほうにあるのだけど、3.0 にはない）。

あと、画像まわりが、WriteableBitmap と cv::Mat の相互変換とか、Bitmap 形式とかが混在するので、結構ややこしいかもしれません。OpenCVExample では、下記のようにベタで memcpy していました。

```cpp
void  OpenCVExample::MainPage::UpdateImage(const cv::Mat& image)
{
	// Create the WriteableBitmap
	WriteableBitmap^ bitmap = ref new WriteableBitmap(image.cols, image.rows);

	// Get access to the pixels
	IBuffer^ buffer = bitmap->PixelBuffer;
	unsigned char* dstPixels = nullptr;

	// Obtain IBufferByteAccess
	ComPtr<IBufferByteAccess> pBufferByteAccess;
	ComPtr<IInspectable> pBuffer((IInspectable*)buffer);
	pBuffer.As(&pBufferByteAccess);

	// Get pointer to pixel bytes
	HRESULT get_bytes = pBufferByteAccess->Buffer(&dstPixels);
	if (get_bytes == S_OK) {
		memcpy(dstPixels, image.data, image.step.buf[1] * image.cols*image.rows);

		// Set the bitmap to the Image element
		storedImage->Source = bitmap;
	}
	else {
		printf("Error loading image into buffer\n");
	}
}
```