GPUImage简介

GPUImage

概述

GPUImage是一个遵循BSD的iOS开源库,通过使用它可以为图片、实时视频和影片添加GPU加速的滤镜和其他特效。GPUImage支持部署在iOS 4.0以上的系统,并提供简单的接口用于实现自定义的滤镜。
对于处理像图片和实时视频帧这类有大量相似操作的数据时,相对于使用CPU做处理,GPU有更显著的性能优势。在iPhone4上使用GPU运行一个简单的图片滤镜,会比CPU快超过100倍。

http://www.sunsetlakesoftware.com/2010/10/22/gpu-accelerated-video-processing-mac-and-ios

尽管如此,如果要使用运行在GPU的自定义滤镜,我们必须编写非常多的代码去配置和处理OpenGL ES的渲染目标,并且当中会编写大量重复的样板代码。GPUImage封装了大部分在处理图片和视频时会遇到的任务,所以使用者不需要关心OpenGL ES 2.0的使用。

基本架构

GPUImage使用OpenGL ES 2.0 着色器处理图片和视频,比在CPU中处理的效率高很多。并且GPUImage使用简化的Objective-C接口封装了复杂的OpenGL ES API调用。通过使用GPUImage接口来定义图片和视频的输入源,然后添加滤镜到处理链中,最后发送图片和视频的处理结果到屏幕、UIImage或者磁盘的视频中。

视频的图片或帧通过源对象(Source Object)被加载,源对象为GPUImageOutput的子类,包括GPUImageOutput(用于iOS相机拍摄实时视频)、GPUImageStillCamera(用于iOS相机拍摄照片)、GPUImagePicture(用于静态照片)、GPUImageMovie(用于影片)。源对象把静态图片帧作为纹理加载到OpenGL ES,然后把纹理传递到处理链的下一个对象中。

处理链中的滤镜以及后续的元素都遵循GPUImageMovie协议,这样实现使它们能接收在处理链的上一环节所提供或被处理过的纹理,并且可以做进一步的操作。在处理链中进一步往下传递的对象称为目标(Targets),处理过程可以拆分为依次添加多个目标到一个输出或滤镜中。

例如,在一个应用程序使用相机拍摄实时视频时,要把视频的色调转为深褐色,然后显示到屏幕中,这个过程会建立一条如下所示的处理链:

GPUImageVideoCamera -> GPUImageSepiaFilter -> GPUImageView

处理常规任务

过滤实时视频

为实时视频添加滤镜的代码如下:

GPUImageVideoCamera *videoCamera = [[GPUImageVideoCamera alloc] initWithSessionPreset:AVCaptureSessionPreset640x480 cameraPosition:AVCaptureDevicePositionBack];
videoCamera.outputImageOrientation = UIInterfaceOrientationPortrait;

GPUImageFilter *customFilter = [[GPUImageFilter alloc] initWithFragmentShaderFromFile:@"CustomShader"];
GPUImageView *filteredVideoView = [[GPUImageView alloc] initWithFrame:CGRectMake(0.0, 0.0, viewWidth, viewHeight)];

// Add the view somewhere so it's visible

[videoCamera addTarget:customFilter];
[customFilter addTarget:filteredVideoView];

[videoCamera startCameraCapture];

如果想要在视频采集中记录声音,你需要设置PUImageVideoCameraaudioEncodingTarget,如下:

videoCamera.audioEncodingTarget = movieWriter;

采集和过滤静态图片

拍摄和过滤静态图片的过程和视频类似,只需要把GPUImageVideoCamera替换为GPUImageStillCamera

stillCamera = [[GPUImageStillCamera alloc] init];
stillCamera.outputImageOrientation = UIInterfaceOrientationPortrait;

filter = [[GPUImageGammaFilter alloc] init];
[stillCamera addTarget:filter];
GPUImageView *filterView = (GPUImageView *)self.view;
[filter addTarget:filterView];

[stillCamera startCameraCapture];

如果想采集一张图片,你需要使用一个block回调,如下:

[stillCamera capturePhotoProcessedUpToFilter:filter withCompletionHandler:^(UIImage *processedImage, NSError *error){
    NSData *dataForJPEGFile = UIImageJPEGRepresentation(processedImage, 0.8);

    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
    NSString *documentsDirectory = [paths objectAtIndex:0];

    NSError *error2 = nil;
    if (![dataForJPEGFile writeToFile:[documentsDirectory stringByAppendingPathComponent:@"FilteredPhoto.jpg"] options:NSAtomicWrite error:&error2])
    {
        return;
    }
}];

