C#

【WPF】OpenCVSharpでWebカメラの画像を表示する

OpenCvSharpは、様々な画像処理/画像解析を行うことができます。
これまでに紹介した方法は、画像ファイルを読み込んで、画像の色調(トーン)や画像の中から顔や体の部位を検出する方法でした。
今回は、カメラで撮影している映像をリアルタイムで取得して画像処理をする方法を紹介します。

在宅ワークやリモートワークという新しい働き方が広がる中、必要性をあまり感じなかったwebカメラやpc内蔵カメラなどの使用頻度が高まっています。
このカメラを使って、体温測定ができるようなアプリケーション開発をするかもしれないので、事前予習も兼ねて記事を作成していきたいと思います。

プログラムの仕様

外付けのWebカメラを持っていないので、PCに内蔵されているカメラを使います。

  1. カメラの映像をリアルタイムでimageコントロールに反映
  2. ボタンを押したら映像をグレースケールに加工
  3. 顔を認識した部分に赤枠を表示

 

UI画面の実装

カメラの映像を読み取り開始するボタン、読み取りを終了するボタン、グレースケールに加工するボタン、顔を検出するボタンを配置します。読み取りしたカメラの映像はイメージコントロールに表示します。

<Grid>
    <Grid.RowDefinitions>
        <RowDefinition Height="50" />
        <RowDefinition Height="*" />
    </Grid.RowDefinitions>
    <Grid.ColumnDefinitions>
        <ColumnDefinition />
        <ColumnDefinition />
    </Grid.ColumnDefinitions>

    <StackPanel
        Grid.ColumnSpan="2"
        HorizontalAlignment="Left"
        Orientation="Horizontal">
        <materialDesign:PackIcon
            Width="35" Height="35" Margin="10" VerticalAlignment="Center"
            Foreground="{StaticResource PrimaryHueMidBrush}"
            Kind="PictureInPictureTopRightOutline" />
        <TextBlock
            Margin="0,0,40,0" HorizontalAlignment="Center"
            Style="{StaticResource MaterialDesignHeadline4TextBlock}"
            Text="OpenCvSharp" />
    </StackPanel>
    <StackPanel
        Grid.ColumnSpan="2"
        HorizontalAlignment="Right"
        Orientation="Horizontal">
        <Button
            x:Name="btnStart" Width="80" Margin="10,0"
            Click="btnStart_Click" Content="開始" />
        <Button
            x:Name="btnFinifh" Width="80" Margin="10,0"
            Click="btnFinifh_Click" Content="終了" />
        <Button
            x:Name="btnProcessImage" Width="80" Margin="10,0"
            Click="btnProcessImage_Click" Content="変換" />
        <Button
            x:Name="btnDetect" Width="80" Margin="10,0"
            Click="btnDetect_Click_1" Content="顔検出" />
    </StackPanel>

    <Image
        x:Name="ImageData" Grid.Row="1" Grid.ColumnSpan="2" Margin="5,5,5,5" />
</Grid>

 

アプリケーションの実装

カメラの映像をリアルタイムでimageコントロールに反映

OpenCvSharpには、VideoCaptureというカメラを扱うための専用のクラスが用意されています。このクラスを使うことで、PCに接続されているWebカメラや内臓されているカメラに写っている映像を取得することができます。

どのカメラに接続して映像を取得するか、カメラのデバイスIDを指定して設定をします。カメラが1台しか接続されていない場合は、0を指定します。今回はPCに内臓されているカメラのみしか接続されていない状況ですので、デバイスIDは0にしてVideoCaptureのインスタンスを生成します。

その後にフレームの縦横のサイズを指定します。ここでは解像度はが120万画素数(1280×720ピクセル)の720p FaceTime HDカメラを使うので、とりあえず半分のサイズを指定しました。

//カメラ画像取得用のVideoCaptureのインスタンス生成
var capture = new VideoCapture(index);
capture.FrameHeight = 640;
capture.FrameWidth = 360;

 

カメラのデバイス名取得方法

