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

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

一脸懵逼.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的文章。如果我写的不好或者理解的不深甚至有错误的话,请帮忙指出。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,830评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,992评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,875评论 0 331
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,837评论 1 271
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,734评论 5 360
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,091评论 1 277
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,550评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,217评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,368评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,298评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,350评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,027评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,623评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,706评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,940评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,349评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,936评论 2 341

推荐阅读更多精彩内容

  • 老骥伏枥,志在千里 前记 最近一直在研究图像处理方面,既上一篇iOS Quart2D绘图之UIImage简单使用后...
    半笑半醉間阅读 4,357评论 0 14
  • --绘图与滤镜全面解析 概述 在iOS中可以很容易的开发出绚丽的界面效果,一方面得益于成功系统的设计,另一方面得益...
    韩七夏阅读 2,695评论 2 10
  • 前言现在很多的APP当中选择图片都会带有图片处理效果,一些类似于美图,PS的功能,其实在iOS中系统内部也有这样一...
    清溪丷阅读 6,915评论 3 42
  • 前言 最近在研究 Core Image 自定义 Filter 相关内容,重新学习了 Core Image,对 Co...
    泥孩儿0107阅读 743评论 0 4
  • 荷 一池碧水恋芙蓉, 素面无黛润潮红。 花盖欲滴迎风笑, 洁净淡雅立风中。 荷 洁入群芳谱, 淡雅心自清。 待到雨...
    荷静阅读 297评论 10 20