如果只是简单的调用相机来拍照的话苹果为我们提供了UIImagePickerController这个简单的拍照功能的实现。但是它的界面是固定的。当你想自定义拍照界面以及使用其他更高级的功能的时候就需要用到AVFoundation这个框架。
AVFoundation 相关类
AVFoundation 框架基于以下几个类实现图像捕捉 ,通过这些类可以访问来自相机设备的原始数据并控制它的组件。
- AVCaptureDevice 是关于相机硬件的接口。它被用于控制硬件特性,诸如镜头的位置、曝光、闪光灯等。
- AVCaptureDeviceInput 提供来自设备的数据。
- AVCaptureOutput 是一个抽象类,描述 capture session 的结果。以下是三种关于静态图片捕捉的具体子类:
AVCaptureStillImageOutput 用于捕捉静态图片
AVCaptureMetadataOutput 启用检测人脸和二维码
AVCaptureVideoDataOutput (原文显示为AVCaptureVideoOutput,但是我用到的是这个)为实时预览图提供原始帧 - AVCaptureSession 管理输入与输出之间的数据流,以及在出现问题时生成运行时错误。
- AVCaptureVideoPreviewLayer是 CALayer的子类,可被用于自动显示相机产生的实时图像。它还有几个工具性质的方法,可将 layer 上的坐标转化到设备上。它看起来像输出,但其实不是。另外,它拥有 session (outputs 被 session 所拥有)。
以上引用自这篇文章
使用
初始化
- 如上文所说AVCaptureSession是管理输入输出的类。担任管理调度的角色。因此需要先创建它
_session = [[AVCaptureSession alloc] init];
_session.sessionPreset = AVCaptureSessionPresetPhoto;
使用AVCaptureSessionPresetPhoto会自动设置为最适合的拍照配置。比如它可以允许我们使用最高的感光度 (ISO) 和曝光时间,基于相位检测的自动对焦, 以及输出全分辨率的 JPEG 格式压缩的静态图片。
- AVCaptureDevice是用来控制硬件的接口。在拍照的时候我们需要一个摄像头的设备。因此我们需要遍历所有设备找到相应的摄像头。
// 用来返回是前置摄像头还是后置摄像头
-(AVCaptureDevice *)cameraWithPosition:(AVCaptureDevicePosition) position {
// 返回和视频录制相关的所有默认设备
NSArray *devices = [AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo];
// 遍历这些设备返回跟position相关的设备
for (AVCaptureDevice *device in devices) {
if ([device position] == position) {
return device;
}
}
return nil;
}
- AVCaptureDeviceInput,找到相应的摄像头之后就能通过这个获取来自硬件的数据。
// 后置摄像头输入
-(AVCaptureDeviceInput *)backCameraInput {
if (_backCameraInput == nil) {
NSError *error;
_backCameraInput = [[AVCaptureDeviceInput alloc] initWithDevice:[self backCamera] error:&error];
if (error) {
NSLog(@"获取后置摄像头失败~");
}
}
return _backCameraInput;
}
获得到AVCaptureDeviceInput后加入到AVCaptureSession上
// 添加后置摄像头的输入
if ([_session canAddInput:self.backCameraInput]) {
[_session addInput:self.backCameraInput];
self.currentCameraInput = self.backCameraInput;
}
- AVCaptureOutput获取输出数据的类。拍照的时候用的是AVCaptureStillImageOutput。它是用来捕获静态图片的类。
// 静态图像输出
-(AVCaptureStillImageOutput *)stillImageOutput
{
if (_stillImageOutput == nil) {
_stillImageOutput = [[AVCaptureStillImageOutput alloc] init];
NSDictionary *outputSettings = [[NSDictionary alloc] initWithObjectsAndKeys:AVVideoCodecJPEG,AVVideoCodecKey,nil];
_stillImageOutput.outputSettings = outputSettings;
}
return _stillImageOutput;
}
// 添加静态图片输出(拍照)
if ([_session canAddOutput:self.stillImageOutput]) {
[_session addOutput:self.stillImageOutput];
}
AVCaptureMetadataOutput用来进行人脸或者二维码一维码的识别。不同于AVCaptureStillImageOutput,它需要在加载如session中之后才能进行设置,不然会报错。
// 添加元素输出(识别)
if ([_session canAddOutput:self.metaDataOutput]) {
[_session addOutput:self.metaDataOutput];
// 人脸识别
[_metaDataOutput setMetadataObjectTypes:@[AVMetadataObjectTypeFace]];
// 二维码,一维码识别
// [_metaDataOutput setMetadataObjectTypes:@[AVMetadataObjectTypeCode39Code,AVMetadataObjectTypeCode128Code,AVMetadataObjectTypeCode39Mod43Code,AVMetadataObjectTypeEAN13Code,AVMetadataObjectTypeEAN8Code,AVMetadataObjectTypeCode93Code]];
[_metaDataOutput setMetadataObjectsDelegate:self queue:self.sessionQueue];
}
AVCaptureVideoDataOutput用来录制视频或者从输出数据流捕捉单一的图像帧。比如进行身份证和手机号识别的过程中就需要不断从数据流中获取图像,这个时候需要用到它。
// 视频输出
-(AVCaptureVideoDataOutput *)videoDataOutput {
if (_videoDataOutput == nil) {
_videoDataOutput = [[AVCaptureVideoDataOutput alloc] init];
[_videoDataOutput setSampleBufferDelegate:self queue:self.sessionQueue];
NSDictionary* setcapSettings = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithInt:kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange], kCVPixelBufferPixelFormatTypeKey,
nil];
_videoDataOutput.videoSettings = setcapSettings;
}
return _videoDataOutput;
}
操作
- 启动相机
在session和相机设备中完成的操作都利用block来调用。因此这些操作都建议分配到后台串行队列中。
dispatch_async(self.sessionQueue, ^{
[self.session startRunning];
});
- 拍照
利用AVCaptureStillImageOutput来进行拍照,开启闪光灯的话会在拍照后关闭,有快门声音。注意,通过拍照方法获取的照片旋转了90度,并且其大小并不是预览窗口的大小,需要进行截取。
#pragma mark - 拍照
-(void)takePhotoWithImageBlock:(void (^)(UIImage *, UIImage *, UIImage *))block
{
__weak typeof(self) weak = self;
[self.stillImageOutput captureStillImageAsynchronouslyFromConnection:[self imageConnection] completionHandler:^(CMSampleBufferRef imageDataSampleBuffer, NSError *error) {
if (!imageDataSampleBuffer) {
return ;
}
NSData *imageData = [AVCaptureStillImageOutput jpegStillImageNSDataRepresentation:imageDataSampleBuffer];
UIImage *originImage = [[UIImage alloc] initWithData:imageData];
CGFloat squareLength = weak.previewLayer.bounds.size.width;
CGFloat previewLayerH = weak.previewLayer.bounds.size.height;
CGSize size = CGSizeMake(squareLength * 2, previewLayerH * 2);
UIImage *scaledImage = [originImage resizedImageWithContentMode:UIViewContentModeScaleAspectFill bounds:size interpolationQuality:kCGInterpolationHigh];
CGRect cropFrame = CGRectMake((scaledImage.size.width - size.width) / 2, (scaledImage.size.height - size.height) / 2, size.width, size.height);
UIImage *croppedImage = [scaledImage croppedImage:cropFrame];
UIDeviceOrientation orientation = [UIDevice currentDevice].orientation;
if (orientation != UIDeviceOrientationPortrait) {
CGFloat degree = 0;
if (orientation == UIDeviceOrientationPortraitUpsideDown) {
degree = 180;// M_PI;
} else if (orientation == UIDeviceOrientationLandscapeLeft) {
degree = -90;// -M_PI_2;
} else if (orientation == UIDeviceOrientationLandscapeRight) {
degree = 90;// M_PI_2;
}
croppedImage = [croppedImage rotatedByDegrees:degree];
scaledImage = [scaledImage rotatedByDegrees:degree];
originImage = [originImage rotatedByDegrees:degree];
}
if (block) {
block(originImage,scaledImage,croppedImage);
}
}];
}
- 识别
利用AVCaptureMetadataOutputObjectsDelegate方法筛选相应的元素对象
#pragma mark - AVCaptureMetadataOutputObjectsDelegate
-(void)captureOutput:(AVCaptureOutput *)captureOutput didOutputMetadataObjects:(NSArray *)metadataObjects fromConnection:(AVCaptureConnection *)connection
{
if (self.faceRecognition) {
for(AVMetadataObject *metadataObject in metadataObjects) {
if([metadataObject.type isEqualToString:AVMetadataObjectTypeFace]) {
AVMetadataObject *transform = [self.previewLayer transformedMetadataObjectForMetadataObject:metadataObject];
dispatch_async(dispatch_get_main_queue(), ^{
[self showFaceImageWithFrame:transform.bounds];
});
}
}
}
}
- 从输出数据流捕捉单一的图像帧
利用AVCaptureVideoDataOutputSampleBufferDelegate获取相应的数据流,然后获取某一帧。与拍照一样,这种方式获取的图片依然角度大小不正确,要进行相应的处理。
#pragma mark - 从输出数据流捕捉单一的图像帧
-(void)captureOutput:(AVCaptureOutput *)captureOutput didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection
{
if (self.isStartGetImage) {
UIImage *originImage = [self imageFromSampleBuffer:sampleBuffer];
CGFloat squareLength = self.previewLayer.bounds.size.width;
CGFloat previewLayerH = self.previewLayer.bounds.size.height;
CGSize size = CGSizeMake(squareLength*2, previewLayerH*2);
UIImage *scaledImage = [originImage resizedImageWithContentMode:UIViewContentModeScaleAspectFill bounds:size interpolationQuality:kCGInterpolationHigh];
CGRect cropFrame = CGRectMake((scaledImage.size.width - size.width) / 2, (scaledImage.size.height - size.height) / 2, size.width, size.height);
UIImage *croppedImage = [scaledImage croppedImage:cropFrame];
UIDeviceOrientation orientation = [UIDevice currentDevice].orientation;
if (orientation != UIDeviceOrientationPortrait) {
CGFloat degree = 0;
if (orientation == UIDeviceOrientationPortraitUpsideDown) {
degree = 180;// M_PI;
} else if (orientation == UIDeviceOrientationLandscapeLeft) {
degree = -90;// -M_PI_2;
} else if (orientation == UIDeviceOrientationLandscapeRight) {
degree = 90;// M_PI_2;
}
croppedImage = [croppedImage rotatedByDegrees:degree];
}
dispatch_async(dispatch_get_main_queue(), ^{
if (self.getimageBlock) {
self.getimageBlock(croppedImage);
self.getimageBlock = nil;
}
});
self.isStartGetImage = NO;
}
}
- 对焦
通过设置AVCaptureDevice的AVCaptureFocusMode来进行设置对焦模式。
AVCaptureFocusMode
是个枚举,描述了可用的对焦模式:
Locked 指镜片处于固定位置
AutoFocus指一开始相机会先自动对焦一次,然后便处于 Locked
模式。
ContinuousAutoFocus 指当场景改变,相机会自动重新对焦到画面的中心点。
可以通过变换 “感兴趣的点 (point of interest)” 来设定另一个区域。这个点是一个 CGPoint,它的值从左上角{0,0}到右下角 {1,1},{0.5,0.5} 为画面的中心点。通常可以用视频预览图上的点击手势识别来改变这个点,想要将 view 上的坐标转化到设备上的规范坐标,我们可以使用[self.previewLayer captureDevicePointOfInterestForPoint:devicePoint]转换view上的坐标到感兴趣的点。(在进行二维码识别的时候也可以通过设置这个调整识别的重点位置)
总结
总的来说自定义相机能做的事情还是挺多的,还能够对曝光、白平衡进行调节。项目中暂时没用到,用到再进行补充。
注意点
1. 通过拍照方法获取的照片旋转了90度,并且其大小并不是预览窗口的大小,需要进行裁剪
2. 所有对相机进行的操作建议都放到后台进行,包括切换相机之类
3. 更改相机配置时需要先锁定相机,更改完成后再解开锁定