ここでは使用しませんが、カメラのデバイス名を取得する方法を紹介します。例えば、カメラのデバイス名をコンボボックスに追加して、コンボボックスの中から選択したデバイス名と接続するといったような使い方ができそうです。

デバイス名を取得する場合、Win32_PnPSignedDriverのクラスを使います。これは署名されたドライバの情報を取得することができ、ManagementObjectSearcherで検索条件を指定することで、欲しいデバイスの情報を取得することができます。検索条件は、データベースで使用されるクエリー(query)形式で記述します。

参考

Win32_PnPSignedDriverクラスはSystem.Managementの名前空間の中で定義されていますので、usingディレクトリで名前空間の追加と参照マネージャーで参照先(System.Management)を追加します。
参照マネージャーは、Visual studioのメニューバーから[プロジェクト]>[参照先の追加]の順で選択すると表示されます。

下のサンプルコードでデバックすると出力ビューにデバイスの名前が表示されました。

//参照先を追加
using System.Management;

namespace Sample
{
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();

            var searcher = new ManagementObjectSearcher("SELECT * FROM Win32_PnPSignedDriver WHERE DeviceClass = 'Camera'");

            foreach (var device in searcher.Get().Cast().OrderBy(n => n["PDO"]))
            {
                var name = device["FriendlyName"];
                Debug.WriteLine("Device Name:" + name);
            }
        }
    } 
}

 

カメラの映像を取得する方法

映像を取得するには、VideoCaptureのReadメソッドを使用します。常に映像を更新し続けるためにwhile文でループさせます。
注意しなければならないのが、ループ処理を非同期にして映像をUI画面に表示させなければならないということです。非同期処理にする方法として、Task.Run()の引数にwhile文でループさせている処理を渡す方法があります。
これだけで元々同期だった処理を非同期で処理することができますが、UIスレッドと別スレッドで処理されます。非同期中にUIのコントロールへデータを渡す場合は、Dispatcher.Invoke()使ってUIスレッドで実行をする様にします。

//カメラの映像取得
IsLoop = true;
using (capture)
using (Mat img = new Mat())
{
    await Task.Run(() =>
    {
        while (IsLoop)
        {
            capture.Read(img);

            if (img.Empty()) break;

            Dispatcher.Invoke(() => Image.Source = img.ToWriteableBitmap());
        }
    });
}

 

ボタンを押したら映像をグレースケールに加工

グレースケールに加工するにはCvtColorメソッドを使います。詳しくはこちらの記事に記載していますので参考にしてください。

【WPF】OpenCVSharpで画像の読み取りC#のWPFで、OpenCVSharpで読み取りした画像を表示する方法と画像をグレースケールに変換する方法について紹介します。画像の読み取りはMatクラスを使い、引数に画像ファイルのパスを指定します。ImageコントロールのSourceプロパティに渡すために、ToWriteableBitmapメソッドでWriteableBitmap形式に変換することで表示することができます。...

顔を認識した部分に赤枠を表示

顔検出を行うにはcascade.detectMultiScaleメソッドを使います。詳しくはこちらの記事に記載していますので参考にしてください。

【WPF】OpenCVSharpで画像の中にある顔を検出するC#のWPFでOpenCvSharpを使って、画像の中の顔や体などを検出(認識)する方法について説明しています。カスケード識別器とは人間の顔の特徴について学習をしたデータをまとめたファイルのことで、このファイルを利用すると画像から顔などを検出(認識)することができます。...

この仕様をうまい具合にまとめたのが以下になります。

public partial class UserControlOpenCvSharpCapture : UserControl
{
    private Mode mode = Mode.None;
    private enum Mode
    {
        None,
        Process,
        Detect
    };

    //映像読み取りを続行するかしないか
    private bool IsLoop = false;

