こんな動画をみつけたのでC#でもできないかと思い調べてみました。PythonでできることがC#ではできないなんてあってはなりません!(キリッ)
OpenCvSharpを使えばできます。ただWindowsFormsAppではなくWinFormsAppを使います。いつも使っているWindowsFormsAppだとうまくできませんでした。
プロジェクトのプロパティで対象のフレームワークは「.NET 5.0」を選択。
これでコンパイルすると
Microsoft.NET.Sdk.WindowsDesktop SDK を使用する必要はなくなりました。
ルート プロジェクト要素の SDK 属性を ‘Microsoft.NET.Sdk’ に変更することをご検討ください。
という警告がでます。これはプロジェクトファイル(.csproj)を変更すれば解決します。
1 行目に
<Project Sdk=”Microsoft.NET.Sdk.WindowsDesktop”>
と書かれていますが、これを
<Project Sdk=”Microsoft.NET.Sdk”>
に変更すると、警告が表示されなくなります。
ではNuGetでOpenCvSharp4.Windowsをインストールしましょう。そしてコードの上のほうに
1 2 3 |
using OpenCvSharp; using OpenCvSharp.Extensions; using System.IO; |
と書きます。ファイルを操作するのでSystem.IOも使います。
次に画像の顔検出などを機械学習に関連した学習済みデータの分類器をダウンロードします。ここからダウンロードしましょう。
https://github.com/opencv/opencv/tree/master/data/haarcascades
今回使用するのはhaarcascade_frontalface_default.xmlですが、他にも以下のようなものがあります。
ファイル名 | 内容 |
---|---|
haarcascade_eye.xml | 目 |
haarcascade_eye_tree_eyeglasses.xml | 眼鏡 |
haarcascade_frontalcatface.xml | 猫の顔(正面) |
haarcascade_frontalcatface_extended.xml | 猫の顔(正面) |
haarcascade_frontalface_alt.xml | 顔(正面) |
haarcascade_frontalface_alt2.xml | 顔(正面) |
haarcascade_frontalface_alt_tree.xml | 顔(正面) |
haarcascade_frontalface_default.xml | 顔(正面) |
haarcascade_fullbody.xml | 全身 |
haarcascade_lefteye_2splits.xml | 左目 |
haarcascade_licence_plate_rus_16stages.xml | ロシアのナンバープレート(全体) |
haarcascade_lowerbody.xml | 下半身 |
haarcascade_profileface.xml | 顔(証明写真) |
haarcascade_righteye_2splits.xml | 右目 |
haarcascade_russian_plate_number.xml | ロシアのナンバープレート(数字) |
haarcascade_smile.xml | 笑顔 |
haarcascade_upperbody.xml | 上半身 |
これらを実行ファイルと同じ場所におきます。
注:サンプルプログラムでは指定しやすいように同じフォルダに置いているだけです。管理しやすい場所であればどこでもいいです。
では準備が整ったところではじめましょう。
まずボタンが押されたら処理の対象になる画像ファイルを選択させる処理を示します。選択されたファイルが存在し、画像ファイルなのかどうかも確認して問題なければファイルパスを返します。画像ファイルかどうかはImage.FromFile(filePath)メソッドを実行して例外が発生しないかどうかで判断しています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
public partial class Form1 : Form { string GetOpenFilePathFromDialog() { string filePath = ""; OpenFileDialog dialog = new OpenFileDialog(); if (dialog.ShowDialog() == DialogResult.OK) filePath = dialog.FileName; dialog.Dispose(); if (!File.Exists(filePath)) return ""; Image image; try { image = Image.FromFile(filePath); image.Dispose(); return filePath; } catch { return ""; } } } |
処理対象の画像ファイルのパスが取得できたら顔認識の処理をして、該当部分を矩形で囲ったBitmapを取得します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 |
public partial class Form1 : Form { Bitmap GetResultBitmap(string inputImagePath) { // 結果画像 Mat matRetImage = null; // 顔認識用カスケードファイルパス string classifierFilePath = Application.StartupPath + "\\haarcascade_frontalface_default.xml"; if (!File.Exists(classifierFilePath)) { MessageBox.Show("顔認識用カスケードファイルがみつかりません"); return null; } // 顔認識用カスケード分類器を作成 using (var haarCascade = new CascadeClassifier(classifierFilePath)) // 判定画像ファイルをロード using (var matSrcImage = new Mat(inputImagePath, ImreadModes.Color)) using (var matGrayscaleImage = new Mat()) { matRetImage = matSrcImage.Clone(); // 入力画像をグレースケール化 Cv2.CvtColor( src: matSrcImage, dst: matGrayscaleImage, code: ColorConversionCodes.BGR2GRAY); // 顔認識を実行 var faces = haarCascade.DetectMultiScale( image: matGrayscaleImage, scaleFactor: 1.1, minNeighbors: 3, minSize: new OpenCvSharp.Size(100, 100)); // 認識した顔の周りを枠線で囲む foreach (var face in faces) { Cv2.Rectangle( img: matRetImage, rect: new Rect(face.X, face.Y, face.Width, face.Height), //color: new Scalar(0, 255, 255), color: new Scalar(0, 0, 255), thickness: 2); } } // 結果画像を表示 Bitmap retBitmap = BitmapConverter.ToBitmap(matRetImage); matRetImage.Dispose(); return retBitmap; } } |
フォーム上にピクチャーボックスを置き、ボタンが押されたら顔認識された部分が矩形で囲われたビットマップを表示させます。別のボタンを押したらファイルとして保存できるようにGetResultBitmapメソッドが返したビットマップはフィールド変数に保存しておきます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
public partial class Form1 : Form { private void button1_Click(object sender, EventArgs e) { // ダイアログでファイルを選択 string inputImagePath = GetOpenFilePathFromDialog(); // 適切ではないファイルが選択された場合は空の文字列が返される if (inputImagePath == "") { MessageBox.Show("ファイルが存在しないか画像ファイルではありません"); return; } Bitmap retBitmap = GetResultBitmap(inputImagePath); // ピクチャーボックスに表示 pictureBox1.SizeMode = PictureBoxSizeMode.Zoom; pictureBox1.Image = retBitmap; // フィールド変数に保存。古いものはDisposeする if(Bitmap != null) Bitmap.Dispose(); Bitmap = retBitmap; } } |
以下は顔認識された部分が矩形で囲われたビットマップをファイルとして保存する処理です。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
public partial class Form1 : Form { private void button2_Click(object sender, EventArgs e) { if (Bitmap == null) return; string filePath = GetSaveFilePathFromDialog(); if (filePath == "") return; // 拡張子で保存形式を変える(Bmp、Jpeg、Png、それ以外はなくていいよね?) FileInfo info = new FileInfo(filePath); if(info.Extension.ToLower() == ".bmp") Bitmap.Save(filePath, System.Drawing.Imaging.ImageFormat.Bmp); if (info.Extension.ToLower() == ".jpg") Bitmap.Save(filePath, System.Drawing.Imaging.ImageFormat.Jpeg); if (info.Extension.ToLower() == ".png") Bitmap.Save(filePath, System.Drawing.Imaging.ImageFormat.Png); } string GetSaveFilePathFromDialog() { string filePath = ""; SaveFileDialog dialog = new SaveFileDialog(); dialog.Filter = "bitmap(*.bmp)|*.bmp|jpeg(*.jpg)|*.jpg|png(*.png)|*.png"; if (dialog.ShowDialog() == DialogResult.OK) filePath = dialog.FileName; dialog.Dispose(); return filePath; } } |
最後に終了するときですが、なぜか終了後もプロセスが残ってしまいます。そこでEnvironment.Exit メソッドで強制的に終了させます。
1 2 3 4 5 6 7 8 9 |
public partial class Form1 : Form { protected override void OnClosed(EventArgs e) { base.OnClosed(e); Environment.Exit(0); } } |
実行結果ですが、顔の方向が悪いとうまく認識されません。
使用した写真
https://www.photo-ac.com/main/detail/4191092
https://www.photo-ac.com/main/detail/1597634
https://www.photo-ac.com/main/detail/3214135