技術屋卵の足跡

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

OpenCVSharpで顔識別

OpenCVSharpシリーズもこれでネタ切れ。

Googleで"OpenCV 顔認識"なんで検索するとほとんどCascadeの記事で、顔の識別までやっているのは結構少なかったり、ましてやOpenCVSharpで触れられているものはないので、顔識別というのは結構マイナーなのかもしれない。そこで今回はCascade分類器で抽出した画像が、誰の顔なのか判断する方法について書く。

OpenCVの顔認識器は以下のアルゴリズムを使用する。

  • Eigenface
  • Fisherface
  • LBPH

使い方は全部FaceRecognizerのインターフェースを持っているので同じ。

faceRecognizer = FaceRecognizer.CreateEigenFaceRecognizer();
//faceRecognizer = FaceRecognizer.CreateFisherFaceRecognizer();
//faceRecognizer = FaceRecognizer.CreateLBPHFaceRecognizer();
faceRecognizer.Train(faces, label);
faceRecognizer.Predict(roi,out result, out confidence);

ただしEigenとFisherは入力する画像サイズがすべて一定で、Fisherについては分類対象が2つ以上ないと学習できないという制限がある。 というわけで実用上はLBPHを使えば何も問題なく使えると思う。

Predictに関しては信頼度を出してくれるので、それを基に分類すればだれの顔かよくわかる。

以上の流れをコードに書くとこんな感じ。ただしPoC的な感じなのでロジックと使う関数群は正しいがコピペでは動かず、適切に変数を宣言したり画像を整えたりなどをしないとうまく動作/認識できないかも。

//顔認識器の作成
//画像ファイルを開いて学習する
//画像ファイルはCascade分類器で検出した矩形(このコードでいうとroi)をImWriteすることで作成できる
//プログラム中でトレーニングするならそのまま配列で渡せばよい
List<Mat> faces = new List<Mat>();
List<int> label = new List<int>();
foreach(var f in paths)
{
    var img = Cv2.ImRead(f,LoadMode.GrayScale);
    if (img.Data == null) continue;
    //LPBHではサイズを一定にしなくても動く
    Cv2.Resize(img,img,new OpenCvSharp.CPlusPlus.Size(150, 150));
    faces.Add(img);
    //対象に応じてラベル付け
    //今回は1人だけ認識するので定数、増やす場合は適当に番号をつければOK
    label.Add(1);
}
faceRecognizer = FaceRecognizer.CreateLBPHFaceRecognizer();
faceRecognizer.Train(faces, label);

//顔認識の対象を抽出するために顔検出をするCascade分類器
var faceDetector = new CascadeClassifier("haarcascade_frontalface_default.xml");

while(true)
{
    Mat frame = new Mat();
    //カメラから読み込み
    source.Read(frame);
    //画像処理セクション
    var grayFrame = new Mat(0, 0, MatType.CV_8U);
    // グレースケールへ変換
    Cv2.CvtColor(frame, grayFrame, ColorConversion.RgbToGray);
    //顔の検出
    var faces = faceDetector.DetectMultiScale(grayFrame,
        minNeighbors: 50,
        minSize: new OpenCvSharp.CPlusPlus.Size(50,50), 
        maxSize: new OpenCvSharp.CPlusPlus.Size(500, 500));
    for(var i= 0; i<faces.Length; i++)
    {
        //対象エリアは顔の検出範囲
        Mat roi = new Mat(grayFrame, faces[i]);
        Cv2.Resize(roi, roi,new OpenCvSharp.CPlusPlus.Size(150, 150));
        int result;
        double confidence;
        //顔認識、resultに判別したラベルが入るが、今回は1人しか学習してないので無視
        faceRecognizer.Predict(roi,out result, out confidence);
        //一定以上の自信があれば表示
        if(confidence< 100)
        {
            //0に近いほうが信頼度が高いので、パーセントっぽく正規化
            confidence = 100 - confidence;
            //枠で囲って強調する
            frame.Rectangle(faces[i], new Scalar(0,0,255),2);
            frame.PutText(Math.Round(confidence,2).ToString()+"%", 
                faces[i].TopLeft, FontFace.HersheyPlain, 1.5, new Scalar(0, 0, 255));
        }
        else
        {
            //自信がなければ囲うだけ
            frame.Rectangle(faces[i], new Scalar(200, 0, 0),1);
        }
    }
    //表示, そのままだと大量にWindowが生成されるのでpictureboxなどに変えたほうがいい
    Cv2.ImShow("frame",frame);
}

フリーな同じ人の大量の顔データはなかなか落ちてないし、自分の顔を出すのもあれなので今回は動作例は省略。 動かしてみた感じだと学習済みのものと非常に似ていれば判定可能、写っている角度とか表情が未学習のモノだと壊滅的。 まあ学習も一瞬で終わるしこんなもんか。