MediaFoundation --- 動画の読み込み

下記記事にてOpenCVで動画を読み書きする方法を調べました。
OpenCVで動画読み込み&書き込み - 何でもプログラミング

今回はWindows特有のMediaFoundationを利用して動画を読み込んでみます。

全体の流れの把握重視の為、エラー処理は全て省いてあります。

アプリケーションコード

動画を読み込んで、そのまま書き出しています。

今回は書き出しにはOpenCVを利用しました。

IMFSourceReaderを作成、デコーダ設定、読み込み、の流れになります。

動画はOpenCVのサンプルである、vtest.aviを利用しています。

MediaFoundationを利用するにあたり、MFStartupを忘れないようにしてください。(CoInitializeも、されていなければ呼び出してください。)

ConfigureVideoDecoder、GetVideoInfo、Captureは後程記載します。

#include <mfapi.h>
#include <mfidl.h>
#include <mfreadwrite.h>
#pragma comment(lib, "mfplat.lib")
#pragma comment(lib, "mfuuid.lib")
#pragma comment(lib, "mfreadwrite.lib")

#include <opencv2\opencv.hpp>
#pragma comment(lib, "opencv_world320.lib")

int main()
{
    CoInitialize(NULL);
    MFStartup(MF_VERSION);

    IMFSourceReader* reader;
    MFCreateSourceReaderFromURL(L"c:\\lib\\opencv3.2\\sources\\samples\\data\\vtest.avi", NULL, &reader);

    ConfigureVideoDecoder(reader, MFVideoFormat_RGB24);

    UINT32 width, height;
    double fps;
    GetVideoInfo(reader, &width, &height, &fps);

    auto writer = cv::VideoWriter("output.avi", cv::VideoWriter::fourcc('X', 'V', 'I', 'D'), fps, cv::Size(width, height));
    while (true)
    {
        auto data = Capture(reader);
        if (data.empty())
            break;

        cv::Mat frame(height, width, CV_8UC3, data.data());
        writer << frame;
    }

    reader->Release();
    MFShutdown();    
    CoUninitialize();
    return 0;
}

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

ConfigureVideoDecoder

SourceReaderにどのフォーマットで出力するか設定します。

上記ではMFVideoFormat_RGB24を指定していますが、デコーダによってはMFVideoFormat_YUY2しか出力しない等ありますので、利用の際は注意してください。

対応出力は下記ページのVideo Codecs、Decoderのリンク先を参照してください。
Supported Media Formats in Media Foundation (Windows)

void ConfigureVideoDecoder(IMFSourceReader* reader, GUID format)
{
    IMFMediaType* mediaType;
    MFCreateMediaType(&mediaType);
    mediaType->SetGUID(MF_MT_MAJOR_TYPE, MFMediaType_Video);
    mediaType->SetGUID(MF_MT_SUBTYPE, format);

    reader->SetCurrentMediaType(MF_SOURCE_READER_FIRST_VIDEO_STREAM, 0, mediaType);

    mediaType->Release();
}


GetVideoInfo

SourceReaderからサイズとfpsを取得しています。

void GetVideoInfo(IMFSourceReader* reader, UINT32* width, UINT32* height, double* fps)
{
    IMFMediaType* mediaType;
    reader->GetCurrentMediaType(MF_SOURCE_READER_FIRST_VIDEO_STREAM, &mediaType);

    MFGetAttributeSize(mediaType, MF_MT_FRAME_SIZE, width, height);

    UINT32 nume, denom;
    MFGetAttributeRatio(mediaType, MF_MT_FRAME_RATE, &nume, &denom);
    *fps = (double)nume / denom;

    mediaType->Release();
}


Capture

ReadSampleにてIMFSampleを取得し、そこからIMFMediaBufferを取得、データ取り出しを行っています。

ReadSample時にflagsを渡さないとsampleがnullptrになってしまいます。

std::vector<BYTE> Capture(IMFSourceReader* reader)
{
    DWORD flags;
    IMFSample* sample;
    reader->ReadSample(MF_SOURCE_READER_FIRST_VIDEO_STREAM, 0, NULL, &flags, NULL, &sample);
    if (sample == nullptr)
        return std::vector<BYTE>();

    IMFMediaBuffer* buffer;
    sample->GetBufferByIndex(0, &buffer);

    BYTE* p;
    DWORD size;
    buffer->Lock(&p, NULL, &size);
    std::vector<BYTE> data(size);
    memcpy(data.data(), p, size);
    buffer->Unlock();

    buffer->Release();
    sample->Release();
    return data;
}