    //識別器の読み込み
    private string face_cascade = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Desktop), "haarcascade_frontalface_default.xml");

    public UserControlOpenCvSharpCapture()
    {
        InitializeComponent();
    }

    //開始ボタン押下処理
    private async void btnStart_Click(object sender, RoutedEventArgs e)
    {
        mode = Mode.None;
        IsLoop = await StartRollingCamera(0);
        ImageData.Source = null;
    }

    //終了ボタン押下処理
    private void btnFinish_Click(object sender, RoutedEventArgs e)
    {
        IsLoop = false;
    }

    //加工ボタン押下処理
    private void btnProcessImage_Click(object sender, RoutedEventArgs e)
    {
        mode = Mode.Process;
    }

    //顔検出ボタン押下処理
    private void btnDetect_Click_1(object sender, RoutedEventArgs e)
    {
        mode = Mode.Detect;
    }

    private async Task StartRollingCamera(int index)
    {
        //カメラ画像取得用のVideoCaptureのインスタンス生成
        var capture = new VideoCapture(index);
        capture.FrameHeight = 640;
        capture.FrameWidth = 360;

        //カメラの接続確認
        if (!capture.IsOpened())
        {
            MessageBox.Show("Can't use camera.");
            return false;
        }

        //カメラの映像取得
        IsLoop = true;
        using (capture)
        using (Mat img = new Mat())
        {
            await Task.Run(() =>
            {
                while (IsLoop)
                {
                    capture.Read(img);

                    if (img.Empty()) break;

                    //Modeに応じて加工処理を行う
                    switch (mode)
                    {
                        case Mode.None:
                            //何も加工しない
                            break;
                        case Mode.Process:
                            //グレースケールに加工
                            OpenCvSharp.Cv2.CvtColor(img, img, OpenCvSharp.ColorConversionCodes.RGB2GRAY);
                            break;
                        case Mode.Detect:
                            //グレースケールに加工
                            var gray = new Mat();
                            OpenCvSharp.Cv2.CvtColor(img, gray, OpenCvSharp.ColorConversionCodes.RGB2GRAY);

                            //顔を検知
                            using (CascadeClassifier cascade = new CascadeClassifier(face_cascade))
                            {
                                foreach (OpenCvSharp.Rect rectFace in cascade.DetectMultiScale(gray))
                                {
                                    // 検知した顔を赤枠で囲む
                                    OpenCvSharp.Rect rect = new OpenCvSharp.Rect(rectFace.X, rectFace.Y, rectFace.Width, rectFace.Height);
                                    Cv2.Rectangle(img, rect, new OpenCvSharp.Scalar(0, 0, 255), 3);
                                }
                            }
                            break;
                    }
                    Dispatcher.Invoke(() => ImageData.Source = img.ToWriteableBitmap());
                }
            });
        }
        return false;
    }
}

 

アプリケーション起動

さっそく起動してみましょう。撮影したのは、2559回目のZIP冒頭の映像です。
GIFのファイル容量を減らすためにカットしまくってちょっと雑な動きになってしまっています。
カメラで撮影した映像をImageコントロールに反映できています。またボタンをクリックすることで、グレースケールへ加工したり顔認識したりと仕様通りの動作をすることができました。

まとめ

今回は、カメラで撮影している映像をリアルタイムで取得して画像処理する方法を紹介しました。顔認識処理をする行うと重くなりImageコントロールに表示される映像がぎこちなくなる場合があるので、実際に使用するなら改善が必要だと思います。
とりあえずカメラの映像が反映されているので、この記事はここまでにします。

以上、最後まで読んで頂きありがとうございました。

プログラミングを学習したいなら…

プログラミングスキルを身に付けるなら、プログラミングを効率良く学べる
プログラミングスクール」がオススメです。

特にこんな方にオススメ!!
これからエンジニアを目指したい
プログラミングの専門性を高めたい
プログラミングを学んで副業をしたい
エンジニアに転職して年収をアップさせたい

プログラミングを触ったことがない未経験からでも、プログラミングスクールで学習すれば、エンジニアへ就職・転職することも可能です。

あなたの「行動力」と「やる気」で、あなたの人生を大きく変えるチャンスになることでしょう。

プログラミングスクールに興味がある方は是非チェックしてみてください。

> プログラミングを学ぶ <

COMMENT

メールアドレスが公開されることはありません。

CAPTCHA