美文网首页
GPUImage源码分析(六):GPUImageGaussian

GPUImage源码分析(六):GPUImageGaussian

作者: 奔向火星005 | 来源:发表于2019-01-08 22:33 被阅读0次

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


    相关文章

      网友评论

          本文标题:GPUImage源码分析(六):GPUImageGaussian

          本文链接:https://www.haomeiwen.com/subject/cgiurqtx.html