zoukankan      html  css  js  c++  java
  • 转载:又一篇高斯模糊算法,有对tap的解释

    From: http://blog.sina.com.cn/s/blog_4b97ab670100aa3a.html

    - 理论 -


    高斯分布函数可表示为一个一维的函数G(x)
    今天来说说高斯模糊

    或者一个二维的函数G(x,y)
    今天来说说高斯模糊

    在这些函数中, X和Y代表了相对于原始中心点(center tap)像素的偏移(pixel offsets)值。也就是说,他们距离中心多少像素。这里的center tap,通常翻译为“中心抽头”,它在电学中的概念是:在整个次级线圈的中心拉出的一段导线上,它相对于另外两边的抽头电压居中,而为0,两边的电压就是一正一负。在这里,我们也可以做相似的理解。即,它表示,以某个像素为中心进行取样,假设它的坐标为(x,y),那么周围四个点的坐标就是(x-1,y)(x+1,y)(x,y-1)(x,y+1)。对于整个分布来说,我们也可以用一个平均参数来实施位移,从而取代center tap的作用。但这样的模糊不符合我们的要求,我们希望我们的分布是基于中心的。

    函数中的σ,即“西格玛”。在统计学中,它常用来表示“标准差”。高斯模糊的标准差,表示模糊的延伸距离。它的缺省值一般设为1,你可以提高它,来获得更强的效果。

    函数中的e是欧拉数(Euler's number),它的值一般取2.7。你可以到http://blog.sina.com.cn/s/blog_54ab9583010007u5.html看看精确到小数点后10000位的e。

    这个函数的结果,就是以x(在一个方向上),或者(x,y)(在两个方向上),为中心的加权(weight)值,或者说该点在多大程度上影响模糊后的像素。

    那么,它能获得什么样的结果呢?当σ 值取1时,我们可以得到:
    G(0) = 0.3989422804
    G(1) = G(-1) = 0.2419707245
    G(2) = G(-2) = 0.0539909665
    G(3) = G(-3) = 0.0044318484
    ......

    这些结果有什么意义呢?
    当x = 0时,表示这一点就是原始中心点(center tap), 这个点需要保留0.39(39%)的颜色
    当x = 1以及x = -1时, 即有一个像素的位移时, 这一点需要保留0.24的颜色值
    当x = 2以及x = -2时, 即有两个像素的位移时, 这一点需要保留0.05的颜色值
    ......

    我们可以进行任意次这样的计算。 采样越高,精度越高。

    相信现在你已经大致了解高斯模糊的原理了。



    - 实际应用 -


    当然,理论只是基于理想环境下的计算。在一次Bloom shader的测试中,我发现了两个问题:

    1、模糊后的画面,其发光度和亮度大都比原始图像要低,画面显得很暗
    2、上述的采样点(tap)的标准差是随意的吗?或者说,它有什么要求(或限制)?

    以下各段,我将以一维高斯分布为例

    首先解决明度问题。假设你有一个全白的单通道采样贴图,那么它所有的像素值都为1。经过第一次(水平方向)的高斯模糊运算之后,每个像素的值的总和,是所有高斯采样的加权总合。对于一个5 × 5的分布,标准差为1时,有:

    ΣHBlur = G(0) + G(-1) + G(1) + G(-2) + G(2)

    ΣHBlur = 0.9908656625

    看起来似乎是正确的,其实不然。为了使明度不变,这个结果必须绝对是1。

    基于此结果,在第二次运算后,它的值进一步下降为:ΣHVBlur = 0.9818147611

    这就是说,我们必须增强加权值,通过确定的一个商,以取得正确的结果。答案非常简单,缺少了这个商,我们当然不能得到1:ΣHBlur^-1.

    AugFactor = 1 / ΣHBlur
    AugFactor = 1.009218543

    如果我们再用这个增量,乘以该点的加权以及两个方向(水平、垂直)的运算,ΣHVBlur的值就是1了!



    其次是标准差的问题。我使用的解决方案并不太“官方”,但是它确实有效。我们假设一个5 × 5的模糊必须有一个其值为1的标准差。现在我们知道,在一个5 × 5的分布中,距离中心最远的采样点,其加权值最小。其它尺寸的采样分布(比如7× 7),计算出各点的加权值也有这一规律(函数已经有了,你可以简单证明一下)。

    要做的是,只调整标准差,以获取最后的采样点加权值,该值越接近这一结果越好。

    σ = 1 -> G(2)aug = 0.054488685
    σ = 1.745 -> G(3)aug = 0.054441945
    σ = 2.7 -> G(4)aug = 0.054409483

    这些都是我反复调试出的最接近的结果。所有这些权重增加,他们的和总是1。因此,寻找最佳标准差,最主要的是:尽可能多提取出模糊的采样数,而不产生偏差。



    - 一维复合运算 | 二维单一运算 -



    为什么会有一维和二维这两个函数呢?因为我们有两种选择:

    1、使用一维函数的两次;一次横向,然后(以横向模糊的结果)再做一次垂直运算 ;
    2、用二维函数,只需要一步就能完成。

    你也许会问,既然有了二维函数,用它不就行了,何必要用一维函数进行两次计算,那多麻烦呀!我的第一反应是,这也许是因为早期Shader模型施加的一些限制。在做了更多的研究之后,我发现有一个更好的解释,来说明为什么要分开运算。

    假设n是模糊的线性尺寸(或者说在水平/垂直方向的采样点数量),而p是整个画面的像素总数,那么:

    在一维函数中,你要做2np次纹理采样
    在一维函数中,你要做n²p次纹理采样

    这意味着,如果你对一个512×512尺寸的图像做7×7采样的模糊,一位维函数需要做367万次搜索,而如果用二维函数,那么要完成这幅图像需要1284万次,是前者的3.5倍!

    把一个二维坐标的点分解为两个一维的线性矢量是完全可能的,因为高斯模糊是一个可分割的卷积运算。具体的内容你可以到这里看看:http://nuttybar.drama.uga.edu/pipermail/dirgames-l/2002-December/020874.html

    所以,为了追求更快的速度、更高的效率,我宁可放弃二维函数。


    - 执行!-


    在hlsl中,执行一维高斯模糊最有效的方式是:

    1、在顶点着色器(Vertex Shader)中,预先为不同的采样点计算出纹理的坐标偏移(coordinate offsets)。每个坐标占据一个float2 ,所以我能够用尺寸为4的float4阵列来实现9taps(x/y用一个坐标,z/w用另一个),而原始中心点在一个单独的float2里。
    因为所有像素采样点的总和是奇数,所以最好的方法就是,让中心点作为一个单独的坐标,然后两个数组为正负两个方向采样点。
    使用的“ W ”坐标的免费数据,在ps_1_4及更早的版本中是不允许的,但无论如何,我已经放弃了这些旧版本的Shader。

    2、在像素着色器(Pixel Shader)中,使用矩阵的累积样本。如果需要,可以有一个矩阵为正向采样,另一个负向采样,虽然这在5 × 5的情况下是没有必要的。这些矩阵有i行和j列,假设i表示我们正在累加的采样数目,而j是目前我们正在处理的通道数目(通常是4:R,G,B,A,即红、绿、蓝和透明通道)。一旦获取了采样,矩阵乘法之间的权向量和采样矩阵将给予加权平均…就这么简单!使用一个或两个矩阵,经过运算,得到一个或两个向量,这时需要把它们加在一起,然后添加到已经加权的原始中心点。



    希望你没被我弄晕。

    还有不清楚的自己研究代码吧,一窍不通的话也没关系,我把这个Shader放出来,只管用就行了

    Shader实例如下,希望它对你有帮助:

    // -------------------------------------------------------------
    // Those settings are (and must be) set by the host program
    // -------------------------------------------------------------
    //#define TAPS 5
    //#define TAPS 7
    //#define TAPS 9

    // -------------------------------------------------------------
    // Texture & Sampler
    // -------------------------------------------------------------
    texture texTexture : TEXTURE0;
    sampler sampTexture = sampler_state {
        Texture = (texTexture);
        AddressU = Clamp;
        AddressV = Clamp;
    };

    // -------------------------------------------------------------
    // Parameters
    // -------------------------------------------------------------
    const float centerTapWeight;
    const float2 texelSize;
    #if TAPS == 5
        const float tapOffsets[4] = {-2, -1, 1, 2};
        const float4 tapWeights;
    #endif   
    #if TAPS == 7
        const float tapOffsets[3] = {1, 2, 3};
        const float3 tapWeights;
    #endif
    #if TAPS == 9
        const float tapOffsets[4] = {1, 2, 3, 4};
        const float4 tapWeights;
    #endif

    // -------------------------------------------------------------
    // Input/Output channels
    // -------------------------------------------------------------
    struct VS_INPUT
    {
        float4 position : POSITION;
        float2 texCoord : TEXCOORD0;
    };
    struct VS_OUTPUT
    {
        float4 position : POSITION;
        float2 centerTap : TEXCOORD0;   
        #if TAPS == 5
            float2 taps[4] : TEXCOORD1;
        #endif
        #if TAPS == 7
            float2 positiveTaps[3] : TEXCOORD1;
            float2 negativeTaps[3] : TEXCOORD4;       
        #endif
        #if TAPS == 9
            // Doesn't fit in float2 format!
            float4 positiveTaps[2] : TEXCOORD1;
            float4 negativeTaps[2] : TEXCOORD3;
        #endif       
    };
    #define PS_INPUT VS_OUTPUT

    // -------------------------------------------------------------
    // Vertex Shader
    // -------------------------------------------------------------
    VS_OUTPUT VS(const VS_INPUT IN, uniform float2 DIRECTION)
    {
        VS_OUTPUT OUT;
        OUT.position = IN.position;
        OUT.centerTap = IN.texCoord;   
       
        #if TAPS == 5
            for(int i=0; i<4; i++)
                OUT.taps[i] = IN.texCoord + tapOffsets[i] * DIRECTION * texelSize;
        #endif
        #if TAPS == 7
            for(int i=0; i<3; i++)
            {
                OUT.positiveTaps[i] = IN.texCoord + tapOffsets[i] * DIRECTION * texelSize;
                OUT.negativeTaps[i] = IN.texCoord - tapOffsets[i] * DIRECTION * texelSize;
            }       
        #endif       
        #if TAPS == 9
            for(int i=0; i<2; i++)
            {
                OUT.positiveTaps[i].xy = IN.texCoord + tapOffsets[i*2] * DIRECTION * texelSize;
                OUT.negativeTaps[i].xy = IN.texCoord - tapOffsets[i*2] * DIRECTION * texelSize;
                OUT.positiveTaps[i].zw = IN.texCoord + tapOffsets[i*2+1] * DIRECTION * texelSize;
                OUT.negativeTaps[i].zw = IN.texCoord - tapOffsets[i*2+1] * DIRECTION * texelSize;           
            }       
        #endif
       
        return OUT;
    }

    // -------------------------------------------------------------
    // Pixel Shader function
    // -------------------------------------------------------------
    float4 PS(PS_INPUT IN) : COLOR
    {
        #if TAPS == 5
            float4x4 samples;
        #endif   
        #if TAPS == 7
            float3x4 positiveSamples;
            float3x4 negativeSamples;
        #endif
        #if TAPS == 9
            float4x4 positiveSamples;
            float4x4 negativeSamples;
        #endif
       
        float4 color = tex2D(sampTexture, IN.centerTap) * centerTapWeight;

        #if TAPS == 5
            for(int i=0; i<4; i++)
                samples[i] = tex2D(sampTexture, IN.taps[i]);
        #endif
        #if TAPS == 7
            for(int i=0; i<3; i++)
            {
                positiveSamples[i] = tex2D(sampTexture, IN.positiveTaps[i]);
                negativeSamples[i] = tex2D(sampTexture, IN.negativeTaps[i]);
            }
        #endif
        #if TAPS == 9
            for(int i=0; i<2; i++)
            {
                positiveSamples[i*2] = tex2D(sampTexture, IN.positiveTaps[i].xy);
                negativeSamples[i*2] = tex2D(sampTexture, IN.negativeTaps[i].xy);
                positiveSamples[i*2+1] = tex2D(sampTexture, IN.positiveTaps[i].zw);
                negativeSamples[i*2+1] = tex2D(sampTexture, IN.negativeTaps[i].zw);       
            }
        #endif
       
        #if TAPS == 5
            color += mul(tapWeights, samples);
        #else
            color += mul(tapWeights, positiveSamples) + mul(tapWeights, negativeSamples);   
        #endif
       
        return color;
    }

    // -------------------------------------------------------------
    // Techniques
    // -------------------------------------------------------------
    technique TSM3
    {
        pass HBlur
        {
            VertexShader = compile vs_3_0 VS(float2(1, 0));
            PixelShader = compile ps_3_0 PS();
        }
        pass VBlur
        {
            VertexShader = compile vs_3_0 VS(float2(0, 1));
            PixelShader = compile ps_3_0 PS();
        }       
    }
    technique TSM2a
    {
        pass HBlur
        {
            VertexShader = compile vs_2_0 VS(float2(1, 0));
            PixelShader = compile ps_2_a PS();
        }
        pass VBlur
        {
            VertexShader = compile vs_2_0 VS(float2(0, 1));
            PixelShader = compile ps_2_a PS();
        }       
    }
    technique TSM2b
    {
        pass HBlur
        {
            VertexShader = compile vs_2_0 VS(float2(1, 0));
            PixelShader = compile ps_2_b PS();
        }
        pass VBlur
        {
            VertexShader = compile vs_2_0 VS(float2(0, 1));
            PixelShader = compile ps_2_b PS();
        }       
    }
    technique TSM2
    {
        pass HBlur
        {
            VertexShader = compile vs_2_0 VS(float2(1, 0));
            PixelShader = compile ps_2_0 PS();
        }
        pass VBlur
        {
            VertexShader = compile vs_2_0 VS(float2(0, 1));
            PixelShader = compile ps_2_0 PS();
        }       
    }

  • 相关阅读:
    675 对象的引用-浅拷贝-深拷贝
    674 vue3侦听器watch
    673 vue计算属性:缓存,setter和getter
    明明有了promise,为啥还需要async await?
    Js常用数组方法汇总
    一些非常有用的Js单行代码
    Js获取验证码倒计时
    前端截取字符串:JS截取字符串之substring、substr和slice详解
    javascript全局变量与局部变量
    JS实现快速排序算法
  • 原文地址:https://www.cnblogs.com/super119/p/2805332.html
Copyright © 2011-2022 走看看