iOS上的相机捕捉

UIImagePickerController

UIImagePickerController 类用于管理可定制的,基于系统支持的用户界面,用于在支持的设备上拍摄照片和视频,并且在你的 app 中为用户保存照片和视频。 图像选择器控制器管理用户交互并将这些交互的结果传递给委托对象。

UIImagePickerController 继承于 UINavigationController,归属于 UIKit 框架,可以实现图片选取、拍照、录制视频等功能,使用起来十分方便。

使用方式

需要导入框架:import <MobileCoreServices/MobileCoreServices.h>;
拍摄视频需要导入包:#import <AssetsLibrary/AssetsLibrary.h>
需要遵循的协议:<UINavigationControllerDelegateUIImagePickerControllerDelegate>;

:需要导入 <MobileCoreServices/MobileCoreServices.h> 框架是因为 kUTTypeImagekUTTypeMovie 被定义在其中。

:实际上我们不会实现 UINavigationControllerDelegate 中定义的任何协议方法,但由于 UIImagePickerController 继承自 UINavigationController,并且改变了 UINavigationController的行为。因此,我们仍然需要主动声明我们的视图控制器遵守 UINavigationControllerDelegate 协议。

1. 创建 UIImagePickerController 图片选择器对象

UIImagePickerController *imagePicker = [[UIImagePickerController alloc] init];

2. 设置 sourceType 媒体数据源类型

UIImagePickerControllerSourceType 是一个枚举类型,用于设置要跳转到哪个界面(相机拍照、照片图库、相册) :

typedef NS_ENUM(NSInteger, UIImagePickerControllerSourceType) {
    UIImagePickerControllerSourceTypePhotoLibrary API_DEPRECATED("Will be removed in a future release, use PHPicker.", ios(2, API_TO_BE_DEPRECATED)), // 照片库
    UIImagePickerControllerSourceTypeCamera, // 系统内置像机
    UIImagePickerControllerSourceTypeSavedPhotosAlbum API_DEPRECATED("Will be removed in a future release, use PHPicker.", ios(2, API_TO_BE_DEPRECATED)), // 相册
} API_UNAVAILABLE(tvos);

💡 查看 Apple 的源码可以发现,部分类型已经被标注为 API_DEPRECATED,意味着该属性即将被废弃,推荐使用 PHPicker 代替。

设置示例:

// 验证设备是否能够从所需的来源获取内容
BOOL isSourceTypeAvailable = [UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypeCamera];
if (!isSourceTypeAvailable) {
    // 可能是权限未打开,也可能是手机硬件不支持相机功能。
    NSLog(@"启动相机失败,您的相机功能未开启,请转到手机设置中开启相机权限!");
}else{
    imagePicker.sourceType = UIImagePickerControllerSourceTypeCamera;
}

3. 设置摄像头捕捉模式

如果设置了 sourceType 媒体数据源类型为 UIImagePickerControllerSourceTypeCamera,那你还需要设置是打开相机的拍照还是录制视频功能。

UIImagePickerControllerCameraCaptureMode 是一个枚举类型:

UIImagePickerControllerCameraCaptureModePhoto, // 拍照模式,默认
UIImagePickerControllerCameraCaptureModeVideo   // 视频录制模式

拍摄图片:

// 设定拍摄的媒体类型:图片
imagePicker.mediaTypes = @[(NSString *)kUTTypeImage];
// 设置摄像头捕捉模式为捕捉图片,默认
imagePicker.cameraCaptureMode = UIImagePickerControllerCameraCaptureModePhoto;

拍摄视频:

// 设定拍摄的媒体类型:视频
imagePicker.mediaTypes = @[(NSString *)kUTTypeMovie];
// 设置摄像头捕捉模式为捕捉视频
imagePicker.cameraCaptureMode = UIImagePickerControllerCameraCaptureModePhoto;

4. 摄像头设备

