高斯模糊的原理是什麼,怎樣在界面中實現?


高斯模糊,聽起來很高大上,其實就是一種很基礎的數學演算法應用,不要被「高斯」迷惑了雙眼,它並沒有高斯本人那麼深不可測。。

高斯模糊之所以叫高斯模糊,是因為它運用了高斯的正態分布的密度函數,我記得是大二時候學的:

其中,μ是x的均值,σ是x的標準差(評論提醒)。由於每次計算都以當前計算點為原點,所以μ等於0。於是公式進一步簡化為:

在圖形上,正態分布是一種鐘形曲線,越接近中心,取值越大,越遠離中心,取值越小。


計算平均值的時候,我們只需要將"中心點"作為原點,其他點按照其在正態曲線上的位置,分配權重,就可以得到一個加權平均值。

而他的二維方程是:

也就是說,把它運用於圖像演算法中,就會使圖像出現模糊的效果:

我從最基礎的開始舉例子,以一維方程做起,將x,y軸分別進行高斯模糊。

我們假設σ為1.5吧,假設有x軸上三個像素點,中間點為原點:

帶入公式後,三個點的值為:

這3個像素的權重總和等於0.80858986,如果只計算這3個點的加權平均,還必須讓它們的權重之和等於1,因此上面3個值還要分別除以0.80858986。

那麼,這個數字有什麼用呢?

我們都知道,每一個像素點,決定它色差的是RGB值,計算加權平均的時候要分別計算;

現在我們把GB設為0,只計算R,我們來假設一個點,R值為27,125,22:

將三個數字乘以加權平均值後,分別為6.8391,61.6725,6.8391

而中心點的R值就變為 6.8391+61.6725+6.8391 = 75.5137 記為75;

變為:

就使像素點相對於旁邊兩點更加柔和了。

具體到代碼實現上面:

循環遍歷各個點,歸一化之後放入臨時數據:

在x軸方向上進行高斯模糊:

y軸同理:

最後清理內存:

這個是c語言的代碼。。不論ios安卓,在原生代碼中通過JNI調用就可以了

參考地址:
http://www.ruanyifeng.com/blog/2012/11/gaussian_blur.html

http://www.swageroo.com/wordpress/how-to-program-a-gaussian-blur-without-using-3rd-party-libraries/

http://m.2cto.com/kf/201605/506616.html


就是圖像和高斯函數的卷積。
等價於傅里葉變換後乘上高斯函數再逆變換回來(高斯函數的傅里葉變換也是高斯函數),沒錯就是低通濾波。

實現方式可以按照卷積公式算,複雜度O(n2m2)。
考慮到二維高斯函數G(x,y)可以寫成兩個一維高斯函數G(x)和G(y)的乘積,也就是G(x)δ(y)和G(y)δ(x)的卷積,也就是可以x和y兩個方向分別做高斯模糊。O(n2m)


卷積核很大的時候也可以用FFT-&>相乘-&>IFFT的方式實現,複雜度O((m+n)2log(m+n)),不過高斯模糊的m也就是幾個像素,就不要考慮這個了。


----------------- 1/24更新:----------------------

評論區有問到濾波和模糊區別的,那就一步到位,說下幾個容易搞混的概念:

濾波(高通、低通、帶通、帶阻) 、模糊、去噪、平滑等。

看圖:

它們的範圍大小以及相互包含關係如圖所示


濾波是對輸入信號進行卷積處理的一個過程,寫成一個函數的形式是這樣的:
濾波 = 卷積( 輸入信號 ,卷積模板 ) 卷積模板/掩膜 的不同決定了不同的濾波方式,也因此產生了高通、低通、帶通、帶阻等基本的濾波方式。

針對低通濾波,就是保留將信號中的低頻部分,抑制高頻部分。要達到這個目的,可以利用均值掩膜、高斯掩膜等對輸入信號進行處理。
採用均值掩膜對輸入信號進行卷積的濾波方式叫均值濾波;
採用高斯掩膜對輸入信號進行卷積的濾波方式叫高斯濾波;

有問到:

高斯濾波和高斯模糊一樣嗎

答:一樣。不加「高斯」這個限定詞時,濾波的範疇要遠遠大於模糊,後者只是前者的一個子集。當加上「高斯」限定以後,所指代的卷積操作就是同一個操作了,因為根據方程 濾波 = 卷積( 輸入信號 ,卷積模板 ),當兩個操作的輸入信號和卷積模板都一樣時,這兩個操作就是一個操作了。

那為什麼不直接叫高斯濾波,還起個高斯模糊的名字呢?

