指尖检测测试(二)

    接着上一篇《指尖检测(一)》讲,当用曲率的方式检测出候选指尖点后,我们需要踢出非指尖点,主要就是手指间的凹槽。从原理上分析,如果候选点集Pi(i=0,1,2....)按照一定方式排列,比如都按顺时针或者逆时针存储在一个数组中,那么向量(Pi-1,Pi)和(Pi,Pi+1)(说明,这里向量表示不是这样的,因为这上面编辑公式不太方便就用这种方式表示了,大家理解就行) 的叉乘正负性不同也就是方向不同,通过这样的方式可以检测出来那哪些是指尖。理论上是可行的,但是我在具体编程时发现不太好写,因为我所找到的点是通过Opencv的一个函数得到的,点放在CvSeq的对象中,我输出了所以点进行观察,但是发现那些点既不是顺时针也不是逆时针排列,搞不清楚怎么排的。

    还有种方法也可以去掉凹槽,那就是先算出手部轮廓座标中心(横座标平均,纵座标平均)或者算重心(用矩算),然后根据点之间距离差异滤掉干扰点,但是在设置阈值上有点麻烦,如果手部轮廓图大小发生变化,那么固定阈值就不起作用。我考虑过用自适应阈值的方式,那就是通过图像大小和手部轮廓大小的比例关系来设置阈值,但我还为做实验验证是否可行。

    那么由于有这些问题所在,我最后采用凸包和曲率结合的办法,在用曲率确定了候选点后,再用凸包算法找到手部轮廓凸包,再将凸包顶点与候选点比较获得手指点。

凸包算法简介:

1.在所有点中选取y座标最小的一点H,当作基点。如果存在多个点的y座标都为最小值,则选取x座标最小的一点。座标相同的点应排除。然后按照其它各点p和基点构成的向量<H,p>x轴的夹角进行排序,夹角由大至小进行顺时针扫描,反之则进行逆时针扫描。实现中无需求得夹角,只需根据向量的内积公式求出向量的模即可。以下图为例,基点为H,根据夹角由小至大排序后依次为HKCDLFGEIBAJ。下面进行逆时针扫描。

 

 

2.线段<H, K>一定在凸包上,接着加入C。假设线段<K, C>也在凸包上,因为就HKC三点而言,它们的凸包就是由此三点所组成。但是接下来加入D时会发现,线段<K, D>才会在凸包上,所以将线段<K,C>排除,C点不可能是凸包。

3.即当加入一点时,必须考虑到前面的线段是否会出现在凸包上。从基点开始,凸包上每条相临的线段的旋转方向应该一致,并与扫描的方向相反。如果发现新加的点使得新线段与上线段的旋转方向发生变化,则可判定上一点必然不在凸包上。实现时可用向量叉积进行判断,设新加入的点为pn + 1,上一点为pn,再上一点为pn - 1。顺时针扫描时,如果向量<pn - 1, pn><pn, pn + 1>的叉积为正(逆时针扫描判断是否为负),则将上一点删除。删除过程需要回溯,将之前所有叉积符号相反的点都删除,然后将新点加入凸包。

 

 

在上图中,加入K点时,由于线段<H,K>相对于<H,C>为顺时针旋转,所以C点不在凸包上,应该删除,保留K点。接着加入D点,由于线段<K, D>相对<H,K>为逆时针旋转,故D点保留。按照上述步骤进行扫描,直到点集中所有的点都遍例完成,即得到凸包。

贴出部分代码仅供参考:

float Multiply(CvPoint p1,CvPoint p2,CvPoint p0) 
{ 
	return((p1.x-p0.x)*(p2.y-p0.y)-(p2.x-p0.x)*(p1.y-p0.y)); 
} 
float Dis(CvPoint p1,CvPoint p2) 
{ 
	return(sqrt((float)((p1.x-p2.x)*(p1.x-p2.x)+(p1.y-p2.y)*(p1.y-p2.y)))); 
} 

void Graham_scan(CvPoint pointVec[],CvPoint vertexSet[],int n,int &len) 
{ 
	int i,j,k=0,top=2; 
	CvPoint tmp; 
	//找到最下且偏左的那个点 
	for(i=1;i<n;i++) 
		if ((pointVec[i].y<pointVec[k].y)||((pointVec[i].y==pointVec[k].y)&&(pointVec[i].x<pointVec[k].x))) 
			k=i; 
		//将这个点指定为PointSet[0] 
		tmp=pointVec[0]; 
		pointVec[0]=pointVec[k]; 
		pointVec[k]=tmp; 
		//按极角从小到大,距离偏短进行排序 
		for (i=1;i<n-1;i++) 
		{ 
			k=i; 
			for (j=i+1;j<n;j++) 
				if( (Multiply(pointVec[j],pointVec[k],pointVec[0])>0) 
					||((Multiply(pointVec[j],pointVec[k],pointVec[0])==0) 
					&&(Dis(pointVec[0],pointVec[j])<Dis(pointVec[0],pointVec[k]))) ) 
					k=j;//k保存极角最小的那个点,或者相同距离原点最近 
				tmp=pointVec[i]; 
				pointVec[i]=pointVec[k]; 
				pointVec[k]=tmp; 
		} 
		//第三个点先入栈 
		vertexSet[0]=pointVec[0]; 
		vertexSet[1]=pointVec[1]; 
		vertexSet[2]=pointVec[2]; 
		//判断与其余所有点的关系 
		for (i=3;i<n;i++) 
		{ 
			//不满足向左转的关系,栈顶元素出栈 
			while(Multiply(pointVec[i],vertexSet[top],vertexSet[top-1])>=0) top--; 
			vertexSet[++top]=pointVec[i]; 
		} 
		len=top+1; 
} 

对手部轮廓凸包计算的效果图:


手部轮廓总的点数和凸包的顶点数:


最后检测的效果图:



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章