UIImagePickerControllerCameraDevice 是一个枚举类型,代表前置/后置摄像头:

UIImagePickerControllerCameraDeviceRear,   //后置摄像头,默认
UIImagePickerControllerCameraDeviceFront   //前置摄像头         

设置示例:

// 设置前置摄像头
if ([UIImagePickerController isCameraDeviceAvailable:UIImagePickerControllerCameraDeviceFront]) {
    imagePicker.cameraDevice = UIImagePickerControllerCameraDeviceFront;
}

5. 设置录制视频质量

UIImagePickerControllerQualityType 是一个枚举类型,设置返回图片的质量:

UIImagePickerControllerQualityTypeHigh // 高清
UIImagePickerControllerQualityTypeMedium // 中等,适合 WiFi 传输
UIImagePickerControllerQualityTypeLow // 低质量,适合蜂窝网传输
UIImagePickerControllerQualityType640x480 // VGA 质量,640*480
UIImagePickerControllerQualityTypeIFrame1280x720 // 1280*720
UIImagePickerControllerQualityTypeIFrame960x540 // 960*540

设置示例:

imagePicker.videoQuality = UIImagePickerControllerQualityTypeLow;

6. 设置闪光灯模式

在调用摄像头的时候我们可以选择使用闪光灯,但是默认条件下对视频有 10 分钟的限制,需要用 videoMaximumDuration 属性更改默认时间。

UIImagePickerControllerCameraFlashMode 是一个枚举类型,用于设置闪光灯关闭、自动、打开。

UIImagePickerControllerCameraFlashModeOff  = -1,
UIImagePickerControllerCameraFlashModeAuto = 0,默认
UIImagePickerControllerCameraFlashModeOn   = 1

设置示例:

imagePicker.cameraFlashMode = UIImagePickerControllerCameraFlashModeAuto

7. 遵循协议

imagePicker.delegate = self;

8. 是否显示系统自带的摄像头控制面板,默认YES

// 显示标准相机 UI,
imagePicker.showsCameraControls = NO;

9. 设置自定义覆盖图层

UIImageView *overlayImageView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.height)];
overlayImageView.image = [UIImage imageNamed:@"circle3.png"];
UIView *cameraOverlay = overlayImageView;

imagePicker.cameraOverlayView = cameraOverlay

10. 以模态形式显示 UIImagePickerController 对象

[self presentViewController:imagePicker animated:YES completion:nil];

11. 遵守并实现 UIImagePickerControllerDelegate 协议中的方法

你需要关注协议中的这两个方法:

// 媒体项(图片或视频)选择完成后调用
- (void)imagePickerController:(UIImagePickerController *)picker 
didFinishPickingMediaWithInfo:(NSDictionary<NSString *,id> *)info;
// 取消选择媒体项调用
- (void)imagePickerControllerDidCancel:(UIImagePickerController *)picker;

扩展函数,用于保存到相簿:

/* 保存图片到相簿 */
void UIImageWriteToSavedPhotosAlbum(
    UIImage *image,//保存的图片UIImage
    id completionTarget,//回调的执行者
    SEL completionSelector, //回调方法
    void *contextInfo//回调参数信息
);
//上面一般保存图片的回调方法为:
- (void)image:(UIImage *)image 
        didFinishSavingWithError:(NSError *)error 
        contextInfo:(void *)contextInfo;

/* 判断是否能保存视频到相簿 */
BOOL UIVideoAtPathIsCompatibleWithSavedPhotosAlbum(NSString *videoPath);
/* 保存视频到相簿 */
void UISaveVideoAtPathToSavedPhotosAlbum(
    NSString *videoPath, //保存的视频文件路径
    id completionTarget, //回调的执行者
    SEL completionSelector,//回调方法
    void *contextInfo//回调参数信息
);
//上面一般保存视频的回调方法为:
- (void)video:(NSString *)videoPath 
        didFinishSavingWithError:(NSError *)error 
        contextInfo:(void *)contextInfo;

