上一篇有介紹了設(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