本章我们在学习一下基于索引表的细化算法。

假设要处理的图像为二值图,前景值为1,背景值为0。

索引表细化算法使用下面的8邻域表示法:

一个像素的8邻域,我们可以用8位二进制表示,比如下面的8邻域,表示为00111000=0x38=56

我们可以枚举出各种情况下,当前像素能否删除的表,该表大小为256。它的索引即为8邻域表示的值,表中存的值为0或1,0表示当前像素不能删除,1表示可以删除。deletemark[256]

比如下图第一个表示,索引值为0,它表示孤立点,不能删除,所以deletemark[0]=0,第二个表示索引值为17,它表示端点,也不能删除,所以deletemark[17]=0,第三个表示索引为21,删除的话会改变连通域数量,所以deletemark[21]=0,第四个表示索引值为96,此时可以删除,所以deletemark[96]=1。

最终我们会定义一张完整的表来表示当前像素能否删除。

索引表细化算法描述很简单。

1.找到轮廓,其值用4表示

2.查找值为4的轮廓,查找索引表判断能否删除,能删除的话把它置为0。

循环迭代1,2直到再也没有可以删除的点为止。

下面的算法的代码:

void gThin::cvidxThin1(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);
} // P0 P1 P2
// P7 P3
// P6 P5 P4
unsigned char deletemark[256] = {
0,0,0,0,0,0,0,1, 0,0,1,1,0,0,1,1,
0,0,0,0,0,0,0,0, 0,0,1,1,1,0,1,1,
0,0,0,0,0,0,0,0, 1,0,0,0,1,0,1,1,
0,0,0,0,0,0,0,0, 1,0,1,1,1,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,0,0,0,0,0,0,
0,0,0,0,0,0,0,0, 1,0,0,0,1,0,1,1,
1,0,0,0,0,0,0,0, 1,0,1,1,1,0,1,1,
0,0,1,1,0,0,1,1, 0,0,0,1,0,0,1,1,
0,0,0,0,0,0,0,0, 0,0,0,1,0,0,1,1,
1,1,0,1,0,0,0,1, 0,0,0,0,0,0,0,0,
1,1,0,1,0,0,0,1, 1,1,0,0,1,0,0,0,
0,1,1,1,0,0,1,1, 0,0,0,1,0,0,1,1,
0,0,0,0,0,0,0,0, 0,0,0,0,0,1,1,1,
1,1,1,1,0,0,1,1, 1,1,0,0,1,1,0,0,
1,1,1,1,0,0,1,1, 1,1,0,0,1,1,0,0
};//索引
int i, j;
int width, height;
//之所以减1,是方便处理8邻域,防止越界
width = src.cols -1;
height = src.rows -1;
int step = src.step;
int p0, p1, p2,p3,p4,p5,p6,p7;
uchar* img;
bool ifEnd;
bool border = false; //交换删除的次序,防止从一边细化
while(1)
{ border = !border;
img = dst.data;
for(i = 1; i < height; i++)
{
img += step;
for(j =1; j<width; j++)
{
uchar* p = img + j;
//如果p点是背景点,继续循环
if(p[0]==0) continue;
p0 = p[-step-1]>0?1:0;
p1 = p[-step]>0?1:0;
p2 = p[-step+1]>0?1:0;
p3 = p[1]>0?1:0;
p4 = p[step+1]>0?1:0;
p5 = p[step]>0?1:0;
p6 = p[step-1]>0?1:0;
p7 = p[-1]>0?1:0; //如果sum等于0,则不是内部点,是轮廓点,设置其像素值为2
int sum;
sum = p0 & p1 & p2 & p3 & p4 & p5 & p6 & p7; //判断是否是邻接点或孤立点,0,1分别对于那个孤立点和端点
if(sum==0)
{
dst.at<uchar>(i,j) = 4; //满足删除条件,设置当前像素为0
} }
}
//printf("\n");
//PrintMat(dst);
//执行删除操作
ifEnd = false; img = dst.data;
for(i = 1; i < height; i++)
{
img += step;
for(j =1; j<width; j++)
{
uchar* p = img + j;
//如果p点是背景点,继续循环
if(p[0]!=4) continue;
p0 = p[-step-1]>0?1:0;
p1 = p[-step]>0?1:0;
p2 = p[-step+1]>0?1:0;
p3 = p[1]>0?1:0;
p4 = p[step+1]>0?1:0;
p5 = p[step]>0?1:0;
p6 = p[step-1]>0?1:0;
p7 = p[-1]>0?1:0; p1 = p1<<1;
p2 = p2<<2;
p3 = p3 <<3;
p4 = p4<<4;
p5 = p5<<5;
p6 = p6 <<6;
p7 = p7 << 7; //求的8邻域在索引表中的索引
int sum;
sum = p0 | p1 | p2 | p3 | p4 | p5 | p6 | p7; //判断是否是邻接点或孤立点,0,1分别对于那个孤立点和端点
if(deletemark[sum] == 1)
{
dst.at<uchar>(i,j) = 0; //满足删除条件,设置当前像素为0
ifEnd = true;
} }
} //printf("\n");
//PrintMat(dst);
//printf("\n"); //已经没有可以细化的像素了,则退出迭代
if(!ifEnd) break;
}