示例代码:

#pragma mark - 完成选择图片
- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary<NSString *,id> *)info{
    NSLog(@"%s",__func__);
    
    // 从info取出此时摄像头的媒体类型
    NSString *mediaType = info[UIImagePickerControllerMediaType];
    
    if ([mediaType isEqualToString:(NSString *)kUTTypeImage]) {
        // 【拍照模式】
        // 通过info字典获取选择的照片
        UIImage *image = info[UIImagePickerControllerOriginalImage];
     
        // 将照片放入UIImageView对象显示在UI界面
        self.imageView.image = image;
        
        // 保存图像到相簿
        UIImageWriteToSavedPhotosAlbum(image, self, @selector(image:didFinishSavingWithError:contextInfo:), nil);

    }else if ([mediaType isEqualToString:(NSString *)kUTTypeMovie]) {
        // 【录像模式】
        
        NSURL *url = info[UIImagePickerControllerMediaURL];
        NSString *path = url.path;
        
        
        if (UIVideoAtPathIsCompatibleWithSavedPhotosAlbum(path)) {
            // 保存视频到相簿
            UISaveVideoAtPathToSavedPhotosAlbum(path, self, @selector(video:didFinishSavingWithError:contextInfo:), nil);
        }
    }
    
    // 关闭UIImagePickerController对象
    [self dismissViewControllerAnimated:YES completion:nil];
}

#pragma mark 取消拍照
- (void)imagePickerControllerDidCancel:(UIImagePickerController *)picker {
    NSLog(@"%s",__func__);
    [self dismissViewControllerAnimated:YES completion:nil];
}

#pragma mark - 保存图像或视频完成的回调
- (void)image:(UIImage *)image didFinishSavingWithError:(NSError *)error contextInfo:(void *)contextInfo {
    NSLog(@"保存图片完成");
}

- (void)video:(NSString *)videoPath didFinishSavingWithError:(NSError *)error contextInfo:(void *)contextInfo {
    NSLog(@"保存视频完成");
}

封装使用

VideoCaptureDemo 中将 UIImagePickerController 封装成了一个Object 类使用,可以参考:

IDImagePickerCoordinator.h

#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>

/**
 图片选择协调器
 */
@interface IDImagePickerCoordinator : NSObject

#pragma mark - getter 方法
- (UIImagePickerController *)cameraVC;

@end

IDImagePickerCoordinator.m

#import "IDImagePickerCoordinator.h"
#import <MobileCoreServices/MobileCoreServices.h>
#import <AssetsLibrary/AssetsLibrary.h>

@interface IDImagePickerCoordinator () <UINavigationControllerDelegate, UIImagePickerControllerDelegate>

@property (nonatomic, strong) UIImagePickerController *camera;

@end

@implementation IDImagePickerCoordinator

#pragma mark - Init methods
- (instancetype)init
{
    self = [super init];
    if(self){
        _camera = [self setupImagePicker];
    }
    return self;
}

- (UIImagePickerController *)cameraVC
{
    return _camera;
}

#pragma mark - Private methods

- (UIImagePickerController *)setupImagePicker
{
    UIImagePickerController *camera;
    if([self isVideoRecordingAvailable]){
        camera = [UIImagePickerController new];
        camera.sourceType = UIImagePickerControllerSourceTypeCamera;
        camera.mediaTypes = @[(NSString *)kUTTypeMovie];
        camera.delegate = self;
    }
    return camera;
}

- (BOOL)isVideoRecordingAvailable
{
    if([UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypeCamera]){
        NSArray *availableMediaTypes = [UIImagePickerController availableMediaTypesForSourceType:UIImagePickerControllerSourceTypeCamera];
        if([availableMediaTypes containsObject:(NSString *)kUTTypeMovie]){
            return YES;
        }
    }
    return NO;
}

