OpenCV

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

概要

UnityのNativePlugiinを使い、C++のOpenCVを呼び出し使用する。

参考

目次

  • 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

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

CMakeでビルド

CMakeの準備

以下のサイトからCMakeの最新バージョンをダウンロード
https://cmake.org/download/
image

CMakeの実行

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

image

Configueをクリックし「Visual Studio 15 2017 Win64」を選択。
選択項目は「Use default native compilers」を選択し。
image

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

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

image

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

image

VisualStudioによるビルド

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

image

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

ビルドが完了すると

opencv\build\install\x64\

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

C++プロジェクトの作成

新規プロジェクトの作成

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

image

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

プロジェクトの設定

プロジェクトからプロジェクトのプロパティ開き、設定を行う。
まず、C/C++/全般 の追加のインクルードディレクトリに、OpenCVのincludeフォルダのパスを設定する。今回の場合は以下の通り。設定は全ての構成、x64で行います。
112
D:\opencv-3.2.0\opencv\build\install\include

image

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

image

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

image

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などのオブジェクトにアタッチすることでカメラ画像を貼り付ける事ができます。