那是因為這個濾波的效果看起來像是把圖像給弄模糊了,所以又有了「高斯模糊」的叫法。


平滑也是類似的道理。

起這麼多名字好亂啊,幹嘛不統一?

就不! 你咬我啊


------------------ 以下原答案 ---------------------

模式識別剛入坑那會兒,還沒有opencv,老闆讓我手寫一個演算法(純c):

比較下中值濾波、均值濾波、高斯濾波、雙邊濾波、引導濾波(Guided Filter)對一組輸入圖像(含雜訊)的處理效果。

此時我的圖像處理課才剛上完第一章

--------------------


懵逼過程省略一萬字,我講下我自己遇到的坑及學習心得:
(結合高斯濾波,把雙邊濾波也講下,它們其實很相似,雖然雙邊看起來高大上許多)

首先請記住一句話:濾波是掩膜(mask)和圖像的卷積。

再記住第二句話:濾波過程分兩步:
1)計算掩膜
2)卷積——掩膜上每一個位置的值和圖像上對應位置的像素值的乘加運算

(原諒我的啰嗦)

講課開始:


1. 高斯分布不是高斯模糊/濾波!!! ( x 3)

看起來很好笑,但對於當時初學者的我來說,確實被坑的不淺。

1.1 高斯分布

絕大多數講高斯濾波的文章或者博客都會給出下面的公式:

高斯分布公式:f(x)= frac{1} {sqrt{2pi}sigma}{e^{-{(x-u)}^2}/{2{sigma}^2}}qquad


以及告訴你它的形狀和性質:

(如圖為一維高斯分布示意圖,mu決定了分布對稱中心,sigma決定了分布的形狀——sigma越小形狀越瘦高,sigma越大越矮胖)


然而在這裡高斯分布公式到底是做什麼的?

答:計算高斯濾波用的掩膜(Mask)

1.2 利用高斯分布求高斯掩膜

1.2.1 均值掩膜

首先看一下掩膜到底長什麼樣子,我們來一個最簡單的——3x3均值濾波的掩膜:

(對,就這個樣子)
可以看到,均值掩膜內所有係數均相等——為1/9,且他們的和為1。同理可知5x5均值濾波的掩膜。

1.2.2 高斯掩膜

高斯分布公式終於要出場了!

有個小問題:我們上面給出的高斯分布公式是一維的,但掩膜是二維的怎麼辦?
很簡單,只需要把數值x變為向量vec x即可
f(vec x)= frac{1} {(sqrt{2pi}sigma)^2}{e^{-{(vec x-vec u)}^2/{2{sigma}^2}}}qquad
(其中不必糾結於係數frac{1}{(sqrt{2pi}sigma)^2},因為它只是!一個! 常數!並不會影響互相之間的比例關係並且最終都要進行歸一化,所以在實際計算時我們是忽略它而只計算後半部分的)
 vec x為二維時,vec x =(x,y)這個公式其實可以表示任意維度的高斯分布。
需要特別注意的是:
vec x, vec mu本質上都是二維空間中的坐標:vec x是掩膜內任一點的坐標,vec mu是掩膜中心的坐標!(統計學中稱為均值,即坐標的均值)

敲黑板再說一遍: x和u, 是位置,是坐標!

於是乎,對於圖像中任意一點(x,y),它周圍的坐標為:

中間的(x,y)就是我們公式中的vec mu了, 當vec x遍歷(x-1,y-1)...(x+1,y+1)時,

({vec x - vec mu})^2

(只剩下常數沒有x,y是因為在相減的過程中把x,y抵消掉了)

e^{-{(vec x-vec u)}^2/{2{sigma}^2}} qquad

(這裡取sigma為1.0)

歸一化就得到高!斯!掩!膜!了

所以你看,高斯掩膜的求解與位置(x,y)無關,因為在計算過程中x,y被抵消掉了,

因!此!——

高斯掩膜只要求解一次就夠了! 當得到了這個模板,對圖像的所有像素用這同一套模板去卷積就OK了!

1.3 利用高斯掩膜和圖像進行卷積求解高斯模糊

這個過程就很簡單了,如下圖所示,沒有別的,就是卷積:

117 =
102 * 0.075 + 108 * 0.124 + 110 * 0.075 +
119 * 0.124 + 120 * 0.204 + 110 * 0.124 +
129 * 0.075 + 130 * 0.124 + 132 * 0.075


130 =
172 * 0.075 + 175 * 0.124 + 172 * 0.075 +
112 * 0.124 + 123 * 0.204 + 88 * 0.124 +
98 * 0.075 + 115 * 0.124 + 128 * 0.075
所有像素都用圖上的那一套模板進行卷積運算,最終得到濾波的結果。

貼個效果圖:

嗯,可以的。
(為了防止被舉報... ... 一點薄碼,不成敬意) (逃

2. 演算法實現
原理如果懂了,演算法實現應該很簡單,而且opencv也有現成函數可以調用,但opencv的效率其實也不高,可以看這個回答:OpenCV已經將圖像處理(識別)的演算法寫成函數了,那我們還有必要去學習這些演算法不? - mpcv 的回答 - 知乎
在這裡提兩個可以加速的地方:
1)把二維高斯濾波拆成兩個一維高斯濾波——先x方向再y方向
他們是等價的,有興趣的同學可以自行查找下相關證明。
例如:一個5x5的二維高斯濾波,每個像素需要進行25次乘、 24次加 運算;
變為2個1x5的高斯濾波後,每個像素進行 10次乘 、8次加 運算;

2)利用查表代替乘運算
可以看到,在卷積的過程中,所有的乘運算都是發生在掩膜係數和像素值之間。
而且: ① 掩膜係數是固定的:對於1x5的掩膜,只有3個不同的參數;
② 像素值的範圍也是固定的: 0~255,256個值;

因此,所有係數和像素值的乘積只有3x256=768種不同的結果。 所以,這768個結果我們直接把它保存在一張表中即可!等用時直接查表!!!


於是上文的10次乘、8次加運算進一步縮減為了只剩8!次!加 !運 ! 算 !


最終我們的運算量改變為:

25次乘、24次加 -------------&> 8次加!!!

WTF?

3. 雙邊濾波
雙邊濾波其實很簡單,它只是比高斯濾波多了一種掩膜而已。兩種掩膜都是套用高斯分布公式得出來的,只是其中的vec x , vec mu代表的含義不同而已。
f(vec x)= frac{1} {(sqrt{2pi}sigma)^2}{e^{-{(vec x-vec u)}^2/{2{sigma}^2}}}qquad
求第一個掩膜所用的高斯分布中,vec x代表掩膜中每一個點的位置,其中vec mu為掩膜中心的位置
求第二個掩膜所用的高斯分布中,vec x代表掩膜中每一個點的像素亮度值,其中vec mu為掩膜中心的像素亮度值
(這裡有一個是需要注意的:第二個掩膜是需要每次都求取的,因為像素的亮度值無法預先確定,不是維持一個固定不變的pattern)

對這就是雙邊濾波,不光考慮了像素在空間中位置遠近程度的影響,還考慮了像素亮度相近程度的影響。

僅此而已。

客官,來都來了,點個贊再走唄(?&>ω&<*?)


常用的兩種模糊演算法,一種是均值,一種是高斯,實質上是分別與不同的卷積核做二維卷積,產生一種低通濾波的效果。同樣一些銳度提高的方法其實是反過來做高頻增強。
均值在頻譜上大致是用sinc函數做過濾,這個函數有零點和旁瓣,導致的缺點是特定頻率會完全過濾掉,比如說特定周期重複的斑紋,做平均之後剛好到處都是0,更高一些頻率的反而留下來了。
高斯模糊就不會這樣,它可以讓低頻到高頻逐次減小,而且很容易控制。
了解原理之後就可以知道,經過模糊的圖像,有可能通過再做一次卷積和上一次卷積抵消,來恢復出原始的圖像,千萬不要用來為非常重要信息打碼。
另外理想低通濾波應該是用sinc卷積,不知道為什麼從沒見過呢……


2/21更新
貼點數據

高斯核尺寸:(6sigma+1)	imes(6sigma+1)

window 示意圖

性能對比

這個結果是在 CPU 上的結果,在 GPU 上使用 window 反而要比分離卷積慢

================================================

想要大的卷積核的話,會消耗大量計算時間,

但是你可以通過若干次小核的均值模糊來逼近大核高斯模糊,而且誤差極小可計算控制。

更何況均值模糊是可以水平垂直分離的,這樣的模糊在gpu上飛快。如果是在cpu上的話可以使用 window,同樣飛快。


高斯模糊


二維均線。


原理很簡單
那些近視的同學,不戴眼鏡看不清的時候,他的看到的世界就是高斯模糊的世界。

高斯模糊就是把原本清晰的畫面,加一個放大鏡,讓你的眼睛不能聚焦。


用正態分布的離散值做權重為中心點算一個值


圖像高斯模糊演算法的原理及實現
我以前寫過博客,裡面還有代碼


高斯模糊是一個低通濾波,傅里葉變換到頻域之後可以看到能量都會集中在低頻區域。一般實現都在圖像的時域上做卷積。同時高斯模糊是可分離式核,可分解為不同坐標軸上分別獨立做高斯模糊。高斯模糊應用極其廣泛,可以用來去除信號中的高頻分量,從而降低或去除採樣中的aliasing。在laplacian金字塔中上一層也是由下一層降採樣加高斯模糊獲得。當然還可以用來去除雜訊。在遊戲渲染中,根據LoD需要選擇不同層級的紋理時,也是需要將紋理預處理生成高斯金字塔,做mipmap。


高斯模糊有兩種實現方式, 分別是空間域高斯濾波頻域高斯濾波. 由於空間域的卷積等於頻域的乘機, 因此空間域域頻域高斯濾波是等效的, 用的都是正態分布.

關於空間域高斯濾波很多答主都回答了, 我這裡提一下頻域濾波. 我曾寫過一系列文章, 直接拿過來吧:


正態分布: http://accu.cc/content/math_normal_distribution/

空間域高斯濾波實現: http://accu.cc/content/pil_spatial_filter_gaussian_blur/

圖像的傅里葉變換(從空間域 -&> 頻域): http://accu.cc/content/pil_frequency_filter/

頻域高斯濾波實現: http://accu.cc/content/pil_frequency_filter_lpf/


就是一個低通濾波,圖像和濾波函數做一個卷積就可以了,之所以叫高斯模糊是因為濾波函數是 Gauss distribution.


抖個機靈好了。

如圖,這是高斯。

這是高斯模糊


玩完中文又來玩化學,你們真是的


剛好我之前寫了一篇博客,就是在Android上實現高斯模糊。傳送門:Android平台毛玻璃UI效果實現原理初探
不過我只是想實現下效果,做個demo,所以是用Java實現的。下面直接把文章抄過來:

首先說一下樸素的模糊思想

樸素的模糊思想

Android端圖片對應的類是Drawable。其中Drawable作為一個抽象基類下面又派生出很多具體的圖片類,如ColorDrawable、BitmapDrawable、LayerDrawable、ShapeDrawable等等,高斯模糊是針對BitmapDrawable的。

每個BitmapDrawable內部都持有一個Bitmap引用,這個Bitmap類盛放著像素矩陣。其中每個像素有自己的顏色,而這個像素點的具體顏色的表示形式又取決於Bitmap的Config。

Bitmap的Config有以下幾種:

  • ARGB_8888
    大部分圖像的像素點的存儲形式都是ARGB_8888。這種格式的圖像中每個像素點有4個顏色通道,其中A表示alpha,也就是透明通道,另外3個通道RGB則分別表示三原色:紅、綠和藍。這4個通道每個各佔8bit,也就是一個位元組。4個通道一共佔4個位元組,也就是一個int類型的大小。所以對於ARGB型的Bitmap,我們可以用一個int型的變數來表示其中的一個像素點。
  • ARGB_4444
    每個通道佔4bit,一個像素點佔2個位元組。已經被Google標註為Deprecated,理由是圖像質量太差。
  • RGB_565
    透明通道被去掉,3個通道共佔2個位元組。這個格式還是會用到的,因為大部分圖片是不會用到透明通道的,我們可以通過使用這種格式來降低圖片尺寸,進一步降低內存佔用率。
  • ALPHA_8
    沒有紅綠藍的通道,一般用於表示掩碼。

以上是Bitmap.Config的枚舉值,實際在圖像處理中我們還會經常用到另一種圖像,就是灰度圖。對於一副灰度圖,每個像素點只有一個灰度通道,灰度值的取值範圍從0到255,從一副RGB圖轉化到灰度圖很簡單,就是每個像素點的灰度取R、G、B的平均值。比如一個綠色的像素點0xFF00FF00,對應的灰度值就是

(0 + 255 + 0) / 3 = 85

以ARGB_8888為例,下圖說明一個像素點各通道是怎樣存儲的。

從上圖可以看到,一個像素周圍有8個鄰接像素。那麼,想要使圖片變得模糊,一個很簡單的想法就是:

讓這個像素的顏色值取周圍9個像素的平均值。

那麼對於原圖片P到模糊之後的圖片Q,對於灰度圖,我們可以有以下公式:

q(i,j)表示第i行,第j列被模糊化處理之後的像素。p(k,l)表示原圖中的像素點。

說白了就是對上圖中紅框框里的像素點(也把紅框框叫」盒子「)取平均值,得到的值就是3x3的小方塊中心那個像素點的新的灰度值。剛才說的是對灰度圖的處理,對於ARGB圖像的處理也相似,可以分別對每個通道求平均值,然後合在一起。

還有一個問題,對於一個Bitmap非邊界處的像素點,可以找出3x3的小方塊並求出平均值,但是對於邊界處,3x3的小方塊必然會越界,可以有多種處理方案,一是收縮小方塊,比如對於q11,把方塊縮小到2x2。也可以在越界處複製一些"虛擬像素",將越界的像素賦值為方塊中心的像素的顏色值。

按照以上的想法,可以在Android平台寫出以下代碼來對一個ImageView所包含的BitmapDrawable進行模糊化處理:

private class MeanFilterTask extends AsyncTask&{
private Bitmap bitmap;//源Bitmap
private Bitmap target;//模糊後的Bitmap

public MeanFilterTask(ImageView imageViewTarget){
super();
Drawable drawable = imageViewTarget.getDrawable();
bitmap = ((BitmapDrawable) drawable).getBitmap();
bitmap = Bitmap.createScaledBitmap(bitmap, bitmap.getWidth() / 10, bitmap.getHeight() / 10, false);//縮小圖片,減少運算量,改善模糊效果
}

@Override
protected void onPreExecute() {
super.onPreExecute();
target = Bitmap.createBitmap(bitmap.getWidth(), bitmap.getHeight(), bitmap.getConfig());
}

@Override
protected Void doInBackground(Void... params) {
meanBlur(bitmap, target);//均值模糊
return null;
}

@Override
protected void onProgressUpdate(Double... values) {
super.onProgressUpdate(values);
}

@Override
protected void onPostExecute(Void aVoid) {
super.onPostExecute(aVoid);
imageTarget.setImageDrawable(new BitmapDrawable(getResources(), target));
}
}

/**
* 均衡模糊
* @param src 源Bitmap
* @param target 目標Bitmap
*/
private void meanBlur(Bitmap src, Bitmap target) {
if (src.getConfig() != Bitmap.Config.ARGB_8888) {//簡化考慮,只支持ARGB8888
return;
}

int boxWidth = 3;// 定義一個3x3的盒子,對盒子內的像素點取平均
int boxHeight = 3;
for (int i = 0; i &< src.getHeight(); i ++){ for (int j = 0; j &< src.getWidth(); j ++){ int meanPixel = filter(boxWidth, boxHeight, i, j, src, new MeanFilter(boxHeight, boxWidth));// 求平均值 target.setPixel(j, i, meanPixel);// 寫入像素點 } } } /** * 根據濾波模板進行濾波 * @param boxWidth 盒子寬度(此處為3) * @param boxHeight 盒子高度(此處為3) * @param rowIndex targetBitmap的目標像素點在第xx行 * @param colIndex targetBitmap的目標像素點在第xx列 * @param src 源Bitmap * @param filter 濾波模板 * @return */ private int filter(int boxWidth, int boxHeight, int rowIndex, int colIndex, Bitmap src, Filter filter){ if ( boxWidth % 2 == 0 || boxHeight % 2 == 0) return 0; int targetPixel = 0xff000000;//計算的結果 int redSum = 0; int greenSum = 0; int blueSum = 0; int temp; for (int i = rowIndex - boxHeight / 2, boxRow = 0; i &<= rowIndex + boxHeight / 2; i ++, boxRow ++){ for (int j = colIndex - boxWidth / 2, boxCol = 0; j &<= colIndex + boxWidth / 2; j ++, boxCol ++){ if (i &< 0 || i &>= src.getHeight() || j &< 0 || j &>= src.getWidth()) //越界
temp = src.getPixel(colIndex, rowIndex);
else
temp = src.getPixel(j, i);//依次取出盒子內的像素點
redSum += ((temp 0x00ff0000) &>&> 16) * filter.weight(boxRow, boxCol);//求均值,先計算sum,對於 均值模糊, 這裡的weight為1
greenSum += ((temp 0x0000ff00) &>&> 8) * filter.weight(boxRow, boxCol);//求均值,先計算sum,對於 均值模糊, 這裡的weight為1
blueSum += (temp 0x000000ff) * filter.weight(boxRow, boxCol);//求均值,先計算sum,對於 均值模糊, 這裡的weight為1
}
}

int meanRed = ((int) (redSum * 1.0 / filter.total() )) &<&< 16;//ARGB red通道需要左移16bit歸位,對於 均值模糊, 這裡的total為9 int meanGreen = ((int) (greenSum * 1.0 / filter.total() )) &<&< 8;//ARGB green通道需要左移8bit歸位,對於 均值模糊, 這裡的total為9 int meanBlue = ((int) (blueSum * 1.0 / filter.total() ));//,對於 均值模糊, 這裡的total為9 targetPixel = (targetPixel | meanRed | meanGreen | meanBlue);//或運算 將3個求均值的結果合一 return targetPixel; }

以上代碼寫了注釋,相信不難看懂。這裡面我又引入了一個濾波模板的概念,也就是Filter介面。相關代碼如下:

/**
* Created by zjl on 2016/12/13.
* 濾波模板
*/
public interface Filter {
int weight(int rowIndex, int colIndex);//盒子元素與濾波模板元素做乘積最後求平均,在均值模糊中,這裡的weight為1

int total();//前面求出sum然後除以total求出最後的平均顏色值
}

/**
* Created by zjl on 2016/12/13.
* 平均模糊的濾波模板
*/
class MeanFilter implements Filter {

private int width;//盒子的寬度,文章假設為3x3的盒子
private int height;

public MeanFilter(int boxHeight, int boxWidth) {
this.height = boxHeight;
this.width = boxWidth;
}

@Override
public int weight(int rowIndex, int colIndex) {
return 1;
}

@Override
public int total() {
return width * height;
}
}

等會再說濾波模板幹啥用的,先看一下這種樸素的模糊方法的效果:

模糊對比

改良模糊效果

可以看到,模糊確實是有效果了,但是感覺還沒有到毛玻璃的那種效果。實際上,這種模糊對盒子里的所有像素點一視同仁,這種模糊方法有些」粗暴「,為了得到更自然的平滑效果,我們可以適當加大盒子中心點的權重,降低盒子邊界處像素點的權重。換言之,本來我們對盒子里的所有像素點取平均值,現在我們對每個像素點區別對待,給每個像素點乘上一個權重,靠近盒子中心的權重更大,遠離中心的權重更小,最後再除以權重的總和來求出一個平均。

上面的公式看起來比較抽象,這裡以一個3x3的模板舉個例子:

這裡給出的濾波模板的第一行是1 2 1,第二行是 2 4 2, 第三行是 1 2 1。其實這就是最常見的3x3的高斯模糊的模板。為什麼叫高斯模糊呢,因為這個模板其實是二維高斯分布(也叫二維正態分布)在離散域的近似。二維高斯分布的公式長這個樣子:

對應函數圖像:

二維高斯分布


本來如果要按照這個公式實現高斯模糊的話,運算量就太大了,又有除法運算又有乘方運算的,但我們可以用二項分布去近似逼近高斯分布(正態分布)。現在返回去觀察那個3x3的高斯濾波模板,其中第一行的1 2 1就是一個二項式展開。而整個3x3的模板的由來,其實就是一個二項式展開式的各個部分分別乘以這個展開式本身,我們把這個過程叫做一維濾波模板的卷積。

卷積

有了這個近似逼近的方法就方便編程實現了。剛才在上面定義了一個濾波器介面,接下來需要寫一個高斯濾波器類來實現那個介面。對於weight()方法,可以通過對二項展開式的卷積得到,對於total()方法,可以通過二項式的定理得到權重和是一個二次冪:

以下是高斯模糊的代碼實現:

private class GaussFilterTask extends AsyncTask&{
private Bitmap bitmap;
private Bitmap target;

public GaussFilterTask(){
super();
Drawable drawable = imageTarget.getDrawable();
bitmap = ((BitmapDrawable) drawable).getBitmap();
bitmap = Bitmap.createScaledBitmap(bitmap, bitmap.getWidth()/20, bitmap.getHeight()/20,false);
}

@Override
protected void onPreExecute() {
super.onPreExecute();
target = Bitmap.createBitmap(bitmap.getWidth(), bitmap.getHeight(), bitmap.getConfig());
}

@Override
protected Void doInBackground(Void... params) {
gaussBlur(bitmap, target);//調用高斯模糊方法
return null;
}

@Override
protected void onProgressUpdate(Double... values) {
super.onProgressUpdate(values);
}

@Override
protected void onPostExecute(Void aVoid) {
super.onPostExecute(aVoid);
imageTarget.setImageDrawable(new BitmapDrawable(getResources(), target));
}
}

private void gaussBlur(Bitmap src, Bitmap target) {
if (src.getConfig() != Bitmap.Config.ARGB_8888) {//簡化考慮,只支持ARGB8888格式的Bitmap,即 透明、紅、綠、藍四個通道各佔一個位元組
return;
}

int boxWidth = 7;//盒子大小為7x7
int boxHeight = 7;
GaussFilter filter = new GaussFilter(boxHeight, boxWidth);//實例化GaussFilter,並傳遞給filter()方法

for (int i = 0; i &< src.getHeight(); i ++){ for (int j = 0; j &< src.getWidth(); j ++){ int meanPixel = filter(boxWidth, boxHeight, i, j, src, filter); target.setPixel(j, i, meanPixel); } } } /** * Created by zjl on 2016/12/13. * 使用二項式分布逼近的高斯濾波器 */ class GaussFilter implements Filter { private int width; private int height; public GaussFilter(int boxHeight, int boxWidth) { this.height = boxHeight; this.width = boxWidth; } @Override public int weight(int rowIndex, int colIndex) { int me = C(width - 1, colIndex); return me * C(height - 1, rowIndex); } @Override public int total() { int result = (int) Math.pow(2, width + height - 2); return result; } private int C(int n, int k){ //n次二項展開式,第k項 if (k &<= 0) return 1; if (k &> n / 2)
return C(n, n - k);

int result = 1;
for (int i = 1; i &<= k; i++) result *= (n - i + 1); for (int i = 1; i &<= k; i++) result /= i; return result; } }

以上代碼中,把模糊操作放到AsyncTask中是因為這是個耗時任務,圖像的size一大很容易ANR。

在盒子大小為3x3時,縮放10倍時的效果:

3x3 縮放10倍

在盒子大小為3x3時,縮放20倍時的效果:

3x3 縮放20倍

在盒子大小為7x7時,縮放20倍時的效果:

7x7 縮放20倍

可以看到:

  • 對圖像進行一定程度的縮放可以很大程度改善高斯模糊的效果。同時降低運算量。
  • 盒子大小則表示局部模糊的範圍,盒子越小,對布局細節的保留相對越多,盒子越大,細節丟失地越嚴重。
  • 高斯模糊比起最初的均值模糊,模糊效果過渡得更」平滑「,不那麼突兀。我們平時用得毛玻璃的效果的底層實現,基本上是高斯模糊或優化後的高斯模糊演算法。

說明

最後再說明一下,這篇文章主要是為了介紹了模糊演算法的一些基本原理,以上代碼沒有經過優化,不僅運算速度慢,而且容易出現OOM,完全沒法用在工程中,只是演示一下效果罷了。


我不是要噴,只是覺得最高票並沒有說到本質;
核心只有三點:卷積、傅里葉變換、高斯濾波器。。
圖像(或者一維信號),可以理解為一堆餘弦信號的疊加,所謂的「模糊」,其實就是把高頻信號(邊緣)過濾掉,把低頻信號(輪廓)保留;
但是在時域看不出所謂高頻低頻呀怎麼辦?所以就需要做個傅里葉變換,轉換成頻域,然後直接在頻域上把高頻對應的「基」的權值置零(這個好像叫理想濾波器),然後再做逆傅里葉變換;
但是這樣做好慢,要做兩次傅里葉變換,生產中不大可能這麼干,所以有沒辦法直接在時域干相同的事情呢?必須是有的,這裡涉及到一些數學公式,簡單來說就是:時域的卷積,相當於,頻域的傅里葉變換逐點相乘f(t) ast g(t) Leftrightarrow IFFT(F(x) star G(x)));
所以一般來說,設計一個濾波器,都要從頻域去看它的性質,然後逆變換到時域,跟原信號做卷積就成了,而上述的理想濾波器,逆變換後在時域的形狀有點複雜,不大適合做卷積(這個理由其實是我自己吹的);
那麼高斯分布這個怪物出現了,這個東西它在時域與頻域的形狀竟然是一樣的(別的性質也很爽,比如多維高斯分布的邊緣分布也是高斯分布,kalman filter就是hmm利用了高斯分布的性質搞出來的)。。所以直接用它對原信號做卷積,其實就相當於變換到頻域,逐點相乘再逆變換,然後你看哈,用那形狀做逐點相乘,不就保留了低頻信號(靠近原點)了嘛,而且過度絲般順滑。。
就這樣


關於高斯模糊的詳細介紹及python代碼實現

高斯模糊的演算法


講的是Gaussian Blur,講的很詳細,值得仔細閱讀!

Python最常用的圖像處理庫是PIL(PythonImaging Library),它內置了高斯模糊方法,簡單代碼如下:

[python] view plain copy

  1. import Image
  2. import ImageFilter
  3. im=Image.open("im.jpg")
  4. im=im.filter(ImageFilter.GaussianBlur(radius=2))
  5. im.show()

其中這個GaussianBlur()函數源代碼有錯誤:設置radius無效,也就是說radius不管設置成多大,都是按2來計算的。錯誤太明顯了!我們都知道radius越大圖像越模糊。這個錯誤在PIL源文件的ImageFilter.py中,第156行。截取部分源碼可知:

[python] view plain copy

  1. # Gaussian blur filter.
  2. class GaussianBlur(Filter):
  3. name = "GaussianBlur"
  4. def __init__(self, radius=2):
  5. self.radius = 2& &#這個地方明顯不對
  6. def filter(self, image):
  7. return image.gaussian_blur(self.radius)

自己改正過來:

[python] view plain copy

  1. class MyGaussianBlur(ImageFilter.Filter):
  2. name="GaussianBlur"
  3. def __init__(self, radius=2):
  4. self.radius=radius
  5. def filter(self, image):
  6. return image.gaussian_blur(self.radius)

