0

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

4

2 回答 2

2

您可以为此使用 Apple 的Accelerate 框架。顺便说一下,它可以在 iOS 和 MacOS 上使用,因此以后可能会重用您的代码。

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

  • 如果您的卷积核是可分离的,请使用可分离的实现。这是对称核(例如高斯卷积)的情况。这将节省一个数量级的计算时间;
  • 如果您的图像具有二次方大小,请考虑使用 FFT 技巧。空间域中的卷积(复杂度 N^2)等价于傅里叶域中的乘法(复杂度 N)。因此,您可以 1) 对图像和内核进行 FFT,2) 将结果逐项相乘,以及 3) 反转结果的 FFT。由于 FFT 算法速度很快(例如 Aple 在 Accelerate 框架中的 FFT),这一系列操作可以带来性能提升。

你可以在这本书中找到更多关于 iOS 图像处理优化的见解,我也在这里查看过。

于 2013-10-14T06:50:03.683 回答
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 个纹素的通道。

在 Accelerate on CPU 中执行此类操作比在 OpenGL ES 中更快的交叉点将因您正在运行的设备而异。一般来说,iOS 设备上的 GPU 在最近几代的性能增长方面都超过了 CPU,因此在过去的几款 iOS 型号中,这一标准已经进一步转移到 GPU 方面。

于 2013-10-14T15:13:28.663 回答