[OpenGL]未來視覺7-Android的高斯模糊方案

這段時間工作真心比較忙,而空餘時間,都在在研究補全一些opencv技術的基礎,然後近來有個業務和圖像有關,背景高斯模糊的方案了。
可知Android可以使用高斯模糊的方案也真的不少,但是最終還是要看效果和效率的。
至於高斯模糊有什麼作用呢?
模糊陰影,其原理也是使用高斯模糊的。
在美顏磨皮這方面,使用高斯模糊是一個很好的方向。

要學習高斯模糊基礎還是要先知道底層原理,其原理是基於數學的正態分佈,越接近中心,權重就越大。



正態分佈是一維的

如何反映出正態分佈?則需要使用高斯函數來實現。
上面的正態分佈是一維的,而對於圖像都是二維的,所以我們需要二維的正態分佈。

正態分佈的密度函數叫做"高斯函數"(Gaussian function)。它的一維形式是:

其中,μ是x的均值,σ是x的方差。因爲計算平均值的時候,中心點就是原點,所以μ等於0。

根據一維高斯函數,可以推導得到二維高斯函數:

3*3的矩陣


爲了計算權重矩陣,需要設定σ值。現假定σ=1.5,則模糊半徑爲1的權重矩陣如下:


這9個點的權重總和等於0.4787147,如果只計算這9個點的加權平均,還必須讓它們的權重之和等於1,因此上面9個值還要分別除以0.4787147,得到最終的權重矩陣。

目的是讓濾鏡的權重總值等於1。否則的話,使用總值大於1的濾鏡會讓圖像偏亮,小於1的濾鏡會讓圖像偏暗。

有了權重矩陣,就可以計算高斯模糊的值了。
假設現有9個像素點,灰度值(0-255)如下:

每個點乘以自己的權重值:

得到

將這9個值加起來,就是中心點的高斯模糊的值。
對所有點重複這個過程,就得到了高斯模糊後的圖像。

對於彩色圖片來說,則需要對RGB三個通道分別做高斯模糊。

如果到達邊界的時候,還需要給邊界側補0處理


當圖像處理的時候,高斯濾波會從左到右、從上到下的處理各個點的色值合成。

一.Glide

Glide可以使用一個擴展庫glide-transformations,就是自己寫Transform,然後在transform中添加高斯模糊的計算。具體代碼如下
FastBlur.java
裏面變換的規則有點複雜,因爲圖像變化是基於java上層來完成,效率和速度是最低的。

二.Genius-blur

Genius-blur
這是使用jni的方案,大小隻有20k左右。
內部blur_ARGB_8888的實現方法和Glide的FastBlur的算法方式是一樣的,但是因爲使用C++底層實現,速度肯定要快上很多。

三.RenderScript

使用RenderScript框架來加載渲染高斯模糊的渲染腳本,基礎還是會使用底層的渲染Rs渲染庫。
https://github.com/CameraKit/blurkit-android
兼容問題
官方說明
只能api17以上才能使用,如果低版本只能使用v8版本的。網上有簡單的在build.gradle的defaultConifg添加
renderscriptTargetApi 26
renderscriptSupportModeEnabled true
添加v8的renderscript兼容包,容量加大一百多k。
但是我在AS3.3無法使用這種方法編譯成功,提示我無法找到RenderScript的類,結果我只能手動引入了Sdk\build-tools\27.0.3\renderscript\lib\packaged裏面的so和jar包,才能編譯通過,但是這樣打出來的aar包2.2M,只是一個模糊兼容就增大這麼大的容量,不怎麼友好啊。
radius的範圍是0~25。值越大越模糊。

public Bitmap blur(Bitmap src, int radius) {
            final Allocation input = Allocation.createFromBitmap(rs, src);
            final Allocation output = Allocation.createTyped(rs, input.getType());
            final ScriptIntrinsicBlur script = ScriptIntrinsicBlur.create(rs, Element.U8_4(rs));
            script.setRadius(radius);
            script.setInput(input);
            script.forEach(output);
            output.copyTo(src);
       
        return src;
    }

1.最終我採取了17以上使用renderScipt,以下的使用Genius-blur這個庫,打包出來的aar大概是20k左右,足夠兼容簡單的高斯模糊要求。

2.正常的高斯模糊,是不包含色值,有些需要會要求帶有特定色值的高斯模糊,這時候可以使用ImageView把高斯模糊的圖設置爲背景,然後把帶有色值的透明圖來設置到imageView的src圖。這樣看起來就會帶有色值的高斯模糊了。

3.如果做到高斯模糊漸變消息,其實這裏是使用障眼法,設置Bitmap的alpha值來達到漸變消失的。

四.使用Opencv來處理高斯圖像

如果你框架中已經加入了Opencv的sdk,使用這種方法也是非常高效的。
Mat代表Opencv中的圖像數據
Imgproc是Opencv的工具類
Imgproc.GaussianBlur是Opencv的高斯模糊處理


public class GaussianBlur {
    public static void main(String[] args) {
        try{
            System.loadLibrary(Core.NATIVE_LIBRARY_NAME);
            
            Mat src=Imgcodecs.imread("本地圖片地址");
            //讀取圖像到矩陣中
            if(src.empty()){
                throw new Exception("no file");
            }
            
            Mat dst = src.clone();
            //複製矩陣進入dst
            
            Imgproc.GaussianBlur(src,dst,new Size(13,13),10,10);
            //圖像模糊化處理11
            Imgcodecs.imwrite("寫入地址", dst);
            
            Imgproc.GaussianBlur(src,dst,new Size(31,5),80,3);
            //圖像模糊化處理33
            Imgcodecs.imwrite("寫入地址", dst);
        }catch(Exception e){
            System.out.println("例外:" + e);
        }
    }
}

