C# + OpenCvSharp + WFP で Webカメラ画像を表示する

C#で利用できるOpenCVのラッパーライブラリの「OpenCvSharp」を使ってWebカメラで撮影した画像をWPFに表示したいと思います。

OpenCVを使用すれば数行で実現できるのでとっても簡単に実装できます。

今回使用するOpenCvSharpはバージョンが、3.4.1.20180319で2018年5月30日現在の最新版です。

準備編

外部ライブラリが必要なので、OpenCvSharp3をNuGetから取得します。

まずソリューションエクスプローラー上のプロジェクトから

参照 > NuGet パッケージ管理

f:id:Takachan:20180530003326p:plain

を選択します。次に、参照タブにて、OpenCvShapr3 と入力しパッケージを検索します。

f:id:Takachan:20180530003403p:plain

そうしたら画像のパッケージを選択してインストールを実行します。

f:id:Takachan:20180530003417p:plain

これで準備は完了です。

WPFで画面を作成

一応上部に撮影終了ボタン、下に撮影した画像の表示をする画面を用意します。

画像を表示するのはImageで、Window の Loaded イベントでWebカメラ処理を開始します。

<Window x:Class="CameraCapture.MainWindow"
        // ...省略
        Title="WebCamera Capture"
        Loaded="Window_Loaded">
    <StackPanel Margin="10">
        <StackPanel Orientation="Horizontal">
            <Button Width="120" Height="24" Content="End Capture" Click="Button_Click"/>
        </StackPanel>
        <Image x:Name="_Image"
               Margin="0 10 0 0" 
               Width="480"
               Height="270"
               HorizontalAlignment="Left"/>
    </StackPanel>
</Window>

C#側の実装

関係ない処理の方が多いですが、Window_Loaded イベントハンドラでスレッドプールにCaptureメソッドの実行を投げています。

WebCamera制御は VideoCaptureクラス、読んだ画像を格納するために Matクラスを使用します。

WPFとOpenCvSharp3は連携がシームレスにできるように、Matクラスに ToWriteableBitmap があり、戻り値が WriteableBitmap のためImage.Source にそのまま設定することが可能です。

using OpenCvSharp;
using OpenCvSharp.Extensions; // これ追加しておく
using System.Threading;
using System.Windows;

namespace CameraCapture
{
    /// <summary>
    /// MainWindow.xaml の相互作用ロジック
    /// </summary>
    public partial class MainWindow : System.Windows.Window
    {
        public bool IsExitCapture { get; set; }

        public MainWindow()
        {
            this.InitializeComponent();
        }

        /// <summary>
        /// カメラ画像を取得して次々に表示を切り替える
        /// </summary>
        public virtual void Capture(object state)
        {
            var camera = new VideoCapture(0/*0番目のデバイスを指定*/)
            {
                // キャプチャする画像のサイズフレームレートの指定
                FrameWidth = 480, 
                FrameHeight = 270,
                // Fps = 60
            };

            using (var img = new Mat()) // 撮影した画像を受ける変数
            using (camera)
            {
                while (true)
                {
                    if (this.IsExitCapture)
                    {
                        this.Dispatcher.Invoke(() => this._Image.Source = null);
                        break;
                    }

                    camera.Read(img); // Webカメラの読み取り(バッファに入までブロックされる

                    if (img.Empty())
                    {
                        break;
                    }

                    this.Dispatcher.Invoke(() =>
                    {
                        this._Image.Source = img.ToWriteableBitmap(); // WPFに画像を表示
                    });
                }
            }
        }

        // ---- EventHandlers ----

        /// <summary>
        /// Windowがロードされた時
        /// </summary>
        private void Window_Loaded(object sender, RoutedEventArgs e)
        {
            ThreadPool.QueueUserWorkItem(this.Capture);
        }

        /// <summary>
        /// Exit Captureボタンが押され時
        /// </summary>
        protected virtual void Button_Click(object sender, RoutedEventArgs e)
        {
            this.IsExitCapture = true;
        }
    }
}

【注意】パフォーマンスがとっても悪い

VideoCaptureのコンストラクタに画像のサイズを指定できるのですが、今回、1920x1080のFHD画質で30FPSが出るWebカメラだったため、初期化に1920x1080を指定したところRead()メソッドの呼び出しが120ミリ秒もかかって画面表示が10FPS以下(!?)という結果になりました。(Imageに画像を設定するところは数ミリ秒)

CPU使用率もかなり高く、これ以上コードもシンプル化できそうにないのでパフォーマンスは想像しているよりかなり悪いようです。