CoreImage系列一:运用CoreImage与GLKit实现

作者: 鐵甲陳小寶 | 来源:发表于2017-07-14 00:04 被阅读419次

    初衷

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

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

    一脸懵逼.jpg
    别着急,苹果爸爸肯定不会为难我们的。
    我们可以获取filter的inputKeys和outputKeys来获取输入参数和输出参数。而且这些参数是接受什么参数也有标志,当然,苹果的官方文档也是有个,传送门
    接下来我们就可以传入图片了,传图片直接用KVC进行传入,key是kCIInputImageKey,value是CIImage类型的对象,你可以用NSData、CIColor、UIImage、CGImgae、CVImageBufferRef等等来创建。
    这样就完成了所以准备,现在你只要去取outputImage就行了,它会传出一个CIImage对象,你可以用它来生成真正的Image进行显示。
    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实现

        本文链接:https://www.haomeiwen.com/subject/lwvghxtx.html