五.OpenGL

之前使用Opengl,來採集圖像用於實時的美顏方案的。
使用高斯模糊是可以給人臉皮膚模糊,做到去磨皮等效果的。
可以看到RenderScript最高支持25範圍的卷積核來做模糊,卷積核越大越模糊,計算量越大,速度越慢。計算太大就會造成卡頓,卷積核太小,模糊效果不夠明細那。但是爲了效率問題,Opengl這邊只使用了統一幾組採集點,就是在一定範圍內只採集一些對應的點,例如周圍點附近幾組菱形頂點作爲採集點。然後使用這種採集來做配置。

    /**模糊取值紋理座標**/
    blurCoordinates[0] = inputTextureCoordinate.xy + singleStepOffset * vec2(0.0, -10.0);
   ....
    blurCoordinates[19] = inputTextureCoordinate.xy + singleStepOffset * vec2(4.0, -4.0);
    
    float sampleColor = centralColor.g * 20.0;
    sampleColor += texture2D(inputImageTexture, blurCoordinates[0]).g;
    …
    /** 不同權重**/
    sampleColor += texture2D(inputImageTexture, blurCoordinates[19]).g * 2.0;

    /**最終模糊均值**/
    sampleColor = sampleColor / 48.0;
    /** .用原圖綠色通道值減去sampleColor,加上0.5,整個步驟是PS中的高保留反差**/
    float highPass = centralColor.g - sampleColor + 0.5;

人臉是偏紅色,所以紅色通道的色值比較大,而人臉細節方面是保留在綠色通道上比較多。
如果需要美白,需要使用強光處理

/**5次強光處理**/
for(int i = 0; i < 5;i++)
    {
        highPass = hardLight(highPass);
    }

float hardLight(float color)
{
    if(color <= 0.5)
        color = color * color * 2.0;
    else
        color = 1.0 - ((1.0 - color)*(1.0 - color) * 2.0);
    return color;
}

灰度圖生成,公式爲0.299R + 0.587G + 0.114*B

const highp vec3 W = vec3(0.299,0.587,0.114);
float luminance = dot(centralColor, W);

    float alpha = pow(luminance, params);

將灰度值作爲閾值,用來排除非皮膚部分

    /** pow(x,y)是x的y次方**/
    float alpha = pow(luminance, params);
    /** 原圖rgb值與高反差後的結果相比,噪聲越大,兩者相減後的結果越大,在原結果基礎上加上一定值,來提高亮度,消除噪聲。
pow函數中第二個參數可調(1/3~1),值越小,alpha越大,磨皮效果越明顯,修改該值可作爲美顏程度**/
    vec3 smoothColor = centralColor + (centralColor-vec3(highPass))*alpha*0.1;

以灰度值作爲透明度將原圖與混合後結果進行濾色、柔光等混合,並調節飽和度

gl_FragColor = vec4(mix(smoothColor.rgb, max(smoothColor, centralColor), alpha), 1.0);

但是因爲圖片比例問題,高斯模糊的方式是沒問題的,但是採集點的影響需要根據寬高比例調整
Cain_Huang對人臉磨皮的高斯模糊調整Android OpenGLES 實時美顏(磨皮)的優化

他在磨皮優化二的章節,提供了更高效的高斯模糊計算方式。
在頂點着色器,預先計算出周邊點保存到數組,然後傳遞到片段着色器

uniform mat4 uMVPMatrix;
attribute vec4 aPosition;
attribute vec4 aTextureCoord;

// 高斯算子左右偏移值,當偏移值爲5時,高斯算子爲 11 x 11
const int SHIFT_SIZE = 5;

uniform highp float texelWidthOffset;
uniform highp float texelHeightOffset;

varying vec2 textureCoordinate;
varying vec4 blurShiftCoordinates[SHIFT_SIZE];

void main() {
    gl_Position = uMVPMatrix * aPosition;
    textureCoordinate = aTextureCoord.xy;
    // 偏移步距
    vec2 singleStepOffset = vec2(texelWidthOffset, texelHeightOffset);
    // 記錄偏移座標
    for (int i = 0; i < SHIFT_SIZE; i++) {
        blurShiftCoordinates[i] = vec4(textureCoordinate.xy - float(i + 1) * singleStepOffset,
                                       textureCoordinate.xy + float(i + 1) * singleStepOffset);
    }
}

片段着色器,使用一個for循環來取得偏移座標的色值總和,然後計算平均值。這裏沒有使用高斯核權重,所以並不算標準被高斯模糊計算。

precision mediump float;
varying vec2 textureCoordinate;
uniform sampler2D inputTexture;
const int SHIFT_SIZE = 5; // 高斯算子左右偏移值
varying vec4 blurShiftCoordinates[SHIFT_SIZE];
void main() {
    // 計算當前座標的顏色值
    vec4 currentColor = texture2D(inputTexture, textureCoordinate);
    mediump vec3 sum = currentColor.rgb;
    // 計算偏移座標的顏色值總和
    for (int i = 0; i < SHIFT_SIZE; i++) {
        sum += texture2D(inputTexture, blurShiftCoordinates[i].xy).rgb;
        sum += texture2D(inputTexture, blurShiftCoordinates[i].zw).rgb;
    }
    // 求出平均值
    gl_FragColor = vec4(sum * 1.0 / float(2 * SHIFT_SIZE + 1), currentColor.a);
}

高斯模糊的應用和一些計算就分析到這裏,五種高斯模糊的計算和分析,希望對大家有幫助。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章