Hilditch 細化算法

Hilditch 細化算法是經典的二值圖像細化算法,然而,在網上卻很難找到一個詳細、正確的介紹和實現。可以找到一輛個 Hilditch 算法的C實現,但缺乏注釋,代碼可讀性也很差。在期刊網上找到幾篇論文,提及了Hilditch 算法,結果一篇說的羅哩羅嗦根本看不懂,另一篇說的說的易懂,卻是錯誤的!拿來主義是行不通了,於是只好結合著這幾個論文和代碼,從頭寫 Hilditch 細化算法。

假設像素p的3×3鄰域結構為:

image

Hilditch 細化算法的步驟為:

對圖像從左向右從上向下迭代每個像素,是為一個迭代周期。在每個迭代周期中,對於每一個像素p,如果它同時滿足6個條件,則標記它。在當前迭代周期結束時,則把所有標記的像素的值設為背景值。如果某次迭代周期中不存在標記點(即滿足6個條件的像素),則算法結束。假設背景值為0,前景值為1,則:

6個條件為:

(I):p 為1,即p不是背景;

(2):x1,x3,x5,x7不全部為1(否則把p標記刪除,圖像空心了);

(3):x1~x8 中,至少有2個為1(若只有1個為1,則是線段的端點。若沒有為1的,則為孤立點);

(4):p的8連通聯結數為1;

聯結數指在像素p的3*3鄰域中,和p連接的圖形分量的個數:

image

上圖中,左圖的4連通聯結數是2,8連通聯結數是1,而右圖的4聯通聯結數和8聯通聯結數都是2。

 

 

 

 

4連通聯結數計算公式是:

image

8連通聯結數計算公式是:

image其中,

image 

至於公式怎麼來的就不管了,直接用就行了。

(5)假設x3已經標記刪除,那麼當x3為0時,p的8聯通聯結數為1;

(6)假設x5已經標記刪除,那麼當x5為0時,p的8聯通聯結數為1。

 

 

 

代碼如下:

 

 

======

在程序中,我使用的是這樣的鄰域編碼:

image

