作者:牛栋凯
图片模糊是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而是对应边界点的值。
一般认为图像是连续的数据,所以一般用图像边界的值进行拓展,计算边界的像素值。计算示例如下:
值得注意的是,通常来说卷积核需要满足如下条件:
均值滤波器(Mean Filter)是最简单的一种滤波器,它是最粗糙的一种模糊图像的方法。均值滤波器的卷积核通常是m*m的矩阵,其中每个元素为1/(m^2),可以看出卷积核的元素总和为1。比如3x3的均值滤波器,卷积核的每个元素就是1/9。如下图所示:
高斯滤波器是均值滤波器的高级版本,唯一的区别在于,均值滤波器的卷积核的每个元素都相同,而高斯滤波器的卷积核的元素服从高斯分布。这样的话越在模糊半径外围的像素权重越低,造成的影响就越小,越在内侧的像素得到的权重最高,因为内侧像素更加重要,他们的颜色应该与我们要处理的中心像素更接近,更密切。
下图一个一维高斯分布的图,我们都知道,取μ越近的值概率大,而取离μ越远的值的概率越小;σ越小,分布越集中在μ附近,σ越大,分布越分散。从图中可以看出,在3σ的时候只有0.1%的权重。
然而我们需要处理的图像是二维数组,当选择一个中心像素时,我们需要平均该中心周围所有的像素来得到模糊值,而不仅仅是左边和右边的像素。所以我们需要用到二维高斯分布,其公式和分布图如下:
在上面的公式中x和y表示周边像素对于中心像素的相对坐标,σ控制曲线的平缓程度,值越大,越平缓,最高点越低。当x=0且y=0时值最大,即卷积核的中心点权重最大。一般经验,卷积核的半径定为3σ。
Android业务开发过程中,实现图片模糊的方案大致有以下几种:
接下来对上面集中方案作介绍。
现在很多的业务服务器都支持在图片的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主要在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;
}
FastBlur和后面的Android-stackblur分别是StackBlur算法用Java和c++的实现。这个算法是一个介于均值滤波和高斯滤波之间的算法,效果接近高斯滤波,而在执行效率上比高斯滤波快了近7倍。这里给出原作者注释的StackBlur的原理:
需要特别说明的是FastBlur因为是Java代码实现,所以效率并不高,本文后面也给出了测试结果。而且由于是整体加载bitmap做处理,所以在渲染一些比较大的图片时,可能会出现OOM。所以当使用FastBlur时,一般建议的做法是:先将目标图进行缩放几倍,然后再进行模糊处理,处理完成的图片再放大到目标尺寸,这样可以显著提高执行效率。下图给出了不同缩放比例下的高斯模糊效果:
Android-stackblur是用JNI实现StackBlur算法对图片做模糊处理,当模糊半径增大时,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。
网易云产品免费体验馆,无套路试用,零成本体验云计算价值。
本文来自网易实践者社区,经作者牛栋凯授权发布