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];
如果想要在视频采集中记录声音,你需要设置PUImageVideoCamera的audioEncodingTarget,如下:
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];
当影片在记录完成前被打断,该记录会丢失。