type
status
date
slug
summary
tags
category
icon
password
"模糊"的算法有很多种,其中有一种叫做"高斯模糊"(Gaussian Blur)。它将正态分布(又名"高斯分布")用于图像处理,主要用来降低图像的噪声和减少图像的细节。
在Android开发中我们常会遇到做毛玻璃特效的需求(接下来毛玻璃==高斯模糊),而Android并没有提供像IOS一样的毛玻璃蒙层(UIVisualEffectView),所以Android做毛玻璃特效是比较麻烦的,很多时候都是直接使用半透明的深色背景,但这种处理往往效果不好。

1 原理

学过数字图像处理或深度学习的同学应该对卷积核(滤波器)这个概念不陌生,事实上模糊就是减少图像的高频信息,高斯模糊就是一个低通滤波器(个人理解图像的高频信息就是图像的细节,低频信息是图像的轮廓)。
模糊在图像中的意思可理解为:中心像素的像素值为由周围像素的像素值的和的平均值。如图:
notion image
第一幅图为原始图像,其中心像素的像素值为2,第二幅图为中心像素进行模糊后的图像,其像素值为周围像素值的和的平均值。
对整幅图像来说,高斯模糊就是对图像的所有像素点做上述的卷积操作(卷积核不变),如下图:
notion image

1.1 模糊半径

模糊半径指的是中心点与周围像素的距离取值,3*3大小的卷积核模糊半径就是1.5,5*5的模糊半径为2.5依此类推。
计算平均值时,模糊半径越大,参与模糊的像素点越多,图像越模糊,下图分别是原图、模糊半径3像素、模糊半径10像素的效果。
notion image

1.2 高斯核

既然每个点都要取周边像素的平均值,那么就涉及到了权重分配的问题。如果使用简单平均,显然不是很合理,因为图像都是连续的,越靠近的点关系越密切,越远离的点关系越疏远。因此,加权平均更合理,距离越近的点权重越大,距离越远的点权重越小。而高斯模糊就是用正态分布来分配周围像素的权重。
一维正态分布
一维正态分布
二维正态分布
二维正态分布
在图形上,正态分布是一种钟形曲线,越接近中心,取值越大,越远离中心,取值越小。计算平均值的时候,我们只需要将中心像素作为原点,其他点按照其在正态曲线上的位置,分配权重,就可以得到一个加权平均值。一维正态分布图像如下图所示,由于图像都是二维的,所以我们需要二维的正态分布。
这里直接给出二维高斯函数(在计算平均值的时候,中心点就是原点,所以默认等于0,为模糊半径):
高斯核函数得出的卷积核模板称之为高斯核,假设一个高斯函数的卷积和模板是5*5,那么他这25个点的x,y具体取值为:
notion image
如果要构建一个模糊半径为2的权重矩阵那么他的矩阵应该是(2*2+1)(2*2+1),为什么要加1呢是因为作为权重矩阵往往都是奇数矩阵,通常来讲这是为了能有一个中心点,如果不是奇数那么中心点的半径就不是统一的,例如如果是22的矩阵那么就无法确定准确的中心点位置,奇数刚好可以解决这个问题
有了这个矩阵我们就可以开始利用高斯函数来进行计算了,将x,y带入函数中,并将带入,可以得到权重矩阵:
notion image
为了得到权重矩阵需要对上面的矩阵进行归一化处理,得到如下权重矩阵(高斯核):
notion image

1.3 色值计算

得到高斯核后就可以做滑块运算(卷积)了,计算出每个像素点的色值,如果是彩色图像,需要对RGB三个颜色通道进行运算再叠加。
notion image
为了提高卷积运算的速度,将高斯核分解为一个行向量和一个列向量进行运算,能减低时间复杂度,这里不展开讲了,感兴趣的可以搜索二维高斯核的可分离核形式,参考 二维高斯模糊和可分离核形式的快速实现
这么看下来高斯模糊的原理是不是很简单,那接下来我们看一下在Android中如何实现高斯模糊。
 

2 RenderScript

