GPUImage源码分析(六):GPUImageGaussian

2019-01-08  本文已影响0人  奔向火星005

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再进行列滤波。通过两个一维滤波器实现二维图像的滤波。简略画了个图:


上一篇 下一篇

猜你喜欢

热点阅读