技術屋卵の足跡

いつか振り返ったときにどれだけ這いずり回ったかを確認したい

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なんて古いバージョンを使っているのと同じように、GUIWPFではなく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();
        }
    }
}

f:id:pfpfdev:20200715163957p:plain
レナさんを検出する様子

参考

メモリ対策 atsukanrock.hatenablog.com

次は自作の学習ファイルを作成する話かな。