RenderScript 是用于在 Android 上以高性能运行计算密集型任务的框架。RenderScript 主要用于数据并行计算,不过串行工作负载也可以从中受益。RenderScript 运行时可在设备上提供的多个处理器(如多核 CPU 和 GPU)间并行调度工作。这样您就能够专注于表达算法而不是调度工作。RenderScript 对于专注于图像处理、计算摄影或计算机视觉的应用来说尤其有用。
这里我们使用Android提供的RenderScript进行高斯模糊,Android上实现高斯模糊有三种方式:
  1. Java : FastBlur.java ,是由应用非常广泛的 StackBlur 模糊算法实现代码,但是效率最低。
  1. C++ :有两种实现,1:标准高斯模糊算法; 2:均值模糊,效率属于中等。
  1. RenderScript :用来在 Android 上编写高性能代码的一种语言(使用C99标准,运行时机器再次优化编译, 可以均衡的运行在多个CPU 和 GPU上,有一个半径限制小于25的限制),运算效率最高,但在API17以上才可以使用,如果使用兼容包的话,会导致APK 体积增大,support包约160k。
因为我们要做的高斯模糊蒙层是需要实时刷新的,所以这里使用RenderScript更加合适,

2.1 模糊实现

 
1. 创建一个RenderScript实例:是用.create()方法创建实例,有好几个重载的方法可以选。同一时间点最好只创建一个RenderScript实例。
2. 创建一个或多个Allocation类实例:Allocation类用于存储需要进行处理的对象数据。包含很多静态创建方法,比较常用的是createFromBitmap(…)createTyped(…)
3. 创建需要使用的脚本,这里脚本需要分成两类:
  • ScriptC 这是我们自己编写的.rs文件。编译器会为我们自动映射一个java类,名字是ScriptC_文件名。假如我们写的文件是abc.rs,映射的类名就是ScriptC_abc,实例化操作如下:
  • ScriptIntrinsic 这是RenderScript内置的已经帮我们写好了的常用脚本,比如有高斯模糊、图像融合等等,它们都继承抽象类ScriptIntrinsic
4. 设置必要的脚本变量:
RenderScript内置的脚本设置参数就不用说了,主要讲下我们自定义的脚本如何在java中赋值。 如果在你自己写的.rs文件中需要使用到额外的参数,你可以定义全局变量,比如int xyz;,那么自动映射的java类中将会自动产生get和set方法,如set_xyz(int);。这样就可以在java代码中做赋值操作,你需要做的仅仅是定义全局变量。 注意静态变量和常量不会生成set方法,只有get。
5. 执行脚本:
最终执行一般调用forEach方法。在自定义的.rs文件中,有参数的方法都会映射个java方法forEach_方法名,一个是参数跟你自己定义相同的方法,另一个重载的方法是多了一个Script.LaunchOptions类型的参数,可以设置x、y、z三个维度的起点和结束点(比如可以针对图片的某个区域进行操作)。
6. 从Allocation复制数据:将计算完成的数据从Allocation中复制出来,调用copyto(…)方法。 (例如复制到一个bitmap)
7. 销毁RenderScript实例:最后,当然不要忘记销毁RenderScript实例,调用它的destroy()方法销毁

2.2 实时效果实现

上面简单介绍了如何讲一个图片(bitmap)模糊化展示出来,而毛玻璃蒙层需要实时的模糊效果,下面简单介绍一些实时模糊效果的实现方案。
实时模糊事实上就是在绘制之前将即将展示出来的bitmap模糊化并替换掉原有的bitmap,也就是说每次执行onDraw方法之前都需要进行一次模糊操作(实时模糊确实会产生一定的性能压力)。
Android给我们提供了一个监听接口ViewTreeObserver.OnPreDrawListener,每次触发onDraw之前都会进入这个监听器的onPreDraw() 方法中,我们可以在这个方法中处理模糊效果,代码如下:
上面代码先确定模糊的位置,将模糊的区域透明化,再将模糊后的bitmap加上去。详细的代码实现可以看参考中的几个Github。
参考:
 
Html.formHtml取消合并空格Android基础-Handler
LuluNotion
LuluNotion
一个普通的干饭人🍚
公告
type
status
date
slug
summary
tags
category
icon
password
🎉NotionNext 4.0即将到来🎉
-- 感谢您的支持 ---
👏欢迎更新体验👏