利用AVFoundation自定義相機(jī)

如果只是簡單的調(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 所擁有)调窍。

以上引用自這篇文章

使用

初始化

  1. 如上文所說AVCaptureSession是管理輸入輸出的類。擔(dān)任管理調(diào)度的角色积担。因此需要先創(chuàng)建它
_session = [[AVCaptureSession alloc] init];
_session.sessionPreset = AVCaptureSessionPresetPhoto;

使用AVCaptureSessionPresetPhoto會自動設(shè)置為最適合的拍照配置。比如它可以允許我們使用最高的感光度 (ISO) 和曝光時間猬仁,基于相位檢測的自動對焦, 以及輸出全分辨率的 JPEG 格式壓縮的靜態(tài)圖片帝璧。

  1. 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;
}
  1. 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;
        }
  1. 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;
}

操作

  1. 啟動相機(jī)
    在session和相機(jī)設(shè)備中完成的操作都利用block來調(diào)用征唬。因此這些操作都建議分配到后臺串行隊(duì)列中捌显。
dispatch_async(self.sessionQueue, ^{
        [self.session startRunning];
    });
  1. 拍照
    利用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);
        }
    }];
}
  1. 識別
    利用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];
                });
            }
        }
    }
}
  1. 從輸出數(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;
    }
}
  1. 對焦
    通過設(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ī),更改完成后再解開鎖定

我是demo

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末窒升,一起剝皮案震驚了整個濱河市缀遍,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌饱须,老刑警劉巖域醇,帶你破解...
    沈念sama閱讀 211,423評論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡譬挚,警方通過查閱死者的電腦和手機(jī)锅铅,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,147評論 2 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來减宣,“玉大人盐须,你說我怎么就攤上這事∑犭纾” “怎么了贼邓?”我有些...
    開封第一講書人閱讀 157,019評論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長闷尿。 經(jīng)常有香客問我塑径,道長,這世上最難降的妖魔是什么填具? 我笑而不...
    開封第一講書人閱讀 56,443評論 1 283
  • 正文 為了忘掉前任统舀,我火速辦了婚禮,結(jié)果婚禮上劳景,老公的妹妹穿的比我還像新娘誉简。我一直安慰自己,他們只是感情好枢泰,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,535評論 6 385
  • 文/花漫 我一把揭開白布描融。 她就那樣靜靜地躺著铝噩,像睡著了一般衡蚂。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上骏庸,一...
    開封第一講書人閱讀 49,798評論 1 290
  • 那天毛甲,我揣著相機(jī)與錄音,去河邊找鬼具被。 笑死玻募,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的一姿。 我是一名探鬼主播七咧,決...
    沈念sama閱讀 38,941評論 3 407
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼叮叹!你這毒婦竟也來了艾栋?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,704評論 0 266
  • 序言:老撾萬榮一對情侶失蹤蛉顽,失蹤者是張志新(化名)和其女友劉穎蝗砾,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,152評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡悼粮,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,494評論 2 327
  • 正文 我和宋清朗相戀三年闲勺,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片扣猫。...
    茶點(diǎn)故事閱讀 38,629評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡菜循,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出苞笨,到底是詐尸還是另有隱情债朵,我是刑警寧澤,帶...
    沈念sama閱讀 34,295評論 4 329
  • 正文 年R本政府宣布瀑凝,位于F島的核電站序芦,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏粤咪。R本人自食惡果不足惜谚中,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,901評論 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望寥枝。 院中可真熱鬧宪塔,春花似錦、人聲如沸囊拜。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,742評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽冠跷。三九已至南誊,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間蜜托,已是汗流浹背抄囚。 一陣腳步聲響...
    開封第一講書人閱讀 31,978評論 1 266
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留橄务,地道東北人幔托。 一個月前我還...
    沈念sama閱讀 46,333評論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像蜂挪,于是被迫代替她去往敵國和親重挑。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,499評論 2 348

推薦閱讀更多精彩內(nèi)容