圖像編程學習筆記8――圖像的平滑(去噪)
第一種方法:高斯模版
以下文字內容copy于<<數字圖像處理編程入門>>,code為自己實現,是win32控制臺程序。
先舉個例子說明一下什么是平滑(smoothing),如下面兩幅圖所示:可以看到,圖3.2比圖3.1柔和一些(也模糊一些)。是不是覺得很神奇?其實實現起來很簡單。我們將原圖中的每一點的灰度和它周圍八個點的灰度相加,然后除以9,作為新圖中對應點的灰度,就能實現上面的效果。
這么做并非瞎蒙,而是有其道理的。大概想一想,也很容易明白。舉個例子,就象和面一樣,先在中間加點水,然后不斷把周圍的面和進來,攪拌幾次,面就均勻了。
用信號處理的理論來解釋,這種做法實現的是一種簡單的低通濾波器(low pass filter)。哇,好深奧呀!不要緊,這些理論的內容并不多,而且知道一些理論也是很有好處的。在灰度連續變化的圖象中,如果出現了與相鄰象素的灰度相差很大的點,比如說一片暗區中突然出現了一個亮點,人眼能很容易覺察到。就象看老電影時,由于膠片太舊,屏幕上經常會出現一些亮斑。這種情況被認為是一種噪聲。灰度突變在頻域中代表了一種高頻分量,低通濾波器的作用就是濾掉高頻分量,從而達到減少圖象噪聲的目的。
為了方便地敘述上面所說的“將原圖中的每一點的灰度和它周圍八個點的灰度相加,然后除以9,作為新圖中對應點的灰度”這一操作,我們采用如下的表示方法:
(3.1)
這種表示方法有點象矩陣,我們稱其為模板(template)。中間的黑點表示中心元素,即,用哪個元素做為處理后的元素。例如[2. 1]表示將自身的2倍加上右邊的元素作為新值,而[2 1.]表示將自身加上左邊元素的2倍作為新值。
通常,模板不允許移出邊界,所以結果圖象會比原圖小,例如模板是,原圖是
,經過模板操作后的圖象為
;其中數字代表灰度,x表示邊界上無法進行模板操作的點,通常的做法是復制原圖的灰度,不進行任何處理。
模板操作實現了一種鄰域運算(NeighborhoodOperation),即某個象素點的結果灰度不僅和該象素灰度有關,而且和其鄰域點的值有關。在以后介紹的細化算法中,我們還將接觸到鄰域運算。模板運算的數學涵義是一種卷積(或互相關)運算,你不需要知道卷積的確切含義,只要有這么一個概念就可以了。
模板運算在圖象處理中經常要用到,可以看出,它是一項非常耗時的運算。以
(3.2)
為例,每個象素完成一次模板操作要用9個乘法、8個加法、1個除法。對于一幅n×n(寬度×高度)的圖象,就是9n2個乘法,8n2個加法和n2個除法,算法復雜度為O(n2),這對于大圖象來說,是非常可怕的。所以,一般常用的模板并不大,如3×3,4×4。有很多專用的圖象處理系統,用硬件來完成模板運算,大大提高了速度。另外,可以設法將二維模板運算轉換成一維模板運算,對速度的提高也是非常可觀的。例如,(3.2)式可以分解成一個水平模板和一個垂直模板,即,
(3.3)
我們來驗證一下。
設圖象為 ,經過(3.2)式處理后變為
,經過(3.3)式處理后變為
,兩者完全一樣。如果計算時不考慮周圍一圈的象素,前者做了4×(9個乘法,8個加法,1個除法),共36個乘法,32個加法,4個除法;后者做了4×(3個乘法,2個加法)+4×(3個乘法,2個加法)+4個除法,共24個乘法,16個加法,4個除法,運算簡化了不少,如果是大圖,效率的提高將是非常客觀的。
平滑模板的思想是通過將一點和周圍8個點作平均,從而去除突然變化的點,濾掉噪聲,其代價是圖象有一定程度的模糊。上面提到的模板(3.1),就是一種平滑模板,稱之為Box模板。Box模板雖然考慮了鄰域點的作用,但并沒有考慮各點位置的影響,對于所有的9個點都一視同仁,所以平滑的效果并不理想。實際上我們可以想象,離某點越近的點對該點的影響應該越大,為此,我們引入了加權系數,將原來的模板改造成,可以看出,距離越近的點,加權系數越大。
新的模板也是一個常用的平滑模板,稱為高斯(Gauss)模板。為什么叫這個名字,這是因為這個模板是通過采樣2維高斯函數得到的。
設圖象為 ,分別用兩種平滑模板處理(周圍一圈象素直接從原圖拷貝)。采用Box模板的結果為
,采用高斯模板的結果為
。
可以看到,原圖中出現噪聲的區域是第2行第2列和第3行第2列,灰度從2一下子跳到了6,用Box模板處理后,灰度從3.11跳到4.33;用高斯模板處理后,灰度從3.跳到4.56,都緩和了跳變的幅度,從這一點上看,兩者都達到了平滑的目的。但是,原圖中的第3,第4行總的來說,灰度值是比較高的,經模板1處理后,第3行第2列元素的灰度變成了4.33,與第3,第4行的總體灰度相比偏小,另外,原圖中第3行第2列元素的灰度為6,第3行第3列元素的灰度為4,變換后,后者4.56反而比前者4.33大了。而采用高斯模板沒有出現這些問題,究其原因,就是因為它考慮了位置的影響。
舉個實際的例子:下圖中,從左到右分別是原圖,用高斯模板處理的圖,用Box模板處理的圖,可以看出,采用高斯模板,在實現平滑效果的同時,要比Box模板清晰一些。
功能實現函數代碼:
[cpp] view plaincopy
- double tem[9] = {1.0,2.0,1.0,2.0,4.0,2.0,1.0,2.0,1.0};
- void smooth()
- {
- int height = bmpInfoHeader.biHeight;
- int width = bmpInfoHeader.biWidth;
- int imgSize = bmpInfoHeader.biSizeImage;
- int lineByte = (width * 8 +31) / 32 * 4; //每行像素所占字節數
- //處理是基于原圖的,所以原圖的數據不能改變,用pNewBmpData存儲改變之后的數據
- memcpy(pNewBmpData,pBmpData,imgSize); //把原圖數據復制給pNewBmpData
- double sum;
- for(int i = 1; i < height - 1; i++ )
- {
- for(int j = 1; j < width - 1; j++ )
- {
- sum = 0; //清零
- sum += (double)(*(pBmpData + (i-1) * lineByte + j - 1)) * tem[0]; //該點左下角
- sum += (double)(*(pBmpData + (i-1) * lineByte + j)) * tem[1]; //下
- sum += (double)(*(pBmpData + (i-1) * lineByte + j + 1)) * tem[2]; //右下
- sum += (double)(*(pBmpData + i * lineByte + j - 1)) * tem[3]; //左
- sum += (double)(*(pBmpData + i * lineByte + j)) * tem[4]; //該點位置
- sum += (double)(*(pBmpData + i * lineByte + j + 1)) * tem[5]; //右
- sum += (double)(*(pBmpData + (i+1) * lineByte + j - 1)) * tem[6]; //左上
- sum += (double)(*(pBmpData + (i+1) * lineByte + j)) * tem[7]; //上
- sum += (double)(*(pBmpData + (i+1) * lineByte + j + 1)) * tem[8]; //右上
- *(pNewBmpData + i * lineByte + j) = (unsigned char)(sum / 16.0);
- }
- }
- }
第二種方法:中值濾波
中值濾波也是一種典型的低通濾波器,它的目的是保護圖象邊緣的同時去除噪聲。所謂中值濾波,是指把以某點(x,y)為中心的小窗口內的所有象素的灰度按從大到小的順序排列,將中間值作為(x,y)處的灰度值(若窗口中有偶數個象素,則取兩個中間值的平均)。中值濾波是如何去除噪聲的呢?舉個例子就很容易明白了。
圖中數字代表該處的灰度。可以看出原圖中間的6和周圍的灰度相差很大,是一個噪聲點。經過3×1窗口(即水平3個象素取中間值)的中值濾波,得到右邊那幅圖,可以看出,噪聲點被去除了。
下面將中值濾波和上面介紹的兩種平滑模板作個比較,看看中值濾波有什么特點。我們以一維模板為例,只考慮水平方向,大小為3×1(寬×高)。Box模板為 ,高斯模板為
。
先考察第一幅圖:
從原圖中不難看出左邊區域灰度值低,右邊區域灰度值高,中間有一條明顯的邊界,這一類圖象稱之為“step”(就象灰度上了個臺階)。應用平滑模板后,圖象平滑了,但是也使邊界模糊了。應用中值濾波,就能很好地保持原來的邊界。所以說,中值濾波的特點是保護圖象邊緣的同時去除噪聲。
再看第二幅圖:
不難看出,原圖中有很多噪聲點(灰度為正代表灰度值高的點,灰度為負代表灰度值低的點),而且是雜亂無章,隨機分布的。這也是一類很典型的圖,稱之為高斯噪聲。經過Box平滑,噪聲的程度有所下降。Gauss模板對付高斯噪聲非常有效。而中值濾波對于高斯噪聲則無能為力。
最后看第三幅圖:
從原圖中不難看出,中間的灰度要比兩邊高許多。這也是一類很典型的圖,稱之為脈沖 (impulse)。可見,中值濾波對脈沖噪聲非常有效。
綜合以上三類圖,不難得出下面的結論:中值濾波容易去除孤立點,線的噪聲同時保持圖象的邊緣;它能很好的去除二值噪聲,但對高斯噪聲無能為力。要注意的是,當窗口內噪聲點的個數大于窗口寬度的一半時,中值濾波的效果不好。這是很顯然的。
CODE:
[cpp] view plaincopy
- /**
- * 函數名: medianFilter
- * 功 能: 對圖像進行水平中值濾波處理
- */
- void medianFilter()
- {
- int height = bmpInfoHeader.biHeight;
- int width = bmpInfoHeader.biWidth;
- int imgSize = bmpInfoHeader.biSizeImage;
- int lineByte = (width * 8 +31) / 32 * 4; //每行像素所占字節數
- //處理是基于原圖的,所以原圖的數據不能改變,用pNewBmpData存儲改變之后的數據
- memcpy(pNewBmpData,pBmpData,imgSize); //把原圖數據復制給pNewBmpData
- unsigned char g[3]; //要取的三個點
- //注意邊界點不處理,所以i從1到高度-2,j類似
- for(int i = 1; i < height - 1; i++ )
- {
- for(int j = 1; j < width - 1; j++ )
- {
- g[0] = *(pBmpData + i * lineByte + j - 1);
- g[1] = *(pBmpData + i * lineByte + j);
- g[2] = *(pBmpData + i * lineByte + j + 1);
- sort(g,g+3); //排序
- *(pNewBmpData + i * lineByte + j) = g[1];
- }
- }
- }