iOS AVFoundation實現(xiàn)人臉識別

上一篇有介紹了設(shè)置session時添加輸入輸出設(shè)備,實現(xiàn)人臉設(shè)別也比較簡單,把獲取到的人臉用在預(yù)覽視圖上用框框圈出來.


#import "THCameraController.h"
#import <AVFoundation/AVFoundation.h>

@interface THCameraController ()<AVCaptureMetadataOutputObjectsDelegate>
@property(nonatomic,strong)AVCaptureMetadataOutput  *metadataOutput;

@end

@implementation THCameraController

- (BOOL)setupSessionOutputs:(NSError **)error {

    self.metadataOutput = [[AVCaptureMetadataOutput alloc]init];
     //為捕捉會話添加設(shè)備
    if ([self.captureSession canAddOutput:self.metadataOutput]){
        [self.captureSession addOutput:self.metadataOutput];
        
        //獲得人臉屬性
        NSArray *metadatObjectTypes = @[AVMetadataObjectTypeFace];
        
        //設(shè)置metadataObjectTypes 指定對象輸出的元數(shù)據(jù)類型雕拼。
        /*
         限制檢查到元數(shù)據(jù)類型集合的做法是一種優(yōu)化處理方法÷芽剩可以減少我們實際感興趣的對象數(shù)量
         支持多種元數(shù)據(jù)。這里只保留對人臉元數(shù)據(jù)感興趣
         */
        self.metadataOutput.metadataObjectTypes = metadatObjectTypes;
        
        //創(chuàng)建主隊列: 因為人臉檢測用到了硬件加速冬阳,而且許多重要的任務(wù)都在主線程中執(zhí)行倘核,所以需要為這次參數(shù)指定主隊列谷浅。
        dispatch_queue_t mainQueue = dispatch_get_main_queue();
        
        
        //通過設(shè)置AVCaptureVideoDataOutput的代理,就能獲取捕獲到一幀一幀數(shù)據(jù)
        [self.metadataOutput setMetadataObjectsDelegate:self queue:mainQueue];
     
        return YES;
    }else
    {
        //報錯
        if (error) {
            NSDictionary *userInfo = @{NSLocalizedDescriptionKey:@"Failed to still image output"};
            
            *error = [NSError errorWithDomain:THCameraErrorDomain code:THCameraErrorFailedToAddOutput userInfo:userInfo];
            
        }
        return NO;
    }

}

//捕捉數(shù)據(jù)
- (void)captureOutput:(AVCaptureOutput *)captureOutput
didOutputMetadataObjects:(NSArray *)metadataObjects
       fromConnection:(AVCaptureConnection *)connection {
    
    //使用循環(huán)局雄,打印人臉數(shù)據(jù)
    for (AVMetadataFaceObject *face in metadataObjects) {
        NSLog(@"Face detected with ID:%li",(long)face.faceID);
        NSLog(@"Face bounds:%@",NSStringFromCGRect(face.bounds));
        
    }
    
    //將元數(shù)據(jù) 傳遞給 THPreviewView.m   將元數(shù)據(jù)轉(zhuǎn)換為layer
    [self.faceDetectionDelegate didDetectFaces:metadataObjects];
}

@end



#import "THPreviewView.h"

@interface THPreviewView ()


@property(nonatomic,strong)CALayer *overlayLayer;

@property(strong,nonatomic)NSMutableDictionary *faceLayers;

@property(nonatomic,strong)AVCaptureVideoPreviewLayer *previewLayer;

@end

@implementation THPreviewView

+ (Class)layerClass {
    //重寫layerClass方法
    return [AVCaptureVideoPreviewLayer class];
}

- (id)initWithFrame:(CGRect)frame {
    self = [super initWithFrame:frame];
    if (self) {
        [self setupView];
    }
    return self;
}

- (id)initWithCoder:(NSCoder *)coder {
    self = [super initWithCoder:coder];
    if (self) {
        [self setupView];
    }
    return self;
}