シーク

SourceReaderにはSetCurrentPositionがあり、100ns単位で指定します。

ただしその位置に一番近いキーフレームに移動します。

void SeekToKeyframe(IMFSourceReader* reader, int _100ns)
{
    PROPVARIANT var;
    InitPropVariantFromInt64(_100ns, &var);
    auto hr = reader->SetCurrentPosition(GUID_NULL, var);
    PropVariantClear(&var);
}


動画時間取得

durationは100ns単位の値になります。

PROPVARIANT var;
reader->GetPresentationAttribute(MF_SOURCE_READER_MEDIASOURCE, MF_PD_DURATION, &var);
LONGLONG duration;
PropVariantToInt64(var, &duration);
PropVariantClear(&var);


Fourcc取得

IMFMediaType* mediaType;
reader->GetNativeMediaType(MF_SOURCE_READER_FIRST_VIDEO_STREAM, 0, &mediaType);

GUID subtype;
mediaType->GetGUID(MF_MT_SUBTYPE, &subtype);

char fourcc[] =
{
    (char) (subtype.Data1 & 0XFF),
    (char)((subtype.Data1 & 0XFF00) >> 8),
    (char)((subtype.Data1 & 0XFF0000) >> 16),
    (char)((subtype.Data1 & 0XFF000000) >> 24),
};






OpenCVで動画読み込み&書き込み --- C++/CLIでラップ

下記記事にてOpenCVで動画を読み書きする方法を調べました。
OpenCVで動画読み込み&書き込み - 何でもプログラミング

今回は.Netで利用できるよう、C++/CLIでラップしてみたいと思います。

尚、Matクラス(Mat8UC1クラス)は下記記事のものを利用しています。
OpenCVをC++/CLIでラップ - 何でもプログラミング

VideoCapture

FourccはStringで返すようにしてみました。

Mat8UC1を利用しているため、Captureの中で8UC1に変換しています。ここは適宜変更してください。

using namespace msclr::interop;
using namespace System;

public ref class VideoCapture
{
public:
    property int Width  { int get()    { return (int)_capture->get(CV_CAP_PROP_FRAME_WIDTH); } }
    property int Height { int get()    { return (int)_capture->get(CV_CAP_PROP_FRAME_HEIGHT); } }
    property double Fps { double get() { return      _capture->get(CV_CAP_PROP_FPS); } }
    property int Count  { int get()    { return (int)_capture->get(CV_CAP_PROP_FRAME_COUNT); } }
    property String^ Fourcc
    {
        String^ get()
        {
            int i = (int)_capture->get(CV_CAP_PROP_FOURCC);
            char fourcc[] =
            {
                (char)(i & 0XFF),
                (char)((i & 0XFF00) >> 8),
                (char)((i & 0XFF0000) >> 16),
                (char)((i & 0XFF000000) >> 24),
                0
            };
            return gcnew String(fourcc);
        }
    }
    property bool IsOpened { bool get() { return _capture->isOpened(); } }
    VideoCapture(String^ path)
    {
        _capture = new cv::VideoCapture(marshal_as<std::string>(path));
    }
    !VideoCapture()
    {
        if (_capture != nullptr)
        {
            delete _capture;
            _capture = nullptr;
        }
    }
    ~VideoCapture() { this->!VideoCapture(); }
    Mat8UC1^ Capture()
    {
        cv::Mat src;
        *_capture >> src;

        auto dst = gcnew Mat8UC1(src.rows, src.cols);
        if (src.channels() == 1)
            src.copyTo(*dst->_mat);
        else if (src.channels() == 3 || src.channels() == 4)
            cv::cvtColor(src, *dst->_mat, CV_RGB2GRAY);
            
        return dst;
    }
    void Seek(int frame)
    {
        _capture->set(CV_CAP_PROP_POS_FRAMES, frame);
    }
private:
    cv::VideoCapture* _capture;
};


VideoWriter

Fourcc文字列からIntに変換するにはcv::VideoWriter::fourccを利用します。

Write関数内でカラー化していますが、ここは適宜変更してください。