处理静态图片

有两种方式处理静态图片。第一种方式是创建一个静态图片源(GPUImagePicture)并手动创建一条滤镜处理链:

UIImage *inputImage = [UIImage imageNamed:@"Lambeau.jpg"];

GPUImagePicture *stillImageSource = [[GPUImagePicture alloc] initWithImage:inputImage];
GPUImageSepiaFilter *stillImageFilter = [[GPUImageSepiaFilter alloc] init];

[stillImageSource addTarget:stillImageFilter];
[stillImageFilter useNextFrameForImageCapture];
[stillImageSource processImage];

UIImage *currentFilteredVideoFrame = [stillImageFilter imageFromCurrentFramebuffer];

记住通过滤镜手动采集图片的方式,需要调用-useNextFrameForImageCapture来告诉滤镜你将要使用它来采集图片。GPUImage默认会通过复用滤镜中的帧缓存(framebuffers)来维护内存,所以如果需要持有该帧缓存,要先提前告诉它。

对于想应用到图片的简单滤镜,可以简单地用以下方式实现:

GPUImageSepiaFilter *stillImageFilter2 = [[GPUImageSepiaFilter alloc] init];
UIImage *quickFilteredImage = [stillImageFilter2 imageByFilteringImage:inputImage];

编写自定义滤镜

GPUImage相对Core Image的显著优势在于可以编写自定义的图片和视频滤镜。这些滤镜通过OpenGL ES 2.0的片段着色器来实现。片段着色器则通过类似C语言的GLSL语言来编写。

一个自定义滤镜的初始化代码像这样:

GPUImageFilter *customFilter = [[GPUImageFilter alloc] initWithFragmentShaderFromFile:@"CustomShader"];

片段着色器文件的扩展名为“.fsh”。另外,如果你想使用Application Bundle外的片段着色器,可以使用-initWithFragmentShaderFromString: 初始化方法,以字符串的形式提供片段着色器。

在过滤阶段,片段着色器通过使用GLSL,为每一个要被渲染的像素执行相应的运算。以下为一个棕黑色调滤镜的片段着色器例子:

varying highp vec2 textureCoordinate;

uniform sampler2D inputImageTexture;

void main()
{
    lowp vec4 textureColor = texture2D(inputImageTexture, textureCoordinate);
    lowp vec4 outputColor;
    outputColor.r = (textureColor.r * 0.393) + (textureColor.g * 0.769) + (textureColor.b * 0.189);
    outputColor.g = (textureColor.r * 0.349) + (textureColor.g * 0.686) + (textureColor.b * 0.168);    
    outputColor.b = (textureColor.r * 0.272) + (textureColor.g * 0.534) + (textureColor.b * 0.131);
    outputColor.a = 1.0;

    gl_FragColor = outputColor;
}

过滤和重编码影片

影片可以通过GPUImageMovie被加载,然后过滤,最后使用GPUImageMovieWriter输出。

以下例子展示了如何加载一段示例影片,通过一个像素化滤镜,然后被记录为一个480 x 640的h.264视频到磁盘中。

movieFile = [[GPUImageMovie alloc] initWithURL:sampleURL];
pixellateFilter = [[GPUImagePixellateFilter alloc] init];

[movieFile addTarget:pixellateFilter];

NSString *pathToMovie = [NSHomeDirectory() stringByAppendingPathComponent:@"Documents/Movie.m4v"];
unlink([pathToMovie UTF8String]);
NSURL *movieURL = [NSURL fileURLWithPath:pathToMovie];

movieWriter = [[GPUImageMovieWriter alloc] initWithMovieURL:movieURL size:CGSizeMake(480.0, 640.0)];
[pixellateFilter addTarget:movieWriter];

movieWriter.shouldPassthroughAudio = YES;
movieFile.audioEncodingTarget = movieWriter;
[movieFile enableSynchronizedEncodingUsingMovieWriter:movieWriter];

[movieWriter startRecording];
[movieFile startProcessing];

当记录完成时,你需要从处理链中移除影片记录器(GPUImageMovieWriter),关闭记录的代码如下:

[pixellateFilter removeTarget:movieWriter];
[movieWriter finishRecording];

当影片在记录完成前被打断,该记录会丢失。

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

推荐阅读更多精彩内容