- (void)setupView {

    //初始化faceLayers屬性  字典
    self.faceLayers = [NSMutableDictionary dictionary];
    
    //設(shè)置videoGravity 使用AVLayerVideoGravityResizeAspectFill 鋪滿整個預(yù)覽層的邊界范圍
    self.previewLayer.videoGravity = AVLayerVideoGravityResizeAspectFill;
    
    //初始化overlayLayer
    self.overlayLayer = [CALayer layer];
    
    //設(shè)置它的frame
    self.overlayLayer.frame = self.bounds;
    
    //子圖層形變 sublayerTransform屬性   Core  Animation動畫
    self.overlayLayer.sublayerTransform = CATransform3DMakePerspective(1000);
    
    //將子圖層添加到預(yù)覽圖層來
    [self.previewLayer addSublayer:self.overlayLayer];
    
}

//會話的get方法
- (AVCaptureSession*)session {

    return self.previewLayer.session;
}


//會話的set方法
- (void)setSession:(AVCaptureSession *)session {

    
    self.previewLayer.session = session;
    

}

//獲得layer
- (AVCaptureVideoPreviewLayer *)previewLayer {

    return (AVCaptureVideoPreviewLayer *)self.layer;
}



//將檢測到的人臉進行可視化
- (void)didDetectFaces:(NSArray *)faces {

    //創(chuàng)建一個本地數(shù)組 保存轉(zhuǎn)換后的人臉數(shù)據(jù)
    NSArray *transformedFaces = [self transformedFacesFromFaces:faces];
    
    //獲取faceLayers的key甥啄,用于確定哪些人移除了視圖并將對應(yīng)的圖層移出界面。
    /*
        支持同時識別10個人臉
     */
    NSMutableArray *lostFaces = [self.faceLayers.allKeys mutableCopy];
    
    //遍歷每個轉(zhuǎn)換的人臉對象
    for (AVMetadataFaceObject *face in transformedFaces) {
        
        //獲取關(guān)聯(lián)的faceID炬搭。這個屬性唯一標(biāo)識一個檢測到的人臉
        NSNumber *faceID = @(face.faceID);
        
        //將對象從lostFaces 移除
        [lostFaces removeObject:faceID];
        
        //拿到當(dāng)前faceID對應(yīng)的layer
        CALayer *layer = self.faceLayers[faceID];
        
        //如果給定的faceID 沒有找到對應(yīng)的圖層
        if (!layer) {
            
            //調(diào)用makeFaceLayer 創(chuàng)建一個新的人臉圖層
            layer = [self makeFaceLayer];
            
            //將新的人臉圖層添加到 overlayLayer上
            [self.overlayLayer addSublayer:layer];
            
            //將layer加入到字典中
            self.faceLayers[faceID] = layer;
            
        }
        
        //設(shè)置圖層的transform屬性 CATransform3DIdentity 圖層默認變化 這樣可以重新設(shè)置之前應(yīng)用的變化
        layer.transform = CATransform3DIdentity;
        
        //圖層的大小 = 人臉的大小
        layer.frame = face.bounds;
        
        //判斷人臉對象是否具有有效的斜傾交蜈漓。
        if (face.hasRollAngle) {
            
            //如果為YES,則獲取相應(yīng)的CATransform3D 值
            CATransform3D t = [self transformForRollAngle:face.rollAngle];
            
            //將它與標(biāo)識變化關(guān)聯(lián)在一起鼻由,并設(shè)置transform屬性
            layer.transform = CATransform3DConcat(layer.transform, t);
        }
        
        
        //判斷人臉對象是否具有有效的偏轉(zhuǎn)角
        if (face.hasYawAngle) {
            
            //如果為YES,則獲取相應(yīng)的CATransform3D 值
            CATransform3D  t = [self transformForYawAngle:face.yawAngle];
            layer.transform = CATransform3DConcat(layer.transform, t);
            
        }
    }
    
    
    //遍歷數(shù)組將剩下的人臉I(yè)D集合從上一個圖層和faceLayers字典中移除
    for (NSNumber *faceID in lostFaces) {
        
        CALayer *layer = self.faceLayers[faceID];
        [layer removeFromSuperlayer];
        [self.faceLayers  removeObjectForKey:faceID];
    }
    
}


