博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

摘要:本文所述算法来自IEEE PAMI的文章"Multiprimitive Segmenatation of Planar Curves-A Two-Level Breakpoint Classification and Tuning Approach"。大家可以到百度文库:http://wenku.baidu.com/view/e01f28f10242a8956bece43f.html?st=1下载。如果没有文库号的,可以到新浪爱问:http://ishare.iask.sina.com.cn/f/34715669.html免费下载。本文算法程序均系作者原创,欢迎各位研究指正。如需转载,请注明出处。


 

一、AKC函数介绍

首先把特征点分为两类,一类角点(corners),一类平滑结点(Smooth joints)。(注意:论文中把特征点称为Breakpoints断点,两者是同一概念。如果宽泛的将,有时我们也把特征点或者断点统称为角点)。其中角点类表明其两侧具有不连续的切线。其中平滑结点类表明其两侧具有不连续的曲率。如图1图2。

 

 

所谓AKC函数,即Adaptive k-Curvature Function自适应的k曲率函数。所谓k曲率有兴趣的读者,可以参考其鼻祖文章"Angle Detection on Digital Curves",下载地址:http://ishare.iask.sina.com.cn/f/34715883.html。其实AKC函数定义就是基于k曲率值。其定义如下:考虑三个连续的特征点Pi-1,Pi和Pi+1,由Pi-1Pi,PiPi+1组成的两条线段Si和Si+1。定义Si和Si+1线段长度为l1和l2,k=min(l1,l2),并且Pi点的ROS(支撑区域)为[Pi-k~, Pi+k~],其中k~=k/2。定义点Pj属于[Pi-k~, Pi+k~]的k向量为:

a~jk=(Pj+(x)-Pj(x),Pj+(y)-Pj(y)),  (1a)

a~jk=(Pj-(x)-Pj(x),Pj-(y)-Pj(y)),  (1b)

这里Pj+=Pj+k~,Pj-=Pj-k~,那么ajk和bjk之间的k-cosine的值为

cjk=(a~jk•b~jk)/||a~jk||||b~jk||  (2)

定义Pi点的AKC函数为cjk任意的Pj属于[Pi-k~,Pi+k~],如图3。

 

注意到AKC函数是水平对称的,而且和起点无关。这就克服了其他算法中方向和起始点的问题。那么图1和2的AKC函数到底如何呢?请看图4。

注意到在图4a,4b,4c,4d和4fAKC函数中存在全局最大值,这5幅图均来自角点的AKC函数。由图可见在角点处切线不连续,且k-cosine值很大。然而,对于平滑结点,其切线连续,故其k-cosine值平滑变化,而且其局部极大值并不显著。如图4g,4h和4i。很显然,如果在断点处存在全局最大值,那么为角点;否则为平滑结点。

二、算法实现

1、定义数据结构

由于算法处理对象为平面曲线的轮廓,因此需要考虑轮廓点,角点,平滑结点等数据。因此定义结构体如下图5:

 

其中x:轮廓点的横坐标

y:轮廓点的纵坐标

csp[0]:轮廓点顺序1,2,3,...

csp[1]:角点顺序1,2,3,...不为角点的轮廓点记为0

csp[2]:该点为corner置1;smooth joint置2

csp[3]:该点前面的曲线为corner置1;smooth joint置2

csp[4]:该点后面的曲线为corner置1;smooth joint置2

csp[5...]:冗余空间先置为0

prior:结点的前驱指针

next:结点的后继指针 

View Code
1 typedef struct LNode
2 {
3     struct LNode *prior;
4     int x;
5     int y;
6     int csp[6];
7     struct LNode *next;
8 }LNode, *Link;

 其中LNode为结点类型,Link为指向结点类型的指针。定义完结点,下面就是创建结点,初始化结点,释放结点等函数的编写。在此不再赘述。

View Code
 1 bool MakeNode(Link &p)
 2 {
 3     // 构造p指向的LNode结点,成功返回true,失败返回false
 4     p = new LNode;
 5     return p!=NULL;
 6 }
 7 
 8 void FreeNode(const Link &p)
 9 {
10     // 释放p指向的LNode结点
11     delete p;
12 }
13 
14 void InitNode(const Link &p)
15 {
16     // 初始化p指向的LNode结点
17     p->prior = NULL;
18     p->x = 0;
19     p->y = 0;
20     p->csp[0] = 0;
21     p->csp[1] = 0;
22     p->csp[2] = 0;
23     p->csp[3] = 0;
24     p->csp[4] = 0;
25     p->csp[5] = 0;
26     p->next = NULL;
27 }

OK!那么最终的平面曲线上的点应该被表现为图6,其中黑色代表头结点,内容为空。从第二结点开始存储轮廓点,其中红色的轮廓点为特征点,蓝色的轮廓点为普通非特征点。考虑到曲线轮廓的闭合性,也方便将来的遍历操作,这里设置为循环链表。

 

现在基本的数据结构有了,下面讨论AKC函数的实现。其设计思想,上一部分和论文已经说得很清楚了。首先就是遍历整个轮廓中的特征点,对于每一个特征点,确定其计算区间,再求出其a~jk和b~jk,然后算出一系列的cjk,通过找寻特征点出是否有最大值来判断是否为角点or平滑点。下面对每一个特征点设计AKC函数如下:

View Code
 1 double* AKC(Link &p, int kb)
 2 {
 3     // AKC函数
 4     int i=0;
 5     Link PJ = kPriorPoint(p, kb);    // PJ is Pj Pj∈(p-k, p+k) 
 6     Link PJN = NULL;    // PJN is Pj-, Pj-=Pj-k
 7     Link PJP = NULL;    // PJP is Pj+, Pj+=Pj+k
 8     int ajx, ajy, bjx, bjy;
 9     int tk=2*kb;
10     double *c = new double [tk+1];    
11     while(i<=tk)
12     {
13         PJN = kPriorPoint(PJ, kb);
14         PJP = kNextPoint(PJ, kb);
15         ajx = PJP->x-PJ->x;
16         ajy = PJP->y-PJ->y;
17         bjx = PJN->x-PJ->x;
18         bjy = PJN->y-PJ->y;
19         c[i] = (ajx*bjx+ajy*bjy)/(sqrt(ajx*ajx+ajy*ajy)*sqrt(bjx*bjx+bjy*bjy));
20         PJ = PJ->next;
21         if (PJ->csp[0] == 0)
22         {
23             PJ=PJ->next;
24         }
25         i++;
26     }
27     return c;
28 }

 

其中Link kPriorPoint(Link &p, int k);和Link kNextPoint(Link &p, int k);函数为寻找当前节点的前面k个结点,和后面k个结点。

三、实验结果

对下图进行操作。

 

上图的角点(正坐标在前,纵坐标在后)49 75;49 100;65 117;156 119;156 56;72 56;共6个角点。那么使用AKC函数后,结果如何?先看看每一点的AKC函数图,从72 56点开始,逆时钟转依次显示每一点的AKC,共6幅图。

从图中可以清晰看出最后两幅图对应为角点,其余为smooth点。

四、结束语

其实AKC函数是为后面的PHF函数做准备的,因此本文没有列举大量的实例。后面的PHF函数将会有更直观的分类感受。写博客真不是件轻松的事情啊,图片还得一张张传,字还得慢慢敲......好不容易写了篇,加油吧!