OpenCV學習(15) 細化算法(3)


      本章我們學習一下Hilditch算法的基本原理,從網上找資料的時候,竟然發現兩個有很大差別的算法描述,而且都叫Hilditch算法。不知道那一個才是正宗的,兩個算法實現的效果接近,第一種算法更好一些。

第一種算法描述參考paper和代碼:

Linear Skeletons from Square Cupboards

Speedup Method for Real-Time Thinning Algorithm

http://cis.k.hosei.ac.jp/~wakahara/Hilditch.c

第二種算法描述參考資料:

http://cgm.cs.mcgill.ca/~godfried/teaching/projects97/azar/skeleton.html#algorithm

 

下面我們分別看一下這兩種算法描述:

一、第一種算法描述

假設當前被處理的像素為p0,我們使用下圖所示的8鄰域表示方式。

image

     我們處理的為二值圖像,背景為黑色,值為0,要細化的前景物體像素值為255。

     對於Hilditch算法來說,它並不是一個完全的並行算法,而是串行並行相結合。當前像素是否是能夠刪除的骨架點,不僅是由它周圍的8鄰域決定,而且和前面像素的判定結果有關。一個像素判定為可以刪除,我們並不直接刪除它,而是在目地圖像中設置像素值為GRAY=128,這個信息可能會影響之后其它像素的判定。

      當圖像一次掃描迭代完成后,我們把所有置為GRAY的像素設置為0,從而刪除它。

 

算法的描述如下。

迭代掃描當前圖像

    對於當前像素點,掃描它的8鄰域,如果鄰域的像素值為255,則b[i]=1(i=0…8),像素值為128(GRAY,表示該像素點在前面的循環中被標記為刪除),b[i]=-1,如果像素值為0,則b[i]=0。

imageimage

下面會根據b[i]的值進行6個條件判斷,如果條件滿足,則會標記該像素值為GRAY(128)。

1. b[0]=1,即當前像素必須為前景點。

2. 1-abs(b1) + 1 – abs(b3) + 1 – abs(b5) + 1 – abs(b7) >= 1,該條件表示當前像素為邊界點,即東西南北四個點至少有一個b[i]=0。

3. abs(b1)+…+abs(b8)>=2, 該條件表示不能刪除端點,即p0點周圍只有一個點為1或-1的情況。

4.  統計b1到b8等於1的數量,該數量值必須大於1,該條件表示不能刪除端點。、

5.  連通性檢測,使用下面的公式:首先根據當前像素周圍3*3域的值,記錄d[9]數組,如果b[i]等於0,則d[i]=0, 否則d[i]=1,最后計算 d1-d1*d2*d3+d3-d3*d4*d5+d5-d5*d6*d7+d7-d7*d8*d1是否為1,為1則滿足連通性,可以刪除。

image

6.最后一個條件保證當輪廓是2個像素寬時,只刪除一邊。統計sum的值,當值為8時候,可以刪除。

