C# + OpenCvSharp + WPF で、USBカメラ画像の表示や保存をしてみた

前回はコンソールアプリでUSBカメラ画像の表示や保存をしてみましたが、今回はWPFアプリとして作ってみました。

ただ、まだまだWPFのお約束には慣れていないので、まずはコードビハインドで実装してみます。

また、画像の取得についても、

などの記事を参考に、BackgroundWorkerを使って行うようにします。

 

環境

 

MainWindow.xaml

USBカメラ画像を表示するImageと、保存ボタンだけがあるWindowです。

<Grid>
    <Grid.RowDefinitions>
        <RowDefinition Height="270"/>
        <RowDefinition Height="35"/>
    </Grid.RowDefinitions>
    
    <StackPanel Grid.Row="0">
        <Image Name="Monitor" VerticalAlignment="Top" Loaded="imaging_Loaded"></Image>
    </StackPanel>
    <Button Grid.Row="1" Margin="4" Width="100" Content="保存" Click="Button_Click" />
</Grid>

 

MainWindows.xaml.cs

コードビハインド部分です。

 

BackgroundWorkerの利用

コンストラクタとWindow_Loadedイベントで、BackgroundWorkerに関する処理を記載します。

public MainWindow()
{
    InitializeComponent();

    worker = new BackgroundWorker();

    // ProgressChangedイベントを発生させるようにする
    worker.WorkerReportsProgress = true;

    // RunWorkerAsyncメソッドで呼ばれるDoWorkに、
    // 別スレッドでUSBカメラの画像を取得し続けるイベントハンドラを追加
    worker.DoWork += (sender, e) =>
    {
        using (var capture = Cv.CreateCameraCapture(0))
        {
            IplImage frame;
            while (true)
            {
                frame = Cv.QueryFrame(capture);

                // 新しい画像を取得したので、
                // ReportProgressメソッドを使って、ProgressChangedイベントを発生させる
                worker.ReportProgress(0, frame);
            }
        }
    };

    // ReportProgressメソッドで呼ばれるProgressChangedのイベントハンドラを追加
    worker.ProgressChanged += new ProgressChangedEventHandler(worker_ProgressChanged);
}


private void worker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
    // frameがe.UserStateプロパティにセットされて渡されてくる
    var image = (IplImage)e.UserState;


    // Sourceプロパティにセットするため、frameをWriteableBitmapへと変換(Bitmapだと型変換エラー)
    // WriteableBitmapConverterを使うには、
    // usingディレクティブにOpenCvSharp.Extensionsを追加
    // (OpenCvSharp.UserInterface.dll内)
    Monitor.Source = WriteableBitmapConverter.ToWriteableBitmap(image);
}


private void Window_Loaded(object sender, RoutedEventArgs e)
{
    // DoWorkイベントハンドラの実行を開始
    worker.RunWorkerAsync();
}

なお、イベントハンドララムダ式<hoge>EventHandlerの2つの方法で実装していますが、書き方を比較したかっただけで特に深い意味はありません。ラムダ式のほうが簡潔になりますね。

 

Image.Sourceへの設定

上のコードにある通り、ProgressChangedイベントで設定します。

ただ、USBカメラの画像はIplImage型で渡されてくるため、そのままではSystem.Windows.Media.ImageSource型であるImage.Sourceへと設定することができません。

そのため、OpenCvSharpにあるWriteableBitmapConverterを利用して、 WriteableBitmap型へと変換します *1

なお、WriteableBitmap型へ変換するWriteableBitmapConverter.ToWriteableBitmap()メソッドは、OpenCvSharp.Extensions名前空間 (OpenCvSharp.UserInterface.dll内)にあるため、usingディレクティブに追加しておくとよいかもしれません。

この部分のコードを再掲します。

private void worker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
    var image = (IplImage)e.UserState;
    Monitor.Source = WriteableBitmapConverter.ToWriteableBitmap(image);
}

 

ボタンを押した時の保存処理

Image.Sourceの値を、実行ファイルと同じディレクトリに上書き出力してみます。

また、画像の形式はBitmap以外にも指定できますが、今回はBitmapのまま出力します。

 
まずは、Image.Sourceの値をWriteableBitmap型に変換します。

var image = (WriteableBitmap)Monitor.Source;

 
あとはFileStreamを使って出力します。

using (var fs = new System.IO.FileStream("hoge.bmp", System.IO.FileMode.Create))
{
    //  BmpBitmapEncoderの他に、PngBitmapEncoderとかもある
    var enc = new BmpBitmapEncoder();
    enc.Frames.Add(BitmapFrame.Create(image));
    enc.Save(fs);

    MessageBox.Show("保存しました");
}

ちなみに、BmpBitmapEncoder以外にPngBitmapEncoderなどもあるため、必要に応じて適切なEncoderを使います。
bitmapsource - How to save a WPF image to a file - Stack Overflow

 

画面イメージ

前回とは別の、極早生リンゴの「ちなつ」を表示しています。

f:id:thinkAmi:20140801051909p:plain

 

ソースコード

GitHubに上げました。OpenCvSharpAppプロジェクトに追加しています。
CSharp-Sample/OpenCvSharpApp at master - thinkAmi/CSharp-Sample - GitHub

 

参考

BackgroundWorkerまわり

*1:Bitmap型だと型変換エラー「型'System.Drawing.Bitmap'を型'System.Windows.Media.ImageSource'に暗黙的に変換できません」が発生します