iOS 相機實時濾鏡效果

前言

項目Demo,實現了實時濾鏡、拍照、錄像功能。html

最近玩了哈實時濾鏡,學到挺多東西的。筆者長得醜,看看有沒有機會沒那麼醜。只挑了幾種濾鏡,筆者是個鋼鐵直男,沒有美顏效果。git

QQ20190526-160341-HD.gif

原理

設備獲取圖像輸入流後,通過對該幀處理造成新圖像,最後刷新UIgithub

實現

蘋果有簡單的 UIImagePickerController ,但擴展性差。因此筆者採用的是 AVFoundation 框架。其涉及到輸入流和輸出流,方便咱們對每一幀進行處理,顯示出來bash

若是你對輸入和輸出相關的類不瞭解,應該也不影響你理解本文。但筆者仍是建議你先看哈蘇沫離的博客。好比,OC之輸入管理AVCaptureInputOC之輸出管理 AVCaptureOutputsession

有一個類 AVCaptureMovieFile,直接把音頻和圖像結合起來。但因爲要處理畫面,因此又得拆分開。因此筆者採用的是 AVCaptureAudioDataOutputAVCaptureVideoDataOutput,處理完圖像再拼。app

1. 獲取輸入流

輸入流要經過相關設備初始化。框架

// 圖像
    _device = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
    _cameraDeviceInput = [[AVCaptureDeviceInput alloc] initWithDevice:self.device error:nil];

    // 音頻
    AVCaptureDevice *micDevice = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeAudio];
        _microphoneDeviceInput = [AVCaptureDeviceInput deviceInputWithDevice:micDevice error:nil];
複製代碼

2. 初始化輸出流

_queue = dispatch_queue_create("VideoDataOutputQueue", DISPATCH_QUEUE_SERIAL);

    // 圖像
    _videoDataOutput = [[AVCaptureVideoDataOutput alloc] init];
    _videoDataOutput.videoSettings = @{(id)kCVPixelBufferPixelFormatTypeKey : [NSNumber numberWithInteger:kCVPixelFormatType_32BGRA]};
    _videoDataOutput.alwaysDiscardsLateVideoFrames = YES;
    [_videoDataOutput setSampleBufferDelegate:self queue:_queue];
    
    // 音頻
    _audioDataOutput = [[AVCaptureAudioDataOutput alloc] init];
        [_audioDataOutput setSampleBufferDelegate:self queue:_queue];
複製代碼

建立了一個串行隊列,以確保每一幀按順序處理。輸出流的回調方法爲- (void)captureOutput:(AVCaptureOutput *)output didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection;ide

3. 建立會話,鏈接輸入輸出

AVCaptureSession會話起到中間層的做用。性能

_session = [[AVCaptureSession alloc] init];
    if ([_session canSetSessionPreset:AVCaptureSessionPreset1280x720]) {
        [_session setSessionPreset:AVCaptureSessionPreset1280x720];
    }
    
    { // 把輸入輸出結合起來
        if ([_session canAddInput:_cameraDeviceInput]) {
            [_session addInput:_cameraDeviceInput];
        }
        if ([_session canAddOutput:_videoDataOutput]) {
            [_session addOutput:_videoDataOutput];
        }
    }
複製代碼

4. 開啓會話

開啓輸入流,獲取數據到輸出流。優化

//開始啓動
    [_session startRunning];
複製代碼

要注意的是,若是要修改輸入流或者輸出流,要在一次提交中完成。好比切換攝像頭(修改輸入流)。

//輸入流
    AVCaptureDeviceInput *newInput = [AVCaptureDeviceInput deviceInputWithDevice:newCamera error:nil];
    
    if (newInput != nil) {
        [self.session beginConfiguration];
        //先移除原來的input
        [self.session removeInput:self.cameraDeviceInput];
        if ([self.session canAddInput:newInput]) {
            [self.session addInput:newInput];
            self.cameraDeviceInput = newInput;
            
        } else {
            [self.session addInput:self.cameraDeviceInput];
        }
        [self.session commitConfiguration];
    }
複製代碼

5. 輸出流數據回調方法

// 在這裏處理獲取的圖像,而且保存每一幀到self.outputImg
- (void)captureOutput:(AVCaptureOutput *)output didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection {
    @autoreleasepool {
        if (output == _audioDataOutput && [_audioWriterInput isReadyForMoreMediaData]) {// 處理音頻
            [_audioWriterInput appendSampleBuffer:sampleBuffer];
        }
        
        if (output == self.videoDataOutput) { // 處理視頻幀
            // 處理圖片,保存到self.outputImg中
            [self imageFromSampleBuffer:sampleBuffer];
            
        }
    }
}
複製代碼

在這一步,處理每一幀圖像,加上濾鏡。

6. 濾鏡

這裏用到了CIFilter,是蘋果自帶的CoreImage框架對圖片進行處理的一個框架。其實現了上百種效果,筆者只選取了其中三種。感興趣的能夠去官方文檔查看。

//1.建立基於CPU的CIContext對象    
    self.context = [CIContext contextWithOptions:
    [NSDictionary dictionaryWithObject:[NSNumber numberWithBool:YES]
 forKey:kCIContextUseSoftwareRenderer]];

    //2.建立基於GPU的CIContext對象 
    self.context = [CIContext contextWithOptions: nil];

    //3.建立基於OpenGL優化的CIContext對象,可得到實時性能
    self.context = [CIContext contextWithEAGLContext:[[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2]];

    // 將UIImage轉換成CIImage
    CIImage *ciImage = [[CIImage alloc] initWithImage:[UIImage imageNamed:@"WechatIMG1.jpeg"]];
    // 建立濾鏡
    CIFilter *filter = [CIFilter filterWithName:_dataSourse[indexPath.row]
                                  keysAndValues:kCIInputImageKey, ciImage, nil];
    [filter setDefaults];

    // 獲取繪製上下文
    CIContext *context = [CIContext contextWithOptions:nil];
    // 渲染並輸出CIImage
    CIImage *outputImage = [filter outputImage];
    // 建立CGImage句柄
    CGImageRef cgImage = [self.context createCGImage:outputImage
                                      fromRect:[outputImage extent]];
    imageview.image = [UIImage imageWithCGImage:cgImage];
    // 釋放CGImage句柄
    CGImageRelease(cgImage);
複製代碼

拍照和錄像

在回調方法裏,咱們加完濾鏡獲得每一幀。當點下拍照按鈕,把這一張圖片保存到相冊即完成了拍照功能。

對於錄像,咱們主要用到 AVAssetWriter 以及 AVAssetWriterInputPixelBufferAdaptor

獲取到圖像和音頻流後,咱們將其放到緩衝區內。最終判斷時間戳 ,AVAssetWriter將他們合成視頻。


後記

遇到了一個問題,也記錄一下吧。

前置攝像頭鏡像問題。網上大多思路都是iOS 前置攝像頭鏡像問題,但不能處理。緣由多是由於筆者項目對幀進行了處理,數據不是原生的圖像。對圖片再進行一次鏡像處理便可。

if ([[self.cameraDeviceInput device] position] == AVCaptureDevicePositionFront) {// 前置要鏡像
        result = [result imageByApplyingOrientation:UIImageOrientationUpMirrored];
}
複製代碼

參考

相關文章
相關標籤/搜索