Android端图片模糊的实现原理及方案

未来已来2018-09-14 13:51

作者:牛栋凯


前言

图片模糊是Android客户端开发中一种比较常见的特效,诸如对话框背景半透明效果,头像背景模糊效果都是通过图片模糊技术实现的。本文主要介绍图片模糊的实现原理及实现方案。


图片模糊原理

卷积

卷积(Convolution)是图像处理中最基本的操作,就是一个二维原图像素矩阵A(MxN)和一个二维图像滤波矩阵B(mxn)做若干操作,生成一个滤波后的新像素矩阵C(MxN),其中m和n远小于M和N,B称为卷积核(kernel),又称滤波器矩阵。

这里举个卷积的例子,如图:

上图中,最左边的是源矩阵(8x8),中间是卷积核(3x3,半径为1),最右边是通过对前面两个矩阵做卷积生成的结果矩阵。图中,如果我们要求出结果矩阵中第二行第二列的元素的值,则把卷积核的中心元素(值为0)和源矩阵的第二行第二列(值为6)对齐,然后求加权和,即图中的公式,最后得到-3。

对图像边界像素的操作应特别注意,由于周边没有足够的点,通常有三种的处理方法:1)对称处理:就是把已有的点拷贝到另一面的对应位置,模拟出完整的矩阵;2)赋0:想象图像是无限长的图像的一部分,除了我们给定值的部分,其他部分的像素值都是0;3)赋边界值:想象图像是无限制长,但是默认赋值的不是0而是对应边界点的值。

一般认为图像是连续的数据,所以一般用图像边界的值进行拓展,计算边界的像素值。计算示例如下:

值得注意的是,通常来说卷积核需要满足如下条件:

  • 宽和高都为奇数,这样才会有半径和中心的概念。
  • 元素总和为1。


滤波器

均值滤波器

均值滤波器(Mean Filter)是最简单的一种滤波器,它是最粗糙的一种模糊图像的方法。均值滤波器的卷积核通常是m*m的矩阵,其中每个元素为1/(m^2),可以看出卷积核的元素总和为1。比如3x3的均值滤波器,卷积核的每个元素就是1/9。如下图所示:


高斯滤波器

高斯滤波器是均值滤波器的高级版本,唯一的区别在于,均值滤波器的卷积核的每个元素都相同,而高斯滤波器的卷积核的元素服从高斯分布。这样的话越在模糊半径外围的像素权重越低,造成的影响就越小,越在内侧的像素得到的权重最高,因为内侧像素更加重要,他们的颜色应该与我们要处理的中心像素更接近,更密切。

下图一个一维高斯分布的图,我们都知道,取μ越近的值概率大,而取离μ越远的值的概率越小;σ越小,分布越集中在μ附近,σ越大,分布越分散。从图中可以看出,在3σ的时候只有0.1%的权重。


然而我们需要处理的图像是二维数组,当选择一个中心像素时,我们需要平均该中心周围所有的像素来得到模糊值,而不仅仅是左边和右边的像素。所以我们需要用到二维高斯分布,其公式和分布图如下:


在上面的公式中x和y表示周边像素对于中心像素的相对坐标,σ控制曲线的平缓程度,值越大,越平缓,最高点越低。当x=0且y=0时值最大,即卷积核的中心点权重最大。一般经验,卷积核的半径定为3σ。


图片模糊的实现方案

Android业务开发过程中,实现图片模糊的方案大致有以下几种:

  • 服务端实现:图片链接中拼接参数;
  • Android实现:使用RenderScript;
  • Java实现:使用FastBlur;
  • JNI实现:使用Andorid-stackblur;
  • OpenGL实现:OpenGL绘制;

接下来对上面集中方案作介绍。


图片链接中拼接参数

现在很多的业务服务器都支持在图片的URL后面增加一些参数,以获取特定的效果。如参照NOS富媒体使用手册中的约定,根据原图获取高斯模糊图的方法如下:

接口及参数

GET /{ObjectKey}?imageView&blur={radius}x${sigma} HTTP/1.1


名称 描述 取值范围
blur 高斯模糊关键字
radius 高斯模糊半径(像素),不包含中心点的像素 1~50
sigma 高斯模糊标准差 大于或等于0


示例

原图链接:http://img-sample.nos-eastchina1.126.net/Koala.jpg

效果图链接:http://img-sample.nos-eastchina1.126.net/Koala.jpg**?imageView&blur=10x20**


RenderScript

方案简介

RenderScript主要在Android中用于对图形进行处理,RenderScript采用C99语法进行编写,主要优势在于性能较高。在API 11的时候被加入到 Android 中。使用RenderScript实现高斯模糊功能,关键在于编写对应的rs文件生成响应的Script类。


使用方法

RenderScript提供了一个实现高斯模糊的封装类ScriptIntrinsicBlur,我们可以直接借用此类实现高斯模糊效果。但是封装在在API 17后才正式适配到Android,所以若要兼容4.2以前的版本,则要使用兼容包。要使用RenderScript完成图片高斯模糊只需要以下几步:

