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

假设要处理的图像为二值图,前景值为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. poj1182 食物链(带权并查集)

    题目链接 http://poj.org/problem?id=1182 思路 前面做的带权并查集的权值记录该结点与其父结点是否是同一类,只有两种取值情况(0,1),在这题中某结点a和其父结点b的取值共 ...

  2. react篇章-React Props

    state 和 props 主要的区别在于 props 是不可变的,而 state 可以根据与用户交互来改变.这就是为什么有些容器组件需要定义 state 来更新和修改数据. 而子组件只能通过 pro ...

  3. 超实用 Git 使用方式介绍

    都说程序员若是不知道 GitHub 就不是一个合格的程序员,其实这话说的过分了,不知道就学嘛,今天我们就来说说 Git 和 GitHub 到底是什么. 我们在开发软件的时候,常常是需要多人协作完成,这 ...

  4. 洛谷——P2083 找人

    P2083 找人 题目背景 无 题目描述 小明要到他的同学家玩,可他只知道他住在某一单元,却不知住在哪个房间.那个单元有N层(1,2……N),每层有M(1,2……M)个房间. 小明会从第一层的某个房间 ...

  5. 掩码计算工具netmask

    掩码计算工具netmask   在网络扫描和防火墙配置中,经常需要计算IP地址范围和对应的掩码.为了简化这个过程,Kali Linux预置了一个掩码计算工具netmask.该工具不仅可以根据IP地址范 ...

  6. 02-c#基础之01-基础语法(二)

    1.变量的存储以及变量的几种类型 变量:用来在计算机当中存储数据. 存储变量的语法: 变量类型 变量名: 变量名=值: int number=100: 2.赋值"=" " ...

  7. linux命令大全之cal命令详解(显示日历)

    cal命令可以用来显示公历(阳历)日历. 1.命令格式:cal [参数][月份][年份] 2.命令功能:用于查看日历等时间信息,如只有一个参数,则表示年份(1-9999),如有两个参数,则表示月份和年 ...

  8. 二分搜索之C++实现

    二分搜索之C++实现 一.源代码:BinarySearch.cpp #include<iostream> using namespace std; /*定义输出一维数组的函数*/ void ...

  9. LCT维护子树信息

    有些题目,在要求支持link-cut之外,还会在线询问某个子树的信息.LCT可以通过维护虚边信息完成这个操作. 对于LCT上每个节点,维护两个两sz和si,后者维护该点所有虚儿子的信息,前者维护该点的 ...

  10. Python168的学习笔记1

    在对list的条件选择有两种常用方法,直接使用filter函数,就是filter(func,sequence);另外一种就是迭代操作,类似 x for x in sequence func.这两种方法 ...