【问题标题】:Faster convolution on iosios上更快的卷积
【发布时间】:2013-10-21 16:24:03
【问题描述】:

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

【问题讨论】:

    标签: ios opencv gpu gpuimage convolution


    【解决方案1】:

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

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

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

    您可以在 this book 中找到有关 iOS 图像处理优化的更多见解,我也查看了 here

    【讨论】:

      【解决方案2】:

      您可以使用 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 中在 CPU 上执行此类操作的速度比在 OpenGL ES 中更快的交叉点会因您运行的设备而异。一般来说,iOS 设备上的 GPU 在最近几代的性能增长方面都超过了 CPU,因此在过去的几款 iOS 机型中,这一标准已经转移到 GPU 方面。

      【讨论】:

        猜你喜欢
        • 2019-12-01
        • 1970-01-01
        • 2011-08-27
        • 2015-05-08
        • 2021-04-05
        • 1970-01-01
        • 1970-01-01
        • 2013-12-31
        相关资源
        最近更新 更多