iOSで顔検出

2013.10.07.月
技術

世間で一昔前から、iPhoneやAndroidで顔検出するアプリが結構リリースされいます。

今更ながら、どのような方法で顔を検出しているのか気になったので、調査してみました。

今回はiPhoneでの顔検出について記載します。



実はiOS5からCIDetectorというクラスが利用できるようになっているらしく、それを利用することによって、画像の中の顔の座標を検出できます。

簡単なサンプルアプリを作成してみました。
まずは、完成イメージ。

20131008

画面上部に画像が切り替わる形でUIImageViewがあります。

画面下部に顔検出で取得した顔のパーツの座標を表示するエリアがあります。

画面最下部にはボタンを配置し、タップすることで画像の切替・顔検出メソッド呼び出しを行います。

 

では、MainStoryBoard
storyboard

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に丸やら四角を描画する際に、座標の変換処理を行いました。
(多分、もっと簡単な変換方法があると思いますが・・・)

で、実際に動かした結果が以下になります。
20131008_2

右目・左目・口の位置に丸を描画し、顔全体を矩形でかこってみました。
複数人数映った画像でも、各々の顔を検出できました。

また時間を見つけて、この辺りをもう少し詳しく調べてみようと思います。