GPUImage解析(一) —— 基本概览(一)

版本记录

版本号 时间
V1.0 2017.09.01

前言

GPUImage是直接利用显卡实现视频或者图像处理的技术。

作者

先看一下GPUImage

下面给出该框架的地址。
GPUImage - GitHub

下面我们就看一下该框架的作者。

从上面可以看见:

  • 作者不仅写了OC上的框架GPUImage
  • 同样写了Swift上的框架GPUImage2,这里我们暂时只关注OC上的代码。

总体概括

GPUImage框架是一个BSD许可的iOS库,可让您将GPU加速过滤器和其他效果应用于图像,实况相机视频和电影。

Core Image(iOS 5.0的一部分)相比,GPUImage允许您编写自己的自定义过滤器,支持部署到iOS 4.0,并具有更简单的界面。
然而,它目前缺乏Core Image的一些更先进的功能,如面部检测。

对于大规模并行操作(如处理图像或实时视频帧),GPU具有比CPU更显着的性能优势。
在iPhone 4上,与基于CPU的等效过滤器相比,简单的图像滤镜可以在GPU上执行的速度提升100倍以上。

然而,在GPU上运行自定义过滤器需要大量代码来为这些过滤器设置和维护一个OpenGL ES 2.0渲染目标。
我创建了一个示例项目:

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

并发现在其创建中有很多样板代码。
因此,我将这个框架放在一起,封装了处理图像和视频时遇到的许多常见任务,并使其不需要关心OpenGL ES 2.0基础。

当处理视频时,该框架与Core Image相比,在iPhone 4上仅使用2.5 ms从相机上传帧,应用伽马滤镜和显示,而对于使用Core Image的相同操作,则为106 ms。基于CPU的处理需要460 ms,使GPUImage比Core Image快40倍,在此硬件上进行此操作,比CPU处理速度快184倍。在iPhone 4S上,GPUImage在这种情况下只比Core Image快4倍,比CPU绑定处理快了102倍。

然而,对于更大的半径处的高斯模糊等更复杂的操作,Core Image目前超过GPUImage。


技术要求

  • OpenGL ES 2.0:使用此功能的应用程序不会在原始iPhone,iPhone 3G以及第一代和第二代iPod触摸屏上运行
  • iOS 4.1作为部署目标(4.0没有一些扩展需要电影阅读)。如果您想在静态照片中显示实时视频预览,则需要iOS 4.3作为部署目标。
  • iOS 5.0 SDK来构建
  • 设备必须有相机才能使用相机功能(显然)
  • 该框架使用自动引用计数(ARC),但是如下所述,应支持使用ARC和手动引用计数的项目,如果作为子项目添加。对于针对iOS 4.x的手动引用计数应用程序,您需要为应用程序项目的其他链接器标志添加-fobjc-arc

基本架构

GPUImage使用OpenGL ES 2.0着色器执行图像和视频操作比在CPU绑定例程中可以做得更快。然而,它隐藏了在简化的Objective-C接口中与OpenGL ES API交互的复杂性。此接口允许您定义图像和视频的输入源,在链中附加滤镜,并将结果处理的图像或视频发送到屏幕,UIImage或磁盘上的影片。

图像或视频帧从源对象(GPUImageOutput的子类)上传。这些包括GPUImageVideoCamera(用于iOS相机的实时视频),GPUImageStillCamera(用于拍摄相机),GPUImagePicture(静态图像)和GPUImageMovie(适用于电影)。源对象将静态图像帧上传到OpenGL ES作为纹理,然后将这些纹理关闭到处理链中的下一个对象。

链中的过滤器和其他后续元素符合GPUImageInput协议,允许它们从链中的上一个链接中获取提供或处理的纹理,然后使用它。将链中的一个进一步的对象视为目标,并且可以通过向单个输出或过滤器添加多个目标来分支处理。

例如,从摄像机接收实时视频的应用程序将该视频转换为棕褐色调,然后在屏幕上显示视频将设置链条,如下所示:

GPUImageVideoCamera -> GPUImageSepiaFilter -> GPUImageView

将静态库添加到您的iOS项目

注意:如果要在Swift项目中使用它,则需要使用“将其添加为框架”部分而不是以下步骤中的步骤。Swift需要第三方代码的模块。

