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上下文和任何其他上下文之间共享。
后记
未完,待续~~~