iOS 相机实时滤镜效果

5,818 阅读3分钟

前言

项目Demo,实现了实时滤镜、拍照、录像功能。

最近玩了哈实时滤镜,学到挺多东西的。笔者长得丑,看看有没有机会没那么丑。只挑了几种滤镜,笔者是个钢铁直男,没有美颜效果。

QQ20190526-160341-HD.gif

原理

设备获取图像输入流后,经过对该帧处理形成新图像,最后刷新UI

实现

苹果有简单的 UIImagePickerController ,但扩展性差。所以笔者采用的是 AVFoundation 框架。其涉及到输入流和输出流,方便我们对每一帧进行处理,显示出来

如果你对输入和输出相关的类不了解,应该也不影响你理解本文。但笔者还是建议你先看哈苏沫离的博客。比如,OC之输入管理AVCaptureInputOC之输出管理 AVCaptureOutput

有一个类 AVCaptureMovieFile,直接把音频和图像结合起来。但由于要处理画面,所以又得拆分开。所以笔者采用的是 AVCaptureAudioDataOutputAVCaptureVideoDataOutput,处理完图像再拼。

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;

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];
}

参考