一旦您获得了框架的最新源代码,将其添加到您的应用程序是非常简单的。首先将GPUImage.xcodeproj文件拖到应用程序的Xcode项目中,将框架嵌入到项目中。接下来,转到应用程序的目标,并将GPUImage添加为目标依赖关系。最后,您将要将libGPUImage.a库从GPUImage框架的Products文件夹拖动到应用程序目标中的链接二进制库与构建阶段。

GPUImage需要将其他几个框架链接到您的应用程序中,因此您需要在应用程序目标中添加以下链接库:

  • CoreMedia
  • Corevideo
  • OpenGLES
  • AVFoundation
  • QuartzCore

您还需要找到框架标题,因此在项目的构建设置中,将标题搜索路径设置为从应用程序到GPUImage源目录中的框架/子目录的相对路径。使此标题搜索路径递归。

要在应用程序中使用GPUImage类,只需使用以下内容包含核心框架头:

#import "GPUImage.h"

注意:如果在尝试使用Interface Builder构建接口时遇到错误Interface Builder中的未知类GPUImageView,则可能需要在项目的构建设置中将-ObjC添加到其他链接器标志。

另外,如果您需要将其部署到iOS 4.x,似乎当前版本的Xcode(4.3)要求您将最终应用程序中的Core Video框架链接到弱链接,或者看到有“符号未找到”的崩溃:_CVOpenGLESTextureCacheCreate,当您创建上传到App Store或临时分发的存档。为此,请转到项目的Build Phases选项卡,展开“使用库的链接二进制”组,然后在列表中找到CoreVideo.framework。将列表中最右侧的设置更改为“必需”至“可选”。

另外,这是一个支持ARC的框架,所以如果你想在一个手动引用计数的iOS 4.x的应用程序中使用它,你还需要添加-fobjc-arc到你的其他链接器标志。

在命令行中构建一个静态库

如果您不想将项目作为依赖项包含在应用程序的Xcode项目中,则可以为iOS模拟器或设备构建通用静态库。为此,请build.sh在命令行运行。生成的库和头文件将位于build/Release-iphone。您也可以通过更改IOSSDK_VER变量build.sh(可以使用所有可用的版本)来更改iOS SDK的版本xcodebuild -showsdks


执行常见任务

1. 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设备背面摄像头的视频源,使用预设可以捕获640x480。这个视频被捕获在界面处于纵向模式,其中横向左照相机需要在显示之前使其视频帧旋转。然后将使用文件CustomShader.fsh中的代码的自定义过滤器设置为来自相机的视频帧的目标。这些过滤的视频帧最终在UIView子类的帮助下显示在屏幕上,UIView子类可以呈现由此管道产生的过滤的OpenGL ES纹理。

可以通过设置其fillMode属性来更改GPUImageView的填充模式,以便如果源视频的宽高比与视图的宽高比不同,则视频将被拉伸,以黑色条为中心,或者缩放以填充。

对于混合过滤器和其他占用多个映像的过滤器,您可以创建多个输出,并为这两个输出添加一个过滤器作为目标。将输出添加为目标的顺序将影响输入图像混合或以其他方式处理的顺序。

另外,如果要启用麦克风音频捕获来录制电影,则需要将相机的audioEncodingTarget设置为影片writer,如下所示:

videoCamera.audioEncodingTarget = movieWriter;

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

这将为您提供静态相机预览视频的实时,过滤的Feed。请注意,此预览视频仅在iOS 4.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;
    }
}];

上述代码捕获由预览视图中使用的相同过滤器链处理的全尺寸照片,并将照片作为JPEG保存到应用程序的文档目录中。

请注意,由于纹理尺寸限制,该框架目前无法处理大于2048像素宽或高的旧设备(iPhone 4S,iPad 2或Retina iPad之前)的图像。这意味着iPhone 4的照相机输出的照片比此更大,将无法捕获这样的照片。正在实施平铺机制来解决这个问题。所有其他设备都应该能够使用此方法捕获和过滤照片。

3. Processing a still image - 处理静止图像

有几种方法来处理静态图像并创建结果。您可以通过创建静态图像源对象并手动创建过滤器链来实现此方法。

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会重新使用过滤器中的帧缓冲区来节省内存,因此如果您需要持续使用过滤器的帧缓冲区进行手动映像捕获,则需要事先通知它。