- (BOOL)setFrontFacingCameraOnImagePicker:(UIImagePickerController *)picker
{
    if([UIImagePickerController isCameraDeviceAvailable:UIImagePickerControllerCameraDeviceFront]){
        [picker setCameraDevice:UIImagePickerControllerCameraDeviceFront];
        return YES;
    }
    return NO;
}

- (void)configureCustomUIOnImagePicker:(UIImagePickerController *)picker
{
    UIView *cameraOverlay = [[UIView alloc] init];
    picker.showsCameraControls = NO;
    picker.cameraOverlayView = cameraOverlay;
}

#pragma mark - UIImagePickerControllerDelegate methods

// 完成选择图片
- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingImage:(UIImage *)image editingInfo:(nullable NSDictionary<NSString *,id> *)editingInfo NS_DEPRECATED_IOS(2_0, 3_0) {
    
}

// 完成选择视频
- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info
{
    ALAssetsLibrary *library = [[ALAssetsLibrary alloc] init];
    NSURL *recordedVideoURL= [info objectForKey:UIImagePickerControllerMediaURL];
    if ([library videoAtPathIsCompatibleWithSavedPhotosAlbum:recordedVideoURL]) {
        [library writeVideoAtPathToSavedPhotosAlbum:recordedVideoURL
                                    completionBlock:^(NSURL *assetURL, NSError *error){}
         ];
    }
    
    [picker dismissViewControllerAnimated:YES completion:nil];
}

// 取消选择
- (void)imagePickerControllerDidCancel:(UIImagePickerController *)picker
{
    [picker dismissViewControllerAnimated:YES completion:nil];
}


@end

使用时先导入头文件,设置属性:

@property (nonatomic, strong) IDImagePickerCoordinator *imagePickerCoordinator;

再调用:

self.imagePickerCoordinator = [IDImagePickerCoordinator new];
[self presentViewController:[_imagePickerCoordinator cameraVC] animated:YES completion:nil];

AVFoundation

HQLAVFoundationViewController.h

#import <UIKit/UIKit.h>

/**
 使用AVFoundation类拍照
 */
@interface HQLAVFoundationViewController : UIViewController

@end

HQLAVFoundationViewController.m

#import "HQLAVFoundationViewController.h"
#import <AVFoundation/AVFoundation.h>
#define ThemeColor [UIColor colorWithDisplayP3Red:81/255.0 green:136/255.0 blue:247/255.0 alpha:1.0]

@interface HQLAVFoundationViewController ()

@property (weak, nonatomic) IBOutlet UIImageView *imageView;
@property (weak, nonatomic) IBOutlet UIButton *openCaptureBtn;
@property (weak, nonatomic) IBOutlet UIButton *canptureBtn;

@property (strong, nonatomic) AVCaptureSession *session;    //媒体管理会话
@property (strong, nonatomic) AVCaptureDeviceInput *captureInput;   //输入数据对象
@property (strong, nonatomic) AVCaptureStillImageOutput *imageOutput;   //输出数据对象
@property (strong, nonatomic) AVCaptureVideoPreviewLayer *captureLayer; //视频预览图层


@end

@implementation HQLAVFoundationViewController

#pragma mark - Lifecycle
- (void)viewDidLoad {
    [super viewDidLoad];
    [self setNavigationControllerAppearance];
    // 初始化摄像头
    [self initCaptureSession];
    
    self.openCaptureBtn.hidden = NO;
    self.canptureBtn.hidden = YES;
}

- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}

#pragma mark - Custom Accessors
- (void)setNavigationControllerAppearance {
    // 设置导航栏标题&字体&颜色
    self.navigationItem.title = @"AVFoundation拍照";
    [self.navigationController.navigationBar setTitleTextAttributes:@{NSFontAttributeName:[UIFont systemFontOfSize:17],NSForegroundColorAttributeName:ThemeColor}];
}

