以下文字内容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]
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:
/**
* 函數名: medianfilter
* 功 能: 對圖像進行水準中值濾波處理
*/
void medianfilter()
unsigned char g[3]; //要取的三個點
//注意邊界點不處理,是以i從1到高度-2,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];