对于您要应用于图像的单个过滤器,您可以简单地执行以下操作:

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

4. Writing a custom filter - 编写自定义过滤器

这个框架在iOS上的Core Image(iOS 5.0)上的一个显着优点是能够编写自己的自定义图像和视频处理过滤器。这些过滤器作为OpenGL ES 2.0片段着色器提供,以C类OpenGL着色语言编写。

自定义过滤器使用代码初始化

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

用于片段着色器的扩展名是.fsh。另外,如果您不想在应用程序包中发送片段着色器,则可以使用-initWithFractmentShaderFromString:initializer将片段着色器作为字符串提供。

片段着色器对在该过滤阶段渲染的每个像素执行计算。他们使用OpenGL着色语言(GLSL),一种类似C语言的语言,具有特定于2-D和3-D图形的添加。片段着色器的示例是以下深褐色滤镜:

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

对于GPUImage框架中可以使用的图像过滤器,需要将纹理坐标变化的前两行(纹理中的当前坐标,归一化为1.0)和inputImageTexture均匀(对于实际的输入图像框架纹理) 。

着色器的其余部分在传入纹理中的这个位置处获取像素的颜色,以使其产生棕褐色调的方式进行操作,并将该像素颜色写出以用于下一阶段的处理管道。

在Xcode项目中添加片段着色器时需要注意的一点是,Xcode认为它们是源代码文件。要解决此问题,您需要手动将着色器从“编译源”构建阶段移动到“复制包资源”,以使着色器包含在应用程序包中。

5. Filtering and re-encoding a movie - 过滤和重新编码视频

视频可以通过GPUImageMovie类加载到框架中,过滤,然后使用GPUImageMovieWriter写出。GPUImageMovieWriter也足够快,可以从640x480的iPhone 4的相机实时录制视频,因此可以将直接过滤的视频源投入使用。目前,GPUImageMovieWriter足够快,可以在iPhone 4上录制高达20 FPS的720p视频,以及iPhone 4S(以及新iPad)上的30 FPS的720p和1080p视频。

以下是一个示例,您将如何加载示例视频,将其传递通过像素过滤器,然后将结果记录到磁盘为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];

录制完成后,您需要从过滤器链中删除视频录像机,并使用以下代码关闭录制:

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

一部电影在完成之后将无法使用,所以如果这一点之前中断,录像将会丢失。

6. Interacting with OpenGL ES - 与OpenGL ES进行交互

GPUImage可以分别通过使用其GPUImageTextureOutput和GPUImageTextureInput类来从OpenGL ES导出和导入纹理。这可以让您从OpenGL ES场景中录制一个电影,将其渲染到具有绑定纹理的帧缓冲区对象,或者过滤视频或图像,然后将它们作为要在场景中显示的纹理提供给OpenGL ES。

使用这种方法的一个警告是,这些进程中使用的纹理必须通过共享组或类似的东西在GPUImage的OpenGL ES上下文和任何其他上下文之间共享。

后记

未完,待续~~~

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

推荐阅读更多精彩内容

  • 概述 GPUImage框架是一个获得bsd许可的iOS库,允许您对图像、实时摄像机视频和电影应用gpu加速过滤器和...
    依然小太阳阅读 847评论 0 1
  • 是的,我要开始写关于GPUImage 框架的文章了!先来把简介看一波!借助翻译工具也得啃出来哇!!!看来我又得早起...
    CC老师_HelloCoder阅读 2,756评论 1 15
  • 在iOS中框架是一个目录,包含了共享资源库,用于访问该资源库中储存的代码的头文件,以及图像、声音文件等其他资源。共...
    ch123阅读 1,706评论 0 1
  • iOS 苹果官方Demo合集 字数10517阅读21059评论18喜欢144 其实, 开发了这么久, 不得不说, ...
    bingo居然被占了阅读 9,850评论 2 31
  • 感赏儿子今天早早起床,9:40吃了早餐:一小碗芝麻汤圆、3个刀切馒头、1个小烧卖、一个荷包蛋。又一次吃早餐了。...
    Lucy艳萍_静待花开阅读 362评论 2 5