#pragma mark 初始化摄像头
- (void)initCaptureSession {
    /*
     1️⃣ AVCaptureSession
     媒体捕捉会话,管理输入与输出之间的数据流,以及在出现问题时生成运行时错误。
     负责把捕获到的音频视频数据输出到输出设备上,一个会话可以有多个输入输入。
     */
    // 1.创建媒体管理会话
    AVCaptureSession *captureSession = [[AVCaptureSession alloc] init];
    
    self.session = captureSession;
    
    // 判断分辨率是否支持 1280*720,支持就设置为:1280*720
    if ([captureSession canSetSessionPreset:AVCaptureSessionPreset1280x720]) {
        captureSession.sessionPreset = AVCaptureSessionPreset1280x720;
    }
    
    /*
     2️⃣ AVCaptureDevice
     关于相机硬件的接口。它被用于控制硬件特性,诸如镜头的位置、曝光、闪光灯等。
     */
    
    // 2.获取后置摄像头
    AVCaptureDevice *captureDevice = nil;
    NSArray *cameras = [AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo];
    for (AVCaptureDevice *camera in cameras) {
        if (camera.position == AVCaptureDevicePositionBack) {
            // 获得后置摄像头
            captureDevice = camera;
        }
    }
    if (!captureDevice) {
        NSLog(@"2.取得后置摄像头错误!");
        return;
    }
    
    // 取得前置摄像头
    //    AVCaptureDevice *frontCaptureDevice = [AVCaptureDevice defaultDeviceWithDeviceType:AVCaptureDeviceTypeBuiltInWideAngleCamera mediaType:AVMediaTypeVideo position:AVCaptureDevicePositionFront];
    
    /*
     3️⃣ AVCaptureDeviceInput
     设备输入数据管理对象,管理输入数据
     */
    
    // 3.创建输入数据对象
    NSError *error = nil;
    AVCaptureDeviceInput *captureInput = [AVCaptureDeviceInput deviceInputWithDevice:captureDevice error:&error];
    if (error) {
        NSLog(@"3.创建输入数据对象错误");
        return;
    }
    
    self.captureInput = captureInput;
    
    /*
     4️⃣ AVCaptureOutput
     设备输出数据管理对象,管理输出数据,通常使用它的子类:
     AVCaptureAudioDataOutput   //输出音频管理对象,输出数据为NSData
     AVCaptureStillImageDataOutput  //输出图片管理对象,输出数据为NSData
     AVCaptureVideoDataOutput   //输出视频管理对象,输出数据为NSData
     
     AVCaptureFileOutput
     输出文件管理对象,输出数据以文件形式输出
     {//子类
     AVCaptureAudioFileOutput   //输出是音频文件
     AVCaptureMovieFileOutput   //输出是视频文件
     }
     */
    
    // 4.创建输出数据对象
    AVCaptureStillImageOutput *imageOutpot = [[AVCaptureStillImageOutput alloc] init];
    NSDictionary *setting = @{
                              AVVideoCodecKey:AVVideoCodecJPEG
                              };
    [imageOutpot setOutputSettings:setting];
    
    self.imageOutput = imageOutpot;
    
    // 5️⃣ 5.添加【输入数据对象】和【输出对象】到会话中
    if ([captureSession canAddInput:captureInput]) {
        [captureSession addInput:captureInput];
    }
    if ([captureSession canAddOutput:imageOutpot]) {
        [captureSession addOutput:imageOutpot];
    }
    
    
    /*
     6️⃣ AVCaptureVideoPreviewLayer
     实时预览图层
     AVCaptureVideoPreviewLayer 是 CALayer 的子类,可被用于自动显示相机产生的实时图像。它还有几个工具性质的方法,可将 layer 上的坐标转化到设备上。它看起来像输出,但其实不是。另外,它拥有 session (outputs 被 session 所拥有)。
     */
    
    // 6.创建实时预览图层
    AVCaptureVideoPreviewLayer *previewlayer = [AVCaptureVideoPreviewLayer layerWithSession:captureSession];
    self.view.layer.masksToBounds = YES;
    previewlayer.frame = self.view.bounds;
    previewlayer.videoGravity = AVLayerVideoGravityResizeAspectFill;
    // 【预览图层】插入在【拍照按钮】的下方
    [self.view.layer insertSublayer:previewlayer below:self.canptureBtn.layer];
    
    self.captureLayer = previewlayer;
}

