您可以使用 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 方面。