[OpenGL]未来视觉7-Android的高斯模糊方案

这段时间工作真心比较忙,而空余时间,都在在研究补全一些opencv技术的基础,然后近来有个业务和图像有关,背景高斯模糊的方案了。
可知Android可以使用高斯模糊的方案也真的不少,但是最终还是要看效果和效率的。
至于高斯模糊有什么作用呢?
模糊阴影,其原理也是使用高斯模糊的。
在美颜磨皮这方面,使用高斯模糊是一个很好的方向。

要学习高斯模糊基础还是要先知道底层原理,其原理是基于数学的正态分布,越接近中心,权重就越大。


正态分布.png

正态分布是一维的

如何反映出正态分布?则需要使用高斯函数来实现。
上面的正态分布是一维的,而对于图像都是二维的,所以我们需要二维的正态分布。

高斯函数

正态分布的密度函数叫做"高斯函数"(Gaussian function)。它的一维形式是:

高斯函数

其中,μ是x的均值,σ是x的方差。因为计算平均值的时候,中心点就是原点,所以μ等于0。

一维高斯函数

根据一维高斯函数,可以推导得到二维高斯函数:

二维高斯函数

3*3的矩阵


3*3矩阵单位.png

为了计算权重矩阵,需要设定σ值。现假定σ=1.5,则模糊半径为1的权重矩阵如下:


矩阵权重计算

这9个点的权重总和等于0.4787147,如果只计算这9个点的加权平均,还必须让它们的权重之和等于1,因此上面9个值还要分别除以0.4787147,得到最终的权重矩阵。

目的是让滤镜的权重总值等于1。否则的话,使用总值大于1的滤镜会让图像偏亮,小于1的滤镜会让图像偏暗。

image

有了权重矩阵,就可以计算高斯模糊的值了。
假设现有9个像素点,灰度值(0-255)如下:

image

每个点乘以自己的权重值:

image

得到

image

将这9个值加起来,就是中心点的高斯模糊的值。
对所有点重复这个过程,就得到了高斯模糊后的图像。

对于彩色图片来说,则需要对RGB三个通道分别做高斯模糊。

如果到达边界的时候,还需要给边界侧补0处理


边界处理.png

当图像处理的时候,高斯滤波会从左到右、从上到下的处理各个点的色值合成。

一.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);
}

高斯模糊的应用和一些计算就分析到这里,五种高斯模糊的计算和分析,希望对大家有帮助。


Opengl
Android组件化