如果只是簡單的調(diào)用相機(jī)來拍照的話蘋果為我們提供了UIImagePickerController這個簡單的拍照功能的實(shí)現(xiàn)弄抬。但是它的界面是固定的。當(dāng)你想自定義拍照界面以及使用其他更高級的功能的時候就需要用到AVFoundation這個框架缘薛。
AVFoundation 相關(guān)類
AVFoundation 框架基于以下幾個類實(shí)現(xiàn)圖像捕捉 沃测,通過這些類可以訪問來自相機(jī)設(shè)備的原始數(shù)據(jù)并控制它的組件溶褪。
- AVCaptureDevice 是關(guān)于相機(jī)硬件的接口。它被用于控制硬件特性辨图,諸如鏡頭的位置班套、曝光、閃光燈等故河。
- AVCaptureDeviceInput 提供來自設(shè)備的數(shù)據(jù)吱韭。
- AVCaptureOutput 是一個抽象類,描述 capture session 的結(jié)果鱼的。以下是三種關(guān)于靜態(tài)圖片捕捉的具體子類:
AVCaptureStillImageOutput 用于捕捉靜態(tài)圖片
AVCaptureMetadataOutput 啟用檢測人臉和二維碼
AVCaptureVideoDataOutput (原文顯示為AVCaptureVideoOutput理盆,但是我用到的是這個)為實(shí)時預(yù)覽圖提供原始幀 - AVCaptureSession 管理輸入與輸出之間的數(shù)據(jù)流痘煤,以及在出現(xiàn)問題時生成運(yùn)行時錯誤。
- AVCaptureVideoPreviewLayer是 CALayer的子類猿规,可被用于自動顯示相機(jī)產(chǎn)生的實(shí)時圖像衷快。它還有幾個工具性質(zhì)的方法,可將 layer 上的坐標(biāo)轉(zhuǎn)化到設(shè)備上姨俩。它看起來像輸出蘸拔,但其實(shí)不是。另外环葵,它擁有 session (outputs 被 session 所擁有)调窍。
以上引用自這篇文章
使用
初始化
- 如上文所說AVCaptureSession是管理輸入輸出的類。擔(dān)任管理調(diào)度的角色积担。因此需要先創(chuàng)建它
_session = [[AVCaptureSession alloc] init];
_session.sessionPreset = AVCaptureSessionPresetPhoto;
使用AVCaptureSessionPresetPhoto會自動設(shè)置為最適合的拍照配置。比如它可以允許我們使用最高的感光度 (ISO) 和曝光時間猬仁,基于相位檢測的自動對焦, 以及輸出全分辨率的 JPEG 格式壓縮的靜態(tài)圖片帝璧。
- AVCaptureDevice是用來控制硬件的接口。在拍照的時候我們需要一個攝像頭的設(shè)備湿刽。因此我們需要遍歷所有設(shè)備找到相應(yīng)的攝像頭的烁。
// 用來返回是前置攝像頭還是后置攝像頭
-(AVCaptureDevice *)cameraWithPosition:(AVCaptureDevicePosition) position {
// 返回和視頻錄制相關(guān)的所有默認(rèn)設(shè)備
NSArray *devices = [AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo];
// 遍歷這些設(shè)備返回跟position相關(guān)的設(shè)備
for (AVCaptureDevice *device in devices) {
if ([device position] == position) {
return device;
}
}
return nil;
}
- AVCaptureDeviceInput,找到相應(yīng)的攝像頭之后就能通過這個獲取來自硬件的數(shù)據(jù)诈闺。
// 后置攝像頭輸入
-(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獲取輸出數(shù)據(jù)的類渴庆。拍照的時候用的是AVCaptureStillImageOutput。它是用來捕獲靜態(tài)圖片的類雅镊。
// 靜態(tài)圖像輸出
-(AVCaptureStillImageOutput *)stillImageOutput
{
if (_stillImageOutput == nil) {
_stillImageOutput = [[AVCaptureStillImageOutput alloc] init];
NSDictionary *outputSettings = [[NSDictionary alloc] initWithObjectsAndKeys:AVVideoCodecJPEG,AVVideoCodecKey,nil];
_stillImageOutput.outputSettings = outputSettings;
}
return _stillImageOutput;
}
// 添加靜態(tài)圖片輸出(拍照)
if ([_session canAddOutput:self.stillImageOutput]) {
[_session addOutput:self.stillImageOutput];
}
AVCaptureMetadataOutput用來進(jìn)行人臉或者二維碼一維碼的識別襟雷。不同于AVCaptureStillImageOutput,它需要在加載如session中之后才能進(jìn)行設(shè)置仁烹,不然會報(bào)錯耸弄。
// 添加元素輸出(識別)
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用來錄制視頻或者從輸出數(shù)據(jù)流捕捉單一的圖像幀卓缰。比如進(jìn)行身份證和手機(jī)號識別的過程中就需要不斷從數(shù)據(jù)流中獲取圖像计呈,這個時候需要用到它。
// 視頻輸出
-(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;
}
操作
- 啟動相機(jī)
在session和相機(jī)設(shè)備中完成的操作都利用block來調(diào)用征唬。因此這些操作都建議分配到后臺串行隊(duì)列中捌显。
dispatch_async(self.sessionQueue, ^{
[self.session startRunning];
});
- 拍照
利用AVCaptureStillImageOutput來進(jìn)行拍照,開啟閃光燈的話會在拍照后關(guān)閉总寒,有快門聲音扶歪。注意,通過拍照方法獲取的照片旋轉(zhuǎn)了90度摄闸,并且其大小并不是預(yù)覽窗口的大小击罪,需要進(jìn)行截取哲嘲。
#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方法篩選相應(yīng)的元素對象
#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];
});
}
}
}
}
- 從輸出數(shù)據(jù)流捕捉單一的圖像幀
利用AVCaptureVideoDataOutputSampleBufferDelegate獲取相應(yīng)的數(shù)據(jù)流,然后獲取某一幀媳禁。與拍照一樣眠副,這種方式獲取的圖片依然角度大小不正確,要進(jìn)行相應(yīng)的處理竣稽。
#pragma mark - 從輸出數(shù)據(jù)流捕捉單一的圖像幀
-(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;
}
}
- 對焦
通過設(shè)置AVCaptureDevice的AVCaptureFocusMode來進(jìn)行設(shè)置對焦模式囱怕。
AVCaptureFocusMode
是個枚舉,描述了可用的對焦模式:
Locked 指鏡片處于固定位置
AutoFocus指一開始相機(jī)會先自動對焦一次毫别,然后便處于 Locked
模式娃弓。
ContinuousAutoFocus 指當(dāng)場景改變,相機(jī)會自動重新對焦到畫面的中心點(diǎn)岛宦。
可以通過變換 “感興趣的點(diǎn) (point of interest)” 來設(shè)定另一個區(qū)域台丛。這個點(diǎn)是一個 CGPoint,它的值從左上角{0砾肺,0}到右下角 {1挽霉,1},{0.5变汪,0.5} 為畫面的中心點(diǎn)侠坎。通常可以用視頻預(yù)覽圖上的點(diǎn)擊手勢識別來改變這個點(diǎn)裙盾,想要將 view 上的坐標(biāo)轉(zhuǎn)化到設(shè)備上的規(guī)范坐標(biāo)实胸,我們可以使用[self.previewLayer captureDevicePointOfInterestForPoint:devicePoint]轉(zhuǎn)換view上的坐標(biāo)到感興趣的點(diǎn)。(在進(jìn)行二維碼識別的時候也可以通過設(shè)置這個調(diào)整識別的重點(diǎn)位置)
總結(jié)
總的來說自定義相機(jī)能做的事情還是挺多的番官,還能夠?qū)ζ毓饴辍灼胶膺M(jìn)行調(diào)節(jié)。項(xiàng)目中暫時沒用到徘熔,用到再進(jìn)行補(bǔ)充假褪。
注意點(diǎn)
1. 通過拍照方法獲取的照片旋轉(zhuǎn)了90度,并且其大小并不是預(yù)覽窗口的大小近顷,需要進(jìn)行裁剪
2. 所有對相機(jī)進(jìn)行的操作建議都放到后臺進(jìn)行生音,包括切換相機(jī)之類
3. 更改相機(jī)配置時需要先鎖定相機(jī),更改完成后再解開鎖定