GPUImage翻译一:概述

原文地址:https://github.com/BradLarson/GPUImage

概述

本库可将GPU加速的滤镜和其他效果应用于图片、视频实时录制、电影。相比Core Image, 本库可支持自定义滤镜、最低支持iOS 4.0、交互简洁。不过,本库目前缺少一些Core Image的高级功能,如人脸识别。

对于大规模的并行操作,如对多张图片或者是实时视频帧的处理,GPU比CPU有很多显著的优势。例如,在iPhone4上,一个简单的图片滤镜在GPU上处理要比在CPU上处理快100倍。

但是,在GPU上运行自定义滤镜需要大量代码来设置和维护一个OpenGL ES 2.0渲染的对象。以下是一个示例:

http://www.sunsetlakesoftware.com/2010/10/22/gpu-accelerated-video-processing-mac-and-ios
而且在这个示例的创建中,有大量的样本代码(boilerplate code)不得不写。因此,我在本库中封装了大量的处理图片视频时的常用任务,从而让使用者无需关心OpenGL ES 2.0的基础细节。

Core Image相比,本库在iPhone 4上处理视频时,从相机上传一帧添加上伽马滤镜并展示出来只需2.5ms,而相同条件下用Core Image则需要160ms。基于CPU处理需要460ms,基于本库是Core Image的40倍,是CPU的184倍。在iPhone 4s上,本库比Core Image快4倍,比CPU快120倍。不过,对于更复杂的处理,比如大半径的高斯模糊,Core Image是超过本库的。

技术要求

  • OpenGL ES 2.0: 使用此功能的应用不能运行iPhone1、iPhone、iPhone 3G、及一代二代的iPod touches。
  • 最低支持 iOS4.1。
  • ARC。

General architecture (一般建筑)

GPUImage基于OpenGL ES2.0着色器,比基于CPU的更快,并且比起复杂的OpenGL ES API,它更简约的封装在oc的接口中。这套接口可以定义图片和视频的输入源(input source),在链中添加滤镜,并将生成的图片发送到屏幕、UIImage对象、或者磁盘上的电影。

图片或者视频的帧从一些源对象(source objects,父类未GPUImageOutput)中上传。包括:

  • GPUImageVideoCamera:用于iOS相机的实时视频
  • GPUImageStillCamera:用于使用相机拍摄照片
  • GPUImagePicture:用于静止图像
  • GPUImageMovie:用于电影
    源对象上传静态图片帧到OpenGL ES 作为纹理(textures),然后将其交给处理链(processing chain)中的下个对象。

滤镜和链中后面其他元素都遵循GPUImageInput协议,该协议可以获取链中上个链接中已经处理过的纹理。 Objects one step further down the chain 被视为 targets,可以通过添加多个targets到一个输出(output)或滤镜(filter)将处理过程分解开。

比如,一个从相机中接收实时视频的程序,将视频转为一个棕褐色色调(sepia tone),然后将视频展示到屏幕,可以设置一条这样的链:

GPUImageVideoCamera -> GPUImageSepiaFilter -> GPUImageView

生成静态库添加到工程

Note: 若你想用于Swift项目,请参考 “[生成FrameWork添加到工程]”模块。Swift需要第三方代码模块。

最新代码添加到你的程序非常简单。

  • 首先,将GPUImage.xcodeproj文件拖到你的Xcode工程里,以将framework嵌入。
  • 其次,在工程里的Target->Build Phases中将GPUImage作为一个Target Dependency
  • 最后,将libGPUImage.a文件从GPUImage frameworkProducts文件夹拖入到项目的Link Binary With Libraries build phase中。

另外,GPUImage 还需要添加其他framework,如下:

  • CoreMedia
  • CoreVideo
  • OpenGLES
  • AVFoundation
  • QuartzCore

除此之外,还需要将工程的Header Search Paths设为相对路径。
使用本库,只需导入框架的头文件即可:

#import "GPUImage.h"

如果运行时报错:Unknown class GPUImageView in Interface Builder,或者xxx (or the like when trying to build an interface with Interface Builder),你需要在Other Linker Flags中添加-ObjC

4.x注意事项...(略)。

在命令行中编译静态库