public ref class VideoWriter
{
public:
    property bool IsOpened { bool get() { return _writer->isOpened(); } }
    VideoWriter(String^ path, int width, int height, String^ fourcc, double fps)
    {
        _writer = new cv::VideoWriter(
            marshal_as<std::string>(path), 
            cv::VideoWriter::fourcc(fourcc[0], fourcc[1], fourcc[2], fourcc[3]), 
            fps, 
            cv::Size(width, height));
    }
    !VideoWriter()
    {
        if (_writer != nullptr)
        {
            delete _writer;
            _writer = nullptr;
        }
    }
    ~VideoWriter() { this->!VideoWriter(); }
    void Write(Mat8UC1^ mat)
    {
        cv::Mat color;
        cv::cvtColor(*mat->_mat, color, CV_GRAY2BGR);

        *_writer << color;
    }
private:
    cv::VideoWriter* _writer;
};


F#で利用

動画はopenCVのサンプルMegamind.aviを利用しています。

動画をそのまま保存しているだけですが、Mat8UC1を介しているため出力はグレースケールになっています。

[<EntryPoint>]
let main argv = 
    let capture = new CV.VideoCapture(@"C:\lib\opencv3.2\sources\samples\data\megamind.avi")

    let writer = new CV.VideoWriter(@"output.avi", capture.Width, capture.Height, capture.Fourcc, capture.Fps);

    capture 
    |> Seq.unfold (fun x -> Some(x.Capture(), x)) 
    |> Seq.takeWhile (fun x -> not x.IsEmpty)
    |> Seq.iter writer.Write

    0

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





OpenCVで動画読み込み&書き込み

OpenCVのダウンロードは下記記事を参照してください。
OpenCVをC++/CLIでラップ - 何でもプログラミング

VideoCapture

動画を読み込むにはVideoCaptureクラスを利用します。

パスを指定して開き、>>オペレータでフレームを取得します。

cv::VideoCapture capture("c:\\...");

cv::Mat frame;
capture >> frame;


VideoWriter

動画を出力するにはVideoWriterクラスを利用します。

パス、コーデック、fps、サイズを指定して作成し、<<オペレータでフレームを出力します。

cv::VideoWriter writer("c:\\...", fourcc, fps, cv::Size(width, height));

writer << frame;


エッジ化して出力する例

Win32コンソールアプリケーション(64bit)を想定しています。

動画は、opencvのサンプルとして付属しているMegamind.aviを利用します。(sources/samples/data/Megamind.avi)

開いた動画の情報は、VideoCaptureのget関数で色々取得できます。

作成したexeと同じフォルダにopencv_world320.dllとopencv_ffmpeg320_64.dllをコピーするのを忘れないようにしてください。

#include <opencv2\opencv.hpp>

#pragma comment(lib, "opencv_world320.lib")

int main()
{
    cv::VideoCapture capture("c:\\lib\\opencv3.2\\sources\\samples\\data\\megamind.avi");
    int width  = (int)capture.get(CV_CAP_PROP_FRAME_WIDTH);
    int height = (int)capture.get(CV_CAP_PROP_FRAME_HEIGHT);
    int count  = (int)capture.get(CV_CAP_PROP_FRAME_COUNT);
    int fourcc = (int)capture.get(CV_CAP_PROP_FOURCC);
    double fps = capture.get(CV_CAP_PROP_FPS);

    cv::VideoWriter writer("out.avi", fourcc, fps, cv::Size(width, height), false);

    while (true)
    {
        cv::Mat frame;
        capture >> frame;
        if (frame.empty())
            break;

        cv::Mat edge;
        cv::Canny(frame, edge, 50, 150);

        writer << edge;
    }

    return 0;
}

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

シーク

CV_CAP_PROP_POS_FRAMESでフレーム位置を指定できます。(その他ミリ秒で設定できるCV_CAP_PROP_POS_MSECなどもあります)

capture.set(CV_CAP_PROP_POS_FRAMES, 100);


H.264

上記のアプリケーションでは、H.264の読込は出来ても、書き込みはできません。

書き込みをしたい場合は、下記からopenh264-1.6.0-win64msvc.dllをダウンロードしてexeと同じフォルダに配置してください。
Releases · cisco/openh264 · GitHub

ライセンス関係

ffmpegにはLGPL版とGPL版があり、デフォルトで配布されているopencv_ffmpeg320_64.dllはLGPL版のものになります。(そのためH.264の出力ができず、openH264を利用する形となっています。)

また、dllのライセンス以外に、H.264の利用料があります(MPEG-LA)

openH264は、利用しているアプリをインストールする際にdllをダウンロードするようにすれば、利用料は肩代わりすると書かれています。

ややこしいことを避けたい場合は、OSが提供しているAPI(Media Foundationなど)を利用するのがよいと思います。





F#でWPF --- BitmapImageで画像ロード

下記記事にてOpenCVと連携して画像を表示しました。

画像を読み込むだけであればWPFの標準セットで可能であるため、今回はWPFで画像をロードして表示してみます。

ついでにグレースケール化も行ってみます。

作成するアプリケーション

ファイルダイアログで画像を選び、表示するアプリケーションを作成します。
f:id:any-programming:20170306214920p:plain

アプリケーションコード

読込にはBitmapImageを利用します。(読込失敗の場合は例外が発生します。)

グレースケール化は、FormatConvertedBitmapを利用します。

その他ColorConvertedBitmap、TransformedBitmapなど色々あります。

Xamlは上記記事のものをそのまま使います。

<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity" 
        xmlns:local="clr-namespace:Actions;assembly=OpenCVSample"
        Title="MainWindow" Height="250" Width="400">
    <Grid>
        <Button Content="画像を開く" HorizontalAlignment="Left" Margin="10,10,0,0" VerticalAlignment="Top" Width="100">
            <i:Interaction.Triggers>
                <i:EventTrigger EventName="Click">
                    <local:OpenFileDialogAction Command="{Binding LoadImage}" />
                </i:EventTrigger>
            </i:Interaction.Triggers>
        </Button>
        <UniformGrid Margin="10,40,10,10" Columns="2">
            <Image Source="{Binding SrcImage}" />
            <Image Source="{Binding DstImage}" />
        </UniformGrid>
    </Grid>
</Window>

F#もupdateModel以外は上記記事のものをそのまま使います。

open System
open System.Windows
open System.Windows.Media
open System.Windows.Media.Imaging

type Model = 
    { SrcImage : BitmapSource 
      DstImage : BitmapSource }

let initialModel = 
    { SrcImage = null 
      DstImage = null }

type Msg = LoadImage of string

let updateModel model msg =
    match msg with
    | LoadImage x -> 
        let bmp = try BitmapImage(Uri(x))       
                  with | _ -> failwith "not supported"
        let gray = FormatConvertedBitmap(bmp, PixelFormats.Gray8, null, 0.0)
        { model with SrcImage = bmp
                     DstImage = gray }

[<STAThread>]
[<EntryPoint>]
let main argv = 
    let window = Application.LoadComponent(Uri("MainWindow.xaml", UriKind.Relative)) :?> Window
    window.DataContext <- DataContext(initialModel, updateModel, id)
    Application().Run(window) |> ignore   
    0






F#でWPF --- OpenCV連携

下記記事にてOpenCVを.netから利用できるようC++/CLIでラップする方法を紹介しました。
OpenCVをC++/CLIでラップ - 何でもプログラミング

今回はWPFと連携させて画像を表示してみたいと思います。

作成するアプリケーション

ファイルダイアログで画像を選び、表示するアプリケーションを作成します。折角なのでエッジ化した画像も表示してみます。

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

Canny追加

C++/CLI側のコードは上記記事のものを利用します。

追加してCanny関数を定義します。

[ExtensionAttribute]
static Mat8UC1^ Canny(Mat8UC1^ src, double threshold1, double threshold2)
{
    auto dst = gcnew Mat8UC1(src->Cols, src->Rows);
    cv::Canny(*src->_mat, *dst->_mat, threshold1, threshold2);
    return dst;
}


アプリケーションコード

WPFのImageコントロールはImageSourceクラスを受け取ります。

そのため、BitmapSource.Createを利用してMat8UC1クラスからBitmapSourceを作成しています。

Xaml内のOpenFileDialogActionは下記記事を参照してください。
F#でWPF --- ファイルダイアログCommand - 何でもプログラミング

F#内のDataContextは下記記事を参照してください。
F#でWPF --- Elm Architectureを利用したMVVM - 何でもプログラミング

Xaml

<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity" 
        xmlns:local="clr-namespace:Actions;assembly=OpenCVSample"
        Title="MainWindow" Height="250" Width="400">
    <Grid>
        <Button Content="画像を開く" HorizontalAlignment="Left" Margin="10,10,0,0" VerticalAlignment="Top" Width="100">
            <i:Interaction.Triggers>
                <i:EventTrigger EventName="Click">
                    <local:OpenFileDialogAction Command="{Binding LoadImage}" />
                </i:EventTrigger>
            </i:Interaction.Triggers>
        </Button>
        <UniformGrid Margin="10,40,10,10" Columns="2">
            <Image Source="{Binding SrcImage}" />
            <Image Source="{Binding DstImage}" />
        </UniformGrid>
    </Grid>
</Window>

F#

open System
open System.Windows
open System.Windows.Media
open System.Windows.Media.Imaging
open CV

type Model = 
    { SrcImage : BitmapSource 
      DstImage : BitmapSource }

let initialModel = 
    { SrcImage = null 
      DstImage = null }

type Msg = LoadImage of string

let updateModel model msg =
    match msg with
    | LoadImage x -> 
        let mat = Processor.Load(x)
        let toBitmapSource (x : Mat8UC1) = 
            BitmapSource.Create(x.Cols, x.Rows, 96.0, 96.0, PixelFormats.Gray8, null, x.Data, x.Cols * x.Rows, x.Cols)
        { model with SrcImage = toBitmapSource mat
                     DstImage = toBitmapSource <| mat.Canny(50.0, 200.0) }

[<STAThread>]
[<EntryPoint>]
let main argv = 
    let window = Application.LoadComponent(Uri("MainWindow.xaml", UriKind.Relative)) :?> Window
    window.DataContext <- DataContext(initialModel, updateModel, id)
    Application().Run(window) |> ignore   
    0






C++/CLIラッピング --- 逆引き

C++を.Netで利用する際、C++/CLIで必要になりそうなことをまとめていきます。

随時更新予定です。

クラスラップ基本構造

public ref class NetClass
{
public:
    NetClass()  { _cppClass = new CppClass(); }
    ~NetClass() { this->!NetClass(); } // Dispose()
    !NetClass() { delete _cppClass; }  // ファイナライザ
private:
    CppClass* _cppClass;
};


System::String → std::string (copy)

#include <msclr\marshal_cppstd.h>

System::String^ net;
std::string cpp = msclr::interop::marshal_as<std::string>(net);


std::string → System::String (copy)

std::string cpp;
System::String^ net = gcnew String(cpp.c_str());


System::String → const wchar_t* (no copy)

System::StringはUnicodeのため、wchar_t*しか利用できません。

System::String^ net;
pin_ptr<const wchar_t> pin = PtrToStringChars(net);
const wchar_t* p = pin;


array<int> → int* (no copy)

array<int>^ net;
pin_ptr<int> pin = &net[0];
int* p = pin;


std::vector<int> → array<int> (copy)

std::vector<int> cpp;
auto net = gcnew array<int>(cpp.size());
System::Runtime::InteropServices::Marshal::Copy(System::IntPtr(cpp.data()), net, 0, net->Length);


プロパティ

デフォルトのgetter、setterでよいならproperty int Value;のみでもOKです。

public ref class NetClass
{
public:
    property int Value 
    {
        int get()          { return 10; }
        int set(int value) { Console::WriteLine(value); }
    }
};


インターフェース

public interface class INetClass
{
public:
    virtual int GetValue();
};

public ref class NetClass : public INetClass
{
public:
    virtual int GetValue() { return 10; }
};


抽象クラス

public ref class NetClassBase abstract
{
public:
    virtual int GetValue() abstract;
};

public ref class NetClass : public NetClassBase
{
public:
    virtual int GetValue() override { return 10; }
};


拡張メソッド

using namespace System;
using namespace System::Runtime::CompilerServices;

[ExtensionAttribute]
public ref class Extension abstract sealed
{
public:
    [ExtensionAttribute]
    static String^ AddSpace(String^ src) { return src + " "; }
};


GCにメモリ使用量通知

C++のnew等でアンマネッジドなメモリを確保した場合、GCはそれを感知しません。

GCに通知しておくと、適宜Collectが行われるようになります。

0より大きい値でないと例外が発生します。

using namespace System;

GC::AddMemoryPressure(10);    // 追加
GC::RemoveMemoryPressure(10); // 削除


ref classをスコープ脱出時にデストラクト

gcnewを用いずにインスタンス化すると、通常のcppのクラスのように、スコープを抜けるとデストラクタが呼ばれます。

ref class NetClass
{
public:
    ~NetClass() { Console::WriteLine("destructed"); }
};

{
    NetClass c;
} // "destructed"


メンバのDisposeを自動で呼ぶ

スタックセマンティック(gcnewで作成されていない)のメンバが存在すると、親のDispose時にメンバのDisposeも呼んでくれます。

また、親のDisposeは宣言しなくても自動で作成されます。(宣言した場合は、親のDisposeが先に処理されます。)

ref class Child
{
public:
    ~Child() { Console::WriteLine("child disposed"); }
};

ref class Parent
{
    Child _child;
};

// C#側
Parent parent;
parent.Dispose(); // "child disposed"






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