SourceChord

C#とXAML好きなプログラマの備忘録。最近はWPF系の話題が中心です。

OpenCvSharpを使ってみる

OpenCVC#などの.Net系言語で扱えるようにラップした、OpenCvSharpというライブラリを使ってみました。
コレ、すごくいい感じですね。

特徴

日本の作者さんが作成してる

作者さんのブログなど、日本語の情報が多くて、とっつき安いです。
http://schima.hatenablog.com/entry/2014/01/30/105406

Nugetですぐに使える

Nugetパッケージが公開されているので、気軽に使えます。
OpenCV本体のdllが一緒に含まれたパッケージもあるので、そっちを使えばすぐにC#OpenCVを使ったコードが書けるようになります。
OpenCVを使う時の面倒なライブラリの準備とかも一発で終わるのでとても便利ですねw

using構文を使ったリソース管理

画像データみたいな、大量のメモリやネイティブリソースを消費するクラスはちゃんとIDisposableを実装するように設計されていて、using構文を使ってしっかりとリソース管理できます。
スバラシイ!!

using (var img = new IplImage(@"H:\Test\1.jpg"))
{
    CvWindow.ShowImages(img);
    // usingのスコープを抜けるとimgのリソースは破棄される。
}
WinFormsやWPFの画像クラスとの相互運用性がある

OpenCVのIplImageからWPFのWriteableBitmapに変換する機能があります。
ということで、WPFアプリとの連携もバッチリ!!

Monoでも動作するとのこと

この辺は自分では試してないので省略。。。

使い方

準備

Nugetからインポートできるようになっています。
ということで、Nugetパッケージの管理メニューから、「opencvsharp」で検索して、
OpenCvSharp-AnyCPUというのをインポートします。
f:id:minami_SC:20140922003631p:plain:w300

最初の一歩

まずはコンソールアプリで使ってみます。

画像の読み込み

以下のようにIplImageを作成します。
このとき、using構文を用いると、画像のオブジェクトのリソース破棄のタイミングを管理することができます。
元のOpenCVでは使い終わったら、cvReleaseImageを呼び出してIplImageの解放をしましたが、
OpenCvSharpではusingを使ってリソース破棄を管理できます。

using (var img = new IplImage(@"H:\Test\1.jpg"))
{
    // ここでimgに対する色々な処理を記述
}
ウィンドウを表示する

OpenCV側の機能で、画像を表示するためのウィンドウを生成します。
CvWindowのインスタンスを作って、読み込んだ画像を渡すと新規ウィンドウで表示されます。
さっきと同様に、ここもusing構文で括っておきます。

using (new CvWindow(img))
{
    Cv.WaitKey();
}

また、以下のように書いても表示することができます。

CvWindow.ShowImages(img);

引数は可変長引数になっているので、複数の画像を渡して一気に表示することもできます。
コンソールアプリでは、OpenCVSharpを使って以下のようにして画像表示を行うことができます。。

    class Program
    {
        static void Main(string[] args)
        {
            using (var img = new IplImage(@"H:\Test\1.jpg"))
            {
                CvWindow.ShowImages(img);
            }
        }
    }

f:id:minami_SC:20140922004205j:plain:w400
こんなウィンドウで画像が表示されます。

グレースケールに変換してみる

変換した画像を格納するための変数を作り、この変数もusing構文で括っておきます。
で、CvtColorメソッドで画像の色空間変換を行えます。引数にColorConversion.BgraToGrayを渡してグレースケール変換した結果を取得します。

using (var img = new IplImage(@"H:\Test\1.jpg"))
using (var dst = new IplImage(img.Size, BitDepth.U8, 1))
{
    // imgをグレースケール化する
    Cv.CvtColor(img, dst, ColorConversion.BgraToGray);

    CvWindow.ShowImages(dst);
}

f:id:minami_SC:20140922004215j:plain:w400

WPFとの連携

今度は、OpenCVSharpで加工した画像を、WPFのImageコントロールに表示してみます。

IplImageからWriteableBitmapへの変換

IplImageからWriteableBitmapに変換するために、ToWriteableBitmapという拡張メソッドが用意されています。
これを使うと、手軽にIplImageからWriteableBitmapに変換して、WPF側で使うことができます。

サンプルコード

ボタンを押したら、OpenCVSharpで画像を読み込み、グレースケール化してからImageコントロールで表示をします。
サンプルなので、全部の処理をコードビハインドで書いてます。

MainWindow.xaml
<Window x:Class="OpenCVSharpTest.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow"
        Width="525"
        Height="350">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition />
            <RowDefinition Height="Auto" />
        </Grid.RowDefinitions>
        <Image x:Name="imgResult"
               Margin="10" />
        <Button Grid.Row="1"
                Width="75"
                Margin="10"
                HorizontalAlignment="Right"
                VerticalAlignment="Top"
                Content="表示"
                Click="Button_Click" />
    </Grid>
</Window>
MainWindow.xaml.cs
// ToWriteableBitmap拡張メソッドを使うために↓を追加
using OpenCvSharp.Extensions;

namespace OpenCVSharpTest
{
    /// <summary>
    /// MainWindow.xaml の相互作用ロジック
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }

        private void Button_Click(object sender, RoutedEventArgs e)
        {
            using (var img = new IplImage(@"H:\Test\1.jpg"))
            using (var dst = new IplImage(img.Size, BitDepth.U8, 1))
            {
                // 読み込んだ画像をグレースケール化して、ImageコントロールのSourceに設定
                Cv.CvtColor(img, dst, ColorConversion.BgraToGray);
                imgResult.Source = dst.ToWriteableBitmap();
            }
        }
    }
}

ボタンを押すと、OpenCVで処理した結果をImageコントロールで表示します。
f:id:minami_SC:20140922004343j:plain:w300



ただし、このToWriteableBitmapメソッドは1点注意が必要です。
このメソッドを呼び出すたびに、WriteableBitmapの新規インスタンスを作成します。
なので、頻繁にこのメソッドを呼び出すと、メモリ確保とGCを繰り返すことになり効率が悪くなります。

そんな時は、WriteableBitmapConverterクラスの
既存のインスタンスに対して書き込むようにします。
↓のように、WriteableBitmapのメンバを用意しておいて、そこにIplImageの内容をコピーします。

        // WPF側で常に同じインスタンスを使いまわすために、メンバを定義
        private WriteableBitmap wb = new WriteableBitmap(1152, 864, 96, 96, PixelFormats.Gray8, null);
        private void Button_Click(object sender, RoutedEventArgs e)
        {
            using (var img = new IplImage(@"H:\Test\1.jpg"))
            using (var dst = new IplImage(img.Size, BitDepth.U8, 1))
            {
                // 読み込んだ画像をグレースケール化して、ImageコントロールのSourceに設定
                Cv.CvtColor(img, dst, ColorConversion.BgraToGray);
                // 引数で指定したインスタンスに、画像データをコピーする
                WriteableBitmapConverter.ToWriteableBitmap(dst, wb);
                imgResult.Source = wb;
            }
        }

ちょこっと画像を表示する程度では問題にならないけど、
Webカメラからキャプチャした画像をWPF側で表示する、、
とかやり始めるとこういう部分のメモリ効率も考えなきゃいけなくなりますね。