#pragma mark - IBAction

#pragma mark 打开摄像头
- (IBAction)takePhoto:(UIButton *)sender {
    
    self.captureLayer.hidden = NO;
    self.canptureBtn.hidden = NO;
    self.openCaptureBtn.hidden = YES;
    [self.session startRunning];    //开始捕捉
    
}

#pragma mark 拍照
- (IBAction)takeMedia:(id)sender {
    // 根据设备输出获得连接
    AVCaptureConnection *connection = [self.imageOutput connectionWithMediaType:AVMediaTypeVideo];
    // 通过连接获得设备的输出数据
    [self.imageOutput captureStillImageAsynchronouslyFromConnection:connection completionHandler:^(CMSampleBufferRef imageDataSampleBuffer, NSError *error) {
        
        // 获取输出的JPG图片
        NSData *imgData = [AVCaptureStillImageOutput jpegStillImageNSDataRepresentation:imageDataSampleBuffer];
        UIImage *image = [UIImage imageWithData:imgData];
        
        self.imageView.image = image;
        
        UIImageWriteToSavedPhotosAlbum(image, nil, nil, nil);   //保存到相册
        self.captureLayer.hidden = YES;
        self.canptureBtn.hidden = YES;
        self.openCaptureBtn.hidden = NO;
        [self.session stopRunning];
    }];
}

@end

关于 AVFoundation 视频录制的使用推荐阅读:ctolib:在 iOS 上捕获 视频

以及源码:VideoCaptureDemo

配合使用 CoreImage 实现人脸识别

人脸识别过程分3个步骤:

  1. 首先建立人脸的【面纹数据库】;
  2. 获取当前人脸面像图片;
  3. 用当前的面纹编码与数据库中的面纹编码进行比对。

CIDetectorCoreImage 中的一个特征识别滤镜。它可以找到图片中的人脸,但是是谁无法判断,需要数据库。要想识别可以看 OpenCVFace.com

#pragma mark - 识别人脸

/**
 识别人脸算法

 @param image 输入的图片
 */