上面的算法可以看到细化后的轮廓偏右了,我们可以更改删除的循环条件,把循环拆分成三个,修改后的代码如下:

void gThin::cvidxThin(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);
} // P0 P1 P2
// P7 P3
// P6 P5 P4
unsigned char deletemark[256] = {
0,0,0,0,0,0,0,1, 0,0,1,1,0,0,1,1,
0,0,0,0,0,0,0,0, 0,0,1,1,1,0,1,1,
0,0,0,0,0,0,0,0, 1,0,0,0,1,0,1,1,
0,0,0,0,0,0,0,0, 1,0,1,1,1,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,0,0,0,0,0,0,
0,0,0,0,0,0,0,0, 1,0,0,0,1,0,1,1,
1,0,0,0,0,0,0,0, 1,0,1,1,1,0,1,1,
0,0,1,1,0,0,1,1, 0,0,0,1,0,0,1,1,
0,0,0,0,0,0,0,0, 0,0,0,1,0,0,1,1,
1,1,0,1,0,0,0,1, 0,0,0,0,0,0,0,0,
1,1,0,1,0,0,0,1, 1,1,0,0,1,0,0,0,
0,1,1,1,0,0,1,1, 0,0,0,1,0,0,1,1,
0,0,0,0,0,0,0,0, 0,0,0,0,0,1,1,1,
1,1,1,1,0,0,1,1, 1,1,0,0,1,1,0,0,
1,1,1,1,0,0,1,1, 1,1,0,0,1,1,0,0
};//索引
int i, j;
int width, height;
//之所以减1,是方便处理8邻域,防止越界
width = src.cols -1;
height = src.rows -1;
int step = src.step;
int p0, p1, p2,p3,p4,p5,p6,p7;
uchar* img;
bool ifEnd;
bool border = false; //交换删除的次序,防止从一边细化
while(1)
{ border = !border;
img = dst.data;
for(i = 1; i < height; i++)
{
img += step;
for(j =1; j<width; j++)
{
uchar* p = img + j;
//如果p点是背景点,继续循环
if(p[0]==0) continue;
p0 = p[-step-1]>0?1:0;
p1 = p[-step]>0?1:0;
p2 = p[-step+1]>0?1:0;
p3 = p[1]>0?1:0;
p4 = p[step+1]>0?1:0;
p5 = p[step]>0?1:0;
p6 = p[step-1]>0?1:0;
p7 = p[-1]>0?1:0; //如果sum等于0,则不是内部点,是轮廓点,设置其像素值为2
int sum;
sum = p0 & p1 & p2 & p3 & p4 & p5 & p6 & p7; //判断是否是邻接点或孤立点,0,1分别对于那个孤立点和端点
if(sum==0)
{
dst.at<uchar>(i,j) = 4; //满足删除条件,设置当前像素为0
} }
}
//printf("\n");
//PrintMat(dst);
//执行删除操作
ifEnd = false; img = dst.data;
for(i = 1; i < height; i++)
{
img += step;
for(j =1; j<width; j+=3)
{
uchar* p = img + j;
//如果p点是背景点,继续循环
if(p[0]!=4) continue;
p0 = p[-step-1]>0?1:0;
p1 = p[-step]>0?1:0;
p2 = p[-step+1]>0?1:0;
p3 = p[1]>0?1:0;
p4 = p[step+1]>0?1:0;
p5 = p[step]>0?1:0;
p6 = p[step-1]>0?1:0;
p7 = p[-1]>0?1:0; p1 = p1<<1;
p2 = p2<<2;
p3 = p3 <<3;
p4 = p4<<4;
p5 = p5<<5;
p6 = p6 <<6;
p7 = p7 << 7; //求的8邻域在索引表中的索引
int sum;
sum = p0 | p1 | p2 | p3 | p4 | p5 | p6 | p7; //判断是否是邻接点或孤立点,0,1分别对于那个孤立点和端点
if(deletemark[sum] == 1)
{
dst.at<uchar>(i,j) = 0; //满足删除条件,设置当前像素为0
ifEnd = true;
} }
} img = dst.data;
for(i = 1; i < height; i++)
{
img += step;
for(j =2; j<width; j+=3)
{
uchar* p = img + j;
//如果p点是背景点,继续循环
if(p[0]!=4) continue;
p0 = p[-step-1]>0?1:0;
p1 = p[-step]>0?1:0;
p2 = p[-step+1]>0?1:0;
p3 = p[1]>0?1:0;
p4 = p[step+1]>0?1:0;
p5 = p[step]>0?1:0;
p6 = p[step-1]>0?1:0;
p7 = p[-1]>0?1:0; p1 = p1<<1;
p2 = p2<<2;
p3 = p3 <<3;
p4 = p4<<4;
p5 = p5<<5;
p6 = p6 <<6;
p7 = p7 << 7; //求的8邻域在索引表中的索引
int sum;
sum = p0 | p1 | p2 | p3 | p4 | p5 | p6 | p7; //判断是否是邻接点或孤立点,0,1分别对于那个孤立点和端点
if(deletemark[sum] == 1)
{
dst.at<uchar>(i,j) = 0; //满足删除条件,设置当前像素为0
ifEnd = true;
} }
} img = dst.data;
for(i = 1; i < height; i++)
{
img += step;
for(j =3; j<width; j+=3)
{
uchar* p = img + j;
//如果p点是背景点,继续循环
if(p[0]!=4) continue;
p0 = p[-step-1]>0?1:0;
p1 = p[-step]>0?1:0;
p2 = p[-step+1]>0?1:0;
p3 = p[1]>0?1:0;
p4 = p[step+1]>0?1:0;
p5 = p[step]>0?1:0;
p6 = p[step-1]>0?1:0;
p7 = p[-1]>0?1:0; p1 = p1<<1;
p2 = p2<<2;
p3 = p3 <<3;
p4 = p4<<4;
p5 = p5<<5;
p6 = p6 <<6;
p7 = p7 << 7; //求的8邻域在索引表中的索引
int sum;
sum = p0 | p1 | p2 | p3 | p4 | p5 | p6 | p7; //判断是否是邻接点或孤立点,0,1分别对于那个孤立点和端点
if(deletemark[sum] == 1)
{
dst.at<uchar>(i,j) = 0; //满足删除条件,设置当前像素为0
ifEnd = true;
} }
} //printf("\n");
//PrintMat(dst);
//printf("\n"); //已经没有可以细化的像素了,则退出迭代
if(!ifEnd) break;
} }

修改后的结果:

程序源代码:工程FirstOpenCV11

OpenCV学习(18) 细化算法(6)的更多相关文章

  1. OpenCV学习(16) 细化算法(4)

    本章我们学习Rosenfeld细化算法,参考资料:http://yunpan.cn/QGRjHbkLBzCrn 在开始学习算法之前,我们先看下连通分量,以及4连通性,8连通性的概念: http://w ...

  2. OpenCV学习(15) 细化算法(3)

          本章我们学习一下Hilditch算法的基本原理,从网上找资料的时候,竟然发现两个有很大差别的算法描述,而且都叫Hilditch算法.不知道那一个才是正宗的,两个算法实现的效果接近,第一种算 ...

  3. OpenCV学习(17) 细化算法(5)

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

  4. OpenCV学习(14) 细化算法(2)

          前面一篇教程中,我们实现了Zhang的快速并行细化算法,从算法原理上,我们可以知道,算法是基于像素8邻域的形状来决定是否删除当前像素.还有很多与此算法相似的细化算法,只是判断的条件不一样. ...

  5. OpenCV学习(13) 细化算法(1)

    程序编码参考经典的细化或者骨架算法文章: T. Y. Zhang and C. Y. Suen, "A fast parallel algorithm for thinning digita ...

  6. OpenCV学习(19) 细化算法(7)

    最后再来看一种通过形态学腐蚀和开操作得到骨架的方法.http://felix.abecassis.me/2011/09/opencv-morphological-skeleton/ 代码非常简单: v ...

  7. c++opencv中线条细化算法

    要达到的效果就是将线条尽量细化成单像素,按照论文上的Hilditch算法试了一下,发现效果不好,于是自己尝试着写了一下细化的算法,基本原理就是从上下左右四个方向向内收缩. 1.先是根据图片中的原则确定 ...

  8. OpenCV学习(9) 分水岭算法(3)

    本教程我学习一下opencv中分水岭算法的具体实现方式. 原始图像和Mark图像,它们的大小都是32*32,分水岭算法的结果是得到两个连通域的轮廓图. 原始图像:(原始图像必须是3通道图像) Mark ...

  9. OpenCV学习(21) Grabcut算法详解

    grab cut算法是graph cut算法的改进.在理解grab cut算之前,应该学习一下graph cut算法的概念及实现方式. 我搜集了一些graph cut资料:http://yunpan. ...

随机推荐

  1. js判断某年某月有多少天

    function getCountDays(ym) { var curDate = new Date(ym); /* 获取当前月份 */ var curMonth = curDate.getMonth ...

  2. MYSQL插入不能中文的问题的解决

    这个问题是由于数据库的字符集不对的问题. 解决方法: 打开要用的数据库,输入命令 status 如果Client characterset 值为utf8,则要改为:set char set 'gbk' ...

  3. Gumbel-Softmax Trick和Gumbel分布

      之前看MADDPG论文的时候,作者提到在离散的信息交流环境中,使用了Gumbel-Softmax estimator.于是去搜了一下,发现该技巧应用甚广,如深度学习中的各种GAN.强化学习中的A2 ...

  4. 全链路压测平台(Quake)在美团中的实践

    背景 在美团的价值观中,以“客户为中心”被放在一个非常重要的位置,所以我们对服务出现故障越来越不能容忍.特别是目前公司业务正在高速增长阶段,每一次故障对公司来说都是一笔非常不小的损失.而整个IT基础设 ...

  5. Swift2.0语言教程之类的嵌套与可选链接

    Swift2.0语言教程之类的嵌套与可选链接 Swift2.0语言类的嵌套 在一个类中可以嵌套一个或者多个类.它们的嵌套形式也是不同的,大致分为了两种:直接嵌套和多次嵌套.下面依次讲解这两种方式. S ...

  6. 1015 Reversible Primes (20)(20 point(s))

    problem A reversible prime in any number system is a prime whose "reverse" in that number ...

  7. Charles 对于线上比一比抓包怎么修改

    1: http://m.1768.com/?act=game_biyibi&abeam=1 Map Local /Users/zhengming/Desktop/biyibi.html biy ...

  8. Java实现杨辉三角形

    Java实现杨辉三角形 一.源代码:YFTriangle.java package cn.com.zfc.day009; import java.util.Scanner; /** * 打印杨辉三角形 ...

  9. Set常用子类特点

    HashSet:       重写   hashCode和equals方法                                        特点:无序,唯一      底层结构是:    ...

  10. spring---transaction(5)---事务的体系

    1.写在前面 事务的模型为3中: 本地事务模式. 编程事务模式. 声明事务模式. 例子1:本地事务模式 Connection conn=jdbcDao.getConnection(); Prepare ...