指尖檢測測試(二)

    接着上一篇《指尖檢測(一)》講,當用曲率的方式檢測出候選指尖點後,我們需要踢出非指尖點,主要就是手指間的凹槽。從原理上分析,如果候選點集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; 
} 

對手部輪廓凸包計算的效果圖:


手部輪廓總的點數和凸包的頂點數:


最後檢測的效果圖:



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