Android影象處理 - 高斯模糊的原理及實現

騰訊雲加社群發表於2018-02-11

歡迎大家前往雲+社群,獲取更多騰訊海量技術實踐乾貨哦~

天天P圖攻城獅 釋出在雲+社群

作者簡介:damonxia(夏正冬),天天P圖Android工程師

前言

高斯模糊是影象處理中幾乎每個程式設計師都或多或少聽過的名詞,但是對其原理大家可能並不瞭解,只知道通過高斯模糊能實現影象毛玻璃效果。

本文首先介紹影象處理中最基本的概念:卷積;隨後介紹高斯模糊的核心內容:高斯濾波器;接著,我們從頭實現了一個Java版本的高斯模糊演算法,以及實現RenderScript版本。由於我們自己實現的Java版本的高斯模糊演算法的效率太低,因此最後介紹比較有名的高斯模糊的開源專案:Blurry以及BlurKit-Android。

BlurDemo是本文的配套Demo:

  • Demo1:Java版本的高斯模糊的簡單實現。
  • Demo2:RenderScript的高斯模糊實現。
  • Demo3:BlurKit-Android的基本使用。
  • Demo4:Blurry的基本使用。

卷積

本文只討論影象,而影象可以表示為二維矩陣,其中每個元素為ARGB畫素值,因此這裡討論二維矩陣的卷積操作。卷積(Convolution)是影象處理中最基本的操作,就是一個二維矩陣A(M*N)和一個二維矩陣B(m*n)做若干操作,生成一個新的二維矩陣C(M*N),其中m和n遠小於M和N,B稱為卷積核(kernel),又稱濾波器矩陣或模板。

這裡舉個卷積的例子,如圖:

上圖中,最左邊的是源矩陣(8*8),中間是卷積核(3*3,半徑為1),最右邊是通過對前面兩個矩陣做卷積生成的結果矩陣。圖中,如果我們要求出結果矩陣中第二行第二列的元素的值,則把卷積核的中心元素(值為0)和源矩陣的第二行第二列(值為6)對齊,然後求加權和,即圖中的公式,最後得到-3。

我們再舉一個例子:

上圖也展示瞭如何做卷積的過程,比如要求出結果矩陣中第一行第一列的值,則把卷積核的中心對準源矩陣的第一行第一列,發現部分割槽域超出源矩陣的範圍了(圖中紅色部分),解決方法有很多,這裡的方案是:用邊界值填充。接著做加權和,結果為-5。接著用同樣的方法依次計算結果矩陣的每個元素即可。

通常來說卷積核需要滿足:

  • 寬和高都為奇數,這樣才會有半徑和中心的概念。
  • 元素總和為1。

濾波器

均值濾波器

均值濾波器(Mean Filter)是最簡單的一種濾波器,它是最粗糙的一種模糊影象的方法,高斯濾波是均值濾波的高階版本。實際上不同的濾波器就是通過改變卷積核(濾波器),從而改變最後的結果矩陣,中間步驟都一樣,都是求加權和。均值濾波器的卷積核通常是m*m的矩陣,其中每個元素為1/(m^2),可以看出卷積核的元素總和為1。比如3*3的均值濾波器,卷積核的每個元素就是1/9。

高斯濾波器

高斯濾波器是均值濾波器的高階版本,唯一的區別在於,均值濾波器的卷積核的每個元素都相同,而高斯濾波器的卷積核的元素服從高斯分佈。

高斯濾波器是基於二維的高斯分佈函式,因此首先介紹二維高斯分佈函式。二維高斯分佈函式和圖如下:

其中x和y表示卷積核中某個元素橫座標和縱座標距離中心點的距離。sigma控制曲線的平緩程度,值越大,越平緩,最高點越低。我們可以輕易看出當x=0且y=0時值最大,即卷積核的中心點權重最大。

比如卷積核中一個元素距離中心點,橫向距離2,縱向距離1,那麼x=2,y=1,就能求出該元素的值。當然為了保證卷積核元素總和為1,最後每個元素都需要除以卷積核中所有元素之和。

怎麼確定卷積核的大小呢?確定sigma之後,雖然不管距離中心點多遠,該元素的高斯分佈函式值總為非負數,但是根據經驗,卷積核的半徑定為3*sigma,因此寬高為6*sigma+1。

如果高斯濾波器的卷積核是二維的(m*n),則演算法複雜度為O(m*n*M*N),複雜度較高,因此接下來我們對演算法複雜度進行優化。

一維的高斯分佈函式和圖如下:

實際上,二維高斯分佈函式可以分解為兩個一維高斯分佈函式相乘,如下:

因此原本的源矩陣和二維卷積核做卷積等價於源矩陣先與1*m的一維卷積核做卷積,再與m*1的一維卷積核做卷積。一維卷積核的半徑仍定為3*sigma。此時演算法複雜度變為O(2*m*M*N)。

高斯模糊的實現

Java版本

這裡實現了簡單版本的高斯模糊,通過使用橫向和縱向的一維高斯濾波器分別對源矩陣卷積,通過設定sigma的大小能控制圖片的模糊程度,值越大越模糊。但是演算法速度仍比較慢,建議直接使用RenderScript版本或直接使用成熟的開源專案。

由於程式碼過長,不能截圖,因此直接給出Gist地址:https://gist.github.com/xiazdong/d57bf5441f56db197163a5de69dfa65f

效果如下:

RenderScript版本

RenderScript是Android提出的一個計算密集型任務的高效能框架,能並行的處理任務,他可以充分利用多核CPU和GPU,你不需要管怎麼排程你的任務,只需要管任務具體做什麼。這裡不深入介紹RenderScript,因為RenderScript已經提供了一個實現高斯模糊的類:ScriptIntrinsicBlur。

實現起來非常簡單:

開源專案

關於Android影象模糊的開源專案有很多,比如Blurry是專門針對Bitmap或View做模糊,可以設定模糊的基底色,而且還能對模糊操作非同步化;BlurKit-Android也能對Bitmap做高斯模糊(內部通過RenderScript實現),但最吸引人的是實現了毛玻璃的遮罩,效果如下:

BlurKit-Android支援的最低版本是Android 4.1(API 16),因此如果應用需要支援的最低版本是4.0,則不能使用該庫,Blurry支援的最低版本是3.0。

BlurKit-Android

配置過程如下:

  • 在build.gradle中設定:compile 'com.wonderkiln:blurkit:1.0.0',並在defaultConfig中設定renderscriptTargetApi 24renderscriptSupportModeEnabled true
  • 在Application的onCreate()最開始處加入BlurKit.init(this);

配置完成後,通過呼叫BlurKit.getInstance().blur(Bitmap src, int radius);實現高斯模糊,並會把高斯模糊的結果圖寫入src,其中0<radius<=25。

該庫還提供了fastBlur()實現速度更快的高斯模糊,和blur()的區別在於,fastBlur()在高斯模糊之前對圖片取樣,使得圖片大小縮小好幾倍,從而加快高斯模糊的速度。這種加快速度的方法是合理的,因為高斯模糊並不需要原影象很精確的資訊。

BlurKit-Android最吸引人的是提供高斯模糊的遮罩(BlurLayout),隨著遮罩下面的內容的變化,高斯模糊效果也會隨之改變。使用如下:

該Layout能夠實現實時的對該Layout下面的內容做高斯模糊。

Blurry

配置方法:在build.gradle中新增compile 'jp.wasabeef:blurry:2.1.1'

使用方法如下:

總的來說,這兩個庫都使用起來非常方便。

相關閱讀

Android影象處理系列 - 高斯模糊的幾種優化方法

iOS影象處理系列 - 雙重曝光技術的GPUImage實現

iOS影象處理系列 - GPUImage原始碼解讀(二)

此文已由作者授權雲加社群釋出,轉載請註明文章出處


相關文章