最近自己在玩一个手势识别的项目,项目要实现的目标是让手可以模拟鼠标的移动,单击,双击,这样用户可以脱离鼠标直接用手操控电脑光标进行人机交互。
在本项目中的一个重要问题是手掌重心的提取。获得手掌重心通常的做法是计算整个手部的重心,并以该重心位置近似手掌重心,这种方法只适用于没有手指伸出或只有一个手指伸出的情况,如下图a所示,如果五个手指都伸出,通过该方法获得的手掌重心位置将严重偏离真实位置,如图b所示。
要获得准确的手掌重心必须要将伸出的手指去除,否则手指必然会对手掌重心的计算产生影响。本文介绍一种基于距离变换的手掌重心提取算法,该算法可以避免手指的影响,精确找到手掌中心。
距离变换的基本含义是计算一个图像中非零像素点到最近的零像素点的距离,也就是到零像素点的最短距离。由于伸出的手指相对于手掌来说比较细(如下图“src”窗口图像所示),也就是说手指上的像素距离零像素距离很短,所以经过距离变换后的图像在手指部位的像素值较小(如下图“dst”窗口图像所示),通过设定合理的阈值进行二值化,则可以得到去除手指的图像(如下图“bidist”窗口图像所示),手掌重心即为该图像的几何中心。
在OpenCV的距离变换函数是cvDistTransform,关于此函数各个参数的具体含义在此就不做介绍了,请大家参加OpenCV的用户手册,上面有详细的介绍。下面是代码,使用工具为VS2008+OpenCV2.0.
#include <cv.h> #include <highgui.h> int main() { char* filename = "D:\\technology\\CV\\Databases\\image\\bi5.jpg"; IplImage* src_image = cvLoadImage(filename,1); if(!src_image) return -1; cvNamedWindow("src"); CvSize size = cvGetSize(src_image); IplImage* gray_image = cvCreateImage(size,8,1); cvCvtColor(src_image,gray_image,CV_BGR2GRAY); IplImage* dist_image = cvCreateImage(size,32,1); IplImage* bi_src = cvCreateImage(size,8,1); IplImage* dist8u_image = cvCreateImage(size,8,1); IplImage* bi_dist = cvCreateImage(size,8,1); //原图像二值化 cvThreshold(gray_image,bi_src,100,255,CV_THRESH_BINARY); //距离变换 cvDistTransform(bi_src,dist_image,CV_DIST_L2,3,0,0); //找最大值 double max; cvMinMaxLoc(dist_image,0,&max,0,0); cvCvtScale(dist_image,dist8u_image,255./max); //对距离图像二值化,去除手指部分 cvThreshold(dist8u_image,bi_dist,80,255,CV_THRESH_BINARY); //求重心 float s=0.0, x=0.0, y=0.0; uchar* data = (uchar*)bi_dist->imageData; int step = bi_dist->widthStep; for(int h=0;h<bi_dist->height;h++) for(int w=0;w<bi_dist->width;w++) if(255 == data[step*h+w]) { x += w; y += h; s++; } if(s>0) { x = x/s; y = y/s; } CvPoint pos = cvPoint((int)x,(int)y); cvCircle(src_image,pos,3,CV_RGB(255,0,0),1,CV_AA); cvShowImage("src",src_image); cvWaitKey(-1); cvDestroyWindow("src"); cvReleaseImage(&src_image); cvReleaseImage(&gray_image); cvReleaseImage(&bi_src); cvReleaseImage(&dist8u_image); cvReleaseImage(&bi_dist); return 0; }
程序运行结果截图: