CoreImage系列一:运用CoreImage与GLKit实现摄像实时滤镜

初衷

CoreImage系列是关于最近学习CoreImage处理图片和视频的一些总结,如果有高手看到有错误的地方请帮我指出来,免得误导了大家。谢谢。


CoreImage一瞥

CoreImage是苹果在iOS5出的一个对图片和视频的图像数据进行实时分析、加工的图像框架。通过运用CPU或GPU进行图片处理,开发者不用太关注于底层的OpenGL等技术就能进行强大的图片编辑。
它的核心类有以下几个:

  • CIContext:CoreImage的上下文,这个上下文是框架真正工作的地方,它需要分配必要的内存,并编译和运行滤镜内核来执行图像处理。
  • CIFilter:CoreImage进行图像的滤镜处理的对象。滤镜可以单独使用,也可以组成一个滤镜链来进行处理 ( 因为CoreImage并不是一个真正的图片对象,而是一个图片生成的"模板",所以在运用滤镜链的时候,并不会对一个图片进行多次滤镜,而是会把这些滤镜在底层的kernel进行像素处理混合,所以只会处理一次图片,效率大大增加 ) 。
  • CIKernel:对图片进行处理的核心模块,负责进行像素变化等工作,最后返回加工完成的图片。开发者可以自定义Kernel进行滤镜开发。
    。。。
    当然,还有一些其他的类,在这里就不一一介绍,在接下来需要的时候会进行介绍。

1.运用CoreImage进行图片滤镜

CoreImage进行图片的滤镜开发其实还是挺简单的,最主要的是选择合适的滤镜,在CoreImage中提供了127个滤镜进行处理 ( 看今年的WWDC上的CoreImage session,好像今年又加入了很多滤镜,达到了196个 ,Amazing! ) 。
选择滤镜首先选择合适的滤镜分类,有些分类是用来处理图片修补的、有些是用来合并或者转场的等等。在这儿我们就进行简单的进行图片的处理就行了。
首先我们需要生成一个CIFilter,我们可以通过filterNamesInCategory或者filterNamesInCategories来获取到一个分类或多个分类的滤镜 ( 如果你想获取所有的 ,传入nil就可以了 )。

你拿到了filter的名字之后就可以用filterWithName来生成一个CIFilter:
CIFilter *filter = [CIFilter filterWithName:@"CIPhotoEffectMono"];

但是一般滤镜都有一些参数可以设置的,我们怎么知道这些参数喃?而且每个滤镜的参数都可能不一样啊!!

CoreImage系列一:运用CoreImage与GLKit实现摄像实时滤镜_第1张图片
一脸懵逼.jpg

别着急,苹果爸爸肯定不会为难我们的。
我们可以获取filter的inputKeys和outputKeys来获取输入参数和输出参数。而且这些参数是接受什么参数也有标志,当然,苹果的官方文档也是有个, 传送门。
接下来我们就可以传入图片了,传图片直接用KVC进行传入,key是kCIInputImageKey,value是CIImage类型的对象,你可以用NSData、CIColor、UIImage、CGImgae、CVImageBufferRef等等来创建。
这样就完成了所以准备,现在你只要去取outputImage就行了,它会传出一个CIImage对象,你可以用它来生成真正的Image进行显示。
CoreImage系列一:运用CoreImage与GLKit实现摄像实时滤镜_第2张图片
Still Image Filter.png


2.进行视频滤镜

现在我们能够进行简单的图片处理了,现在我们更深入一点,进行视频处理。
因为我们需要实时的处理视频,所以需要拿到视频每一帧的图像,所以像UIImagePickerController这样的视频录制就不能满足我们的需求了,我们需要进行深度挖掘,我们就会用到AVCaptureAudioDataOutput,它能将我们录制的每一帧传出供我们处理,但是我们必须在一定的时间内进行处理,否则它就会将这一帧丢掉,从而出现卡顿现象,就像我们的UITableView一样。像这样实时的处理,如果我们用CPU的话显然是不行的,Core Graphics的效率我们都是知道的,嘻嘻。所以需要GPU出场,基于GPU处理,我们可以用GLKit和Metal来实现,这篇文章我们先用GLKit来实现,下一篇我会用Metal来实现。

对于GLKit,我就用官方的原话来介绍了

The GLKit framework provides functions and classes that reduce the effort required to create new shader-based apps or to port existing apps that rely on fixed-function vertex or fragment processing provided by earlier versions of OpenGL ES or OpenGL。

简单说就是基于OpenGL进行封装吧,让我们好用。

回归正题,我们首先需要建立视频连接,让我们能看到摄像头拍摄的东西,

    //视频输入
    AVCaptureDevice *video = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
    AVCaptureDeviceInput *videoInput = [AVCaptureDeviceInput deviceInputWithDevice:video error:nil];

    AVCaptureSession *session = [[AVCaptureSession alloc] init];
    if ([session canAddInput:videoInput]) {
        [session addInput:videoInput];
    }
    _session = session;

    //视频输出
    _queue = dispatch_queue_create("DataOutputQueue", DISPATCH_QUEUE_SERIAL);
    _videoOutput = [AVCaptureVideoDataOutput new];
    NSDictionary *videoSettings = [NSDictionary dictionaryWithObjectsAndKeys:
                                   [NSNumber numberWithInt:kCVPixelFormatType_32BGRA], kCVPixelBufferPixelFormatTypeKey,
                                   nil];
    _videoOutput.videoSettings = videoSettings;
    [_videoOutput setAlwaysDiscardsLateVideoFrames:YES];
    [_videoOutput setSampleBufferDelegate:self queue:self.queue];
    if ([session canAddOutput:_videoOutput]){
        [session addOutput:_videoOutput];
    }
    AVCaptureConnection *connection = [_videoOutput connectionWithMediaType:AVMediaTypeVideo];
    connection.videoOrientation = AVCaptureVideoOrientationPortrait;

    [session startRunning];

这样我们就能在videoOutPut的代理回调

- (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection

只不过拿到录制的视频帧了,就是CMSampleBufferRef对象。

然后我们需要创建滤镜相关的东西了,首先是GLKView的生成,它有一个方法来进行创建

- (instancetype)initWithFrame:(CGRect)frame context:(EAGLContext *)context;

但是这个EAGLContext又是什么喃?其实这个是GLKit相关的上下文对象,跳到EAGLContext的定义里面,我们能看到它的创建方式:

- (instancetype) init NS_UNAVAILABLE;
- (instancetype) initWithAPI:(EAGLRenderingAPI) api;
- (instancetype) initWithAPI:(EAGLRenderingAPI) api sharegroup:(EAGLSharegroup*) sharegroup NS_DESIGNATED_INITIALIZER;

第一种不能使用,二三种都有一个EAGLRenderingAPI的枚举,其实就是你要使用的OpenGL 的版本,sharegroup也只是一个用于debug时方便查看的对象,所以我们直接用第二种:[[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];
因为我们需要对图像进行处理,所以我们需要关闭GLKView的自动渲染,.enableSetNeedsDisplay = NO;

有了GLKit的上下文,我们就能生成CIFilterd的上下文了,

[CIContext contextWithEAGLContext:_eaglContext options:@{kCIContextWorkingColorSpace : [NSNull null]} ]

因为CIContext的创建开销很大,所以我们很多时候都会复用一个context。
接下来我们生成一个滤镜就行了。
准备工作已经完成,接下来就是对实时视频帧进行处理了。

    CVImageBufferRef imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer); 
    CIImage *sourceImage = [CIImage imageWithCVPixelBuffer:(CVPixelBufferRef)imageBuffer options:nil];
    CIImage *filteredImage = RunFilter(sourceImage, self.filter);

    [_videoPreviewView bindDrawable];
    if (filteredImage)
        [self.context drawImage:filteredImage inRect:CGRectMake(0, 0, self.videoPreviewView.drawableWidth, self.videoPreviewView.drawableHeight) fromRect:sourceImage.extent];
    [_videoPreviewView display];

我们首先需要将CoreMedia的数据对象转换成图像数据,通过CMSampleBufferRef-> CVImageBufferRef-> CIImage,我们就拿到了我们需要的原始图像数据,接下来就是对图像进行滤镜处理,这里和上边处理图片一样的,就不贴代码了。
接下来我们就让滤镜上下文开始着色和让GLKView进行渲染。

这样,我们就实现了拍摄实时滤镜了,demo在此。

参考文章

About Core Image

Core Image 介绍

CoreImage session

ps

最近在学习视频相关的东西,本来想的是仿写一个VUE来试试的;恰逢今天纯银大大的新产品发布了,而且看那个准维密模特看得我一愣一愣的,准备参考一下猫饼来写一个,不知道纯银大大有没有意见啊,咳咳~

如果这篇还可以的话,接下来就再写一些Core Image的文章。如果我写的不好或者理解的不深甚至有错误的话,请帮忙指出。

你可能感兴趣的:(CoreImage系列一:运用CoreImage与GLKit实现摄像实时滤镜)