為了方便計算聯結數,以0作為前景,1作為背景。程序如下(完整程序見:http://smartimage.googlecode.com/svn/trunk/src/Orc.SmartImage.Common/UnmanagedImage/Ima

///  
/// 計算八聯結的聯結數,計算公式為: 
///     (p6 - p6*p7*p0) + sigma(pk - pk*p(k+1)*p(k+2)), k = {0,2,4) 
///  
///  
///  
private unsafe Int32 DetectConnectivity(Int32* list) 
{ 
    Int32 count = list[6] - list[6] * list[7] * list[0]; 
    count += list[0] - list[0] * list[1] * list[2]; 
    count += list[2] - list[2] * list[3] * list[4]; 
    count += list[4] - list[4] * list[5] * list[6]; 
    return count; 
}

private unsafe void FillNeighbors(Byte* p, Int32* list, Int32 width, Byte foreground = 255) 
{ 
    // list 存儲的是補集,即前景點為0,背景點為1,以方便聯結數的計算

    list[0] = p[1] == foreground ? 0 : 1; 
    list[1] = p[1 - width] == foreground ? 0 : 1; 
    list[2] = p[-width] == foreground ? 0 : 1; 
    list[3] = p[-1 - width] == foreground ? 0 : 1; 
    list[4] = p[-1] == foreground ? 0 : 1; 
    list[5] = p[-1 + width] == foreground ? 0 : 1; 
    list[6] = p[width] == foreground ? 0 : 1; 
    list[7] = p[1 + width] == foreground ? 0 : 1; 
}

///  
/// 使用 hilditch 算法進行細化 
///  
public unsafe void Thinning(Byte foreground = 255) 
{ 
    Byte* start = this.Start; 
    Int32 width = this.Width; 
    Int32 height = this.Height; 
    Int32* list = stackalloc Int32[8]; 
    Byte background = (Byte)(255 - foreground); 
    Int32 length = this.Length;

    using (ImageU8 mask = new ImageU8(this.Width, this.Height)) 
    { 
        mask.Fill(0);

        Boolean loop = true; 
        while (loop == true) 
        { 
            loop = false; 
            for (Int32 r = 1; r < height - 1; r++) 
            { 
                for (Int32 c = 1; c < width - 1; c++) 
                { 
                    Byte* p = start + r * width + c;

                    // 條件1:p 必須是前景點 
                    if (*p != foreground) continue;

                    //  p3  p2  p1 
                    //  p4  p   p0 
                    //  p5  p6  p7 
                    // list 存儲的是補集,即前景點為0,背景點為1,以方便聯結數的計算 
                    FillNeighbors(p, list, width, foreground);

                    // 條件2:p0,p2,p4,p6 不皆為前景點 
                    if (list[0] == 0 && list[2] == 0 && list[4] == 0 && list[6] == 0) 
                        continue;

                    // 條件3: p0~p7至少兩個是前景點 
                    Int32 count = 0; 
                    for (int i = 0; i < 8; i++) 
                    { 
                        count += list[i]; 
                    }

                    if (count > 6) continue;

                    // 條件4:聯結數等於1 
                    if (DetectConnectivity(list) != 1) continue;

                    // 條件5: 假設p2已標記刪除,則令p2為背景,不改變p的聯結數 
                    if (mask[r - 1, c] == 1) 
                    { 
                        list[2] = 1; 
                        if (DetectConnectivity(list) != 1) 
                            continue; 
                        list[2] = 0; 
                    }

                    // 條件6: 假設p4已標記刪除,則令p4為背景,不改變p的聯結數 
                    if (mask[r, c - 1] == 1) 
                    { 
                        list[4] = 1; 
                        if (DetectConnectivity(list) != 1) 
                            continue; 
                    } 
                    mask[r, c] = 1; // 標記刪除 
                    loop = true; 
                } 
            }

            for (int i = 0; i < length; i++) 
            { 
                if (mask[i] == 1) 
                { 
                    this[i] = background; 
                } 
            } 
        } 
    } 
}

======


 

 

我的代碼如下:

 

void HilditchThinning(int w,int h,BYTE *imgBuf)< xmlnamespace prefix ="o" ns ="urn:schemas-microsoft-com:office:office" />
{
    //           p3  p2  p1
    // 8近鄰     p4  p   p0
    //           p5  p6  p7
    int neighbor[8]; 
    BYTE *mask=new BYTE[w*h];
    memset(mask,0,w*h);
 
    BOOL loop=TRUE;
    int x,y,k,index;
 
    while(loop)
    {
       loop=FALSE;
       loopNum++;
 
       for(y=0;y<<span style="line-height: 28px; color: rgb(1, 0, 1);">h;y++)
       {
           for(x=0;x<<span style="line-height: 28px; color: rgb(1, 0, 1);">w;x++)
           {
              index=y*w+x; ;
 
              //條件1:p必須是前景點
              if(imgBuf[index]==0 ) continue;
 
              neighbor[0]=x+1<<span style="line-height: 28px; color: rgb(1, 0, 1);">w ? imgBuf[y*w+x+1] : 0;
              neighbor[1]=y-1>0&&x+1<<span style="line-height: 28px; color: rgb(1, 0, 1);">w ? imgBuf[(y-1)*w+x+1] : 0;
              neighbor[2]=y-1>0 ? imgBuf[(y-1)*w+x] : 0;
              neighbor[3]=y-1>0&&x-1<0 ? imgBuf[(y-1)*w+x-1] : 0;
              neighbor[4]=x-1>0 ? imgBuf[y*w+x-1] : 0;
              neighbor[5]=x-1>0&&y+1<<span style="line-height: 28px; color: rgb(1, 0, 1);">h ? imgBuf[(y+1)*w+x-1] : 0;
              neighbor[6]=y+1<<span style="line-height: 28px; color: rgb(1, 0, 1);">h ? imgBuf[(y+1)*w+x] : 0;
              neighbor[7]=y+1<<span style="line-height: 28px; color: rgb(1, 0, 1);">h&&x+1<<span style="line-height: 28px; color: rgb(1, 0, 1);">w ? imgBuf[(y+1)*w+x+1] : 0;
 
              //條件2:p0,p2,p4,p6不全為前景色(否則把點p刪了,圖像空心)
              if(neighbor[0]&&neighbor[2]&&neighbor[4]&&neighbor[6])
                  continue;
 
              //條件3:p0~p7中,至少有個為前景色
              //(若只有一個為,則為端點,若沒有為的,則為孤立點)
              int count=0;
              for(int i=0;i<8;i++)
              {
                  if(neighbor[i]==255)
                     count++;
              }
              if(count<2) 
              {
                  continue;
              }
 
              //條件4:p的八近鄰連接數必須為1
              if(Get8Connectivity(neighbor)!=1) continue;
 
              //條件5:若p2已經被標記刪除,則當p2為背景色時,P的連接數仍需為1
              k=(y-1)*w+x;
              if(y-1>0 && mask[k]==1)
              {            
                  imgBuf[k]=0;
                  if(Get8Connectivity(neighbor)!=1) continue;
                  imgBuf[k]=1;
              }
 
              //條件6:若p4已經被標記刪除,則當p4為背景色時,P的連接數仍需為1
              k=y*w+x-1;
              if(x-1>0 && mask[k]==1)
              {            
                  imgBuf[k]=0;
                  if(Get8Connectivity(neighbor)!=1) continue;
                  imgBuf[k]=1;
              }
 
              //標記刪除
              mask[w*y+x]=1;   
              loop=TRUE;
           }
       }
 
 
       //將標記刪除的點置為背景色
       for(y=0;y<<span style="line-height: 28px; color: rgb(1, 0, 1);">h;y++)
       {
           for(x=0;x<<span style="line-height: 28px; color: rgb(1, 0, 1);">w;x++)
           {
              k=y*w+x;
              if(mask[k]==1) imgBuf[k]=0;
           }
       }  
 
 
    }
   
 
}
//                                  p3  p2  p1
//*************計算近鄰的連接數     p4  p   p0
//                                  p5  p6  p7
int Get8Connectivity(int* neighbor)
{  
 
    //計算補集x^=1-x;
    for(int i=0;i<8;i++)
    {
       neighbor[i]=neighbor[i]==0?1:0;
    }  
   
    int count= neighbor[0]-(neighbor[0]&neighbor[1]&neighbor[2]);
    count+= neighbor[2]-(neighbor[2]&neighbor[3]&neighbor[4]);
    count+= neighbor[4]-(neighbor[4]&neighbor[5]&neighbor[6]);
    count+= neighbor[6]-(neighbor[6]&neighbor[7]&neighbor[0]);
 
    return count;
}
 
這個細化算法,細化後會產生毛刺


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值