GPUImageGaussianBlurFilter是高斯滤波,只分析下代码,原理不再讲述,类图如下:
从类图可看出,GPUImageGaussianBlurFilter继承自GPUImageTwoPassFilter,它内部会有两次OpenGL流水线图像处理,一次进行横向滤波,一次进行纵向滤波。
我们看下初始化代码:
- (id)init;
{
//生成顶点着色器代码,默认半径为4,sigma为2
NSString *currentGaussianBlurVertexShader = [[self class] vertexShaderForOptimizedBlurOfRadius:4 sigma:2.0];
//生成片元着色器代码,默认半径为4,sigma为2
NSString *currentGaussianBlurFragmentShader = [[self class] fragmentShaderForOptimizedBlurOfRadius:4 sigma:2.0];
//初始化两个program,第一个用于行滤波,第二个用于列滤波
return [self initWithFirstStageVertexShaderFromString:currentGaussianBlurVertexShader firstStageFragmentShaderFromString:currentGaussianBlurFragmentShader secondStageVertexShaderFromString:currentGaussianBlurVertexShader secondStageFragmentShaderFromString:currentGaussianBlurFragmentShader];
}
下面看下如何构建顶点着色器
+ (NSString *)vertexShaderForOptimizedBlurOfRadius:(NSUInteger)blurRadius sigma:(CGFloat)sigma;
{
if (blurRadius < 1)
{
return kGPUImageVertexShaderString;
}
// First, generate the normal Gaussian weights for a given sigma
//standardGaussianWeights为标准高斯核,当blurRadius为4,sigma为2时,
//standardGaussianWeights[0]=0.1995;[1]=0.1760;[2]=0.1210;[3]=0.0648;[4]=0.0270
GLfloat *standardGaussianWeights = calloc(blurRadius + 1, sizeof(GLfloat));
GLfloat sumOfWeights = 0.0;
for (NSUInteger currentGaussianWeightIndex = 0; currentGaussianWeightIndex < blurRadius + 1; currentGaussianWeightIndex++)
{
standardGaussianWeights[currentGaussianWeightIndex] = (1.0 / sqrt(2.0 * M_PI * pow(sigma, 2.0))) * exp(-pow(currentGaussianWeightIndex, 2.0) / (2.0 * pow(sigma, 2.0))); //标准正态分布函数
if (currentGaussianWeightIndex == 0)
{
sumOfWeights += standardGaussianWeights[currentGaussianWeightIndex];
}
else
{
sumOfWeights += 2.0 * standardGaussianWeights[currentGaussianWeightIndex];
}
}
// Next, normalize these weights to prevent the clipping of the Gaussian curve at the end of the discrete samples from reducing luminance
//规格化后,standardGaussianWeights[0]=0.2042; [1]=0.1802; [2]=0.1238; [3]=0.0663; [4]=0.0276
for (NSUInteger currentGaussianWeightIndex = 0; currentGaussianWeightIndex < blurRadius + 1; currentGaussianWeightIndex++)
{
standardGaussianWeights[currentGaussianWeightIndex] = standardGaussianWeights[currentGaussianWeightIndex] / sumOfWeights;
printf("standardGaussianWeights[%d]:%.4f. \n", currentGaussianWeightIndex, standardGaussianWeights[currentGaussianWeightIndex]);
}
// From these weights we calculate the offsets to read interpolated values from
//optimizedGaussianOffsets为“优化后”的离核中心的偏移值,优化原理是啥,还没搞懂
NSUInteger numberOfOptimizedOffsets = MIN(blurRadius / 2 + (blurRadius % 2), 7);
GLfloat *optimizedGaussianOffsets = calloc(numberOfOptimizedOffsets, sizeof(GLfloat));
for (NSUInteger currentOptimizedOffset = 0; currentOptimizedOffset < numberOfOptimizedOffsets; currentOptimizedOffset++)
{
GLfloat firstWeight = standardGaussianWeights[currentOptimizedOffset*2 + 1];
GLfloat secondWeight = standardGaussianWeights[currentOptimizedOffset*2 + 2];
GLfloat optimizedWeight = firstWeight + secondWeight;
optimizedGaussianOffsets[currentOptimizedOffset] = (firstWeight * (currentOptimizedOffset*2 + 1) + secondWeight * (currentOptimizedOffset*2 + 2)) / optimizedWeight;
printf("optimizedGaussianOffsets[%d]:%.4f. \n", currentOptimizedOffset, optimizedGaussianOffsets[currentOptimizedOffset]);
}
NSMutableString *shaderString = [[NSMutableString alloc] init];
// Header
[shaderString appendFormat:@"\
attribute vec4 position;\n\
attribute vec4 inputTextureCoordinate;\n\
\n\
uniform float texelWidthOffset;\n\
uniform float texelHeightOffset;\n\
\n\
varying vec2 blurCoordinates[%lu];\n\
\n\
void main()\n\
{\n\
gl_Position = position;\n\
\n\
vec2 singleStepOffset = vec2(texelWidthOffset, texelHeightOffset);\n", (unsigned long)(1 + (numberOfOptimizedOffsets * 2))];
// Inner offset loop
[shaderString appendString:@"blurCoordinates[0] = inputTextureCoordinate.xy;\n"];
for (NSUInteger currentOptimizedOffset = 0; currentOptimizedOffset < numberOfOptimizedOffsets; currentOptimizedOffset++)
{
[shaderString appendFormat:@"\
blurCoordinates[%lu] = inputTextureCoordinate.xy + singleStepOffset * %f;\n\
blurCoordinates[%lu] = inputTextureCoordinate.xy - singleStepOffset * %f;\n", (unsigned long)((currentOptimizedOffset * 2) + 1), optimizedGaussianOffsets[currentOptimizedOffset], (unsigned long)((currentOptimizedOffset * 2) + 2), optimizedGaussianOffsets[currentOptimizedOffset]];
}
// Footer
[shaderString appendString:@"}\n"];
free(optimizedGaussianOffsets);
free(standardGaussianWeights);
return shaderString;
}
生成片元着色器的实现:
+ (NSString *)fragmentShaderForOptimizedBlurOfRadius:(NSUInteger)blurRadius sigma:(CGFloat)sigma;
{
if (blurRadius < 1)
{
return kGPUImagePassthroughFragmentShaderString;
}
// First, generate the normal Gaussian weights for a given sigma
//standardGaussianWeights为标准高斯核,当blurRadius为4,sigma为2时,
//standardGaussianWeights[0]=0.1995;[1]=0.1760;[2]=0.1210;[3]=0.0648;[4]=0.0270
GLfloat *standardGaussianWeights = calloc(blurRadius + 1, sizeof(GLfloat));
GLfloat sumOfWeights = 0.0;
for (NSUInteger currentGaussianWeightIndex = 0; currentGaussianWeightIndex < blurRadius + 1; currentGaussianWeightIndex++)
{
standardGaussianWeights[currentGaussianWeightIndex] = (1.0 / sqrt(2.0 * M_PI * pow(sigma, 2.0))) * exp(-pow(currentGaussianWeightIndex, 2.0) / (2.0 * pow(sigma, 2.0))); //标准正态分布函数
if (currentGaussianWeightIndex == 0)
{
sumOfWeights += standardGaussianWeights[currentGaussianWeightIndex];
}
else
{
sumOfWeights += 2.0 * standardGaussianWeights[currentGaussianWeightIndex];
}
}
// Next, normalize these weights to prevent the clipping of the Gaussian curve at the end of the discrete samples from reducing luminance
//规格化后,standardGaussianWeights[0]=0.2042; [1]=0.1802; [2]=0.1238; [3]=0.0663; [4]=0.0276
for (NSUInteger currentGaussianWeightIndex = 0; currentGaussianWeightIndex < blurRadius + 1; currentGaussianWeightIndex++)
{
standardGaussianWeights[currentGaussianWeightIndex] = standardGaussianWeights[currentGaussianWeightIndex] / sumOfWeights;
printf("standardGaussianWeights[%d]:%.4f. \n", currentGaussianWeightIndex, standardGaussianWeights[currentGaussianWeightIndex]);
}
// From these weights we calculate the offsets to read interpolated values from
NSUInteger numberOfOptimizedOffsets = MIN(blurRadius / 2 + (blurRadius % 2), 7);
NSUInteger trueNumberOfOptimizedOffsets = blurRadius / 2 + (blurRadius % 2);
NSMutableString *shaderString = [[NSMutableString alloc] init];
// Header
#if TARGET_IPHONE_SIMULATOR || TARGET_OS_IPHONE
[shaderString appendFormat:@"\
uniform sampler2D inputImageTexture;\n\
uniform highp float texelWidthOffset;\n\
uniform highp float texelHeightOffset;\n\
\n\
varying highp vec2 blurCoordinates[%lu];\n\
\n\
void main()\n\
{\n\
lowp vec4 sum = vec4(0.0);\n", (unsigned long)(1 + (numberOfOptimizedOffsets * 2)) ];
#else
[shaderString appendFormat:@"\
uniform sampler2D inputImageTexture;\n\
uniform float texelWidthOffset;\n\
uniform float texelHeightOffset;\n\
\n\
varying vec2 blurCoordinates[%lu];\n\
\n\
void main()\n\
{\n\
vec4 sum = vec4(0.0);\n", 1 + (numberOfOptimizedOffsets * 2) ];
#endif
// Inner texture loop
[shaderString appendFormat:@"sum += texture2D(inputImageTexture, blurCoordinates[0]) * %f;\n", standardGaussianWeights[0]];
//optimizedWeight为优化后高斯核的权重,优化原理不太懂
for (NSUInteger currentBlurCoordinateIndex = 0; currentBlurCoordinateIndex < numberOfOptimizedOffsets; currentBlurCoordinateIndex++)
{
GLfloat firstWeight = standardGaussianWeights[currentBlurCoordinateIndex * 2 + 1];
GLfloat secondWeight = standardGaussianWeights[currentBlurCoordinateIndex * 2 + 2];
GLfloat optimizedWeight = firstWeight + secondWeight;
printf("optimizedWeight[%d]:%.4f. \n", currentBlurCoordinateIndex, optimizedWeight);
[shaderString appendFormat:@"sum += texture2D(inputImageTexture, blurCoordinates[%lu]) * %f;\n", (unsigned long)((currentBlurCoordinateIndex * 2) + 1), optimizedWeight];
[shaderString appendFormat:@"sum += texture2D(inputImageTexture, blurCoordinates[%lu]) * %f;\n", (unsigned long)((currentBlurCoordinateIndex * 2) + 2), optimizedWeight];
}
// If the number of required samples exceeds the amount we can pass in via varyings, we have to do dependent texture reads in the fragment shader
if (trueNumberOfOptimizedOffsets > numberOfOptimizedOffsets)
{
#if TARGET_IPHONE_SIMULATOR || TARGET_OS_IPHONE
[shaderString appendString:@"highp vec2 singleStepOffset = vec2(texelWidthOffset, texelHeightOffset);\n"];
#else
[shaderString appendString:@"vec2 singleStepOffset = vec2(texelWidthOffset, texelHeightOffset);\n"];
#endif
for (NSUInteger currentOverlowTextureRead = numberOfOptimizedOffsets; currentOverlowTextureRead < trueNumberOfOptimizedOffsets; currentOverlowTextureRead++)
{
GLfloat firstWeight = standardGaussianWeights[currentOverlowTextureRead * 2 + 1];
GLfloat secondWeight = standardGaussianWeights[currentOverlowTextureRead * 2 + 2];
GLfloat optimizedWeight = firstWeight + secondWeight;
GLfloat optimizedOffset = (firstWeight * (currentOverlowTextureRead * 2 + 1) + secondWeight * (currentOverlowTextureRead * 2 + 2)) / optimizedWeight;
[shaderString appendFormat:@"sum += texture2D(inputImageTexture, blurCoordinates[0] + singleStepOffset * %f) * %f;\n", optimizedOffset, optimizedWeight];
[shaderString appendFormat:@"sum += texture2D(inputImageTexture, blurCoordinates[0] - singleStepOffset * %f) * %f;\n", optimizedOffset, optimizedWeight];
}
}
// Footer
[shaderString appendString:@"\
gl_FragColor = sum;\n\
}\n"];
free(standardGaussianWeights);
return shaderString;
}
这里面没有用标准的高斯核,而是用了一个“优化过”的核,还不太明白是为什么,当blurRadius为4,sigma为2时,最终生成的核如下:
生成的顶点着色器代码为:
currentGaussianBlurVertexShader:
attribute vec4 position;
attribute vec4 inputTextureCoordinate;
uniform float texelWidthOffset;
uniform float texelHeightOffset;
varying vec2 blurCoordinates[5];
void main()
{
gl_Position = position;
//第一个编译器,进行的是行滤波,因此texelWidthOffset=1/width,texelHeightOffset=0
//对于第二个编译器,进行的是列滤波,因此texelWidthOffset=0,texelHeightOffset=1/height
//(width, height是原图像的宽和高)
vec2 singleStepOffset = vec2(texelWidthOffset, texelHeightOffset);
blurCoordinates[0] = inputTextureCoordinate.xy;
blurCoordinates[1] = inputTextureCoordinate.xy + singleStepOffset * 1.407333;
blurCoordinates[2] = inputTextureCoordinate.xy - singleStepOffset * 1.407333;
blurCoordinates[3] = inputTextureCoordinate.xy + singleStepOffset * 3.294215;
blurCoordinates[4] = inputTextureCoordinate.xy - singleStepOffset * 3.294215;
}
片元着色器代码为:
currentGaussianBlurFragmentShader:
uniform sampler2D inputImageTexture;
uniform highp float texelWidthOffset;
uniform highp float texelHeightOffset;
varying highp vec2 blurCoordinates[5];
void main()
{
lowp vec4 sum = vec4(0.0);
//加权平均
sum += texture2D(inputImageTexture, blurCoordinates[0]) * 0.204164;
sum += texture2D(inputImageTexture, blurCoordinates[1]) * 0.304005;
sum += texture2D(inputImageTexture, blurCoordinates[2]) * 0.304005;
sum += texture2D(inputImageTexture, blurCoordinates[3]) * 0.093913;
sum += texture2D(inputImageTexture, blurCoordinates[4]) * 0.093913;
gl_FragColor = sum;
}
前面说过,GPUImageGaussianBlurFilter是继承自GPUImageTwoPassFilter,也就是说内部是两个filter串联,第一个对图像进行了行滤波,第二个filter再进行列滤波。通过两个一维滤波器实现二维图像的滤波。简略画了个图:
网友评论