概述
AVFoundation 是一個可以用來使用和創(chuàng)建基于時間的視聽媒體數(shù)據(jù)的框架。AVFoundation 的構(gòu)建考慮到了目前的硬件環(huán)境和應(yīng)用程序,其設(shè)計過程高度依賴多線程機制。充分利用了多核硬件的優(yōu)勢并大量使用block和GCD機制,將復(fù)雜的計算機進程放到了后臺線程運行。會自動提供硬件加速操作,確保在大部分設(shè)備上應(yīng)用程序能以最佳性能運行。該框架就是針對64位處理器設(shè)計的,可以發(fā)揮64位處理器的所有優(yōu)勢。

實現(xiàn)效果

捕捉會話
AV Foundation 捕捉棧的核心類是AVCaptureSession。一個捕捉會話相當于一個虛擬的插線板,用于連接輸入和輸出的資源。捕捉會話管理從物理設(shè)備得到的數(shù)據(jù)流。
self.captureSession = [[AVCaptureSession alloc] init];
[self.captureSession setSessionPreset:AVCaptureSessionPreset640x480];
[self.previewView setSession:self.captureSession];
AVCaptureMetadataOutput
在使用捕捉設(shè)備進行處理前,首先要添加輸入、輸出。輸出的類型是 AVCaptureOutput,它是一個抽象基類,用于將捕捉到的數(shù)據(jù)輸出。AV Foundation 框架定義了AVCaptureOutput 的一些擴展,但是在人臉動態(tài)識別的時候我們用到是它的子類 AVCaptureMetadataOutput。AVCaptureMetadataOutput 用于輸出元數(shù)據(jù),如二維碼、條形碼、以及人臉等。我們在使用的時候需要指定 Metadata 的相關(guān)類型,我們可以通過 AVCaptureMetadataOutput 的 ```@property(nonatomic, readonly) NSArray<AVMetadataObjectType> *availableMetadataObjectTypes;
// Output
self.metaDataOutput = [[AVCaptureMetadataOutput alloc] init];
[self.metaDataOutput setMetadataObjectsDelegate:self queue:dispatch_get_global_queue(0, 0)];
if ([self.captureSession canAddOutput:self.metaDataOutput]) {
[self.captureSession addOutput:self.metaDataOutput];
}
for (NSString *metaType in self.metaDataOutput.availableMetadataObjectTypes) {
NSLog(@"%@", metaType);
}
self.metaDataOutput.metadataObjectTypes = @[AVMetadataObjectTypeFace];
####AVMetadataFaceObject
當檢測到人臉的時候AVCaptureMetadataOutput 會輸出子類型為 AVMetadataFaceObject 的數(shù)組。AVMetadataFaceObject 定義了多個描述檢測到人臉的屬性。其中最重要的是人臉的邊界(bounds),它是CGRect類型的變量。它的坐標系是基于設(shè)備標量坐標系,它的范圍是攝像頭原始朝向左上角(0,0)到右下角(1,1)。除了邊界,AVMetadataFaceObject還提供了檢測到人臉的斜傾角和偏轉(zhuǎn)角。斜傾角(rollAngle)表示人的頭部向肩的方向側(cè)傾角度, 偏轉(zhuǎn)角(yawAngle)表示人沿Y軸旋轉(zhuǎn)的角度。AVMetadataFaceObject 定義如下:
@interface AVMetadataFaceObject : AVMetadataObject <NSCopying>
{
@private
AVMetadataFaceObjectInternal *_internal;
}
@property(readonly) NSInteger faceID;
// 斜傾角
@property(readonly) BOOL hasRollAngle;
@property(readonly) CGFloat rollAngle;
// 偏轉(zhuǎn)角
@property(readonly) BOOL hasYawAngle;
@property(readonly) CGFloat yawAngle;
@end
###捕捉預(yù)覽
AVCaptureVideoPreviewLayer是CALayer的子類,可以對捕捉視頻進行實時預(yù)覽。它有個AVLayerVideoGravity屬性可以控制畫面的縮放和拉升效果。
AVCaptureVideoPreviewLayer *previewLayer = [AVCaptureVideoPreviewLayer layerWithSession:self.captureSession];
previewLayer.frame = [UIScreen mainScreen].bounds;
[self.view.layer addSublayer:previewLayer];
####坐標變換
由于 AVMetadataFaceObject 中的人臉的邊界(bounds)的坐標系是基于設(shè)備標量坐標系,它的范圍是攝像頭原始朝向左上角(0,0)到右下角(1,1),因此需要將它轉(zhuǎn)換到我們的視圖坐標系中。在轉(zhuǎn)換的時候系統(tǒng)會考慮orientation, mirroring, videoGravity 等許多因素。在轉(zhuǎn)換的時候我們只需要使用捕捉預(yù)覽 AVCaptureVideoPreviewLayer 提供的 ```- (nullable AVMetadataObject *)transformedMetadataObjectForMetadataObject:(AVMetadataObject *)metadataObject;``` 方法。
- (NSArray *)transformFacesToLayerFromFaces:(NSArray *)faces
{
NSMutableArray *transformFaces = [NSMutableArray array];
for (AVMetadataFaceObject *face in faces) {
AVMetadataObject *transFace = [(AVCaptureVideoPreviewLayer *)self.layer transformedMetadataObjectForMetadataObject:face]
[transformFaces addObject:transFace];
}
return transformFaces;
}
####實現(xiàn)動態(tài)人臉檢測
+ 創(chuàng)建捕捉會話,并設(shè)置輸入、輸出以及預(yù)覽圖層。將 AVCaptureMetadataOutput 的metadataObjectTypes 設(shè)為捕獲人臉 AVMetadataObjectTypeFace。
-
(void)setupSession
{
self.captureSession = [[AVCaptureSession alloc] init];self.captureSession = [[AVCaptureSession alloc] init];
[self.captureSession setSessionPreset:AVCaptureSessionPreset640x480];
[self.previewView setSession:self.captureSession];// Create a device input with the device and add it to the session.
AVCaptureDevice *device = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
NSError *error;
AVCaptureDeviceInput *input = [AVCaptureDeviceInput deviceInputWithDevice:device error:&error];
if (!input) {
return;
}
[self.captureSession addInput:input];// Create a VideoDataOutput and add it to the session
self.videoDataOutput = [[AVCaptureVideoDataOutput alloc] init];
[self.videoDataOutput setSampleBufferDelegate:self queue:dispatch_get_global_queue(0, 0)];
self.videoDataOutput.videoSettings = [NSDictionary dictionaryWithObject:[NSNumber numberWithInt:kCVPixelFormatType_420YpCbCr8BiPlanarFullRange] forKey:(id)kCVPixelBufferPixelFormatTypeKey];
[self.captureSession addOutput:self.videoDataOutput];// Output
self.metaDataOutput = [[AVCaptureMetadataOutput alloc] init];[self.metaDataOutput setMetadataObjectsDelegate:self queue:dispatch_get_global_queue(0, 0)];
if ([self.captureSession canAddOutput:self.metaDataOutput]) {
[self.captureSession addOutput:self.metaDataOutput];
}for (NSString *metaType in self.metaDataOutput.availableMetadataObjectTypes) {
NSLog(@"%@", metaType);
}self.metaDataOutput.metadataObjectTypes = @[AVMetadataObjectTypeFace];
[self.captureSession startRunning];
}
+ 創(chuàng)建預(yù)覽視圖 QMPreviewView,將預(yù)覽視圖的 layerClass 設(shè)置為 AVCaptureVideoPreviewLayer,并初始化相關(guān)視圖。由于我們用到了偏轉(zhuǎn)角(yawAngle)在旋轉(zhuǎn)的時候我們需要用到透視投影,這樣在繞Y軸旋轉(zhuǎn)的時候才更加逼真。
// 透視投影
static CATransform3D PerspectiveTransformMake(CGFloat eyePosition)
{
CATransform3D transform = CATransform3DIdentity;
transform.m34 = -1.0 / eyePosition;
return transform;
}
- (Class)layerClass
{
return [AVCaptureVideoPreviewLayer class];
}
(instancetype)initWithFrame:(CGRect)frame
{
if (self = [super initWithFrame:frame]) {
[self setupView];
}
return self;
}(instancetype)initWithCoder:(NSCoder *)aDecoder
{
if (self = [super initWithCoder:aDecoder]) {
[self setupView];
}
return self;
}-
(void)setupView
{
_faceLayerDict = [NSMutableDictionary dictionary];AVCaptureVideoPreviewLayer *previewLayer = (id)self.layer;
previewLayer.videoGravity = AVLayerVideoGravityResizeAspectFill;self.overlayLayer = [CALayer layer];
self.overlayLayer.frame = self.bounds;
self.overlayLayer.sublayerTransform = PerspectiveTransformMake(1000);
[previewLayer addSublayer:self.overlayLayer];
}
+ 初始化QMPreviewView,初始化后需要為它設(shè)置 AVCaptureSession 才能進行捕捉的實時預(yù)覽。
(void)viewDidLoad
{
[super viewDidLoad];
[self setupView];
[self setupSession];
}(void)setupView
{
QMPreviewView *previewView = [[QMPreviewView alloc] initWithFrame:self.view.bounds];
[self.view addSubview:previewView];
_previewView = previewView;
}
+ 處理AVCaptureMetadataOutputObjectsDelegate回調(diào)方法解析并繪制人臉矩形。每個人臉 AVFoundation 都會給出唯一的faceID,當人臉離開屏幕時候,對應(yīng)的人臉也會在回調(diào)中消失。我們根據(jù)人臉I(yè)D保存著繪制的矩形,當人臉消失的時候,我們需要將繪制的矩形去除。
-
(void)captureOutput:(AVCaptureOutput *)output didOutputMetadataObjects:(NSArray<__kindof AVMetadataObject *> *)metadataObjects fromConnection:(AVCaptureConnection *)connection
{
// for (AVMetadataFaceObject *face in metadataObjects) {
// NSLog(@"face = %ld, bounds = %@", face.faceID, NSStringFromCGRect(face.bounds));
// }[self.previewView onDetectFaces:metadataObjects];
} -
(NSArray *)transformFacesToLayerFromFaces:(NSArray *)faces
{
NSMutableArray *transformFaces = [NSMutableArray array];
for (AVMetadataFaceObject *face in faces) {
AVMetadataObject *transFace = [(AVCaptureVideoPreviewLayer *)self.layer transformedMetadataObjectForMetadataObject:face]
[transformFaces addObject:transFace];}
return transformFaces;
} (CALayer *)makeLayer
{
CALayer *layer = [CALayer layer];
layer.borderWidth = 5.0f;
layer.borderColor = [UIColor colorWithRed:0.0f green:255.0f blue:0.0f alpha:255.0f].CGColor;
return layer;
}(CATransform3D)transformFromYawAngle:(CGFloat)angle
{
CATransform3D t = CATransform3DMakeRotation(DegreeToRadius(angle), 0.0f, -1.0f, 0.0f);
return CATransform3DConcat(t, [self orientationTransform]);
}(CATransform3D)orientationTransform
{
CGFloat angle = 0.0f;
switch ([UIDevice currentDevice].orientation) {
case UIDeviceOrientationPortraitUpsideDown:
angle = M_PI;
break;
case UIDeviceOrientationLandscapeRight:
angle = -M_PI/2.0;
break;
case UIDeviceOrientationLandscapeLeft:
angle = M_PI/2.0;
break;
default:
angle = 0.0f;
break;
}
return CATransform3DMakeRotation(angle, 0.0f, 0.0f, 1.0f);
}
pragma mark - Public
(void)setSession:(AVCaptureSession *)session
{
((AVCaptureVideoPreviewLayer *)self.layer).session = session;
}-
(void)onDetectFaces:(NSArray *)faces
{
// 坐標變換
NSArray *transFaces = [self transformFacesToLayerFromFaces:faces];
NSMutableArray *missFaces = [[self.faceLayerDict allKeys] mutableCopy];for (AVMetadataFaceObject *face in transFaces) {
NSNumber *faceID = @(face.faceID);
// 如果當前人臉還在鏡頭里,則不用移除
[missFaces removeObject:faceID];CALayer *layer = self.faceLayerDict[faceID]; if (!layer) { // 生成新的人臉矩形 layer = [self makeLayer]; self.faceLayerDict[faceID] = layer; [self.overlayLayer addSublayer:layer]; } layer.transform = CATransform3DIdentity; layer.frame = face.bounds; // 根據(jù)偏轉(zhuǎn)角,對矩形進行旋轉(zhuǎn) if (face.hasRollAngle) { CATransform3D t = CATransform3DMakeRotation(DegreeToRadius(face.rollAngle), 0, 0, 1.0); layer.transform = CATransform3DConcat(layer.transform, t); } // 根據(jù)斜傾角,對矩形進行旋轉(zhuǎn)變換 if (face.hasYawAngle) { CATransform3D t = [self transformFromYawAngle:face.yawAngle]; layer.transform = CATransform3DConcat(layer.transform, t); }}
// 去除離開屏幕的人臉和矩形視圖變換
for (NSNumber *faceID in missFaces) {
CALayer *layer = self.faceLayerDict[faceID];
[layer removeFromSuperlayer];
[self.faceLayerDict removeObjectForKey:faceID];
}
}
####參考
[AVFoundation開發(fā)秘籍:實踐掌握iOS & OSX應(yīng)用的視聽處理技術(shù)](https://book.douban.com/subject/26577333/)
源碼地址:[AVFoundation開發(fā) https://github.com/QinminiOS/AVFoundation](https://github.com/QinminiOS/AVFoundation)