UnityのNativePluginからC++のOpenCVを使う

# 概要
UnityのNativePlugiinを使い、C++のOpenCVを呼び出し使用する。
# 参考
* [Unity で OpenCV で作成したテクスチャをネイティブプラグイン経由で利用してみた](http://tips.hecomi.com/entry/2014/01/16/011727)
* [UnityでOpenCVを Native Plugin にして利用する (Windows)](ttp://izmiz.hateblo.jp/entry/2014/12/12/222636)
* [Windows で Unity の Native Plugin を作成する](https://qiita.com/imura/items/39334a02bb77bdef9e91)

# 目次
* Opencv のビルド
* C++プロジェクトの作成
* Native Pluginの実装

# 環境
* Windows10 (64bit)
* Visual Studio 2017
* OpenCV 3.2.0
* CMake 3.8.0

# OpenCVのビルド
##OpenCVの準備
以下のサイトからOpenCV-3.2.0-vc14.exeをダウンロード
https://github.com/opencv/opencv/releases

![image](https://qiita-image-store.s3.amazonaws.com/0/163818/9388ec55-22c3-a19f-40ce-761ceefec9de.png)

私の場合はD直下に「opencv-3.2.0」フォルダを作成し、その配下に解凍。

# CMakeでビルド
## CMakeの準備
以下のサイトからCMakeの最新バージョンをダウンロード
https://cmake.org/download/
![image](https://qiita-image-store.s3.amazonaws.com/0/163818/01ce1e00-0f2b-f923-3050-2898a21efa5e.png)

## CMakeの実行
CMakeを実行し、source codeにOpencvのsourceフォルダを、build the binariesにbuildフォルダを指定。
buildフォルダがない場合は作成する。

![image](https://qiita-image-store.s3.amazonaws.com/0/163818/1c26e3aa-164f-c19d-10a8-e73cca8b90d4.png)

Configueをクリックし「Visual Studio 15 2017 Win64」を選択。
選択項目は「Use default native compilers」を選択し。
![image](https://qiita-image-store.s3.amazonaws.com/0/163818/e7ede631-94c5-6a91-7495-17c953195d4d.png)

Finishをクリックすると、CMakeが動き、ビルドオプションを選択することができる。
今回はOpenCVを使用したコードをdllとして使用したいため、staticのライブラリを作成する。

「BUILD_SHARED_LIBS」のチェックを外し、「BUILD_WITH_STATIC_CRT」のチェックが入っていることを確認する。
また、後のVisualStudioでビルドしたときにperf_testsでエラーが出てしまいました。
詳しい原因はわかりませんが、「BUILD_PERF_TESTS」と「BUILD_TESTS」のチェックを外しておきます。

![image](https://qiita-image-store.s3.amazonaws.com/0/163818/0ff5f714-a044-7c65-f9bd-d4940b7824c5.png)

もう一度Configueをクリックし、Generateをクリックします。
ビルドが終われば、コンソールにdoneと表示されます。

![image](https://qiita-image-store.s3.amazonaws.com/0/163818/3c116fb6-da51-b08f-ac2a-ece3e485c7c9.png)

# VisualStudioによるビルド
opencv/build配下にVisualStudioのプロジェクトファイルであるOpenCV.slnファイルができているので、CMakeで指定したVisualStudio、今回は2017で開き、ビルド→構成マネージャーを開く。
INSTALLのビルドにチェックを入れます。この時アクティブソリューション構成からReleaseとDebug両方を共に設定します。

![image](https://qiita-image-store.s3.amazonaws.com/0/163818/b85ee2bb-091f-214c-43df-28db206b3831.png)

ビルドからソリューションのビルドをDebug,Release共に実行。

ビルドが完了すると

    opencv\build\install\x64\

にライブラリが作成されます。

# C++プロジェクトの作成
## 新規プロジェクトの作成
新規プロジェクトからコンソールアプリケーションを選択し、プロジェクトを作成します。
アプリケーションの種類をDLLにチェックを入れます。
追加オプションは、今回は「シンボルのエクスポート」と「プリコンパイル済みヘッダー」にチェックを入れます。

![image](https://qiita-image-store.s3.amazonaws.com/0/163818/4cd8b163-29ed-c917-7020-1242f1e3b5b7.png)

変数・関数の例とコンストラクターが記述されたコードが生成されます。

## プロジェクトの設定
プロジェクトからプロジェクトのプロパティ開き、設定を行う。
まず、C/C++/全般 の追加のインクルードディレクトリに、OpenCVのincludeフォルダのパスを設定する。今回の場合は以下の通り。設定は全ての構成、x64で行います。
112
    D:\opencv-3.2.0\opencv\build\install\include
    
![image](https://qiita-image-store.s3.amazonaws.com/0/163818/5ddd9d01-fd67-f155-81f2-55184c56c5da.png)    

ビルドしたものはinstallフォルダ配下に作成されることに注意。
次にリンカー/全般から、追加のライブラリディレクトリを設定します。

![image](https://qiita-image-store.s3.amazonaws.com/0/163818/62cf19fe-96d8-2177-fbd4-6fac6878008e.png)

次にC/C++/コード生成のらいタイムライブラリを、Releaseの場合はマルチスレッド(/MT)、Debugの場合はマルチスレッドでバッグ(/MTd)に設定してください。

![image](https://qiita-image-store.s3.amazonaws.com/0/163818/bd548adc-81ff-d1d6-ad5d-18743421e4b4.png)

## OpenCVのインクルード設定
まず、opencvのヘッダをインクルードします。

    // opencv インクルード
    #include "opencv2\opencv.hpp"   
    
この時エラーが出るようなら、プロジェクトプロパティと現在の設定があっているかを確認してください。

次にライブラリのリンク設定を行います。
プロパティから直接指定しても問題ありませんが、手間がかかりますので以下のコードで設定します。

	// バージョン名の取得
	#define CV_VERSION_STR CVAUX_STR(CV_MAJOR_VERSION) CVAUX_STR(CV_MINOR_VERSION) CVAUX_STR(CV_SUBMINOR_VERSION)

	// libファイル名の最後の部分をReleaseとDebugで分ける
	#ifdef _DEBUG
	#define CV_EXT_STR "d.lib"
	#else
	#define CV_EXT_STR ".lib"
	#endif

	#pragma comment(lib,"comctl32.lib")
	#pragma comment(lib,"vfw32.lib")
	#pragma comment(lib,"opencv_calib3d" CV_VERSION_STR CV_EXT_STR)
	#pragma comment(lib,"opencv_features2d" CV_VERSION_STR CV_EXT_STR)
	#pragma comment(lib,"opencv_flann" CV_VERSION_STR CV_EXT_STR)
	#pragma comment(lib,"opencv_core" CV_VERSION_STR CV_EXT_STR)
	#pragma comment(lib,"opencv_highgui" CV_VERSION_STR CV_EXT_STR)
	#pragma comment(lib,"opencv_imgcodecs" CV_VERSION_STR CV_EXT_STR)
	#pragma comment(lib,"opencv_imgproc" CV_VERSION_STR CV_EXT_STR)
	#pragma comment(lib,"opencv_videoio" CV_VERSION_STR CV_EXT_STR)
	#pragma comment(lib,"IlmImf" CV_EXT_STR)
	#pragma comment(lib,"ippicvmt.lib")
	#pragma comment(lib,"libjasper" CV_EXT_STR)
	#pragma comment(lib,"libjpeg" CV_EXT_STR)
	#pragma comment(lib,"libpng" CV_EXT_STR)
	#pragma comment(lib,"libtiff" CV_EXT_STR)
	#pragma comment(lib,"libwebp" CV_EXT_STR)
	#pragma comment(lib,"zlib" CV_EXT_STR)

設定するライブラリは必要に応じて追加・削除を行ってください。
この状態でビルドが通れば一旦OKです。

## C++で画像を取得する
コードは以下の通り

	DLLTEST_API void* getCamera() {
		// ウィンドウを開く
		//cv::namedWindow("hoge", CV_WINDOW_AUTOSIZE | CV_WINDOW_FREERATIO);
		return static_cast<void*>(new cv::VideoCapture(0));
	}

	DLLTEST_API void releaseCamera(void* camera) {
		// ウィンドウを閉じる
		cv::destroyAllWindows();
		auto vc = static_cast<cv::VideoCapture*>(camera);
		delete vc;
	}

	DLLTEST_API void getCameraTexture(void* camera, unsigned char* data, int width, int height) {
		auto vc = static_cast<cv::VideoCapture*>(camera);

		// カメラ画の取得
		cv::Mat img;
		*vc >> img;

		// resize
		cv::Mat resized_img(height, width, img.type());
		cv::resize(img, resized_img, resized_img.size(), cv::INTER_CUBIC);

		// ウィンドウの画の更新
		cv::imshow("window", resized_img);

		// RGB --> ARGB変換
		cv::Mat argb_img;
		cv::cvtColor(resized_img, argb_img, CV_RGB2BGRA);
		std::vector<cv::Mat> bgra;
		cv::split(argb_img, bgra);
		std::swap(bgra[0], bgra[3]);
		std::swap(bgra[1], bgra[2]);
		std::memcpy(data, argb_img.data, argb_img.total() * argb_img.elemSize());
	}

関数名の前についている DLLTEST_API はプロジェクトの名前によって違いますが、変数・関数などの宣言・定義するときに必ず必要になります。
ヘッダーでの宣言は以下。

	extern "C" {
		DLLTEST_API void* getCamera();
		DLLTEST_API void releaseCamera(void* camera);
		DLLTEST_API void getCameraTexture(void* camera, unsigned char* data, int width, int height);
	}
    
extern "C"をつけずに宣言した場合、コンパイル時にネームマングリングされるらしく、Unityからそのまま呼び出すことができないらしい。

これでコンパイルするとdllファイルがプロジェクトフォルダに作成される。

## Unityで使用する
    using UnityEngine;
    using System;
    using System.Runtime.InteropServices;

    public class OpenCV : MonoBehaviour 
    {
    	[DllImport ("OpenCVTest")]
    	private static extern IntPtr getCamera(); 
    	[DllImport ("OpenCVTest")]
    	private static extern void releaseCamera(IntPtr camera); 
    	[DllImport ("OpenCVTest")]
    	private static extern void getCameraTexture(IntPtr camera, IntPtr data, int width, int height); 

    	private IntPtr camera_;
    	private Texture2D texture_;
    	private Color32[] pixels_;
    	private GCHandle pixels_handle_;
    	private IntPtr pixels_ptr_;

    	void Start() 
    	{
    		camera_ = getCamera();
    		texture_ = new Texture2D(320, 180, TextureFormat.ARGB32, false);
    		pixels_ = texture_.GetPixels32();
    		pixels_handle_ = GCHandle.Alloc(pixels_, GCHandleType.Pinned);
    		pixels_ptr_ = pixels_handle_.AddrOfPinnedObject();
    		renderer.material.mainTexture = texture_;
    	}   

    	void Update() 
    	{
    		getCameraTexture(camera_, pixels_ptr_, texture_.width, texture_.height);
    		texture_.SetPixels32(pixels_);
    		texture_.Apply();
    	}

    	void OnApplicationQuit()
    	{
    		pixels_handle_.Free();
    		releaseCamera(camera_);
    	}
    }
    
このスクリプトをPlaneなどのオブジェクトにアタッチすることでカメラ画像を貼り付ける事ができます。