只腐蚀毛刺 腐蚀算法_图像的腐蚀 膨胀及细化
轉(zhuǎn)載:http://blog.sina.com.cn/s/blog_4bdb170b0100sgtj.html
今天所講的內(nèi)容屬于一門新興的學(xué)科:數(shù)學(xué)形態(tài)學(xué)(Mathematical Morphology)。說起來很有意思,它是法國和德國的科學(xué)家在研究巖石結(jié)構(gòu)時建立的一門學(xué)科。形態(tài)學(xué)的用途主要是獲取物體拓?fù)浜徒Y(jié)構(gòu)信息,它通過物體和結(jié)構(gòu)元素相互作用的某些運算,得到物體更本質(zhì)的形態(tài)。在圖象處理中的應(yīng)用主要是:(1)利用形態(tài)學(xué)的基本運算,對圖象進(jìn)行觀察和處理,從而達(dá)到改善圖象質(zhì)量的目的;(2)描述和定義圖象的各種幾何參數(shù)和特征,如面積、周長、連通度、顆粒度、骨架和方向性等。
限于篇幅,我們只介紹二值圖象的形態(tài)學(xué)運算,對于灰度圖象的形態(tài)學(xué)運算,有興趣的讀者可以閱讀有關(guān)的參考書。在程序中,為了處理的方便,還是采用256級灰度圖,不過只用到了調(diào)色板中的0和255兩項。
先來定義一些基本符號和關(guān)系。
1. 元素
設(shè)有一幅圖象X,若點a在X的區(qū)域以內(nèi),則稱a為X的元素,記作a∈X,如圖6.1所示。
2. B包含于X
設(shè)有兩幅圖象B,X。對于B中所有的元素ai,都有ai∈X,則稱B包含于(included in)X,記作B X,如圖6.2所示。
3. B擊中X
設(shè)有兩幅圖象B,X。若存在這樣一個點,它即是B的元素,又是X的元素,則稱B擊中(hit)X,記作B↑X,如圖6.3所示。
4. B不擊中X
設(shè)有兩幅圖象B,X。若不存在任何一個點,它即是B的元素,又是X的元素,即B和X的交集是空,則稱B不擊中(miss)X,記作B∩X=Ф;其中∩是集合運算相交的符號,Ф表示空集。如圖6.4所示。
圖6.1 元素
圖6.2 包含
圖6.3 擊中
圖6.4 不擊中
5. 補(bǔ)集
設(shè)有一幅圖象X,所有X區(qū)域以外的點構(gòu)成的集合稱為X的補(bǔ)集,記作Xc,如圖6.5所示。顯然,如果B∩X=Ф,則B在X的補(bǔ)集內(nèi),即B Xc。
圖6.5 補(bǔ)集的示意圖
6. 結(jié)構(gòu)元素
設(shè)有兩幅圖象B,X。若X是被處理的對象,而B是用來處理X的,則稱B為結(jié)構(gòu)元素(structure element),又被形象地稱做刷子。結(jié)構(gòu)元素通常都是一些比較小的圖象。
7. 對稱集
設(shè)有一幅圖象B,將B中所有元素的坐標(biāo)取反,即令(x,y)變成(-x,-y),所有這些點構(gòu)成的新的集合稱為B的對稱集,記作Bv,如圖6.6所示。
8. 平移
設(shè)有一幅圖象B,有一個點a(x0,y0),將B平移a后的結(jié)果是,把B中所有元素的橫坐標(biāo)加x0,縱坐標(biāo)加y0,即令(x,y)變成(x+x0,y+y0),所有這些點構(gòu)成的新的集合稱為B的平移,記作Ba,如圖6.7所示。
圖6.6 對稱集的示意圖
圖6.7 平移的示意圖
好了,介紹了這么多基本符號和關(guān)系,現(xiàn)在讓我們應(yīng)用這些符號和關(guān)系,看一下形態(tài)學(xué)的基本運算。
6.1 腐蝕
把結(jié)構(gòu)元素B平移a后得到Ba,若Ba包含于X,我們記下這個a點,所有滿足上述條件的a點組成的集合稱做X被B腐蝕(Erosion)的結(jié)果。用公式表示為:E(X)={a| Ba X}=X
B,如圖6.8所示。
圖6.8 腐蝕的示意圖
圖6.8中X是被處理的對象,B是結(jié)構(gòu)元素。不難知道,對于任意一個在陰影部分的點a,Ba 包含于X,所以X被B腐蝕的結(jié)果就是那個陰影部分。陰影部分在X的范圍之內(nèi),且比X小,就象X被剝掉了一層似的,這就是為什么叫腐蝕的原因。
值得注意的是,上面的B是對稱的,即B的對稱集Bv=B,所以X被B腐蝕的結(jié)果和X被 Bv腐蝕的結(jié)果是一樣的。如果B不是對稱的,讓我們看看圖6.9,就會發(fā)現(xiàn)X被B腐蝕的結(jié)果和X被 Bv腐蝕的結(jié)果不同。
圖6.9 結(jié)構(gòu)元素非對稱時,腐蝕的結(jié)果不同
圖6.8和圖6.9都是示意圖,讓我們來看看實際上是怎樣進(jìn)行腐蝕運算的。
在圖6.10中,左邊是被處理的圖象X(二值圖象,我們針對的是黑點),中間是結(jié)構(gòu)元素B,那個標(biāo)有origin的點是中心點,即當(dāng)前處理元素的位置,我們在介紹模板操作時也有過類似的概念。腐蝕的方法是,拿B的中心點和X上的點一個一個地對比,如果B上的所有點都在X的范圍內(nèi),則該點保留,否則將該點去掉;右邊是腐蝕后的結(jié)果??梢钥闯?#xff0c;它仍在原來X的范圍內(nèi),且比X包含的點要少,就象X被腐蝕掉了一層。
圖6.10 腐蝕運算
圖6.11為原圖,圖6.12為腐蝕后的結(jié)果圖,能夠很明顯地看出腐蝕的效果。
圖6.11 原圖
圖6.12 腐蝕后的結(jié)果圖
下面的這段程序,實現(xiàn)了上述的腐蝕運算,針對的都是黑色點。參數(shù)中有一個BOOL變量,為真時,表示在水平方向進(jìn)行腐蝕運算,即結(jié)構(gòu)元素B為 ;否則在垂直方向上進(jìn)行腐蝕運算,即結(jié)構(gòu)元素B為 。
BOOL Erosion(HWND hWnd,BOOL Hori)
{
DWORD????????????????????? ?????? OffBits,BufSize;
LPBITMAPINFOHEADER??? lpImgData;
LPSTR????????????? ???? lpPtr;
HLOCAL?????? ?????????? hTempImgData;
LPBITMAPINFOHEADER??? lpTempImgData;
LPSTR????????????? ?????? ?????? lpTempPtr;
HDC??????????????? ????? hDc;
HFILE????????????? ????? hf;
LONG?????????????? ???? x,y;
unsigned char????? ??????? num;
int??????????????? ??????? i;
//為了處理方便,仍采用256級灰度圖,不過只用調(diào)色板中0和255兩項
if( NumColors!=256){
MessageBox(hWnd,"Must be a mono bitmap with grayscale palette!",
"Error Message",MB_OK|MB_ICONEXCLAMATION);
return FALSE;
}
OffBits=bf.bfOffBits-sizeof(BITMAPFILEHEADER);
//BufSize為緩沖區(qū)大小
BufSize=OffBits+bi.biHeight*LineBytes;
//為新的緩沖區(qū)分配內(nèi)存
if((hTempImgData=LocalAlloc(LHND,BufSize))==NULL)
{
MessageBox(hWnd,"Error alloc memory!","Error Message",
MB_OK|MB_ICONEXCLAMATION);
return FALSE;
}
lpImgData=(LPBITMAPINFOHEADER)GlobalLock(hImgData);
lpTempImgData=(LPBITMAPINFOHEADER)LocalLock(hTempImgData);
//拷貝頭信息和位圖數(shù)據(jù)
memcpy(lpTempImgData,lpImgData,BufSize);
if(Hori)
{
//在水平方向進(jìn)行腐蝕運算
for(y=0;y
//lpPtr指向原圖數(shù)據(jù),lpTempPtr指向新圖數(shù)據(jù)
lpPtr=(char *)lpImgData+(BufSize-LineBytes-y*LineBytes)+1;
lpTempPtr=(char*)lpTempImgData+
(BufSize-LineBytes-y*LineBytes)+1;
for(x=1;x
//注意為防止越界,x的范圍從1到寬度-2
num=(unsigned char)*lpPtr;
if (num==0){? //因為腐蝕掉的是黑點,所以只對黑點處理
*lpTempPtr=(unsigned char)0;? //先置成黑點
for(i=0;i<3;i++){
num=(unsigned char)*(lpPtr+i-1);
if(num==255){
//自身及上下鄰居中若有一個不是黑點,則將該點腐
//蝕成白點
*lpTempPtr=(unsigned char)255;
break;
}
}
}
//原圖中就是白點的,新圖中仍是白點
else *lpTempPtr=(unsigned char)255;
//指向下一個象素
lpPtr++;
lpTempPtr++;
}
}
}
else{
//在垂直方向進(jìn)行腐蝕運算
for(y=1;y
//lpPtr指向原圖數(shù)據(jù),lpTempPtr指向新圖數(shù)據(jù)
lpPtr=(char *)lpImgData+(BufSize-LineBytes-y*LineBytes);
lpTempPtr=(char *)lpTempImgData+(BufSize-LineBytes-y*LineBytes);
for(x=0;x
num=(unsigned char)*lpPtr;
if (num==0){ //因為腐蝕掉的是黑點,所以只對黑點處理
*lpTempPtr=(unsigned char)0; //先置成黑點
for(i=0;i<3;i++){
num=(unsigned char)*(lpPtr+(i-1)*LineBytes);
if(num==255){
//自身及上下鄰居中若有一個不是黑點,則將該點腐
//蝕成白點
*lpTempPtr=(unsigned char)255;
break;
}
}
}
//原圖中就是白點的,新圖中仍是白點
else *lpTempPtr=(unsigned char)255;
//指向下一個象素
lpPtr++;
lpTempPtr++;
}
}
}
if(hBitmap!=NULL)
DeleteObject(hBitmap);
hDc=GetDC(hWnd);
//產(chǎn)生新的位圖
hBitmap=CreateDIBitmap(hDc,(LPBITMAPINFOHEADER)lpTempImgData,
(LONG)CBM_INIT,
(LPSTR)lpTempImgData+
sizeof(BITMAPINFOHEADER)+
NumColors*sizeof(RGBQUAD),
(LPBITMAPINFO)lpTempImgData, DIB_RGB_COLORS);
//起不同的結(jié)果文件名
if(Hori)
hf=_lcreat("c:\\herosion.bmp",0);
else
hf=_lcreat("c:\\verosion.bmp",0);
_lwrite(hf,(LPSTR)&bf,sizeof(BITMAPFILEHEADER));
_lwrite(hf,(LPSTR)lpTempImgData,BufSize);
_lclose(hf);
//釋放內(nèi)存及資源
ReleaseDC(hWnd,hDc);
LocalUnlock(hTempImgData);
LocalFree(hTempImgData);
GlobalUnlock(hImgData);
return TRUE;
}
6.2 膨脹
膨脹(dilation)可以看做是腐蝕的對偶運算,其定義是:把結(jié)構(gòu)元素B平移a后得到Ba,若Ba擊中X,我們記下這個a點。所有滿足上述條件的a點組成的集合稱做X被B膨脹的結(jié)果。用公式表示為:D(X)={a | Ba↑X}=X
B,如圖6.13所示。圖6.13中X是被處理的對象,B是結(jié)構(gòu)元素,不難知道,對于任意一個在陰影部分的點a,Ba擊中X,所以X被B膨脹的結(jié)果就是那個陰影部分。陰影部分包括X的所有范圍,就象X膨脹了一圈似的,這就是為什么叫膨脹的原因。
同樣,如果B不是對稱的,X被B膨脹的結(jié)果和X被 Bv膨脹的結(jié)果不同。
讓我們來看看實際上是怎樣進(jìn)行膨脹運算的。在圖6.14中,左邊是被處理的圖象X(二值圖象,我們針對的是黑點),中間是結(jié)構(gòu)元素B。膨脹的方法是,拿B的中心點和X上的點及X周圍的點一個一個地對,如果B上有一個點落在X的范圍內(nèi),則該點就為黑;右邊是膨脹后的結(jié)果??梢钥闯?#xff0c;它包括X的所有范圍,就象X膨脹了一圈似的。
圖6.13 膨脹的示意圖
圖6.14 膨脹運算
圖6.15為圖6.11膨脹后的結(jié)果圖,能夠很明顯的看出膨脹的效果。
圖6.15 圖6.11膨脹后的結(jié)果圖
下面的這段程序,實現(xiàn)了上述的膨脹運算,針對的都是黑色點。參數(shù)中有一個BOOL變量,為真時,表示在水平方向進(jìn)行膨脹運算,即結(jié)構(gòu)元素B為 ;否則在垂直方向上進(jìn)行膨脹運算,即結(jié)構(gòu)元素B為 。
BOOL Dilation(HWND hWnd,BOOL Hori)
{
DWORD ??????????????????????????? OffBits,BufSize;
LPBITMAPINFOHEADER??? lpImgData;
LPSTR????????????? ???? lpPtr;
HLOCAL???????????? ???? hTempImgData;
LPBITMAPINFOHEADER??? lpTempImgData;
LPSTR???? ? ????????????? lpTempPtr;
HDC??????????????? ???? hDc;
HFILE????????????? ????? hf;
LONG ?????????????????? x,y;
unsigned char????? ??????? num;
int??????????????? ??????? i;
//為了處理的方便,仍采用256級灰度圖,不過只調(diào)色板中0和255兩項
if( NumColors!=256){
MessageBox(hWnd,"Must be a mono bitmap with grayscale palette!",
"Error Message",MB_OK|MB_ICONEXCLAMATION);
return FALSE;
}
OffBits=bf.bfOffBits-sizeof(BITMAPFILEHEADER);
//BufSize為緩沖區(qū)大小
BufSize=OffBits+bi.biHeight*LineBytes;
//為新的緩沖區(qū)分配內(nèi)存
if((hTempImgData=LocalAlloc(LHND,BufSize))==NULL)
{
MessageBox(hWnd,"Error alloc memory!","Error Message",
MB_OK|MB_ICONEXCLAMATION);
return FALSE;
}
lpImgData=(LPBITMAPINFOHEADER)GlobalLock(hImgData);
lpTempImgData=(LPBITMAPINFOHEADER)LocalLock(hTempImgData);
//拷貝頭信息和位圖數(shù)據(jù)
memcpy(lpTempImgData,lpImgData,BufSize);
if(Hori)
{
//在水平方向進(jìn)行膨脹運算
for(y=0;y
//lpPtr指向原圖數(shù)據(jù),lpTempPtr指向新圖數(shù)據(jù)
lpPtr=(char *)lpImgData+(BufSize-LineBytes-y*LineBytes)+1;
lpTempPtr=(char*)lpTempImgData+
(BufSize-LineBytes-y*LineBytes)+1;
for(x=1;x
//注意為防止越界,x的范圍從1到寬度-2
num=(unsigned char)*lpPtr;
//原圖中是黑點的,新圖中肯定也是,所以要考慮的是那些原圖
//中的白點,看是否有可能膨脹成黑點
if (num==255){
*lpTempPtr=(unsigned char)255; //先置成白點
for(i=0;i<3;i++){
num=(unsigned char)*(lpPtr+i-1);
//只要左右鄰居中有一個是黑點,就膨脹成黑點
if(num==0){
*lpTempPtr=(unsigned char)0;
break;
}
}
}
//原圖中就是黑點的,新圖中仍是黑點
else *lpTempPtr=(unsigned char)0;
//指向下一個象素
lpPtr++;
lpTempPtr++;
}
}
}
else{
//在垂直方向進(jìn)行腐蝕運算
for(y=1;y
lpPtr=(char *)lpImgData+(BufSize-LineBytes-y*LineBytes);
lpTempPtr=(char *)lpTempImgData+(BufSize-LineBytes-y*LineBytes);
for(x=0;x
num=(unsigned char)*lpPtr;
if (num==255){
*lpTempPtr=(unsigned char)255;
for(i=0;i<3;i++){
num=(unsigned char)*(lpPtr+(i-1)*LineBytes);
//只要上下鄰居中有一個是黑點,就膨脹成黑點
if(num==0){
*lpTempPtr=(unsigned char)0;
break;
}
}
}
else *lpTempPtr=(unsigned char)0;
lpPtr++;
lpTempPtr++;
}
}
}
if(hBitmap!=NULL)
DeleteObject(hBitmap);
hDc=GetDC(hWnd);
//產(chǎn)生新的位圖
hBitmap=CreateDIBitmap(hDc,(LPBITMAPINFOHEADER)lpTempImgData,
(LONG)CBM_INIT,
(LPSTR)lpTempImgData+
sizeof(BITMAPINFOHEADER)+
NumColors*sizeof(RGBQUAD),
(LPBITMAPINFO)lpTempImgData,
DIB_RGB_COLORS);
//起不同的結(jié)果文件名
if(Hori)
hf=_lcreat("c:\\hdilation.bmp",0);
else
hf=_lcreat("c:\\vdilation.bmp",0);
_lwrite(hf,(LPSTR)&bf,sizeof(BITMAPFILEHEADER));
_lwrite(hf,(LPSTR)lpTempImgData,BufSize);
_lclose(hf);
//釋放內(nèi)存及資源
ReleaseDC(hWnd,hDc);
LocalUnlock(hTempImgData);
LocalFree(hTempImgData);
GlobalUnlock(hImgData);
return TRUE;
}
腐蝕運算和膨脹運算互為對偶的,用公式表示為(X
B)c=(Xc
B),即X 被B腐蝕后的補(bǔ)集等于X的補(bǔ)集被B膨脹。這句話可以形象的理解為:河岸的補(bǔ)集為河面,河岸的腐蝕等價于河面的膨脹。你可以自己舉個例子來驗證一下這個關(guān)系。在有些情況下,這個對偶關(guān)系是非常有用的。例如:某個圖象處理系統(tǒng)用硬件實現(xiàn)了腐蝕運算,那么不必再另搞一套膨脹的硬件,直接利用該對偶就可以實現(xiàn)了。
6.3 開
先腐蝕后膨脹稱為開(open),即OPEN(X)=D(E(X))。
讓我們來看一個開運算的例子(見圖6.16):
圖6.16開運算
在圖16上面的兩幅圖中,左邊是被處理的圖象X(二值圖象,我們針對的是黑點),右邊是結(jié)構(gòu)元素B,下面的兩幅圖中左邊是腐蝕后的結(jié)果;右邊是在此基礎(chǔ)上膨脹的結(jié)果??梢钥吹?#xff0c;原圖經(jīng)過開運算后,一些孤立的小點被去掉了。一般來說,開運算能夠去除孤立的小點,毛刺和小橋(即連通兩塊區(qū)域的小點),而總的位置和形狀不變。這就是開運算的作用。要注意的是,如果B是非對稱的,進(jìn)行開運算時要用B的對稱集Bv膨脹,否則,開運算的結(jié)果和原圖相比要發(fā)生平移。圖6.17和圖6.18能夠說明這個問題。
圖6.17 用B膨脹后,結(jié)果向左平移了
圖6.18 用Bv膨脹后位置不變
圖6.17是用B膨脹的,可以看到,OPEN(X)向左平移了。圖18是用Bv膨脹的,可以看到,總的位置和形狀不變。
圖6.19為圖6.11經(jīng)過開運算后的結(jié)果。
圖6.19 圖6.11經(jīng)過開運算后的結(jié)果
開運算的源程序可以很容易的根據(jù)上面的腐蝕,膨脹程序得到,這里就不給出了。
6.4 閉
先膨脹后腐蝕稱為閉(close),即CLOSE(X)=E(D(X))。
讓我們來看一個閉運算的例子(見圖6.20):
圖6.20 閉運算
在圖6.20上面的兩幅圖中,左邊是被處理的圖象X(二值圖象,我們針對的是黑點),右邊是結(jié)構(gòu)元素B,下面的兩幅圖中左邊是膨脹后的結(jié)果,右邊是在此基礎(chǔ)上腐蝕的結(jié)果可以看到,原圖經(jīng)過閉運算后,斷裂的地方被彌合了。一般來說,閉運算能夠填平小湖(即小孔),彌合小裂縫,而總的位置和形狀不變。這就是閉運算的作用。同樣要注意的是,如果B是非對稱的,進(jìn)行閉運算時要用B的對稱集Bv膨脹,否則,閉運算的結(jié)果和原圖相比要發(fā)生平移。
圖6.21為圖6.11經(jīng)過閉運算后的結(jié)果。
圖6.21 圖.611經(jīng)過閉運算后的結(jié)果
閉運算的源程序可以很容易的根據(jù)上面的膨脹,腐蝕程序得到,這里就不給出了。
你大概已經(jīng)猜到了,開和閉也是對偶運算,的確如此。用公式表示為(OPEN(X))c=CLOSE((Xc)),或者(CLOSE(X))c =OPEN((Xc))。即X 開運算的補(bǔ)集等于X的補(bǔ)集的閉運算,或者X 閉運算的補(bǔ)集等于X的補(bǔ)集的開運算。這句話可以這樣來理解:在兩個小島之間有一座小橋,我們把島和橋看做是處理對象X,則X的補(bǔ)集為大海。如果漲潮時將小橋和島的外圍淹沒(相當(dāng)于用尺寸比橋?qū)挻蟮慕Y(jié)構(gòu)元素對X進(jìn)行開運算),那么兩個島的分隔,相當(dāng)于小橋兩邊海域的連通(對Xc做閉運算)。
6.5 細(xì)化
細(xì)化(thinning)算法有很多,我們在這里介紹的是一種簡單而且效果很好的算法,用它就能夠?qū)崿F(xiàn)從文本抽取骨架的功能。我們的對象是白紙黑字的文本,但在程序中為了處理的方便,還是采用256級灰度圖,不過只用到了調(diào)色板中0和255兩項。
所謂細(xì)化,就是從原來的圖中去掉一些點,但仍要保持原來的形狀。實際上,是保持原圖的骨架。所謂骨架,可以理解為圖象的中軸,例如一個長方形的骨架是它的長方向上的中軸線;正方形的骨架是它的中心點;圓的骨架是它的圓心,直線的骨架是它自身,孤立點的骨架也是自身。文本的骨架嘛,前言中的例子顯示的很明白。那么怎樣判斷一個點是否能去掉呢?顯然,要根據(jù)它的八個相鄰點的情況來判斷,我們給幾個例子(如圖6.22所示)。
圖6.22 根據(jù)某點的八個相鄰點的情況來判斷該點是否能刪除
圖6.22中,(1)不能刪,因為它是個內(nèi)部點,我們要求的是骨架,如果連內(nèi)部點也刪了,骨架也會被掏空的;(2)不能刪,和(1)是同樣的道理;(3)可以刪,這樣的點不是骨架;(4)不能刪,因為刪掉后,原來相連的部分?jǐn)嚅_了;(5)可以刪,這樣的點不是骨架;(6)不能刪,因為它是直線的端點,如果這樣的點刪了,那么最后整個直線也被刪了,剩不下什么;(7)不能刪,因為孤立點的骨架就是它自身。
總結(jié)一下,有如下的判據(jù):(1)內(nèi)部點不能刪除;(2)孤立點不能刪除;(3)直線端點不能刪除;(4)如果P是邊界點,去掉P后,如果連通分量不增加,則P可以刪除。
我們可以根據(jù)上述的判據(jù),事先做出一張表,從0到255共有256個元素,每個元素要么是0,要么是1。我們根據(jù)某點(當(dāng)然是要處理的黑色點了)的八個相鄰點的情況查表,若表中的元素是1,則表示該點可刪,否則保留。
查表的方法是,設(shè)白點為1,黑點為0;左上方點對應(yīng)一個8位數(shù)的第一位(最低位),正上方點對應(yīng)第二位,右上方點對應(yīng)的第三位,左鄰點對應(yīng)第四位,右鄰點對應(yīng)第五位,左下方點對應(yīng)第六位,正下方點對應(yīng)第七位,右下方點對應(yīng)的第八位,按這樣組成的8位數(shù)去查表即可。例如上面的例子中(1)對應(yīng)表中的第0項,該項應(yīng)該為0;(2)對應(yīng)37,該項應(yīng)該為0;(3)對應(yīng)173,該項應(yīng)該為1;(4)對應(yīng)231,該項應(yīng)該為0;(5)對應(yīng)237,該項應(yīng)該為1;(6)對應(yīng)254,該項應(yīng)該為0;(7)對應(yīng)255,該項應(yīng)該為0。
這張表我已經(jīng)替大家做好了,可花了我不少時間呢!
static int erasetable[256]={
0,0,1,1,0,0,1,1,??? ????? 1,1,0,1,1,1,0,1,
1,1,0,0,1,1,1,1,???????????? 0,0,0,0,0,0,0,1,
0,0,1,1,0,0,1,1,???????????? 1,1,0,1,1,1,0,1,
1,1,0,0,1,1,1,1,???????????? 0,0,0,0,0,0,0,1,
1,1,0,0,1,1,0,0,???????????? 0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,???????????? 0,0,0,0,0,0,0,0,
1,1,0,0,1,1,0,0,???????????? 1,1,0,1,1,1,0,1,
0,0,0,0,0,0,0,0,???????????? 0,0,0,0,0,0,0,0,
0,0,1,1,0,0,1,1,???????????? 1,1,0,1,1,1,0,1,
1,1,0,0,1,1,1,1,???????????? 0,0,0,0,0,0,0,1,
0,0,1,1,0,0,1,1,???????????? 1,1,0,1,1,1,0,1,
1,1,0,0,1,1,1,1,???????????? 0,0,0,0,0,0,0,0,
1,1,0,0,1,1,0,0,???????????? 0,0,0,0,0,0,0,0,
1,1,0,0,1,1,1,1,???????????? 0,0,0,0,0,0,0,0,
1,1,0,0,1,1,0,0,???????????? 1,1,0,1,1,1,0,0,
1,1,0,0,1,1,1,0,???????????? 1,1,0,0,1,0,0,0
};
有了這張表,算法就很簡單了,每次對一行一行的將整個圖象掃描一遍,對于每個點(不包括邊界點),計算它在表中對應(yīng)的索引,若為0,則保留,否則刪除該點。如果這次掃描沒有一個點被刪除,則循環(huán)結(jié)束,剩下的點就是骨架點,如果有點被刪除,則進(jìn)行新的一輪掃描,如此反復(fù),直到?jīng)]有點被刪除為止。
實際上,該算法有一些缺陷。舉個簡單的例子,有一個黑色矩形,如圖6.23所示。
圖6.23經(jīng)過細(xì)化后,我們預(yù)期的結(jié)果是一條水平直線,且位于該黑色矩形的中心。實際的結(jié)果確實是一條水平直線,但不是位于黑色矩形的中心,而是最下面的一條邊。
為什么會這樣,我們來分析一下:在從上到下,從左到右的掃描過程中,我們遇到的第一個黑點就是黑色矩形的左上角點,經(jīng)查表,該點可以刪。下一個點是它右邊的點,經(jīng)查表,該點也可以刪,如此下去,整個一行被刪了。每一行都是同樣的情況,所以都被刪除了。到了最后一行時,黑色矩形已經(jīng)變成了一條直線,最左邊的黑點不能刪,因為它是直線的端點,它右邊的點也不能刪,因為如果刪除,直線就斷了,如此下去,直到最右邊的點,也不能刪,因為它是直線的右端點。所以最下面的一條邊保住了,但這并不是我們希望的結(jié)果。
解決的辦法是,在每一行水平掃描的過程中,先判斷每一點的左右鄰居,如果都是黑點,則該點不做處理。另外,如果某個黑點被刪除了,那么跳過它的右鄰居,處理下一個點。這樣就避免了上述的問題。
圖6.23 黑色矩形
圖6.24 圖6.23細(xì)化后的結(jié)果
解決了上面的問題,我們來看看處理后的結(jié)果,如圖6.24所示。這次變成一小段豎線了,還是不對,是不是很沮喪?別著急,讓我們再來分析一下:在上面的算法中,我們遇到的第一個能刪除的點就是黑色矩形的左上角點;第二個是第一行的最右邊的點,即黑色矩形的右上角點;第三個是第二行的最左邊的點;第四個是第二行的最右邊的點;……;整個圖象處理這樣一次后,寬度減少2。每次都是如此,直到剩最中間一列,就不能再刪了。為什么會這樣呢?原因是這樣的處理過程只實現(xiàn)了水平細(xì)化,如果在每一次水平細(xì)化后,再進(jìn)行一次垂直方向的細(xì)化(只要把上述過程的行列換一下),就可以了。
這樣一來,每處理一次,刪除點的順序變成:(先是水平方向掃描)第一行最左邊的點;第一行最右邊的點;第二行最左邊的點;第二行最右邊的點;……最后一行最左邊的點;最后一行最右邊的點;(然后是垂直方向掃描)第二列最上邊的點(因為第一列最上邊的點已被刪除);第二列最下邊的點;第三列最上邊的點;第三列最下邊的點;……倒數(shù)第二列最上邊的點(因為倒數(shù)第一列最上邊的點已被刪除);倒數(shù)第二列最下邊的點。我們發(fā)現(xiàn),剛好剝掉了一圈,這也正是細(xì)化要做的事。實際的結(jié)果也驗證了我們的想法。
以下是源程序,黑體字部分是值得注意的地方。
BOOL Thinning(HWND hWnd)
{
DWORD????????????????????? ?????? OffBits,BufSize;
LPBITMAPINFOHEADER??? lpImgData;
LPSTR????????????? ?????? ?????? lpPtr;
HLOCAL???????????? ???? hTempImgData;
LPBITMAPINFOHEADER??? lpTempImgData;
LPSTR????????????? ???? lpTempPtr;
HDC??????????????? ????? hDc;
HFILE????????????? ????? hf;
LONG?????????????? ???? x,y;
int????????????????? ?????? ??????? ?????? num;
BOOL?????????????? ????? Finished;
int??????????????? ??????? nw,n,ne,w,e,sw,s,se;
//為了處理的方便,仍采用256級灰度圖,不過只用調(diào)色板中0和255兩項
if( NumColors!=256){
MessageBox(hWnd,"Must be a mono bitmap with grayscale palette!",
"Error Message",MB_OK|MB_ICONEXCLAMATION);
return FALSE;
}
OffBits=bf.bfOffBits-sizeof(BITMAPFILEHEADER);
//BufSize為緩沖區(qū)大小
BufSize=OffBits+bi.biHeight*LineBytes;
//為新的緩沖區(qū)分配內(nèi)存
if((hTempImgData=LocalAlloc(LHND,BufSize))==NULL)
{
MessageBox(hWnd,"Error alloc memory!","Error Message",
MB_OK|MB_ICONEXCLAMATION);
return FALSE;
}
lpImgData=(LPBITMAPINFOHEADER)GlobalLock(hImgData);
lpTempImgData=(LPBITMAPINFOHEADER)LocalLock(hTempImgData);
//拷貝頭信息和位圖數(shù)據(jù)
memcpy(lpTempImgData,lpImgData,BufSize);
//結(jié)束標(biāo)志置成假
Finished=FALSE;
while(!Finished){ //還沒有結(jié)束
//結(jié)束標(biāo)志置成假
Finished=TRUE;
//先進(jìn)行水平方向的細(xì)化
for (y=1;y
//lpPtr指向原圖數(shù)據(jù),lpTempPtr指向新圖數(shù)據(jù)
lpPtr=(char *)lpImgData+(BufSize-LineBytes-y*LineBytes);
lpTempPtr=(char *)lpTempImgData+(BufSize-LineBytes-y*LineBytes);
x=1; //注意為防止越界,x的范圍從1到寬度-2
while(x
if(*(lpPtr+x)==0){ //是黑點才做處理
w=(unsigned char)*(lpPtr+x-1);? //左鄰點
e=(unsigned char)*(lpPtr+x+1);? //右鄰點
if( (w==255)|| (e==255)){
//如果左右兩個鄰居中至少有一個是白點才處理
nw=(unsigned char)*(lpPtr+x+LineBytes-1); //左上鄰點
n=(unsigned char)*(lpPtr+x+LineBytes); //上鄰點
ne=(unsigned char)*(lpPtr+x+LineBytes+1); //右上鄰點
sw=(unsigned char)*(lpPtr+x-LineBytes-1); //左下鄰點
s=(unsigned char)*(lpPtr+x-LineBytes); //下鄰點
se=(unsigned char)*(lpPtr+x-LineBytes+1); //右下鄰點
//計算索引
num=nw/255+n/255*2+ne/255*4+w/255*8+e/255*16+
sw/255*32+s/255*64+se/255*128;
if(erasetable[num]==1){ //經(jīng)查表,可以刪除
//在原圖緩沖區(qū)中將該黑點刪除
*(lpPtr+x)=(BYTE)255;
//結(jié)果圖中該黑點也刪除
*(lpTempPtr+x)=(BYTE)255;
Finished=FALSE; //有改動,結(jié)束標(biāo)志置成假
x++; //水平方向跳過一個象素
}
}
}
x++; //掃描下一個象素
}
}
//再進(jìn)行垂直方向的細(xì)化
for (x=1;x
y=1; //注意為防止越界,y的范圍從1到高度-2
while(y
lpPtr=(char *)lpImgData+(BufSize-LineBytes-y*LineBytes);
lpTempPtr=(char*)lpTempImgData+
(BufSize-LineBytes-y*LineBytes);
if(*(lpPtr+x)==0){ //是黑點才做處理
n=(unsigned char)*(lpPtr+x+LineBytes);
s=(unsigned char)*(lpPtr+x-LineBytes);
if( (n==255)|| (s==255)){
//如果上下兩個鄰居中至少有一個是白點才處理
nw=(unsigned char)*(lpPtr+x+LineBytes-1);
ne=(unsigned char)*(lpPtr+x+LineBytes+1);
w=(unsigned char)*(lpPtr+x-1);
e=(unsigned char)*(lpPtr+x+1);
sw=(unsigned char)*(lpPtr+x-LineBytes-1);
se=(unsigned char)*(lpPtr+x-LineBytes+1);
//計算索引
num=nw/255+n/255*2+ne/255*4+w/255*8+e/255*16+
sw/255*32+s/255*64+se/255*128;
if(erasetable[num]==1){ //經(jīng)查表,可以刪除
//在原圖緩沖區(qū)中將該黑點刪除
*(lpPtr+x)=(BYTE)255;
//結(jié)果圖中該黑點也刪除
*(lpTempPtr+x)=(BYTE)255;
Finished=FALSE; //有改動,結(jié)束標(biāo)志置成假
y++;//垂直方向跳過一個象素
}
}
}
y++; //掃描下一個象素
}
}
}
if(hBitmap!=NULL)
DeleteObject(hBitmap);
hDc=GetDC(hWnd);
//產(chǎn)生新的位圖
hBitmap=CreateDIBitmap(hDc,(LPBITMAPINFOHEADER)lpTempImgData,
(LONG)CBM_INIT,
(LPSTR)lpTempImgData+
sizeof(BITMAPINFOHEADER)+
NumColors*sizeof(RGBQUAD),
(LPBITMAPINFO)lpTempImgData,
DIB_RGB_COLORS);
hf=_lcreat("c:\\thinning.bmp",0);
_lwrite(hf,(LPSTR)&bf,sizeof(BITMAPFILEHEADER));
_lwrite(hf,(LPSTR)lpTempImgData,BufSize);
_lclose(hf);
//釋放內(nèi)存及資源
ReleaseDC(hWnd,hDc);
LocalUnlock(hTempImgData);
LocalFree(hTempImgData);
GlobalUnlock(hImgData);
return TRUE;
}
總結(jié):
腐蝕:刪除對象邊界的某些像素,或者去除圖像中的某些孤立噪聲點
膨脹:給圖像中的對象邊界添加像素
細(xì)化:提取圖像中的骨架,但是要保留圖像
算法:
膨脹算法:用3X3的結(jié)構(gòu)元素,掃描二值圖像的每一個像素,用結(jié)構(gòu)元素與其覆蓋的二值圖像做“或”運算,如果都為0,結(jié)構(gòu)圖像的該像素為0,否則為1.結(jié)果:使二值圖像擴(kuò)大一圈。
如果是二值圖像,本質(zhì)就是當(dāng)結(jié)果劃過圖像時,需要考慮的元素與其覆蓋的的元素取最小值
腐蝕算法:用3X3的結(jié)構(gòu)元素,掃描二值圖像的每一個像素,用結(jié)構(gòu)元素與其覆蓋的二值圖像做“與”運算,如果都為1,結(jié)構(gòu)圖像的該像素為1,否則為0.結(jié)果:使二值圖像減小一圈。
如果是二值圖像,本質(zhì)就是當(dāng)結(jié)果劃過圖像時,需要考慮的元素與其覆蓋的的元素取最大值。
細(xì)化算法:
細(xì)化,就是從原來的圖中去掉一些點,但仍要保持原來的形狀。實際上,是保持原圖的骨架。所謂骨架,可以理解為圖象的中軸,例如一個長方形的骨架是它的長方向上的中軸線;正方形的骨架是它的中心點;圓的骨架是它的圓心,直線的骨架是它自身,孤立點的骨架也是自身。文本的骨架嘛,前言中的例子顯示的很明白。那么怎樣判斷一個點是否能去掉呢?顯然,要根據(jù)它的八個相鄰點的情況來判斷,我們給幾個例子(如圖6.22所示)。
圖6.22根據(jù)某點的八個相鄰點的情況來判斷該點是否能刪除
圖6.22中,(1)不能刪,因為它是個內(nèi)部點,我們要求的是骨架,如果連內(nèi)部點也刪了,骨架也會被掏空的;(2)不能刪,和(1)是同樣的道理;(3)可以刪,這樣的點不是骨架;(4)不能刪,因為刪掉后,原來相連的部分?jǐn)嚅_了;(5)可以刪,這樣的點不是骨架;(6)不能刪,因為它是直線的端點,如果這樣的點刪了,那么最后整個直線也被刪了,剩不下什么;(7)不能刪,因為孤立點的骨架就是它自身。
總結(jié)一下,有如下的判據(jù):(1)內(nèi)部點不能刪除;(2)孤立點不能刪除;(3)直線端點不能刪除;(4)如果P是邊界點,去掉P后,如果連通分量不增加,則P可以刪除。
我們可以根據(jù)上述的判據(jù),事先做出一張表,從0到255共有256個元素,每個元素要么是0,要么是1。我們根據(jù)某點(當(dāng)然是要處理的黑色點了)的八個相鄰點的情況查表,若表中的元素是1,則表示該點可刪,否則保留。
查表的方法是,設(shè)白點為1,黑點為0;左上方點對應(yīng)一個8位數(shù)的第一位(最低位),正上方點對應(yīng)第二位,右上方點對應(yīng)的第三位,左鄰點對應(yīng)第四位,右鄰點對應(yīng)第五位,左下方點對應(yīng)第六位,正下方點對應(yīng)第七位,右下方點對應(yīng)的第八位,按這樣組成的8位數(shù)去查表即可。例如上面的例子中(1)對應(yīng)表中的第0項,該項應(yīng)該為0;(2)對應(yīng)37,該項應(yīng)該為0;(3)對應(yīng)173,該項應(yīng)該為1;(4)對應(yīng)231,該項應(yīng)該為0;(5)對應(yīng)237,該項應(yīng)該為1;(6)對應(yīng)254,該項應(yīng)該為0;(7)對應(yīng)255,該項應(yīng)該為0。
總結(jié)
以上是生活随笔為你收集整理的只腐蚀毛刺 腐蚀算法_图像的腐蚀 膨胀及细化的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: java学习心得---循环结构
- 下一篇: iOS 6 自动布局入门