//將設(shè)備的坐標(biāo)空間的人臉轉(zhuǎn)換為視圖空間的對象集合
- (NSArray *)transformedFacesFromFaces:(NSArray *)faces {

    NSMutableArray *transformeFaces = [NSMutableArray array];
    
    for (AVMetadataObject *face in faces) {
        
        //將攝像頭的人臉數(shù)據(jù) 轉(zhuǎn)換為 視圖上的可展示的數(shù)據(jù)
        //簡單說:UIKit的坐標(biāo) 與 攝像頭坐標(biāo)系統(tǒng)(0哨查,0)-(1,1)不一樣。所以需要轉(zhuǎn)換
        //轉(zhuǎn)換需要考慮圖層故觅、鏡像霹娄、視頻重力翎苫、方向等因素 在iOS6.0之前需要開發(fā)者自己計算兔院,但iOS6.0后提供方法
        AVMetadataObject *transformedFace = [self.previewLayer transformedMetadataObjectForMetadataObject:face];
        
        //轉(zhuǎn)換成功后,加入到數(shù)組中
        [transformeFaces addObject:transformedFace];
        
        
    }
    
    return transformeFaces;
    
    
    
}

- (CALayer *)makeFaceLayer {

    //創(chuàng)建一個layer
    CALayer *layer = [CALayer layer];
    
    //邊框?qū)挾葹?.0f
    layer.borderWidth = 5.0f;
    
    //邊框顏色為紅色
    layer.borderColor = [UIColor redColor].CGColor;
    
    layer.contents = (id)[UIImage imageNamed:@"551.png"].CGImage;
    
    //返回layer
    return layer;
    
}



//將 RollAngle 的 rollAngleInDegrees 值轉(zhuǎn)換為 CATransform3D
- (CATransform3D)transformForRollAngle:(CGFloat)rollAngleInDegrees {

    //將人臉對象得到的RollAngle 單位“度” 轉(zhuǎn)為Core Animation需要的弧度值
    CGFloat rollAngleInRadians = THDegreesToRadians(rollAngleInDegrees);

    //將結(jié)果賦給CATransform3DMakeRotation x,y,z軸為0谆吴,0倒源,1 得到繞Z軸傾斜角旋轉(zhuǎn)轉(zhuǎn)換
    return CATransform3DMakeRotation(rollAngleInRadians, 0.0f, 0.0f, 1.0f);
    
}


//將 YawAngle 的 yawAngleInDegrees 值轉(zhuǎn)換為 CATransform3D
- (CATransform3D)transformForYawAngle:(CGFloat)yawAngleInDegrees {

    //將角度轉(zhuǎn)換為弧度值
     CGFloat yawAngleInRaians = THDegreesToRadians(yawAngleInDegrees);
    
    //將結(jié)果CATransform3DMakeRotation x,y,z軸為0,-1句狼,0 得到繞Y軸選擇笋熬。
    //由于overlayer 需要應(yīng)用sublayerTransform,所以圖層會投射到z軸上腻菇,人臉從一側(cè)轉(zhuǎn)向另一側(cè)會有3D 效果
    CATransform3D yawTransform = CATransform3DMakeRotation(yawAngleInRaians, 0.0f, -1.0f, 0.0f);
    
    //因為應(yīng)用程序的界面固定為垂直方向胳螟,但需要為設(shè)備方向計算一個相應(yīng)的旋轉(zhuǎn)變換
    //如果不這樣,會造成人臉圖層的偏轉(zhuǎn)效果不正確
    return CATransform3DConcat(yawTransform, [self orientationTransform]);
}

- (CATransform3D)orientationTransform {

    CGFloat angle = 0.0;
    //拿到設(shè)備方向
    switch ([UIDevice currentDevice].orientation) {
            
            //方向:下
        case UIDeviceOrientationPortraitUpsideDown:
            angle = M_PI;
            break;
            
            //方向:右
        case UIDeviceOrientationLandscapeRight:
            angle = -M_PI / 2.0f;
            break;
        
            //方向:左
        case UIDeviceOrientationLandscapeLeft:
            angle = M_PI /2.0f;
            break;

            //其他
        default:
            angle = 0.0f;
            break;
    }
    
    return CATransform3DMakeRotation(angle, 0.0f, 0.0f, 1.0f);
    
}