- (void)faceDetectWithImage:(UIImage *)image {
    
    NSDictionary *imageOptions =  [NSDictionary dictionaryWithObject:@(5) forKey:CIDetectorImageOrientation];
    
    // 将图像转换为CIImage
    CIImage *personciImage = [CIImage imageWithCGImage:image.CGImage];
    
    // 设置识别参数
    NSDictionary *opts = [NSDictionary dictionaryWithObject:
                          CIDetectorAccuracyHigh forKey:CIDetectorAccuracy];
    CIContext *context = [CIContext contextWithOptions:nil];
    //声明一个CIDetector,并设定识别器类型为人脸识别
    CIDetector *faceDetector=[CIDetector detectorOfType:CIDetectorTypeFace context:context options:opts];
    
    // 识别出人脸数组
    // featuresInImage:方法 识别器会找到所给图像中的人脸,最后返回一个人脸数组
    NSArray *features = [faceDetector featuresInImage:personciImage options:imageOptions];
    
    // 得到图片的尺寸
    CGSize inputImageSize = [personciImage extent].size;
    // 利用仿射变换将image沿Y轴对称
    CGAffineTransform transform = CGAffineTransformScale(CGAffineTransformIdentity, 1, -1);
    // 将图片上移
    transform = CGAffineTransformTranslate(transform, 0, -inputImageSize.height);
    
    // 遍历识别到的人脸
    for (CIFaceFeature *faceFeature in features) {
        
        // 获取人脸的frame
        CGRect faceViewBounds = CGRectApplyAffineTransform(faceFeature.bounds, transform);
        CGSize viewSize = _imageView.bounds.size;
        CGFloat scale = MIN(viewSize.width / inputImageSize.width,
                            viewSize.height / inputImageSize.height);
        
        CGFloat offsetX = (viewSize.width - inputImageSize.width * scale) / 2;
        CGFloat offsetY = (viewSize.height - inputImageSize.height * scale) / 2;
        // 缩放
        CGAffineTransform scaleTransform = CGAffineTransformMakeScale(scale, scale);
        // 修正
        faceViewBounds = CGRectApplyAffineTransform(faceViewBounds, scaleTransform);
        faceViewBounds.origin.x += offsetX;
        faceViewBounds.origin.y += offsetY;
        
        // 描绘人脸区域
        UIView *faceView = [[UIView alloc] initWithFrame:faceViewBounds];
        faceView.layer.borderWidth = 2;
        faceView.layer.borderColor = [UIColor redColor].CGColor;
        [_imageView addSubview:faceView];
        
        // 判断是否有左眼位置
        if(faceFeature.hasLeftEyePosition){}
        // 判断是否有右眼位置
        if(faceFeature.hasRightEyePosition){}
        // 判断是否有嘴位置
        if(faceFeature.hasMouthPosition){}
        // 判断是否微笑
        if (faceFeature.hasSmile){}
    }
    
    // 裁剪识别到的人脸
    if ([features count]>0) {
      
        CIImage *image = [personciImage imageByCroppingToRect:[[features objectAtIndex:0] bounds]];
        UIImage *face = [UIImage imageWithCGImage:[context createCGImage:image fromRect:image.extent]];
        // 显示裁剪后的人脸
        _imageView.image = face;
        
        NSLog(@"识别人脸数::%lu",(unsigned long)[features count]);
    }   
}

关于拍照完成后使用人脸识别并裁剪,显示的人脸图片方向自动逆时针旋转90°显示的问题:
原因:iPhone 默认的方向是HOME 键位于左边的方向,故竖屏情况原始图像被拍摄后的EXIF方向值是6,被裁剪后方向信息会被删除,置为1。
解决方法:在人像识别之前先修改图像的EXIF信息为1,再进行人像识别,CIDetectorImageOrientation 值也需要改为1。

image

参考

OpenCV 相关

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,185评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,445评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,684评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,564评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,681评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,874评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,025评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,761评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,217评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,545评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,694评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,351评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,988评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,778评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,007评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,427评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,580评论 2 349

推荐阅读更多精彩内容

  • 发现 关注 消息 iOS 第三方库、插件、知名博客总结 作者大灰狼的小绵羊哥哥关注 2017.06.26 09:4...
    肇东周阅读 12,066评论 4 62
  • 杜鹃呼喊着东风点燃了晚霞 樱花逃进少女的罗帐爬上脸颊 布谷一声声咽下姹紫嫣红 谁撒下粉色的眼泪葬了暮鼓晨钟 拿酒就...
    野马王阅读 1,195评论 28 23
  • 【作者】佟梓涵 【派别】文魁派 【导师】王玉印老师 【分舵】第二分舵 思维分舵 【舵主】董季节 【导图解说】上次发...
    佟卫东Wendy阅读 318评论 5 0
  • 符号是对民族文化的解读,更是对当地人文的体现 谢谢欣赏,继续努力!
    小茆屋阅读 691评论 0 51
  • 夜晚的月光朦朦胧胧,我们一伙809000后还走街串巷的宣传,为新店开业做预售,大家都激情四射,看不出疲惫,这就是梦...
    Yoga笑笑阅读 145评论 0 0