如果你不想将本库作为dependency,你可以编译一个静态库。在命令行运行build.sh文件,会在build/Release-iphone目录生成library和头文件。你可以在build.sh文件中的IOSSDK_VER中iOS SDK的版本(所有可用的版本可以通过命令xcodebuild -showsdks查找)。

生成FrameWork添加到工程

从Xcode 6、iOS 8以后,开始跟Mac开发一样,支持导入完整的framework,这简化了添加它到程序的过程。导入时,我建议直接拖拽.xcodeproj的工程文件到工程,就跟上面静态库里一样。

  • Build Setting -> Build Phases -> Target Dependencies中导入GPUImageFramework(不是静态库中生成的GPUImage),对Mac开发来说就导入GPUImage
  • Link Binary With Libraries 中,添加GPUImage.framework

This should cause GPUImage to build as a framework. 在xcode6下,这也会作为一个module编译,可用于Swift工程。按照上面的设置,你只需调用:

import GPUImage

然后添加一个新的Copy Fles build phase,设置DestinationFrameworks,然后把GPUImage.framework添加进去。这样,本框架将会跟你的程序绑定在一起(否则,会有一些类似dyld: Library not loaded: @rpath/GPUImage.framework/GPUImage的报错)。

Documentation

Documentation 是生成于用appledoc的标题注释中生成的(Documentation is generated from header comments using appledoc. )。要编译文档,请切换到xcode中的 "Documentation" scheme。确保APPLEDOC_PATH(用户定义的编译设置)指向 appledoc binary,可以从Github或者Homebrew获取。它还会构建和安装一个.docset文件,该文件可以查看喜欢的文档工具。

执行常见任务

Filtering live video (为实时视频添加滤镜)

给iOS相机的实时视频添加滤镜,代码如下:

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

这是设置的iOS后置摄像头的视频源,using a preset that tries to capture at 640x480。该视频实在肖像模式下拍摄的,横向拍摄时需要旋转其帧。CustomShader.fsh文件中的代码可用于自定义滤镜,将用于修饰相机的视频帧。添加了滤镜的视频证将走中在一些UIView子类的帮助下显示在屏幕上,这些子类可以展现出经过滤镜处理过的OpenGL ES纹理。

GPUImage的填充模式可以通过设置fillMode属性来修改,所以如果视频源的长宽比跟view的长宽比不一致时,视屏将会要么被拉伸,两侧有黑边,要么被放大填充。

Blending(融合/合同/混合) 滤镜和其他能接收多个图片的滤镜,你可以创建多个outputs,并且为每个输出增加一个单独的滤镜。这些outputs的顺序将会影响被处理的 input images的顺序。

另外,当你录制movie时,如果想打开麦克风,需要设置camera的audioEncodingTarget为自己的movie writer,像这样:

videoCamera.audioEncodingTarget = movieWriter;

拍摄静态图并为其添加滤镜(Capturing and filtering a still photo)

拍摄静态图并为其添加滤镜,跟上面为视频添加滤镜的过程类似,把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];

这会给你一个实时的,添加了滤镜的相机预览视频反馈。该预览视频只能在iOS4.3及以上。

拍摄一张照片,可以用下面的回调:

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

以上代码通过相同的滤镜链拍摄了一张原始尺寸的图片,并保存在沙盒documents中一张JPEG的图片。

注意,本框架不能处理旧设备(iPhone4s、iPad2、retina iPad以前的设备)中大于2048像素宽的图片,原因是纹理尺寸的限制(texture size limitations)。也就是说,iPhone 4如果拍摄的静态图大于2048,将不能拍摄出这种效果的图片。不过可以用图片平铺的方法解决这个问题,所有设备都能用这个方法拍摄图片并为其添加滤镜。

处理静态图(Processing a still image)

有很多方式处理静态图,第一种方式,创建一个静态图source对象,然后手动创建一个滤镜链:

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

注意,手动从filter获取图片,你需要设置-useNextFrameForImageCapture来告诉filter接下来要从中捕获它。默认情况下,GPUImage会重用filter中的 framebuffers(帧缓冲区) 以节省内存,所以当你手动捕获图片时想控制filter的framebuffer(帧缓冲区),你得先知道这些。

