OpenCVSharpでCascade分類器
Deep Learningに頼らずに結構高速に顔の検出などができることで有名なCascade分類器のOpenCVSharpでの実装。 詳しい理論とか、実行例なんかはPythonで書いている記事がほかにもたくさんあると思うので、ここでは省略しようとおもう。
まずはインスタンスを作成。 ファイルはよく使われるここの学習データを用いた。
var faceDetector = new CascadeClassifier("haarcascade_frontalface_default.xml");
あとは検出したい画像をCV_8UC1のMatで用意してみて検出するだけ。
// グレースケールへ変換 Cv2.CvtColor(img,gray,ColorConversion.RgbToGray); //顔の検出 //最大サイズと最小サイズを定義しておくと //高速化&精度Upなので、できれば埋める var faces = faceDetector.DetectMultiScale(gray, minSize: new OpenCvSharp.CPlusPlus.Size(150, 150), maxSize: new OpenCvSharp.CPlusPlus.Size(250, 250)); foreach(var face in faces) { //長方形で囲う Cv2.Rectangle(frame, face, new Scalar(0, 255, 0),3); }
forで回しながらカメラから画像を取得し続ければ、動画で顔検出も可能。
Pythonで動画再生をするときはimshowで同じWindowタイトルを指定すれば勝手に更新してくれて簡単にできたけど、C#ではたくさんWindowが生成されてしまってそんな簡単にはいかない。さらに恐ろしいことにメモリリークを起こしてしまうとGCが働くまで1GBくらいまでメモリを食ってしまうこともあるので、ちゃんとメモリ対策も行うとこんな感じで実装できそう。
OpenCVSharp v2.4.10なんて古いバージョンを使っているのと同じように、GUIもWPFではなくFormアプリケーションでやらないといけなかった...orz。
public partial class Form1 : Form { public Form1() { InitializeComponent(); } VideoCapture source; CascadeClassifier faceDetector; System.Threading.Timer timer; private void Form1_Load(object sender, EventArgs e) { //カメラ読み込み source = new VideoCapture(0); //カスケード分類器の読み込み faceDetector = new CascadeClassifier("haarcascade_frontalface_default.xml"); //更新頻度 int interval = (int)(1000 / source.Fps); //定期実行 timer = new System.Threading.Timer(update, null, 0, interval); } private void update(object state) { using (Mat img = new Mat()) { source.Read(img); if (img == null) return; Cv2.Resize(img,img,new OpenCvSharp.CPlusPlus.Size(pictureBox1.Width, pictureBox1.Height)); using (Mat gray = new Mat()) { // グレースケールへ変換 Cv2.CvtColor(img, gray, ColorConversion.RgbToGray); //顔の検出 //最大サイズと最小サイズを定義しておくと //高速化&精度Upなので、できれば埋める var faces = faceDetector.DetectMultiScale(gray, minSize: new OpenCvSharp.CPlusPlus.Size(150, 150), maxSize: new OpenCvSharp.CPlusPlus.Size(250, 250)); foreach (var face in faces) { //長方形で囲う Cv2.Rectangle(img, face, new Scalar(0, 255, 0), 2); } } pictureBox1_Update(img); } } //メモリリークを防ぎながらpictureBox1の内容更新を行う private void pictureBox1_Update(Mat mat) { //開放のためにアクセスできるようにしておく var oldImg = pictureBox1.Image; //更新してからでないと例外発生 pictureBox1.Image = BitmapConverter.ToBitmap(mat); //ここで開放しないと見事にメモリリーク if (oldImg != null) { oldImg.Dispose(); } } }
参考
メモリ対策 atsukanrock.hatenablog.com
次は自作の学習ファイルを作成する話かな。