#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wunused"


static CGFloat THDegreesToRadians(CGFloat degrees) {

    return degrees * M_PI / 180;
}


static CATransform3D CATransform3DMakePerspective(CGFloat eyePosition) {
    
    
    //CATransform3D 圖層的旋轉(zhuǎn)筹吐,縮放糖耸,偏移,歪斜和應(yīng)用的透
    //CATransform3DIdentity是單位矩陣丘薛,該矩陣沒有縮放嘉竟,旋轉(zhuǎn),歪斜洋侨,透視舍扰。該矩陣應(yīng)用到圖層上,就是設(shè)置默認值希坚。
    CATransform3D  transform = CATransform3DIdentity;
    
    
    //透視效果(就是近大遠斜咂弧),是通過設(shè)置m34 m34 = -1.0/D 默認是0.D越小透視效果越明顯
    //D:eyePosition 觀察者到投射面的距離
    transform.m34 = -1.0/eyePosition;
    
    return transform;
    
    
}


#pragma clang diagnostic pop

@end

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末裁僧,一起剝皮案震驚了整個濱河市个束,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌锅知,老刑警劉巖播急,帶你破解...
    沈念sama閱讀 216,692評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件脓钾,死亡現(xiàn)場離奇詭異售睹,居然都是意外死亡,警方通過查閱死者的電腦和手機可训,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,482評論 3 392
  • 文/潘曉璐 我一進店門昌妹,熙熙樓的掌柜王于貴愁眉苦臉地迎上來捶枢,“玉大人,你說我怎么就攤上這事飞崖±檬澹” “怎么了?”我有些...
    開封第一講書人閱讀 162,995評論 0 353
  • 文/不壞的土叔 我叫張陵固歪,是天一觀的道長蒜鸡。 經(jīng)常有香客問我,道長牢裳,這世上最難降的妖魔是什么逢防? 我笑而不...
    開封第一講書人閱讀 58,223評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮蒲讯,結(jié)果婚禮上忘朝,老公的妹妹穿的比我還像新娘。我一直安慰自己判帮,他們只是感情好局嘁,可當(dāng)我...
    茶點故事閱讀 67,245評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著晦墙,像睡著了一般悦昵。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上晌畅,一...
    開封第一講書人閱讀 51,208評論 1 299
  • 那天旱捧,我揣著相機與錄音,去河邊找鬼踩麦。 笑死枚赡,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的谓谦。 我是一名探鬼主播贫橙,決...
    沈念sama閱讀 40,091評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼反粥!你這毒婦竟也來了卢肃?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,929評論 0 274
  • 序言:老撾萬榮一對情侶失蹤才顿,失蹤者是張志新(化名)和其女友劉穎莫湘,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體郑气,經(jīng)...
    沈念sama閱讀 45,346評論 1 311
  • 正文 獨居荒郊野嶺守林人離奇死亡幅垮,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,570評論 2 333
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了尾组。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片忙芒。...
    茶點故事閱讀 39,739評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡示弓,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出呵萨,到底是詐尸還是另有隱情奏属,我是刑警寧澤,帶...
    沈念sama閱讀 35,437評論 5 344
  • 正文 年R本政府宣布潮峦,位于F島的核電站囱皿,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏忱嘹。R本人自食惡果不足惜铆帽,卻給世界環(huán)境...
    茶點故事閱讀 41,037評論 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望德谅。 院中可真熱鬧爹橱,春花似錦、人聲如沸窄做。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,677評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽椭盏。三九已至组砚,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間掏颊,已是汗流浹背糟红。 一陣腳步聲響...
    開封第一講書人閱讀 32,833評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留乌叶,地道東北人盆偿。 一個月前我還...
    沈念sama閱讀 47,760評論 2 369
  • 正文 我出身青樓,卻偏偏與公主長得像准浴,于是被迫代替她去往敵國和親事扭。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,647評論 2 354