sum = 0;
for (i = 1; i <= 8; i++)
{
    if (b[i] != -1)
    {
        sum++;
    } else
    {
        copy = b[i];
        b[i] = 0;
        if (func_nc8(b) == 1) sum++;
        b[i] = copy;
    }

     當這6個條件都滿足時候,標記當前像素值為GRAY(128),然后在判斷別的像素。當所有像素都掃描一遍后,完成一次迭代。

此時我們會把剛才標記為GARY的像素,都設置為0,真正的刪除它,如果上一次循環已經沒有標記刪除的像素,則退出迭代,否則進行下一次迭代。

算法代碼:

int gThin::func_nc8(int *b)
//端點的連通性檢測
{
int n_odd[4] = { 1, 3, 5, 7 }; //四鄰域
int i, j, sum, d[10];

for (i = 0; i <= 9; i++) {
j = i;
if (i == 9) j = 1;
if (abs(*(b + j)) == 1)
{
d[i] = 1;
}
else
{
d[i] = 0;
}
}
sum = 0;
for (i = 0; i < 4; i++)
{
j = n_odd[i];
sum = sum + d[j] - d[j] * d[j + 1] * d[j + 2];
}
return (sum);
}

void gThin::cvHilditchThin(cv::Mat& src, cv::Mat& dst)
{
if(src.type()!=CV_8UC1)
{
printf("只能處理二值或灰度圖像\n");
return;
}
//非原地操作時候,copy src到dst
if(dst.data!=src.data)
{
src.copyTo(dst);
}

//8鄰域的偏移量
int offset[9][2] = {{0,0},{1,0},{1,-1},{0,-1},{-1,-1},
{-1,0},{-1,1},{0,1},{1,1} };
//四鄰域的偏移量
int n_odd[4] = { 1, 3, 5, 7 };
int px, py;
int b[9]; //3*3格子的灰度信息
int condition[6]; //1-6個條件是否滿足
int counter; //移去像素的數量
int i, x, y, copy, sum;

uchar* img;
int width, height;
width = dst.cols;
height = dst.rows;
img = dst.data;
int step = dst.step ;
do
{

counter = 0;

for (y = 0; y < height; y++)
{

for (x = 0; x < width; x++)
{

//前面標記為刪除的像素,我們置其相應鄰域值為-1
for (i = 0; i < 9; i++)
{
b[i] = 0;
px = x + offset[i][0];
py = y + offset[i][1];
if (px >= 0 && px < width && py >= 0 && py <height)
{
// printf("%d\n", img[py*step+px]);
if (img[py*step+px] == WHITE)
{
b[i] = 1;
}
else if (img[py*step+px] == GRAY)
{
b[i] = -1;
}
}
}
for (i = 0; i < 6; i++)
{
condition[i] = 0;
}

//條件1,是前景點
if (b[0] == 1) condition[0] = 1;

//條件2,是邊界點
sum = 0;
for (i = 0; i < 4; i++)
{
sum = sum + 1 - abs(b[n_odd[i]]);
}
if (sum >= 1) condition[1] = 1;

//條件3, 端點不能刪除
sum = 0;
for (i = 1; i <= 8; i++)
{
sum = sum + abs(b[i]);
}
if (sum >= 2) condition[2] = 1;

//條件4, 孤立點不能刪除
sum = 0;
for (i = 1; i <= 8; i++)
{
if (b[i] == 1) sum++;
}
if (sum >= 1) condition[3] = 1;

//條件5, 連通性檢測
if (func_nc8(b) == 1) condition[4] = 1;

//條件6,寬度為2的骨架只能刪除1邊
sum = 0;
for (i = 1; i <= 8; i++)
{
if (b[i] != -1)
{
sum++;
} else
{
copy = b[i];
b[i] = 0;
if (func_nc8(b) == 1) sum++;
b[i] = copy;
}
}
if (sum == 8) condition[5] = 1;

if (condition[0] && condition[1] && condition[2] &&condition[3] && condition[4] && condition[5])
{
img[y*step+x] = GRAY; //可以刪除,置位GRAY,GRAY是刪除標記,但該信息對后面像素的判斷有用
counter++;
//printf("----------------------------------------------\n");
//PrintMat(dst);
}
}
}

if (counter != 0)
{
for (y = 0; y < height; y++)
{
for (x = 0; x < width; x++)
{
if (img[y*step+x] == GRAY)
img[y*step+x] = BLACK;

}
}
}

}while (counter != 0);

}

二、第二種算法描述

       第二種算法描述和Zhang的並行算法很相似,特別是前2個條件一模一樣,不同的是3,4兩個條件,還有就是該描述算法並沒有像zhang算法那樣,把一次迭代分成2個階段。

此時我們使用的8鄰域標記為:

image

下面看下它的算法描述:

復制目地圖像到臨時圖像,對臨時圖像進行一次掃描,對於不為0的點,如果滿足以下四個條件,則在目地圖像中刪除該點(就是設置該像素為0)

a. 2<= p2+p3+p4+p5+p6+p7+p8+p9<=6

    大於等於2會保證p1點不是端點或孤立點,因為刪除端點和孤立點是不合理的,小於等於6保證p1點是一個邊界點,而不是一個內部點。等於0時候,周圍沒有等於1的像素,所以p1為孤立點,等於1的時候,周圍只有1個灰度等於1的像素,所以是端點(注:端點是周圍有且只能有1個值為1的像素)。

image

b. p2->p9的排列順序中,01模式的數量為1,比如下面的圖中,有p2p3 => 01, p6p7=>01,所以該像素01模式的數量為2。

image

  之所以要01模式數量為1,是要保證刪除當前像素點后的連通性。比如下面的圖中,01模式數量大於1,如果刪除當前點p1,則連通性不能保證。

image

 

c. p2.p4.p8 = 0 or A(p2)!=1,A(p2)表示p2周圍8鄰域的01模式和。這個條件保證2個像素寬的垂直條不完全被腐蝕掉。

image

d.p2.p4.p6 = 0 or A(p4)!=1,A(p4)表示p4周圍8鄰域的01模式和。這個條件保證2個像素寬的水平條不完全被腐蝕掉。

image

算法代碼:

void gThin::cvHilditchThin1(cv::Mat& src, cv::Mat& dst)
{
//http://cgm.cs.mcgill.ca/~godfried/teaching/projects97/azar/skeleton.html#algorithm
//算法有問題,得不到想要的效果
if(src.type()!=CV_8UC1)
{
printf("只能處理二值或灰度圖像\n");
return;
}
//非原地操作時候,copy src到dst
if(dst.data!=src.data)
{
src.copyTo(dst);
}

int i, j;
int width, height;
//之所以減2,是方便處理8鄰域,防止越界
width = src.cols -2;
height = src.rows -2;
int step = src.step;
int p2,p3,p4,p5,p6,p7,p8,p9;
uchar* img;
bool ifEnd;
int A1;
cv::Mat tmpimg;
while(1)
{
dst.copyTo(tmpimg);
ifEnd = false;
img = tmpimg.data+step;
for(i = 2; i < height; i++)
{
img += step;
for(j =2; j<width; j++)
{
uchar* p = img + j;
A1 = 0;
if( p[0] > 0)
{
if(p[-step]==0&&p[-step+1]>0) //p2,p3 01模式
{
A1++;
}
if(p[-step+1]==0&&p[1]>0) //p3,p4 01模式
{
A1++;
}
if(p[1]==0&&p[step+1]>0) //p4,p5 01模式
{
A1++;
}
if(p[step+1]==0&&p[step]>0) //p5,p6 01模式
{
A1++;
}
if(p[step]==0&&p[step-1]>0) //p6,p7 01模式
{
A1++;
}
if(p[step-1]==0&&p[-1]>0) //p7,p8 01模式
{
A1++;
}
if(p[-1]==0&&p[-step-1]>0) //p8,p9 01模式
{
A1++;
}
if(p[-step-1]==0&&p[-step]>0) //p9,p2 01模式
{
A1++;
}
p2 = p[-step]>0?1:0;
p3 = p[-step+1]>0?1:0;
p4 = p[1]>0?1:0;
p5 = p[step+1]>0?1:0;
p6 = p[step]>0?1:0;
p7 = p[step-1]>0?1:0;
p8 = p[-1]>0?1:0;
p9 = p[-step-1]>0?1:0;
//計算AP2,AP4
int A2, A4;
A2 = 0;
//if(p[-step]>0)
{
if(p[-2*step]==0&&p[-2*step+1]>0) A2++;
if(p[-2*step+1]==0&&p[-step+1]>0) A2++;
if(p[-step+1]==0&&p[1]>0) A2++;
if(p[1]==0&&p[0]>0) A2++;
if(p[0]==0&&p[-1]>0) A2++;
if(p[-1]==0&&p[-step-1]>0) A2++;
if(p[-step-1]==0&&p[-2*step-1]>0) A2++;
if(p[-2*step-1]==0&&p[-2*step]>0) A2++;
}


A4 = 0;
//if(p[1]>0)
{
if(p[-step+1]==0&&p[-step+2]>0) A4++;
if(p[-step+2]==0&&p[2]>0) A4++;
if(p[2]==0&&p[step+2]>0) A4++;
if(p[step+2]==0&&p[step+1]>0) A4++;
if(p[step+1]==0&&p[step]>0) A4++;
if(p[step]==0&&p[0]>0) A4++;
if(p[0]==0&&p[-step]>0) A4++;
if(p[-step]==0&&p[-step+1]>0) A4++;
}


//printf("p2=%d p3=%d p4=%d p5=%d p6=%d p7=%d p8=%d p9=%d\n", p2, p3, p4, p5, p6,p7, p8, p9);
//printf("A1=%d A2=%d A4=%d\n", A1, A2, A4);
if((p2+p3+p4+p5+p6+p7+p8+p9)>1 && (p2+p3+p4+p5+p6+p7+p8+p9)<7 && A1==1)
{
if(((p2==0||p4==0||p8==0)||A2!=1)&&((p2==0||p4==0||p6==0)||A4!=1))
{
dst.at<uchar>(i,j) = 0; //滿足刪除條件,設置當前像素為0
ifEnd = true;
//printf("\n");

//PrintMat(dst);
}
}
}
}
}
//printf("\n");
//PrintMat(dst);
//PrintMat(dst);
//已經沒有可以細化的像素了,則退出迭代
if(!ifEnd) break;
}
}

第一種Hilditch算法的結果:

image

imageimage

第二種Hilditch算法的結果:

image

imageimage

程序代碼:工程FirstOpenCV11


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM