本章我们看下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。
下面是该算法的描述:
1. 求出前景像素的轮廓,并用2表示,如果轮廓点是孤立点,端点或者其它不可删除的点,标记其为3。
2. 在第1步求出的轮廓中判断那些是可以删除的,那些是不可删除的,不可删除的点标记为4。
3. 再次扫描值为2的轮廓点,标记可以删除的点为5。
4. 对值为2和5的点执行删除操作。
重复上述步骤,直到图像中没有可以删除的像素为止。结果如想就是我们要的骨架结构。
第一步是求轮廓的过程,对于一个值为1的像素点。如果它的p0,p2,p4,p6四个点都为1,则该点是内部点,继续循环,判断其它像素。
如果该像素是孤立点或端点,则其像素值标记为3。
如果像素是其它可能改变8连通性的点。比如以下的情况: p3, p7为0,但p4,p5,p6和p0,p1,p2中有非零值,如果删除p点,则连通性会改变。此时都标记当前像素值为3。
第一步后,我们会得到轮廓
第2步对不等于0的像素进行处理
如果像素周围全是2,则标记其为4(不删除)。
还有对于其它不可删除情况,比如下面这种情况,置当前像素为4。
第三步对于值为2的轮廓点再次进行判断,对于可删除的点,标记为5。
第四步删除值为2和5的点。
最终值为4的点为细化后的轮廓点。
算法实现的代码:
void gThin::cvPavlidis(cv::Mat& src, cv::Mat& dst) { if(src.type()!=CV_8UC1) { printf("只能处理二值或灰度图像\n");return; }//非原地操作时候,copy src到dstif(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; }//图像边框像素值为0if(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,则是孤立点,此时标记3if(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 0if((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"); } }
程序源代码:参加工程FirstOpenCV11