OpenCvSharp は、様々な画像処理/画像解析を行うことができます。
これまでに紹介した方法は、画像ファイルを読み込んで、画像の色調(トーン)や画像の中から顔や体の部位を検出する方法でした。
今回は、カメラで撮影している映像をリアルタイムで取得して画像処理をする方法を紹介します。
在宅ワークやリモートワークという新しい働き方が広がる中、必要性をあまり感じなかったwebカメラやpc内蔵カメラなどの使用頻度が高まっています。
このカメラを使って、体温測定ができるようなアプリケーション開発をするかもしれないので、事前予習も兼ねて記事を作成していきたいと思います。
オススメの参考書
C#の使い方が丁寧に解説しており、「基礎からしっかりと学びたい」という初心者の方にオススメの一冊です。サンプルコードも記載してあり、各章の最後に復習問題があるので理解度を確認しながら読み進めることができます。新しい C# のバージョンにも対応している書籍です。
プログラムの仕様
外付けのWebカメラを持っていないので、PCに内蔵されているカメラを使います。
- カメラの映像をリアルタイムでimageコントロールに反映
- ボタンを押したら映像をグレースケールに加工
- 顔を認識した部分に赤枠を表示
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)形式で記述します。
下のサンプルコードでデバックすると出力ビューにデバイスの名前が表示されました。
//参照先を追加
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
メソッドを使います。
詳しくはこちらの記事に記載していますので参考にしてください。
顔を認識した部分に赤枠を表示
顔検出を行うにはcascade.detectMultiScale
メソッドを使います。
詳しくはこちらの記事に記載していますので参考にしてください。
この仕様をうまい具合にまとめたのが以下になります。
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 コントロールに表示される映像がぎこちなくなる場合があるので、実際に使用するなら改善が必要だと思います。
とりあえずカメラの映像が反映されているので、この記事はここまでにします。
以上、最後まで読んで頂きありがとうございました。