[iOS]顔検出をCoreImageとOpenCVでいまさらやってみる

## 概要
CoreImageの機能を使うと、画像中の顔のある領域は簡単に調べることができます。
精度とか速度ってOpenCVの顔認識に比べたらどうなのかな？と少し思ったので自分で試してみました。
（既にやりつくされてるネタとは思いつつ…）

## 条件
OpenCVをカスタムする知識や技術が今のところないので、今回はお手軽な実装同士でどれだけ差が出るかの実験ということにしました。

### CoreImageの設定

```lang:CIDetectorの設定
- (void) useCIDetector:(UIImage *)image {
    CIImage *ciImage = [CIImage imageWithCGImage:image.CGImage];
    CIDetector *ciDetector = [CIDetector detectorOfType:CIDetectorTypeFace
                                                context:nil
                                                options:@{
                                                          CIDetectorAccuracy : CIDetectorAccuracyHigh,
                                                          CIDetectorSmile:@YES
                                                          }];
    
    NSArray *features = [ciDetector featuresInImage:ciImage];
    UIGraphicsBeginImageContextWithOptions(image.size, NO, 0.0);
    //    UIGraphicsBeginImageContext(self.imageView.image.size);
    [image drawInRect:CGRectMake(0, 0, image.size.width, image.size.height)];
    
    for (CIFaceFeature *feature in features) {
        // 顔に枠線
        CGContextRef drawContext = UIGraphicsGetCurrentContext();
        
        CGRect faceRect = feature.bounds;
        faceRect.origin.y = image.size.height - (faceRect.origin.y + faceRect.size.height);
        
        CGContextSetLineWidth(drawContext, 2.f);
        
        // 顔は青の枠線
        CGContextSetStrokeColorWithColor(drawContext, [UIColor blueColor].CGColor);
        CGContextStrokeRect(drawContext,faceRect);
        
        // 目は緑の枠線
        CGContextSetStrokeColorWithColor(drawContext, [UIColor greenColor].CGColor);
        if (feature.hasLeftEyePosition) {
            CGPoint leftEyePoint = feature.leftEyePosition;
            leftEyePoint.y = image.size.height - leftEyePoint.y;
            CGRect leftEyeRect = CGRectMake(leftEyePoint.x - 20, leftEyePoint.y - 20,
                                            40, 40);
            CGContextStrokeRect(drawContext,leftEyeRect);
        }
        if (feature.hasRightEyePosition) {
            CGPoint rightEyePoint = feature.rightEyePosition;
            rightEyePoint.y = image.size.height - rightEyePoint.y;
            CGRect rightEyeRect = CGRectMake(rightEyePoint.x - 20, rightEyePoint.y - 20,
                                             40, 40);
            CGContextStrokeRect(drawContext,rightEyeRect);
        }
    }
    UIImage *drawedImage = UIGraphicsGetImageFromCurrentImageContext();
    
    UIGraphicsEndImageContext();
    self.imageView.image = drawedImage;
}
```

### OpenCVの設定
[こちら](http://qiita.com/shu223/items/ffd2202eaf92d342f83d)を参考にさせて頂きました。（コードは省略します）
内容はcv::circleをcv::rectangleに変更した程度です。以下の２つのcascadeファイルを使って１つの画像に対し処理を行いました。（線色はCIDetectorと同じく 顔：青、目：緑）

* 顔：haarcascade_frontalface_alt.xml
* 目：haarcascade_eye.xml

## 例その１　正面
（上：OpenCV　下：CIDetector）
<img width="375" alt="syoumen.png" src="https://qiita-image-store.s3.amazonaws.com/0/71259/aefd3a92-55a8-46ac-2c22-132a6acae42a.png">

顔・目ともにあまり変わらず。
ただopencvの方は耳辺りに目が認識されてしまってますね。

## 例その２　集合写真
（上：OpenCV　下：CIDetector）
<img width="375" alt="Simulator Screen Shot 2015.12.17 19.01.19.png" src="https://qiita-image-store.s3.amazonaws.com/0/71259/c0d85263-1162-53c4-23be-45478757dec6.png">

顔の精度はあまり変わらず。
さきほどに比べるとサイズ小さめなのですが、CIDetectorは目の位置まで綺麗に検出しています。

## 例その３　横顔
（上：OpenCV　下：CIDetector）
<img width="375" alt="yokogao.png" src="https://qiita-image-store.s3.amazonaws.com/0/71259/776f2f47-b4c4-d901-1ff8-a3b708782f70.png">

どちらも横顔は認識せず。メガネも悪条件かもですね。
opencvはまた目の認識が荒ぶっています。

### まとめると
顔検出はあまり精度に差はなさそうです。
OpenCVにおける目の位置検出は、顔の枠内にあるかどうかの判定が必要そうです。
cascadeファイルを色々変えてみるのもアリだったかも知れません。

## Instrumentで見てみる
( ˘ω˘)軽く調べただけなので、話半分に…
### OpenCVの場合
![cv_result01.png](https://qiita-image-store.s3.amazonaws.com/0/71259/5a9f795e-6ad0-3648-2aee-02714709ee16.png)
![cv_result02.png](https://qiita-image-store.s3.amazonaws.com/0/71259/4cb78bce-1e64-9a91-d206-c3cd66f04992.png)

だいたい7秒ちょい。
スレッドが増えてるのがわかりますね。

### CIDetectorの場合
![ci_result01.png](https://qiita-image-store.s3.amazonaws.com/0/71259/71ea8363-b29d-ea3b-de11-19d16902398e.png)
![ci_result02.png](https://qiita-image-store.s3.amazonaws.com/0/71259/e30d7065-6bcf-6513-53c8-5f811e626427.png)

だいたい4秒弱。
やはりというべきなのかも知れませんが、CoreImageの方が速いですね。

## やってわかった注意点
OpenCVで画像を処理する際、＠2xの画像をOpenCVに引き渡すと、結果として、**画像を半分に縮小した状態で計算・出力が行われます。**
恐らくサイズ情報が表示サイズになるためで、これをやるとそのため精度は著しく落ちてしまいます。

またCIImageに関しても、＠2xの画像がセットされているUIImageViewから取得した場合、今度は検出される顔・目の座標が画面サイズの２倍になってしまうので半分に計算し直す必要があります。

## おわりに
単純な実装だとやはりCoreImageは強力ですね。
OpenCVを使うならやはり顔以外のものに対して、ということになりそうです。少しずつ手を出してみたい…


写真は以下のサイトからお借りしています。

* [ぱくたそ](https://www.pakutaso.com/)
* [CCライブラリー 【フリー素材集】](http://www.photo-ac.com/main/detail_pop/?p_id=92944&f=0919b9c37eb1c76035ff8aa4aadfdee6&c_id=&title=%E4%B8%89%E4%B8%96%E4%BB%A3%E5%AE%B6%E6%97%8F36&width=650&height=600)