SSブログ

Core Imageの顔認識をOS Xで - その6 [OpenCV関係]

昨日までながながとピクセルフォーマットの問題にかかずらってしまったけど、できればこういうのをプログラミングの問題とはしたくない。プログラミングとはアルゴリズムの実装であると僕は考えてるので、エンディアンの問題や実数整数などのバイト表現の問題なんかと同じで、プラットフォームで一貫していてもらって気にしなくてすむようにしてもらいたい、と思う。

まあ、それはもういいとして今日から実装に入る。

5  比較用のテストアプリ

ということで、あとは普通のドキュメントベースではないアプリに仕上げれば比較用のテストアプリができる。

5.1  AV Foudationを使ったFaceTimeカメラの読み込みオブジェクト

AV Foundationの手続きはひとつのクラスにカプセルしてしまおう。こんなやつ。
@interface PTFaceTimeCamera : NSObject <AVCaptureVideoDataOutputSampleBufferDelegate> {
    id                          delegate;
    AVCaptureSession            *session;
    AVCaptureVideoDataOutput    *dataOutput;
    AVCaptureDevice             *device;
    AVCaptureDeviceInput        *deviceInput;
    NSInteger                   droppedFrameCount;
}
....
このオブジェクトがAVCaptureVideoDataOutputのdelegateになり、さらにこのオブジェクトのdelegateへ画像ファイルを渡す、という形にする。このオブジェクトのdelegateが実装する受け取り用のメソッドは
@interface NSObject (PTFaceTimeCameraCategory)

- (void)FaceTimeCamera:(PTFaceTimeCamera *)camera
  passingCVPixelBuffer:(CVPixelBufferRef)imageBuffer
         withTimeStamp:(NSDate *)timeStamp;

- (void)FaceTimeCamera:(PTFaceTimeCamera *)camera
     convertingCIImage:(CIImage *)ciimage
         withTimeStamp:(NSDate *)timeStamp;

- (void)FaceTimeCamera:(PTFaceTimeCamera *)camera
     convertingCGImage:(CGImageRef)cgimage
         withTimeStamp:(NSDate *)timeStamp;

- (void)FaceTimeCamera:(PTFaceTimeCamera *)camera
  convertingNSImageRep:(NSImageRep *)irep
         withTimeStamp:(NSDate *)timeStamp;
@end
というようなもの。おまけでタイムスタンプでもつけるか、と思ってつけた。これはもともとCMSampleBufferがDTS/PTSデータ(MPEG-2のタイムスタンプ)を持っていたので、使えるなら使ってみようと思っただけ。深い意味はない。カメラからのデータのPTS(Presentation Time Stamp)に何か意味があるかと思ってデコードしてみたら、ブートした時刻(uptimeコマンドで返る時刻)が基準になっていた。これをNSDateに変換して渡すようにした。あまり役には立たない。どうやってるかは、あとで公開するソースを見て欲しい。

渡し方のひとつめはCVPixelBufferを直接渡す形で、OpenCVへはこれを使う。ふたつ目はCIImageに変換した画像を渡す形で、これはCIDetector用。あとのふたつはまあこれもおまけ。なにかに使い道があるかもしれない。

5.2  CIDetectorを保持するオブジェクト

CIDetectorは生のまま使ってもいいんだけど、あとのことを考えて一皮ラッパを作った。
@interface PTFaceDetectorUsingCIDetector : NSObject {
    CIDetector  *detector;
}

- (id)init;
- (id)initWithContext:(CIContext *)context;
- (id)initWithContext:(CIContext *)context andOptions:(NSDictionary *)options;

- (NSArray *)featuresInImage:(CIImage *)image;
@end
実質的にCIDetectorに内部で渡して結果を外に出すだけ。

5.3  OpenCVの顔検出オブジェクト

これはけっこうめんどう。とりあえずこういうふうにした。
interface PTFaceDetectorUsingOpenCV : NSObject {
    void    *faceCascade;
    void    *leftEyeCascade;
    void    *rightEyeCascade;
    void    *mouthCascade;
    void    *orgImage;
    void    *grayImage;
    void    *smallImage;
    void    *storage;
    int     scale;
    int     code;
    int     samplePerPixel;
    BOOL    findEyesAndMouths;
}
@property (readwrite) BOOL  findEyesAndMouths;

- (id)initWithImageScaling:(int)scaling;
- (NSArray *)featuresInPixelBuffer:(CVPixelBufferRef)buffer;
CvHaarClassifierCascadeやIplImageをインスタンス変数として保持しているけど、全部void*型になっている。これはOpenCV2のヘッダがC++用になっていて、それをこのヘッダに書くとこれを読み込むファイルは全部Objective-C++対応(拡張子.mm)にしないといけない。

Objective-C++にするとどうもXcodeのcode completionの動作がおかしくなる(C++の関数名だけでなく、知ってたはずのObjective-Cのメソッドも出てこなくなったり、"Jump to definiton"で飛ばなかったりする)。しょうがないのでヘッダはOpenCVとは無関係にするためにこうした。このクラスのimplementationでは <opencv2/opencv.hpp> を読み込んで、void*を使うたびにキャストしている。

ちょっと面倒だけど、こうすれば全部を.mmにせずにすむ。

初期化メソッドはカスケードデータを読み込むだけ。
    NSBundle    *mainBundle = [NSBundle mainBundle];
    tempPath = [mainBundle pathForResource:haarcascadeFrontalfaceAlt2
                                    ofType:haarcascadeType];
    faceCascade = cvLoad([tempPath fileSystemRepresentation], 0, 0, 0);
    if (faceCascade == NULL) {
        [self release];
        return nil;
    }
    
....
こんなふうにバンドルリソースから読み込む。ほんとはIplImageの領域も確保したいけど、初期化時には大きさがわからないので最初のCVPixelBufferがわたってきたときにやる。

そのあと
  1. CVPixelBufferからIplImageにコピー
  2. グレーに変換
  3. サイズをダウンスケール
で顔認識に渡す。

顔認識はCIDetectorにあわせて顔だけでなく目と口も追加した。これの学習データは昔からOpenCVについているhaarcascade_mcs_lefteye.xlmなんかを使った。検出は顔をとらえて、あったらその顔のCvRectをマスクにしてその中で目と口を認識する、という手順。
nice!(0)  コメント(0)  トラックバック(0) 

nice! 0

コメント 0

コメントを書く

お名前:
URL:
コメント:
画像認証:
下の画像に表示されている文字を入力してください。

トラックバック 0

献立08/01献立08/02 ブログトップ

この広告は前回の更新から一定期間経過したブログに表示されています。更新すると自動で解除されます。