public static Bitmap blurBitmap(Context context, Bitmap bitmap, float blurRadius) {
    // 1.创建RenderScript内核对象
    RenderScript rs = RenderScript.create(context);

    // 2. 由于RenderScript并没有使用VM来分配内存,所以需要使用Allocation类来创建和分配内存空间,创建Allocation对象的时候其实内存是空的,需要使用copyTo()将数据填充进去
    Allocation input = Allocation.createFromBitmap(rs, bitmap);

    // 3. 创建相同类型的Allocation对象用来输出
    Type type = input.getType();
    Allocation output = Allocation.createTyped(rs, type);

    // 4. 创建一个模糊效果的RenderScript的工具对象,第二个参数Element相当于一种像素处理的算法,高斯模糊的话用这个就好
    ScriptIntrinsicBlur blurScript = ScriptIntrinsicBlur.create(rs, Element.U8_4(rs));

    // 5. 设置渲染的模糊程度, 25f是最大模糊度
    blurScript.setRadius(blurRadius);

    // 6. 设置blurScript对象的输入内存
    blurScript.setInput(input);

    // 7. 将输出数据保存到输出内存中
    blurScript.forEach(output);

    // 8. 将数据填充到bitmap中
    output.copyTo(bitmap);

    // 9. 销毁它们释放内存
    input.destroy();
    output.destroy();
    blurScript.destroy();
    rs.destroy();
    type.destroy();

    return bitmap;
}

效果展示


优缺点

  • 使用简单,原生的API,十行左右的代码就能完成高斯模糊;
  • 效率较高,是在c/c++层做处理;
  • 模糊半径(radius)越大,性能要求越高,模糊半径不能超过25,所以并不能得到模糊度非常高的图片;
  • 兼容4.2以下的设备,需要引入兼容包(约160k左右),会增大apk体积;


FastBlur

方案简介

FastBlur和后面的Android-stackblur分别是StackBlur算法用Java和c++的实现。这个算法是一个介于均值滤波和高斯滤波之间的算法,效果接近高斯滤波,而在执行效率上比高斯滤波快了近7倍。这里给出原作者注释的StackBlur的原理:


效果展示

需要特别说明的是FastBlur因为是Java代码实现,所以效率并不高,本文后面也给出了测试结果。而且由于是整体加载bitmap做处理,所以在渲染一些比较大的图片时,可能会出现OOM。所以当使用FastBlur时,一般建议的做法是:先将目标图进行缩放几倍,然后再进行模糊处理,处理完成的图片再放大到目标尺寸,这样可以显著提高执行效率。下图给出了不同缩放比例下的高斯模糊效果:


优缺点

  • 没有兼容版本问题;
  • 不用引入三方包,不会增加APK大小;
  • 效率很低,在Java层做处理;
  • 将Bitmap全部加载到内存,较大图片容易OOM;


Android-stackblur

Android-stackblur是用JNI实现StackBlur算法对图片做模糊处理,当模糊半径增大时,StackBlur仍能够保持较好的性能。


优缺点

  • 没有兼容版本问题;
  • c/c++做处理,效率比较高;


方案对比

RenderScript、FastBlur、Android-stackblur性能对比

接下来利用上面介绍的方法对比了各种虚化效果的性能,如下表所示,测试环境:华为畅玩4x,Android 4.4.2。


图片尺寸 模糊半径 RenderScript(ms) FastBlur(ms) stackblur(ms)
2000 * 1333 5 102 934 75
- 10 115 886 89
- 15 163 917 88
- 20 133 900 77
- 25 129 951 88
500 * 333 5 68 57 21
- 10 56 51 35
- 15 55 63 28
- 20 74 62 20
- 25 55 70 15

如上图:以2000 x 1333 的图片为例(每一个半径取5次的均值),使用原尺寸用两种方法进行高斯模糊,在渲染耗时上:StackBlur < RenderScript < FastBlur,这说明StackBlur的效率更高。而在缩小图片后,渲染耗时都有所减少,渲染耗时排列依就为:StackBlur < RenderScript < FastBlur。


总结

  • 使用图片链接自定义参数的形式,最为方便。
  • 如果要在本地实现模糊效果,推荐使用StackBlur方案,效率比较高。RenderScript可以在4.2以上直接使用,如果要兼容低版本,则需要导入兼容库。当处理大图时FastBlur比较耗性能,虽然不存在兼容性问题,但是在图片尺寸较大的情况下建议先压缩再处理。
  • 另一种可行的方案:通过缩小图片,使其丢失一些像素点,接着进行模糊化处理,然后再放大到原来尺寸。由于图片缩小后再进行模糊处理,需要处理的像素点和半径都变小,从而使得模糊处理速度加快。


参考资料

  1. Android图像处理 - 高斯模糊的原理及实现
  2. 图像高斯模糊算法的原理及实现
  3. 高斯模糊的算法
  4. 高斯模糊与图像卷积滤波一些知识点
  5. 图像卷积与滤波的一些知识点
  6. Android平台毛玻璃UI效果实现原理初探
  7. Writing Native Android Code: NDK vs. RenderScript
  8. RenderScript初探
  9. android学习之路(四)----RenderScript
  10. Android 上的 高斯模糊 依我之见
  11. stackblur原理



网易云产品免费体验馆无套路试用,零成本体验云计算价值。  

本文来自网易实践者社区,经作者牛栋凯授权发布