原文地址: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 framework
的Products
文件夹拖入到项目的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
,设置Destination
为 Frameworks
,然后把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
可以分别通过GPUImageTextureOutput
和GPUImageTextureInput
类从Open GL中导出导入纹理(textures)。你可以从OpenGL ES场景(scene)录制movie,该场景会渲染到一个具有bound texture(绑定纹理)的framebuffer(帧缓冲区)对象上,或者为图片视频添加滤镜,然后将它们作为场景里的一个纹理添加到OpenGL ES中。
这种方法需要注意一点,在这些过程中用到的纹理必须在GPUImage 的OpenGL ES 上下文和其他上下文通过share group的或者相似形式共享。