高斯模糊的原理是什麼,怎樣在界面中實現?
高斯模糊,聽起來很高大上,其實就是一種很基礎的數學演算法應用,不要被「高斯」迷惑了雙眼,它並沒有高斯本人那麼深不可測。。
高斯模糊之所以叫高斯模糊,是因為它運用了高斯的正態分布的密度函數,我記得是大二時候學的:
其中,μ是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 高斯分布
絕大多數講高斯濾波的文章或者博客都會給出下面的公式:
高斯分布公式:
以及告訴你它的形狀和性質:
(如圖為一維高斯分布示意圖,決定了分布對稱中心,決定了分布的形狀——越小形狀越瘦高,越大越矮胖)
然而在這裡高斯分布公式到底是做什麼的?
答:計算高斯濾波用的掩膜(Mask)
1.2 利用高斯分布求高斯掩膜
1.2.1 均值掩膜
首先看一下掩膜到底長什麼樣子,我們來一個最簡單的——3x3均值濾波的掩膜:
(對,就這個樣子)
可以看到,均值掩膜內所有係數均相等——為1/9,且他們的和為1。同理可知5x5均值濾波的掩膜。
1.2.2 高斯掩膜
高斯分布公式終於要出場了!
有個小問題:我們上面給出的高斯分布公式是一維的,但掩膜是二維的怎麼辦?
很簡單,只需要把數值變為向量即可:
(其中不必糾結於係數,因為它只是!一個! 常數!並不會影響互相之間的比例關係,並且最終都要進行歸一化,所以在實際計算時我們是忽略它而只計算後半部分的)
當為二維時,。這個公式其實可以表示任意維度的高斯分布。
需要特別注意的是:
本質上都是二維空間中的坐標:是掩膜內任一點的坐標,是掩膜中心的坐標!(統計學中稱為均值,即坐標的均值)
敲黑板再說一遍: x和u, 是位置,是坐標!
於是乎,對於圖像中任意一點(x,y),它周圍的坐標為:
中間的(x,y)就是我們公式中的了, 當遍歷(x-1,y-1)...(x+1,y+1)時,
:
(只剩下常數沒有x,y是因為在相減的過程中把x,y抵消掉了)
:
(這裡取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. 雙邊濾波
雙邊濾波其實很簡單,它只是比高斯濾波多了一種掩膜而已。兩種掩膜都是套用高斯分布公式得出來的,只是其中的代表的含義不同而已。
求第一個掩膜所用的高斯分布中,代表掩膜中每一個點的位置,其中為掩膜中心的位置;
求第二個掩膜所用的高斯分布中,代表掩膜中每一個點的像素亮度值,其中為掩膜中心的像素亮度值;
(這裡有一個是需要注意的:第二個掩膜是需要每次都求取的,因為像素的亮度值無法預先確定,不是維持一個固定不變的pattern)
對這就是雙邊濾波,不光考慮了像素在空間中位置遠近程度的影響,還考慮了像素亮度相近程度的影響。
僅此而已。
客官,來都來了,點個贊再走唄(?&>ω&<*?)
常用的兩種模糊演算法,一種是均值,一種是高斯,實質上是分別與不同的卷積核做二維卷積,產生一種低通濾波的效果。同樣一些銳度提高的方法其實是反過來做高頻增強。
均值在頻譜上大致是用sinc函數做過濾,這個函數有零點和旁瓣,導致的缺點是特定頻率會完全過濾掉,比如說特定周期重複的斑紋,做平均之後剛好到處都是0,更高一些頻率的反而留下來了。
高斯模糊就不會這樣,它可以讓低頻到高頻逐次減小,而且很容易控制。
了解原理之後就可以知道,經過模糊的圖像,有可能通過再做一次卷積和上一次卷積抵消,來恢復出原始的圖像,千萬不要用來為非常重要信息打碼。
另外理想低通濾波應該是用sinc卷積,不知道為什麼從沒見過呢……
2/21更新
貼點數據
高斯核尺寸:
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,完全沒法用在工程中,只是演示一下效果罷了。
我不是要噴,只是覺得最高票並沒有說到本質;
核心只有三點:卷積、傅里葉變換、高斯濾波器。。
圖像(或者一維信號),可以理解為一堆餘弦信號的疊加,所謂的「模糊」,其實就是把高頻信號(邊緣)過濾掉,把低頻信號(輪廓)保留;
但是在時域看不出所謂高頻低頻呀怎麼辦?所以就需要做個傅里葉變換,轉換成頻域,然後直接在頻域上把高頻對應的「基」的權值置零(這個好像叫理想濾波器),然後再做逆傅里葉變換;
但是這樣做好慢,要做兩次傅里葉變換,生產中不大可能這麼干,所以有沒辦法直接在時域干相同的事情呢?必須是有的,這裡涉及到一些數學公式,簡單來說就是:時域的卷積,相當於,頻域的傅里葉變換逐點相乘();
所以一般來說,設計一個濾波器,都要從頻域去看它的性質,然後逆變換到時域,跟原信號做卷積就成了,而上述的理想濾波器,逆變換後在時域的形狀有點複雜,不大適合做卷積(這個理由其實是我自己吹的);
那麼高斯分布這個怪物出現了,這個東西它在時域與頻域的形狀竟然是一樣的(別的性質也很爽,比如多維高斯分布的邊緣分布也是高斯分布,kalman filter就是hmm利用了高斯分布的性質搞出來的)。。所以直接用它對原信號做卷積,其實就相當於變換到頻域,逐點相乘再逆變換,然後你看哈,用那形狀做逐點相乘,不就保留了低頻信號(靠近原點)了嘛,而且過度絲般順滑。。
就這樣
關於高斯模糊的詳細介紹及python代碼實現
高斯模糊的演算法
講的是Gaussian Blur,講的很詳細,值得仔細閱讀!
Python最常用的圖像處理庫是PIL(PythonImaging Library),它內置了高斯模糊方法,簡單代碼如下:
[python] view plain copy
- import Image
- import ImageFilter
- im=Image.open("im.jpg")
- im=im.filter(ImageFilter.GaussianBlur(radius=2))
- im.show()
其中這個GaussianBlur()函數源代碼有錯誤:設置radius無效,也就是說radius不管設置成多大,都是按2來計算的。錯誤太明顯了!我們都知道radius越大圖像越模糊。這個錯誤在PIL源文件的ImageFilter.py中,第156行。截取部分源碼可知:
[python] view plain copy
- # Gaussian blur filter.
- class GaussianBlur(Filter):
- name = "GaussianBlur"
- def __init__(self, radius=2):
- self.radius = 2& &#這個地方明顯不對
- def filter(self, image):
- return image.gaussian_blur(self.radius)
自己改正過來:
[python] view plain copy
- class MyGaussianBlur(ImageFilter.Filter):
- name="GaussianBlur"
- def __init__(self, radius=2):
- self.radius=radius
- def filter(self, image):
- return image.gaussian_blur(self.radius)
其實,上面的例子關於高斯模糊是不完整的。它沒有提供設置sigema值的方法,只提供了設置radius的方法。在pyOpenCV中應該有完整的高斯模糊函數,可以直接調用。我決定簡單地自己實現一下:
下面是具體代碼,只用了PIL庫和numpy,PIL僅用於讀寫圖像, numpy用於矩陣計算。全過程完全符合阮一峰的網路日誌
[python] view plain copy
- # -*- coding: utf-8 -*-
- import math
- import numpy as np
- import Image
- class MyGaussianBlur():
- #初始化
- def __init__(self, radius=1, sigema=1.5):
- self.radius=radius
- self.sigema=sigema
- #高斯的計算公式
- def calc(self,x,y):
- res1=1/(2*math.pi*self.sigema*self.sigema)
- res2=math.exp(-(x*x+y*y)/(2*self.sigema*self.sigema))
- return res1*res2
- #得到濾波模版
- def template(self):
- sideLength=self.radius*2+1
- result = np.zeros((sideLength, sideLength))
- for i in range(sideLength):
- for j in range(sideLength):
- result[i,j]=self.calc(i-self.radius, j-self.radius)
- all=result.sum()
- return result/all
- #濾波函數
- def filter(self, image, template):
- arr=np.array(image)
- h=arr.shape[0]
- w=arr.shape[1]
- newData=np.zeros((height, width))
- for i in range(self.radius, height-self.radius):
- for j in range(self.radius, width-self.radius):
- t=arr[i-self.radius:i+self.radius+1, j-self.radius:j+self.radius+1]
- a= np.multiply(t, template)
- newData[i, j] = a.sum()
- newImage = Image.fromarray(newData)
- return newImage
- r=1 #模版半徑,自己自由調整
- s=2 #sigema數值,自己自由調整
- GBlur=MyGaussianBlur(radius=r, sigema=s)#聲明高斯模糊類
- temp=GBlur.template()#得到濾波模版
- im=Image.open("lena1.bmp")#打開圖片
- image=GBlur.filter(im, temp)#高斯模糊濾波,得到新的圖片
- image.show()#圖片顯示
原圖像:
高斯模糊後的圖像:
我這個例子同時提供了設置radius和sigema的方法。歡迎大家批評指正。
缺點:
運算時間太長了,跟要處理圖像的大小成正比。為了測試能快一點,我只能把lena只取一張臉來測試了!另外,沒有考慮邊緣像素!
轉自:關於高斯模糊的詳細介紹及python代碼實現 - 坤的專欄 - 博客頻道 - CSDN.NET
轉自:PIL處理圖像(一) - 坤的專欄 - 博客頻道 - CSDN.NET
只是一個二維低通fir濾波器罷了。
生成一個卷積核(用高斯函數或者firwin),然後對圖像用這個核做卷積就好了。
Android 的話 SDK 已經提供了方法實現高斯模糊。
就相當於用一個二維正態分布的參數作為係數對圖像進行濾波,而二維正態分布關於原點對稱的,所以最後的應用方式非常簡單。
厲害了
就是圖像和高斯函數來一髮捲積運算。高斯函數就是正態分布那個東西
推薦閱讀:
※用漢語能開發出計算機軟、硬體嗎?
※什麼是函數式編程思維?
※王小波的計算機水平到底有多好?
※epoll 或者 kqueue 的原理是什麼?
※作為一個進大學才學編程的學生,如何能以後達到溫趙輪三位大神的水平?