给图片增加一个单独的滤镜,如下代码:

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

自定义滤镜

跟Core Image相比,本框架的一个重要优势是可以为图片和视频创建自定义的滤镜。这些滤镜是由OpenGL ES 2.0 的 fragment shaders 提供的,它是用类似C语言的 OpenGL Shading Language 编写的。

自定义一个滤镜,代码如下:

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

这里,用于fragment shader的扩展名是.fsh。此外,你可以用-initWithFragmentShaderFromString:方法初始化,区别是该方法要写文件的全路径。

Fragment shaders(段着色器) 为在滤镜中渲染的每个像素执行其计算,用的是 OpenGL Shading Language (GLSL),是一种类似C的语言,其中增加了专门用于2-D和3-D图形的语言。下面是一个Fragment shaders处理sepia-tone滤镜的例子:

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

对于在本框架中的图片滤镜,前两行中 textureCoordinate、inputImageTexture是必需的。(For an image filter to be usable within the GPUImage framework, the first two lines that take in the textureCoordinate varying (for the current coordinate within the texture, normalized to 1.0) and the inputImageTexture uniform (for the actual input image frame texture) are required.

着色器的其余部分抓取了传入纹理位置的像素颜色,像生成 sepia tone 一样进行操作,并将像素颜色写入,以用于处理管道的下一个阶段。(The remainder of the shader grabs the color of the pixel at this location in the passed-in texture, manipulates it in such a way as to produce a sepia tone, and writes that pixel color out to be used in the next stage of the processing pipeline.

添加fragment shaders到工程时,有个主意事项是xcode会把它们识别为source code files。要解决这个问题,需要手动将shader从Compile Sources build phase挪到Copy Bundle Resources,这样工程的bundle就能包含shader了。

给movie添加滤镜和重新编码 (Filtering and re-encoding a movie)

Movies可以通过GPUImageMovie类加载到框架中,并添加滤镜,然后通过GPUImageMovieWriter可以导出。GPUImageMovieWriter在iPhone 4s上即时的录制640x480的video也足够迅速,所以可以直接将填了了滤镜的video source导入。目前,GPUImageMovieWriter的速度足以在iPhone 4上以20 FPS的速度录制实时720p视频,在iPhone 4S(以及新iPad上)上以30 FPS录制720p和1080p视频。

下面是一个示例,如何加载示例movie,加载Pixellate Filter,然后录制成视频保存到沙盒:

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

录制结束时,需要从filter chain中移除movie recorder,并关闭它,代码如下:

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

如果录制未完成前被打断了,生成的movie将不能播放。

与Open GL交互(Interacting with OpenGL ES

GPUImage可以分别通过GPUImageTextureOutputGPUImageTextureInput类从Open GL中导出导入纹理(textures)。你可以从OpenGL ES场景(scene)录制movie,该场景会渲染到一个具有bound texture(绑定纹理)的framebuffer(帧缓冲区)对象上,或者为图片视频添加滤镜,然后将它们作为场景里的一个纹理添加到OpenGL ES中。

这种方法需要注意一点,在这些过程中用到的纹理必须在GPUImage 的OpenGL ES 上下文和其他上下文通过share group的或者相似形式共享。

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

推荐阅读更多精彩内容

  • rljs by sennchi Timeline of History Part One The Cognitiv...
    sennchi阅读 7,279评论 0 10
  • 查看centos系统版本命令 查看版本: 查看64位还是32位: 使用 file /bin/ls
    PHPNOTE阅读 9,314评论 0 1
  • 字是自己写的,写来贴在书案上提醒自己。“静久思动,动久思静。”生活,确实如此。坐得久了,腿发麻,就想起来动动,说...
    御承扬阅读 190评论 2 2
  • 回想一下,平时你吃了面包、饼干、面条之类的食物后,是否出现过拉肚子或便秘等不适症状?如果有的话,可要小心了,你可能...
    瘦瘦的幸福阅读 1,658评论 0 1
  • 熬不过冬天的人,一定看不到春天;挺不过苦痛的人,一定望不到幸福;无论是破茧成蝶,还是鹰的重生,都无可避免的选择过直...
    圆圆的兔兔阅读 112评论 0 0