OpenCVをC++/CLIでラップ

今回はOpenCVをF#から扱えるようにしてみたいと思います。

OpenCVC++のライブラリであるため、C++/CLIを用いてF#から扱えるようにします。

OpenCV取得

下記サイトから、バイナリとソースがセットになったファイルをダウンロードします。

解凍(exe実行)して適当なフォルダに配置してください。

今回は既にビルドされているopencv_world320.dllを利用していきます。
opencv/build/x64/vc14/binフォルダにあります。)

OpenCV | OpenCV
f:id:any-programming:20170302175727p:plain

C++/CLIプロジェクトの準備

VisualC++ / CLR / Class Libraryを選択します。
f:id:any-programming:20170303141633p:plain

opencv_world320.dllが64bitでビルドされているので、64bitビルドに変更します。
f:id:any-programming:20170303142050p:plain

プロジェクトの設定で、OpenCVのincludeとlibの場所を設定します。
f:id:any-programming:20170303210245p:plain

f:id:any-programming:20170303210252p:plain

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ターゲットにするのを忘れないようにしてください。
f:id:any-programming:20170304143417p:plain

open CV

[<EntryPoint>]
let main argv = 
    let image = Processor.Load(@"image.jpg")    
    image.Save(@"image2.jpg")
    0