本章我们看下Pavlidis细化算法,参考资料http://www.imageprocessingplace.com/downloads_V3/root_downloads/tutorials/contour_tracing_Abeer_George_Ghuneim/theo.html

Computer VisiAlgorithms in Image Algebra,second edition

该算法最初是做前景轮廓跟踪的。

假设使用下面的8邻域,且前景像素值为1,背景像素值为0。

opencv边缘闭合 opencv边缘细化_人工智能

下面是该算法的描述:

1. 求出前景像素的轮廓,并用2表示,如果轮廓点是孤立点,端点或者其它不可删除的点,标记其为3。

2. 在第1步求出的轮廓中判断那些是可以删除的,那些是不可删除的,不可删除的点标记为4。

3. 再次扫描值为2的轮廓点,标记可以删除的点为5。

4. 对值为2和5的点执行删除操作。

重复上述步骤,直到图像中没有可以删除的像素为止。结果如想就是我们要的骨架结构。


      第一步是求轮廓的过程,对于一个值为1的像素点。如果它的p0,p2,p4,p6四个点都为1,则该点是内部点,继续循环,判断其它像素。

opencv边缘闭合 opencv边缘细化_i++_02

如果该像素是孤立点或端点,则其像素值标记为3。

opencv边缘闭合 opencv边缘细化_邻域_03

     如果像素是其它可能改变8连通性的点。比如以下的情况: p3, p7为0,但p4,p5,p6和p0,p1,p2中有非零值,如果删除p点,则连通性会改变。此时都标记当前像素值为3。

opencv边缘闭合 opencv边缘细化_人工智能_04

第一步后,我们会得到轮廓

opencv边缘闭合 opencv边缘细化_opencv边缘闭合_05

opencv边缘闭合 opencv边缘细化_连通性_06

第2步对不等于0的像素进行处理

如果像素周围全是2,则标记其为4(不删除)。

opencv边缘闭合 opencv边缘细化_opencv边缘闭合_07

还有对于其它不可删除情况,比如下面这种情况,置当前像素为4。

opencv边缘闭合 opencv边缘细化_邻域_08

第三步对于值为2的轮廓点再次进行判断,对于可删除的点,标记为5。

第四步删除值为2和5的点。

最终值为4的点为细化后的轮廓点。

算法实现的代码:



void gThin::cvPavlidis(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);
        }

char erase, n[8];
unsigned char bdr1,bdr2,bdr4,bdr5;
short k,b;
unsigned long i,j;


int width, height;
    width=dst.cols;
    height= dst.rows;

//把不能于0的值转化为1,便于后面处理
for(i=0; i< height; i++)
        {
for(j=0; j<width; j++)
            {
if(dst.at<uchar>(i,j)!=0)
                {
                dst.at<uchar>(i,j) = 1;
                }
//图像边框像素值为0
if(i==0||i==(height-1)||j==0||j==(width-1))
                dst.at<uchar>(i,j) = 0;
            }
        }

    erase =1;
    width = width - 1;
    height = height - 1;
    uchar* img;