其實,上面的例子關於高斯模糊是不完整的。它沒有提供設置sigema值的方法,只提供了設置radius的方法。在pyOpenCV中應該有完整的高斯模糊函數,可以直接調用。我決定簡單地自己實現一下:


下面是具體代碼,只用了PIL庫和numpy,PIL僅用於讀寫圖像, numpy用於矩陣計算。全過程完全符合阮一峰的網路日誌



[python] view plain copy

  1. # -*- coding: utf-8 -*-
  2. import math
  3. import numpy as np
  4. import Image
  5. class MyGaussianBlur():
  6. #初始化
  7. def __init__(self, radius=1, sigema=1.5):
  8. self.radius=radius
  9. self.sigema=sigema
  10. #高斯的計算公式
  11. def calc(self,x,y):
  12. res1=1/(2*math.pi*self.sigema*self.sigema)
  13. res2=math.exp(-(x*x+y*y)/(2*self.sigema*self.sigema))
  14. return res1*res2
  15. #得到濾波模版
  16. def template(self):
  17. sideLength=self.radius*2+1
  18. result = np.zeros((sideLength, sideLength))
  19. for i in range(sideLength):
  20. for j in range(sideLength):
  21. result[i,j]=self.calc(i-self.radius, j-self.radius)
  22. all=result.sum()
  23. return result/all
  24. #濾波函數
  25. def filter(self, image, template):
  26. arr=np.array(image)
  27. h=arr.shape[0]
  28. w=arr.shape[1]
  29. newData=np.zeros((height, width))
  30. for i in range(self.radius, height-self.radius):
  31. for j in range(self.radius, width-self.radius):
  32. t=arr[i-self.radius:i+self.radius+1, j-self.radius:j+self.radius+1]
  33. a= np.multiply(t, template)
  34. newData[i, j] = a.sum()
  35. newImage = Image.fromarray(newData)
  36. return newImage
  37. r=1 #模版半徑,自己自由調整
  38. s=2 #sigema數值,自己自由調整
  39. GBlur=MyGaussianBlur(radius=r, sigema=s)#聲明高斯模糊類
  40. temp=GBlur.template()#得到濾波模版
  41. im=Image.open("lena1.bmp")#打開圖片
  42. image=GBlur.filter(im, temp)#高斯模糊濾波,得到新的圖片
  43. image.show()#圖片顯示

原圖像:



高斯模糊後的圖像:



我這個例子同時提供了設置radius和sigema的方法。歡迎大家批評指正。


缺點:


運算時間太長了,跟要處理圖像的大小成正比。為了測試能快一點,我只能把lena只取一張臉來測試了!另外,沒有考慮邊緣像素!

轉自:關於高斯模糊的詳細介紹及python代碼實現 - 坤的專欄 - 博客頻道 - CSDN.NET
轉自:PIL處理圖像(一) - 坤的專欄 - 博客頻道 - CSDN.NET


只是一個二維低通fir濾波器罷了。
生成一個卷積核(用高斯函數或者firwin),然後對圖像用這個核做卷積就好了。


Android 的話 SDK 已經提供了方法實現高斯模糊。


就相當於用一個二維正態分布的參數作為係數對圖像進行濾波,而二維正態分布關於原點對稱的,所以最後的應用方式非常簡單。


厲害了


就是圖像和高斯函數來一髮捲積運算。高斯函數就是正態分布那個東西


推薦閱讀:

用漢語能開發出計算機軟、硬體嗎?
什麼是函數式編程思維?
王小波的計算機水平到底有多好?
epoll 或者 kqueue 的原理是什麼?
作為一個進大學才學編程的學生,如何能以後達到溫趙輪三位大神的水平?

TAG:界面 | 演算法 | 編程 | iOS 開發 | Android 開發 |