在ios上更快的卷积

时间:2013-10-14 06:27:29

标签: ios opencv gpu gpuimage convolution

我正在尝试使用16X16生成的内核对图像执行卷积。我使用opencv filterengine类,但它只在CPU上运行,我正在尝试加速应用程序。 我知道opencv也有filterengine_gpu,但据我了解,这不是IOS支持的。 GPUimage允许您使用3X3生成的过滤器执行卷积。有没有其他方法来加速卷积?在GPU上运行的不同库?

2 个答案:

答案 0 :(得分:2)

您可以使用Apple的Accelerate framework。它可以在iOS和MacOS上使用,因此可能会在以后重用您的代码。

为了获得最佳性能,您可能需要考虑以下选项:

  • 如果您的卷积内核是可分的,请使用separable implementation。这是对称内核(例如高斯卷积)的情况。这将节省一个数量级的计算时间;
  • 如果您的图像具有两种尺寸的功率,请考虑使用FFT技巧。空间域中的卷积(复杂度N ^ 2)等效于傅里叶域中的乘法(复杂度N)。因此,您可以1)FFT图像和内核,2)逐项乘以结果,3)反转结果的FFT。由于FFT算法很快(例如,加速框架中的Aple的FFT),这一系列操作可以提高性能。

您可以在this book中找到有关iOS图像处理优化的更多信息,我也会审核here

答案 1 :(得分:1)

您可以使用GPUImage进行16x16卷积,但是您需要编写自己的过滤器来执行此操作。框架中的3x3卷积从输入图像中每个像素周围3x3区域中的像素进行采样,并应用您输入的权重矩阵。框架内的GPUImage3x3ConvolutionFilter.m源文件应该相当容易阅读,但我可以提供如果你想超越我的目标,那就是一个小小的背景。

我要做的第一件事是使用以下顶点着色器:

 attribute vec4 position;
 attribute vec4 inputTextureCoordinate;

 uniform float texelWidth;
 uniform float texelHeight; 

 varying vec2 textureCoordinate;
 varying vec2 leftTextureCoordinate;
 varying vec2 rightTextureCoordinate;

 varying vec2 topTextureCoordinate;
 varying vec2 topLeftTextureCoordinate;
 varying vec2 topRightTextureCoordinate;

 varying vec2 bottomTextureCoordinate;
 varying vec2 bottomLeftTextureCoordinate;
 varying vec2 bottomRightTextureCoordinate;

 void main()
 {
     gl_Position = position;

     vec2 widthStep = vec2(texelWidth, 0.0);
     vec2 heightStep = vec2(0.0, texelHeight);
     vec2 widthHeightStep = vec2(texelWidth, texelHeight);
     vec2 widthNegativeHeightStep = vec2(texelWidth, -texelHeight);

     textureCoordinate = inputTextureCoordinate.xy;
     leftTextureCoordinate = inputTextureCoordinate.xy - widthStep;
     rightTextureCoordinate = inputTextureCoordinate.xy + widthStep;

     topTextureCoordinate = inputTextureCoordinate.xy - heightStep;
     topLeftTextureCoordinate = inputTextureCoordinate.xy - widthHeightStep;
     topRightTextureCoordinate = inputTextureCoordinate.xy + widthNegativeHeightStep;

     bottomTextureCoordinate = inputTextureCoordinate.xy + heightStep;
     bottomLeftTextureCoordinate = inputTextureCoordinate.xy - widthNegativeHeightStep;
     bottomRightTextureCoordinate = inputTextureCoordinate.xy + widthHeightStep;
 }

计算从中对卷积中使用的像素颜色进行采样的位置。因为使用归一化坐标,所以像素之间的X和Y间距分别为1.0 / [图像宽度]和1.0 / [图像高度]。

在顶点着色器中计算要采样的像素的纹理坐标有两个原因:每个顶点执行一次计算效率更高(构成图像矩形的两个三角形中有六个) )比每个片段(像素),并尽可能避免依赖纹理读取。依赖纹理读取是在片段着色器中计算要读取的纹理坐标的位置,而不是简单地从顶点着色器传入,并且它们在iOS GPU上要慢得多。

一旦我在顶点着色器中计算了纹理位置,我将它们作为变化传递到片段着色器中,并在那里使用以下代码:

 uniform sampler2D inputImageTexture;

 uniform mat3 convolutionMatrix;

 varying vec2 textureCoordinate;
 varying vec2 leftTextureCoordinate;
 varying vec2 rightTextureCoordinate;

 varying vec2 topTextureCoordinate;
 varying vec2 topLeftTextureCoordinate;
 varying vec2 topRightTextureCoordinate;

 varying vec2 bottomTextureCoordinate;
 varying vec2 bottomLeftTextureCoordinate;
 varying vec2 bottomRightTextureCoordinate;

 void main()
 {
     vec3 bottomColor = texture2D(inputImageTexture, bottomTextureCoordinate).rgb;
     vec3 bottomLeftColor = texture2D(inputImageTexture, bottomLeftTextureCoordinate).rgb;
     vec3 bottomRightColor = texture2D(inputImageTexture, bottomRightTextureCoordinate).rgb;
     vec4 centerColor = texture2D(inputImageTexture, textureCoordinate);
     vec3 leftColor = texture2D(inputImageTexture, leftTextureCoordinate).rgb;
     vec3 rightColor = texture2D(inputImageTexture, rightTextureCoordinate).rgb;
     vec3 topColor = texture2D(inputImageTexture, topTextureCoordinate).rgb;
     vec3 topRightColor = texture2D(inputImageTexture, topRightTextureCoordinate).rgb;
     vec3 topLeftColor = texture2D(inputImageTexture, topLeftTextureCoordinate).rgb;

     vec3 resultColor = topLeftColor * convolutionMatrix[0][0] + topColor * convolutionMatrix[0][1] + topRightColor * convolutionMatrix[0][2];
     resultColor += leftColor * convolutionMatrix[1][0] + centerColor.rgb * convolutionMatrix[1][1] + rightColor * convolutionMatrix[1][2];
     resultColor += bottomLeftColor * convolutionMatrix[2][0] + bottomColor * convolutionMatrix[2][1] + bottomRightColor * convolutionMatrix[2][2];

     gl_FragColor = vec4(resultColor, centerColor.a);

这将读取9种颜色中的每种颜色,并应用为卷积提供的3x3矩阵的权重。

也就是说,一个16x16的卷积是一个相当昂贵的操作。您正在查看每个像素256个纹理读取。在较旧的设备(iPhone 4左右)上,如果它们是非依赖性读取,则每个像素可以免费获得大约8个纹理读取。一旦你完成了这一点,性能开始急剧下降。不过,后来的GPU显着加快了这一速度。例如,iPhone 5S几乎可以免费获得每个像素超过40个相关纹理读取。即使是1080p视频上最重的着色器也几乎没有减慢速度。

正如sansuiso建议的那样,如果你有办法将内核分成水平和垂直通道(就像高斯模糊内核那样),由于纹理读取的显着减少,你可以获得更好的性能。对于你的16x16内核,你可以从256次读取减少到32次,甚至那些32次也会快得多,因为它们来自一次仅采样16个纹素的传递。

执行此类操作的交叉点在CPU上的加速比在OpenGL ES中更快会因您运行的设备而异。一般来说,iOS设备上的GPU在每一代产品中的性能增长都超过了CPU,因此在过去的几款iOS模型中,这一点已经转移到了GPU端。