int step = dst.step;
while(erase) 
        {

        img = dst.data;
//第一个循环,取得前景轮廓,轮廓用2表示
for(i=1; i< height; i++)
            {
            img += step;
for(j=1; j < width; j++)
                {
                uchar* p= img+j;


if(p[0]!= 1)
continue;

                n[0]=p[1];
                n[1]=p[-step+1];
                n[2]=p[-step];
                n[3]=p[-step-1];
                n[4]=p[-1];
                n[5]=p[step-1];
                n[6]=p[step];
                n[7]=p[step+1];

//bdr1是2进制表示的p0...p6p7排列,10000011,p0=1,p6=p7=1
                bdr1 =0;
for(k=0; k<8; k++)
                    {
if(n[k]>=1)
                        bdr1|=0x80>>k;
                    }
//内部点,p0, p2, p4, p6都是为1, 非边界点,所以继续循环
//0xaa 10101010
//  0   1   0   
//  1         1
//   0   1    0

if((bdr1&0xaa)== 0xaa)
continue;
//不是内部点,则是边界点,对于边界点,我们标记为2,是轮廓
                p[0] = 2;

                b=0;

for(k=0; k<=7; k++)
                    {
                    b+=bdr1&(0x80>>k);
                    }
//在边界点中,等于1,则是端点,等于0,则是孤立点,此时标记3
if(b<=1 )
                    p[0] = 3;

//此条件说明p点是中间点,如果移去会引起断裂
// 0x70        0x7         0x88      0xc1        0x1c      0x22      0x82     0x1      0xa0     0x40     0x28    0x10       0xa      0x4
// 0 0 0     0  1  1     1  0   0    0   0   0    1  1  0    0   0   1  0  0  1  0 0 0    0  0  0   0 0 0    1  0  0   0  0  0  1  0  1   0 1 0
// 1    0     0      1     0       0    0        1    1      0    0        0  0      0  0    1    0      0   0    0    0      0   1      0  0     0    0    0
// 1 1 0     0  0  0     0  0   1    0   1   1    0  0  0    1   0   1  0  0  1  0 0 0    1  0  1   0 1 0    1  0  0   0  0  0  0  0  0   0 0 0
if((bdr1&0x70)!=0&&(bdr1&0x7)!=0&&(bdr1&0x88)==0)
                    p[0] = 3;
else if((bdr1&&0xc1)!=0&&(bdr1&0x1c)!=0&&(bdr1&0x22)==0)
                    p[0] = 3;  
else if((bdr1&0x82)==0 && (bdr1&0x1)!=0)
                    p[0] = 3; 
else if((bdr1&0xa0)==0 && (bdr1&0x40)!=0)
                    p[0] = 3; 
else if((bdr1&0x28)==0 && (bdr1&0x10)!=0)
                    p[0] = 3; 
else if((bdr1&0xa)==0 && (bdr1&0x4)!=0)
                    p[0] = 3; 

                }
            }
//printf("------------------------------\n");
//PrintMat(dst);
        img = dst.data;
for(i=1; i<height; i++)
            {
            img += step;
for(j=1; j<width; j++)
                {
                uchar* p= img+j;

if(p[0]== 0)
continue;

                n[0]=p[1];
                n[1]=p[-step+1];
                n[2]=p[-step];
                n[3]=p[-step-1];
                n[4]=p[-1];
                n[5]=p[step-1];
                n[6]=p[step];
                n[7]=p[step+1];

                bdr1 = bdr2 =0;

//bdr1是2进制表示的当前点p的8邻域连通情况,hdr2是当前点周围轮廓点的连接情况
for(k=0; k<=7; k++)
                    {
if(n[k]>=1)
                        bdr1|=0x80>>k;
if(n[k]>=2)
                        bdr2|=0x80>>k;
                    }

//相等,就是周围全是值为2的像素,继续
if(bdr1==bdr2)
                    {
                    p[0] = 4; 
continue;
                    }

//p0不为2,继续
if(p[0]!=2) continue;
//=4都是不可删除的轮廓点
//     0x80       0xa     0x40        0x1      0x30   0x6
//   0 0 0      1  0 1    0  0  0    0  0  0   0 0 0   0 1 1
//   0    0      0     0    0      0    0      1   1    0   0    0
//   0 0 1      0  0 0    0  1  0    0  0  0   1 0 0   0 0 0

if(   
                    (bdr2&0x80)!=0 && (bdr1&0xa)==0 &&
//    ((bdr1&0x40)!=0 &&(bdr1&0x1)!=0 ||     ((bdr1&0x40)!=0 ||(bdr1 & 0x1)!=0) &&(bdr1&0x30)!=0 &&(bdr1&0x6)!=0 )
                    (    ((bdr1&0x40)!=0 ||(bdr1 & 0x1)!=0) &&(bdr1&0x30)!=0 &&(bdr1&0x6)!=0 )
                    )
                    {
                    p[0]= 4; 
                    }
//
else if((bdr2&0x20)!=0 && (bdr1&0x2)==0 &&
//((bdr1&0x10)!=0 && (bdr1&0x40)!=0 || ((bdr1&0x10)!=0 || (bdr1&0x40)!=0) &&    (bdr1&0xc)!=0 && (bdr1&0x81)!=0)
                    ( ((bdr1&0x10)!=0 || (bdr1&0x40)!=0) &&    (bdr1&0xc)!=0 && (bdr1&0x81)!=0)
                    )
                    {
                    p[0]= 4;
                    }

else if((bdr2&0x8)!=0 && (bdr1&0x80)==0 &&
//((bdr1&0x4)!=0 && (bdr1&0x10)!=0 || ((bdr1&0x4)!=0 || (bdr1&0x10)!=0) &&(bdr1&0x3)!=0 && (bdr1&0x60)!=0)
                    ( ((bdr1&0x4)!=0 || (bdr1&0x10)!=0) &&(bdr1&0x3)!=0 && (bdr1&0x60)!=0)
                    )
                    {
                    p[0]= 4;
                    }

else if((bdr2&0x2)!=0 && (bdr1&0x20)==0 &&
//((bdr1&0x1)!=0 && (bdr1&0x4)!=0 ||((bdr1&0x1)!=0 || (bdr1&0x4)!=0) &&(bdr1&0xc0)!=0 && (bdr1&0x18)!=0)
                    (((bdr1&0x1)!=0 || (bdr1&0x4)!=0) &&(bdr1&0xc0)!=0 && (bdr1&0x18)!=0)
                    )
                    {
                    p[0]= 4;
                    }
                }
            }
//printf("------------------------------\n");
//PrintMat(dst);
        img = dst.data;
for(i=1; i<height; i++)
            {
            img += step;
for(j=1; j<width; j++)
                {
                uchar* p= img+j;

if(p[0]!= 2)
continue;


                n[0]=p[1];
                n[1]=p[-step+1];
                n[2]=p[-step];
                n[3]=p[-step-1];
                n[4]=p[-1];
                n[5]=p[step-1];
                n[6]=p[step];
                n[7]=p[step+1];

                bdr4 = bdr5 =0;
for(k=0; k<=7; k++)
                    {
if(n[k]>=4)
                        bdr4|=0x80>>k;
if(n[k]>=5)
                        bdr5|=0x80>>k;
                    }
//值为4和5的像素
if((bdr4&0x8) == 0)
                    {
                    p[0]=5;
continue;
                    }
if((bdr4&0x20) == 0 && bdr5 ==0)
                    {
                    p[0]=5;
continue;
                    }

                }
            }
        erase = 0;
//printf("------------------------------\n");
//PrintMat(dst);
        img = dst.data;
for(i=1; i<height; i++)
            {
            img += step;
for(j=1; j<width; j++)
                {
                uchar* p= img+j;
if(p[0]==2||p[0]==5)
                    {
                    erase = 1;
                    p[0] = 0;
                    }
                }
            }
//printf("------------------------------\n");
//PrintMat(dst);
//printf("------------------------\n");
        }

    }


opencv边缘闭合 opencv边缘细化_i++_09

 

opencv边缘闭合 opencv边缘细化_连通性_10

opencv边缘闭合 opencv边缘细化_邻域_11


程序源代码:参加工程FirstOpenCV11