世間で一昔前から、iPhoneやAndroidで顔検出するアプリが結構リリースされいます。
今更ながら、どのような方法で顔を検出しているのか気になったので、調査してみました。
今回はiPhoneでの顔検出について記載します。
実はiOS5からCIDetectorというクラスが利用できるようになっているらしく、それを利用することによって、画像の中の顔の座標を検出できます。
簡単なサンプルアプリを作成してみました。
まずは、完成イメージ。
画面下部に顔検出で取得した顔のパーツの座標を表示するエリアがあります。
画面最下部にはボタンを配置し、タップすることで画像の切替・顔検出メソッド呼び出しを行います。
では、MainStoryBoard

ViewController.hは以下のような感じにしました。
#import @interface ViewController : UIViewController { int hoge; } @property (weak, nonatomic) IBOutlet UIImageView *imageView; @property (weak, nonatomic) IBOutlet UILabel *labelFaceX; @property (weak, nonatomic) IBOutlet UILabel *labelFaceY; @property (weak, nonatomic) IBOutlet UILabel *labelRightEyeX; @property (weak, nonatomic) IBOutlet UILabel *labelRightEyeY; @property (weak, nonatomic) IBOutlet UILabel *labelLeftEyeX; @property (weak, nonatomic) IBOutlet UILabel *labelLeftEyeY; @property (weak, nonatomic) IBOutlet UILabel *labelMouthX; @property (weak, nonatomic) IBOutlet UILabel *labelMouthY; @property (weak, nonatomic) IBOutlet UILabel *labelFaceWidth; @property (weak, nonatomic) IBOutlet UILabel *labelFaceHeight; - (IBAction)onClickButtonPicture:(id)sender; - (void)faceDetect:(UIImage*)image; @end
6行目から16行目で、アプリで利用する各パーツを宣言しています。
18行目のonClickButtonPictureが画面最下部のボタンタップ時の処理です。
20行目のfaceDetectで顔検出を行います。
ViewController.mは以下のような感じです。
#import "ViewController.h" #import #import @interface ViewController () @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; self.imageView.contentMode = UIViewContentModeScaleAspectFit; self.imageView.image = [UIImage imageNamed:@"1.jpeg"]; hoge = 0; // Do any additional setup after loading the view, typically from a nib. } - (void)didReceiveMemoryWarning { [super didReceiveMemoryWarning]; // Dispose of any resources that can be recreated. } - (IBAction)onClickButtonPicture:(id)sender { for (UIView *view in [self.imageView subviews]) { [view removeFromSuperview]; } self.labelFaceWidth.text = @""; self.labelFaceHeight.text = @""; self.labelFaceX.text = @""; self.labelFaceY.text = @""; self.labelLeftEyeX.text = @""; self.labelLeftEyeY.text = @""; self.labelRightEyeX.text = @""; self.labelRightEyeY.text = @""; self.labelMouthX.text = @""; self.labelMouthY.text = @""; UIImage *image; if (hoge == 0) { image = self.imageView.image; [self faceDetect:image]; hoge = 1; } else if (hoge == 1) { // 2.jpegに画像切替 image = [UIImage imageNamed:@"2.jpeg"]; self.imageView.image = image; hoge = 2; } else if (hoge == 2) { image = self.imageView.image; [self faceDetect:image]; hoge = 3; } else if (hoge == 3) { // 1.jpegに画像切替 image = [UIImage imageNamed:@"1.jpeg"]; self.imageView.image = image; hoge = 0; } } - (void)faceDetect:(UIImage *)image { // 検出器生成 NSDictionary *options = [NSDictionary dictionaryWithObject:CIDetectorAccuracyLow forKey:CIDetectorAccuracy]; CIDetector *detector = [CIDetector detectorOfType:CIDetectorTypeFace context:nil options:options]; // 検出 CIImage *ciImage = [[CIImage alloc] initWithImage:image]; NSArray *array = [detector featuresInImage:ciImage]; self.labelFaceWidth.text = @"***"; self.labelFaceHeight.text = @"***"; self.labelFaceX.text = @"***"; self.labelFaceY.text = @"***"; self.labelLeftEyeX.text = @"***"; self.labelLeftEyeY.text = @"***"; self.labelRightEyeX.text = @"***"; self.labelRightEyeY.text = @"***"; self.labelMouthX.text = @"***"; self.labelMouthY.text = @"***"; // 検出されたデータを取得 for (CIFaceFeature *faceFeature in array) { self.labelFaceWidth.text = [NSString stringWithFormat:@"%f", faceFeature.bounds.size.width]; self.labelFaceHeight.text = [NSString stringWithFormat:@"%f", faceFeature.bounds.size.height]; self.labelFaceX.text = [NSString stringWithFormat:@"%f", faceFeature.bounds.origin.x]; self.labelFaceY.text = [NSString stringWithFormat:@"%f", faceFeature.bounds.origin.y]; float scale = 0.0f; // 幅と高さの小さい方を把握 if (self.imageView.bounds.size.width < = self.imageView.bounds.size.height) { // 幅の方が小さかった場合 scale = self.imageView.bounds.size.width / self.imageView.image.size.width; } else { // 高さの方が小さかった場合 scale = self.imageView.bounds.size.height / self.imageView.image.size.height; } float dispWidth = self.imageView.image.size.width * scale; float dispHeight = self.imageView.image.size.height * scale; float dispX = (self.imageView.bounds.size.width - dispWidth) / 2; float dispY = (self.imageView.bounds.size.height - dispHeight) / 2; float faceWidth = faceFeature.bounds.size.width * scale; float faceHeight = faceFeature.bounds.size.height * scale; float faceX = faceFeature.bounds.origin.x * scale + dispX; float faceY = faceFeature.bounds.origin.y * scale + dispY; UIView *hogeView = [[UIView alloc] initWithFrame:CGRectMake(faceX, dispHeight - faceY - faceHeight, faceWidth, faceHeight)]; hogeView.layer.borderWidth = 8; hogeView.layer.borderColor = [[UIColor redColor] CGColor]; [self.imageView addSubview:hogeView]; if (faceFeature.hasLeftEyePosition) { self.labelLeftEyeX.text = [NSString stringWithFormat:@"%f", faceFeature.leftEyePosition.x]; self.labelLeftEyeY.text = [NSString stringWithFormat:@"%f", faceFeature.leftEyePosition.y]; float x = faceFeature.leftEyePosition.x * scale + dispX; float y = faceFeature.leftEyePosition.y * scale + dispY; UIView* leftEyeView = [[UIView alloc] initWithFrame:CGRectMake(x - (faceWidth * 0.15), dispHeight - y, faceWidth * 0.3, faceHeight * 0.3)]; [leftEyeView setBackgroundColor:[[UIColor blueColor] colorWithAlphaComponent:0.8]]; [leftEyeView setCenter:CGPointMake(x + (faceWidth * 0.0365), dispHeight - y)]; leftEyeView.layer.cornerRadius = faceWidth*0.15; [self.imageView addSubview:leftEyeView]; } if (faceFeature.hasRightEyePosition) { self.labelRightEyeX.text = [NSString stringWithFormat:@"%f", faceFeature.rightEyePosition.x]; self.labelRightEyeY.text = [NSString stringWithFormat:@"%f", faceFeature.rightEyePosition.y]; float x = faceFeature.rightEyePosition.x * scale + dispX; float y = faceFeature.rightEyePosition.y * scale + dispY; UIView* rightEyeView = [[UIView alloc] initWithFrame:CGRectMake(x - (faceWidth*0.15), dispHeight - y, faceWidth*0.3, faceWidth*0.3)]; [rightEyeView setBackgroundColor:[[UIColor greenColor] colorWithAlphaComponent:0.8]]; [rightEyeView setCenter:CGPointMake(x + (faceWidth * 0.0365), dispHeight - y)]; rightEyeView.layer.cornerRadius = faceWidth*0.15; [self.imageView addSubview:rightEyeView]; } if (faceFeature.hasMouthPosition) { self.labelMouthX.text = [NSString stringWithFormat:@"%f", faceFeature.mouthPosition.x]; self.labelMouthY.text = [NSString stringWithFormat:@"%f", faceFeature.mouthPosition.y]; float x = faceFeature.mouthPosition.x * scale + dispX; float y = faceFeature.mouthPosition.y * scale + dispY; UIView* mouthView = [[UIView alloc] initWithFrame:CGRectMake(x - (faceWidth*0.15), dispHeight - y, faceWidth*0.3, faceWidth*0.3)]; [mouthView setBackgroundColor:[[UIColor purpleColor] colorWithAlphaComponent:0.8]]; [mouthView setCenter:CGPointMake(x + (faceWidth * 0.0365), dispHeight - y)]; mouthView.layer.cornerRadius = faceWidth*0.15; [self.imageView addSubview:mouthView]; } } } @end
11行目のviewDidLoad内で、とりあえず1番目の画像を表示させます。
27行目のonClickButtonPicture内で、ボタンのタップ回数に応じて、画像の切替/顔検出メソッドの呼び出しを行っています。
148行目のfaceDetectで実際に顔検出を行います。
顔検出自体は151行目から156行目で完了です。
156行目の配列arrayの中に顔データであるCIFaceFeatureが格納されます。
今回のアプリでは、取得した顔座標に丸やら四角やらを描画しようと思ったので、170行以降で座標に丸などを描画しています。
ここで1点ややこしかったのが、座標の開始位置です。
UIImageなどは左上が開始位置(0, 0)なのですが、顔データとして取得できるCIFaceFeatureでは左下が開始位置(0, 0)となっています。
そのため、UIImageViewに丸やら四角を描画する際に、座標の変換処理を行いました。
(多分、もっと簡単な変換方法があると思いますが・・・)
で、実際に動かした結果が以下になります。

右目・左目・口の位置に丸を描画し、顔全体を矩形でかこってみました。
複数人数映った画像でも、各々の顔を検出できました。
また時間を見つけて、この辺りをもう少し詳しく調べてみようと思います。