OpenCVをC++/CLIでラップ
今回はOpenCVをF#から扱えるようにしてみたいと思います。
OpenCVはC++のライブラリであるため、C++/CLIを用いてF#から扱えるようにします。
OpenCV取得
下記サイトから、バイナリとソースがセットになったファイルをダウンロードします。
解凍(exe実行)して適当なフォルダに配置してください。
今回は既にビルドされているopencv_world320.dllを利用していきます。
(opencv/build/x64/vc14/binフォルダにあります。)
C++/CLIプロジェクトの準備
VisualC++ / CLR / Class Libraryを選択します。
opencv_world320.dllが64bitでビルドされているので、64bitビルドに変更します。
プロジェクトの設定で、OpenCVのincludeとlibの場所を設定します。
Matクラス(CV_8UC1限定)
内部でcv::Matポインタを保持し、propertyで適宜メンバを公開しています。
GCにメモリが確保されたことを知らせるため、AddMemoryPressureとRemoveMemoryPressureを利用しています。
~Mat8UC1()は.Net側ではDispose()に変わります。
!Mat8UC1()は.Netのファイナライザになります。
public ref class Mat8UC1 { public: property int Cols { int get() { return _mat->cols; } } property int Rows { int get() { return _mat->rows; } } property IntPtr Data { IntPtr get() { return IntPtr(_mat->data); } } Mat8UC1(int rows, int cols) { _mat = new cv::Mat(rows, cols, CV_8UC1); if (_mat->empty() == false) GC::AddMemoryPressure(_mat->total() * _mat->elemSize()); } !Mat8UC1() { if (_mat != nullptr) { if (_mat->empty() == false) GC::RemoveMemoryPressure(_mat->total() * _mat->elemSize()); delete _mat; _mat = nullptr; } } ~Mat8UC1() { this->!Mat8UC1(); } internal: cv::Mat* _mat; };
Prosseorクラス
動作確認のため、画像のロードとセーブを行う関数を作成しました。
ExtensionAttributeを付与することにより、Saveは拡張メソッドとして定義されます。
System::Stringからstd::stringへの変換は"msclr/marshal_cppstd.h"を利用しています。
imreadの代に引数を0にすることにより、グレースケールで読み込むようにしてあります。
[ExtensionAttribute] public ref class Processor abstract sealed { public: static Mat8UC1^ Load(String^ path) { cv::Mat src = cv::imread(msclr::interop::marshal_as<std::string>(path), 0); Mat8UC1^ dst = gcnew Mat8UC1(src.rows, src.cols); src.copyTo(*dst->_mat); return dst; } [ExtensionAttribute] static void Save(Mat8UC1^ src, String^ path) { cv::imwrite(msclr::interop::marshal_as<std::string>(path), *src->_mat); } };
コード全体(ヘッダファイル)
警告が発生するので、"opencv2\opencv.hpp"はunmanagedでコンパイルされるようにしてあります。
#pragma once #pragma unmanaged #include <opencv2\opencv.hpp> #pragma managed #include <msclr\marshal_cppstd.h> #ifdef _DEBUG #pragma comment(lib, "opencv_world320d.lib") #else #pragma comment(lib, "opencv_world320.lib") #endif using namespace System; using namespace System::Runtime::CompilerServices; namespace CV { public ref class Mat8UC1 { public: property int Cols { int get() { return _mat->cols; } } property int Rows { int get() { return _mat->rows; } } property IntPtr Data { IntPtr get() { return IntPtr(_mat->data); } } Mat8UC1(int rows, int cols) { _mat = new cv::Mat(rows, cols, CV_8UC1); if (_mat->empty() == false) GC::AddMemoryPressure(_mat->total() * _mat->elemSize()); } !Mat8UC1() { if (_mat != nullptr) { if (_mat->empty() == false) GC::RemoveMemoryPressure(_mat->total() * _mat->elemSize()); delete _mat; _mat = nullptr; } } ~Mat8UC1() { this->!Mat8UC1(); } internal: cv::Mat* _mat; }; [ExtensionAttribute] public ref class Processor abstract sealed { public: static Mat8UC1^ Load(String^ path) { cv::Mat src = cv::imread(msclr::interop::marshal_as<std::string>(path), 0); Mat8UC1^ dst = gcnew Mat8UC1(src.rows, src.cols); src.copyTo(*dst->_mat); return dst; } [ExtensionAttribute] static void Save(Mat8UC1^ src, String^ path) { cv::imwrite(msclr::interop::marshal_as<std::string>(path), *src->_mat); } }; }
動作確認
単に画像を読み込んで保存するだけです。
exeと同じフォルダにopencv_world320.dllを配置するのを忘れないようにしてください。
(面倒であればリソースに追加して"copy if newer"にするのもアリです。)
また、64bitターゲットにするのを忘れないようにしてください。
open CV [<EntryPoint>] let main argv = let image = Processor.Load(@"image.jpg") image.Save(@"image2.jpg") 0