OpenCVで顔認識など特徴ある領域の検出

# はじめに
前回の記事では、肌色部分の抽出を行いました。今度はもう少し一歩進んで、顔など特徴をもった領域の認識をさせてみたいと思います。

なをOpenCVを初めて使う方は、事前に以下の記事をご参考にして頂きAndroid向けのOpenCVのセットアップをお願いします

http://qiita.com/yokobonbon/items/0804d0418bf7dd612b66

# OpenCV CascadeClassifier
特徴を持った領域の認識には、CascadeClassifierというクラスを利用します。このクラスは、カスケードファイルという物体の特徴を記述したXMLファイルが必要になります。

```java
CascadeClassifier(java.lang.String filename) 
```

カスケードファイルは、SDK内の以下のディレクトリにあります。
OpenCV-android-sdk/sdk/etc/libpcascades

今回は顔認識を致しますので、lbpcascade_frontalface.xmlというファイルを顔認識させるアプリケーションのres/rawにコピーしておいて下さい。

# CascadeClassifierクラスを使用する前の準備

残念ながらCascadeClassifierクラスはリソースを読み込めず、ファイルを作成してファイルパスを指定する必要があります。よって、XMLの内容を事前にファイルに書き出す必要があります。今回は、setupCascadeFile()という以下のメソッドを作成して、特定ディレクトリ以下にカスケードファイルがない場合は作成するという実装をします。

```Java
    private File setupCascadeFile() {
            File cascadeDir = mContext.getDir("cascade", Context.MODE_PRIVATE);
            File cascadeFile = null;
            InputStream is = null;
            FileOutputStream os = null;
            try {
                cascadeFile = new File(cascadeDir, "lbpcascade_frontalface.xml");
                if (!cascadeFile.exists()) {
                    is = mContext.getResources().openRawResource(R.raw.lbpcascade_frontalface);
                    os = new FileOutputStream(cascadeFile);
                    byte[] buffer = new byte[4096];
                    int readLen = 0;
                    while ((readLen = is.read(buffer)) != -1) {
                        os.write(buffer, 0, readLen);
                    }
                }
            } catch (IOException e) {
                return null;
            } finally {
                if (is != null) {
                    try {
                        is.close();
                    } catch (Exception e) {
                        // do nothing
                    }
                }
                if (os != null) {
                    try {
                        os.close();
                    } catch (Exception e) {
                        // do nothing
                    }
                }
            }
            return cascadeFile;
        }
```

# CascadeClassifierインスタンスの作成
上記のカスケードファイルが作成されれば、CascadeClassifierクラスのインスタンス化は容易です。ここでは、OpenCVライブラリの読み込み後に呼び出される、onManagerConntected()メソッド内でCascadeClassifierのインスタンス化を行います。

```java
        private CascadeClassifier setupFaceDetector() {
            File cascadeFile = setupCascadeFile();
            if (cascadeFile == null) {
                return null;
            }

            CascadeClassifier detector = new CascadeClassifier(cascadeFile.getAbsolutePath());
            if (detector.empty()) {
                return null;
            }
            return detector;
        }

        @Override
        public void onManagerConnected(int status) {
            super.onManagerConnected(status);
            switch (status) {
                case LoaderCallbackInterface.SUCCESS:
                    mCameraView.enableView();
                    mFaceDetector = setupFaceDetector();
                    break;
                default:
                    break;
            }
        }
```

# 顔領域の認識
上記のインスタンス化されたCascadeClassifierを利用して領域を認識します。検出には、以下のdetectMultiScaleというメソッドを用います。入力画像にはグレースケールが必要です。

```java
public void detectMultiScale(Mat image,
                             MatOfRect objects,
                             double scaleFactor,
                             int minNeighbors,
                             int flags,
                             Size minSize,
                             Size maxSize)
```

上記のメソッドを用いて獲得された顔領域に枠で囲む例が以下になります。ここでは、最低の領域サイズを画面の縦サイズの1/5と仮にしています。

```java
        @Override
        public void onCameraViewStarted(int width, int height) {
            if (mMinFaceSize == null) {
                mMinFaceSize = new Size(height / 5, height / 5);
            }
        }

        @Override
        public void onCameraViewStopped() {

        }

        @Override
        public Mat onCameraFrame(CameraBridgeViewBase.CvCameraViewFrame inputFrame) {
            Mat rgba = inputFrame.rgba();
            if (mFaceDetector != null) {
                MatOfRect faces = new MatOfRect();
                mFaceDetector.detectMultiScale(inputFrame.gray(), faces, 1.1, 2, 2, mMinFaceSize,
                        new Size());
                Rect[] facesArray = faces.toArray();
                for (int i = 0; i < facesArray.length; i++) {
                    Imgproc.rectangle(rgba, facesArray[i].tl(), facesArray[i].br(), FACE_RECT_COLOR, 3);
                }
            }
            return rgba;
        }
    }
```
<img width="394" alt="スクリーンショット 2015-11-15 22.30.04.png" src="https://qiita-image-store.s3.amazonaws.com/0/85182/b06c3fd9-d816-ca47-cbd5-3c01725deba6.png">


# 顔以外の領域の認識
上記のカスケードファイルを変更すれば他の領域を検出することも可能。例えば、以下のサイトからカスケードファイル(aGest.xml)をダウンロードして使用すると、握りこぶしを認識するようになる。

https://code.google.com/p/ehci/wiki/HandTracking