基于meanshift的手势跟踪与电脑鼠标控制(手势交互系统)

转自网络:http://blog.csdn.net/zouxy09/article/details/17913745

基于meanshift的手势跟踪与电脑鼠标控制(手势交互系统)

[email protected]

http://blog.csdn.net/zouxy09

一年多前开始接触计算机视觉这个领域的时候,年幼无知,倍感吃力。当年惶恐,从而盲从。挣扎了不少时日,感觉自己好像还是处于领域的门外汉一样,在理论与实践的鸿沟中无法挣脱,心里空落落的。在这种挥之不去的烦忧中,某个时候豁然开朗,觉得要看一个系统的代码了,看看别人是怎么写的,理论又是怎么用在实践上的。然后自己就瞄准了TLD这个被炒作地很火的跟踪算法。花了点时间做了详细的源码解读和注释。也发到了博客上,光荣的成为了我第一篇博客的主题。当年还年少气盛,欲借TLD与开源之力,顺势而为,扬言要捣腾一个人机交互系统出来,其中包括手势跟踪、TTS语音合成、语音识别、手势和语音控制鼠标和键盘等等。还naive到要移植到嵌入式设备上。不过,当年也的确开始了点工作,包括将TLD用以手势跟踪鼠标控制Ekho(余音)TTS语音合成PocketSphinx语音识别等。后来因为语音识别效果不好,就卡住停滞不前了。

这篇博文记录的其实和之前的将TLD用以手势跟踪有异曲同工之妙,哈哈。不同的点在于跟踪用的不是TLD了,而是简单的meanshift,当然了,我们也可以使用其他的目标跟踪算法,但这里需要实时的算法。例如我们之前分析的压缩跟踪CT算法,和时空上下文跟踪STC算法都是OK的。连我之前写的最简单的模板匹配感知哈希的跟踪都在这个平台上可以发挥光和热。但其实,真正的手势控制或者手势交互需要的跟踪算法要比这些要求高,它需要更准确和稳定的跟踪,还得抗干扰,克服复杂背景,光照等等。还有一点就是得避免跟踪框的抖动,否则鼠标也会跟着抖动,那多不爽啊。所以俺们的这个系统也是孤独下的催生物而已,也许没有太大的利用价值。哦,还有一点不同是,之前是在Linux平台下的,这次改在windows阵地,所以鼠标控制的接口就不同了。另外,本博文的代码比之前的要规范一点,需要替换跟踪器的时候,也可以较容易的替换。

总的来说,这篇博文只是一个基于2D摄像头的粗糙的手势控制系统(不是一般的粗糙哦,哈哈),包括以下功能:

1)跟踪人手,然后映射到电脑的鼠标,也就是用人手取代鼠标去控制电脑光标(鼠标)的移动。

2)通过检测用户的握拳来模拟鼠标的左键点击功能,也就是我们用手控制鼠标移动到一个地方的时候,握拳,就相当于单击鼠标左键,也就是确定的功能了。

一、手势跟踪

       人手的跟踪我们采用的是简单的meanshift算法。因为人手是肤色的,所以如果背景是没有类肤色的东西的话,跟踪是很有效的。当然了,如果人手移动到人脸的地方,很大可能会影响跟踪结果。这是该算法的弱点之一。

1.1、meanshift跟踪

meanshift均值漂移算法可谓是经典之经典。以至于在江湖上,视觉派和图像派的人如果不认得该秘笈,都不好意思出来混江湖了。对于这么经典的算法,江湖上的解说也一搜一大箩,大家也可以参考文献[2][3][4]。这里就简短介绍下算法主要思想和工作过程。

meanshift算法本质上是最优化理论中的最速下降法。即沿着梯度下降方法寻找目标函数的极值。在跟踪中,就是为了寻找到相似度值最大的候选目标位置。meanshift方法沿着概率密度的梯度方向进行迭代移动,最终达到密度分布的最值位置。其迭代过程本质上是的最速下降法,下降方向为一阶梯度方向,步长为固定值。比较经典的meanshift算法的过程图示要数下面这个了:

在d维空间中,任选一个点,然后以这个点为圆心,h为半径做一个高维球,因为有d维,d可能大于2,所以是高维球。落在这个球内的所有点和圆心都会产生一个向量,向量是以圆心为起点落在球内的点为终点。然后把这些向量都相加。相加的结果就是meanshift向量。如下图,其中黄色箭头就是meanshift向量。

再以meanshift向量的终点为圆心,再做一个高维的球。如下图所以,重复以上步骤,就可得到一个meanshift向量。如此重复下去,meanshift算法可以收敛到概率密度最大得地方。也就是最稠密的地方。

最终的结果如下:

对meanshift的更多理解和推导请参考网络上的更多资料。下面我们分析下meanshift如何用在我们系统的手势跟踪过程中。我们要跟踪的是人手,而人手一般是比较统一的肤色,那么我们在图像中人手的这个区域统计每一种颜色的像素个数,这样我们会得到肤色的像素个数是最多的。然后对于新来的一帧图像,我们怎么找到手的位置呢?我们需要寻找满足人手肤色的地方。具体是通过用上面得到的直方图来计算整幅图像的反向投影。实际上就是一个肤色的概率图,图像中像素越像肤色,那该点属于肤色的概率就越大,在反向投影图中的值越大。例如:在整幅图中,假设某个像素的H分量值为10,然后我们到刚才从人手中统计得到的直方图中查找H=10对应的纵坐标值为50(代表搜索窗口中有50个H值为10的像素),则将整幅图中该像素的值置为50。对所有像素对应查找赋值便得到了反向投影图。可以理解为,它放映的是人手在图像中的位置的概率图(当然了,严格说不是这么理解的,这是像素级别和模板级别的差别)。概率图中值最大的地方也就是手的地方。然后我们通过meanshift算法来找到这个地方。

我们的手势跟踪过程大概描述如下;

(1)初始化:我们检测到图像中人手区域后,统计得到该区域HSV颜色空间H分量的直方图。得到的直方图横坐标为0-255的灰度值(H分量的值),纵坐标为人手图像区域中某个H分量的值的像素个数。

(2)反向投影:利用(1)的直方图计算整幅图像的概率分布。实际计算中不必计算整幅图像,只需计算比搜索窗口大一些的范围。

(3)利用meanshift算法迭代找到概率分布图的重心。也就是人手的位置了。

以上三个步骤,OpenCV都提供的简便的接口可以利用。具体使用方式见后面的代码。

1.2、运动信息的融合

单纯地用肤色来跟踪的话,如果背景有肤色的东西meanshift算法就比较容易跟丢。所以我们可以加入运动信息来辅助,排除背景中静态肤色的干扰,同时人脸一般也是不动的,也可以一定程度的排除。

那我们怎么获得运动信息呢?我之前的博文有稍微总结下现有的运动检测算法。但这里我们为了秉承简单的理念,使用了帧差这个简单到令人发指的运动检测算法。它原理就是前后的两帧图像相减,然后如果是运动的区域的话,那么前后两帧的图像就会不一样,相减的话,结果就会不为0,那如果静止的区域,前后两帧的图像就是一样的,相减的话,值就是0。那么不为0的地方就是图像运动的区域了。

当然了,该算法的缺点也是不言而喻的了。

得到运动信息之后,最简单的和肤色信息的融合方法就是加权融合了。将两个概率图进行加权,得到新的概率图,然后交给meanshift来找到概率最大的地方。

二、鼠标控制

       鼠标控制包括两部分:一是手的跟踪需要映射到鼠标的移动,二是当用户握拳的时候需要激活鼠标单击的指令。之前的那个交互系统是在Linux平台下的,这次改在windows阵地。所以鼠标控制的接口有所不同。那下面就介绍下windows下的鼠标控制接口。

2.1、鼠标移动:手移动

Windows提供mouse_event()这个函数给用户来模拟鼠标事件。它的使用方法如下:

VOID mouse_event(

DWORD dwFlags,              // 鼠标动作标识。

DWORD dx,                        // 鼠标水平方向位置。

DWORD dy,                       // 鼠标垂直方向位置。

DWORD dwData,              //  鼠标轮子转动的数量。

DWORD dwExtraInfo        //  一个关联鼠标动作辅加信息。

);

其中,各参数的使命如下:

dwFlags:标志位集,指定点击按钮和鼠标动作的多种情况。此参数里的各位可以是下列值的任何合理组合:

MOUSEEVENTF_ABSOLUTE:表明参数dx,dy含有规范化的绝对坐标。

MOUSEEVENTF_MOVE:表明发生移动,相对于上次位置的改动位置。

MOUSEEVENTF_LEFTDOWN:表明接按下鼠标左键。

MOUSEEVENTF_LEFTUP:表明松开鼠标左键。

MOUSEEVENTF_RIGHTDOWN:表明按下鼠标右键。

MOUSEEVENTF_RIGHTUP:表明松开鼠标右键。

MOUSEEVENTF_MIDDLEDOWN:表明按下鼠标中键。

MOUSEEVENTF_MIDDLEUP:表明松开鼠标中键。

MOUSEEVENTF_WHEEL:在Windows NT中如果鼠标有一个轮,表明鼠标轮被移动。移动的数量由dwData给出。

dx:当dwFlags设置为MOUSEEVENTF_ABSOLUTE的时候,这里的dx就表示在屏幕中的绝对位置的x坐标。也就是说,这个值是多少,鼠标就位于屏幕的这个点处(相对于左上角(0,0))。需要注意的是,dX和dy是标准化的绝对坐标,其值在0到65535之间。事件程序将此坐标映射到屏幕上。坐标(0,0)映射到显示表面的左上角,(65535,65535)映射到右下角。所以我们的绝对坐标是要规范化到(65535,65535)这个范围再传入这个函数。当dwFlags设置为MOUSEEVENTF_MOVE的时候,dx表示从上次鼠标事件产生以来移动的数量。例如,上次坐标是在屏幕的(100,200)处,如果dx=10,那么这时候就处于(110,200)处了(不过其实系统会乘以一个倍率的,好像在控制面板的鼠标选项的灵敏度中选)。正值表示鼠标向右(或下)移动;负值表示鼠标向左(或上)移动。

dy:和上面的dx一样,只是dy是y轴上的绝对坐标或者相对移动值。

dwData:如果dwFlags为MOUSEEVENTF_WHEEL,则dwData指定鼠标轮移动的数量。正值表明鼠标轮向前转动,即远离用户的方向;负值表明鼠标轮向后转动,即朝向用户。一个轮击定义为WHEEL_DELTA,即120。如果dwFlagsS不是MOUSEEVENTF_WHEEL,则dWData应为零。

dwExtralnfo:指定与鼠标事件相关的附加32位值。

在我们的系统中,使用的是相对坐标。我们得到上一帧对手的跟踪点,然后得到当前帧的手的跟踪点,两个跟踪点在x和y方向的距离dx和dy就是手的移动距离,然后我们乘以一个倍率k,再传入mouse_event()函数中,如下:

mouse_event(MOUSEEVENTF_MOVE,-k * dx, k * dy, 0, 0);

2.2、鼠标点击:检测握拳

我们通过训练adaboost拳头分类器类检测拳头。为了减少耗时,我们跟踪到手后,只在手的位置去检测握拳就可以了。为了避免误检,当重复检测到三次拳头后,我们才相信用户是握拳了。另外,用户的一次握拳可能会被多次检测到,所以当检测到一次握拳后,会等待一定的时间,在这个时间内检测到拳头都会被当成是上次的握拳动作,而被无情却又智能的忽略掉。

mouse_event 函数同时可以模拟鼠标的左键按下:我们设置dwFlags 标志为MOUSEEVENTF_LEFTDOWN时表示左键按下,为MOUSEEVENTF_LEFTUP表示左键松开,向系统发送相应消息。需要注意的是,激活鼠标按下的事件后,都要注意还原,即按下键之后要松开,也就是:

mouse_event(MOUSEEVENTF_LEFTDOWN,0, 0, 0, 0);

mouse_event(MOUSEEVENTF_LEFTUP,0, 0, 0, 0);

完成一个单击动作。

三、人机交互系统的实现

3.1、系统初始化:手掌的检测

在这里,我们也需要训练一个adaboost手掌分类器。这样,当用户举起手准备控制的时候,我们可以获知,并将人手的位置告诉meanshift跟踪算法,吩咐他要跟踪这么个东西。另外,为了一定程度的避免误检,我们需要在手掌分类器检测得到手的区域的时候,我们分析这个区域是否包含足够的肤色区域,因为是人手的,如果它检测的是人手,那么很大的区域肯定是肤色的。当然了,太小的手或者检测到的目标,或者手离图像的边缘太近,系统也会放弃跟踪的,不会理会这种很大可能会出错的跟踪。

3.2、系统实现代码

我的代码是基于VS2010+ OpenCV2.4.2的。在这里贴上代码,至于里面的手掌和拳头分类器,鉴于某些原因,无法公开,大家可以下载腾讯的QQ手势达人forPPT。安装软件后,在软件的安装目录里面有dat后缀的手掌和拳头分类器,同样可以用opencv的接口来加载和使用。在我的代码里面直接替换我的xml后缀的分类器即可。

main.cpp

[cpp] view plaincopy

  1. // Hand tracking using meanshift and mouse control
  2. // Author : zouxy
  3. // Date   : 2014-01-06
  4. // HomePage : http://blog.csdn.net/zouxy09
  5. // Email  : [email protected]
  6. #include "opencv2/opencv.hpp"
  7. #include "handTracker.h"
  8. using namespace cv;
  9. using namespace std;
  10. int main(int argc, char* argv[])
  11. {
  12. Mat frame;
  13. Rect trackBox;
  14. Point prePoint, curPoint;
  15. VideoCapture capture;
  16. bool gotTrackBox = false;
  17. int interCount = 0;
  18. int continue_fist = 0;
  19. capture.open(0);
  20. if (!capture.isOpened())
  21. {
  22. cout<<"No camera!\n"<<endl;
  23. return -1;
  24. }
  25. HandTracker hand_tracker;
  26. while (1)
  27. {
  28. /************ Get hand which need to be tracked ************/
  29. while (!gotTrackBox)
  30. {
  31. capture >> frame;
  32. if(frame.empty())
  33. return -1;
  34. if (hand_tracker.init(frame, trackBox))
  35. {
  36. gotTrackBox = true;
  37. prePoint = Point(trackBox.x + 0.5 * trackBox.width, trackBox.y + 0.5 * trackBox.height);
  38. }
  39. imshow("handTracker", frame);
  40. if (waitKey(2) == 27)
  41. return -1;
  42. }
  43. capture >> frame;
  44. double t = (double)cvGetTickCount();
  45. /****************** Tracking hand **************************/
  46. if (!hand_tracker.processFrame(frame, trackBox))
  47. gotTrackBox = false;
  48. /****************** Control the mouse *********************/
  49. curPoint = Point(trackBox.x + 0.5 * trackBox.width, trackBox.y + 0.5 * trackBox.height);
  50. int dx = curPoint.x - prePoint.x;
  51. int dy = curPoint.y - prePoint.y;
  52. float k = 1.5;
  53. mouse_event(MOUSEEVENTF_MOVE, -k * dx, k * dy, 0, 0);
  54. prePoint = curPoint;
  55. // When you made a fist, means you click left button of mouse
  56. // To avoid successive active within short time, when you had actived a single
  57. // we will ingnore the next 10 frames
  58. interCount++;
  59. if(interCount > 10 && hand_tracker.detectFist(frame, trackBox))
  60. {
  61. // To avoid detecting error, need to successive detect three times successfully
  62. continue_fist++;
  63. if (continue_fist > 3)
  64. {
  65. interCount = 0;
  66. cout << "YOU active a single click command!" << endl;
  67. mouse_event(MOUSEEVENTF_LEFTDOWN, 0, 0, 0, 0);
  68. mouse_event(MOUSEEVENTF_LEFTUP, 0, 0, 0, 0);
  69. }
  70. }
  71. else
  72. continue_fist = 0;
  73. /******************* Show image ****************************/
  74. rectangle(frame, trackBox, Scalar(0, 0, 255), 3);
  75. imshow("handTracker", frame);
  76. /******************* Show cost time ************************/
  77. t = (double)cvGetTickCount() - t;
  78. cout << "cost time: " << t / ((double)cvGetTickFrequency()*1000.) << endl;
  79. if ( cvWaitKey(3) == 27 )
  80. break;
  81. }
  82. return 0;
  83. }

handTracker.h

[cpp] view plaincopy

  1. // hand tracking using meanshift
  2. #pragma once
  3. #include "opencv2/opencv.hpp"
  4. using namespace cv;
  5. using namespace std;
  6. class HandTracker
  7. {
  8. public:
  9. HandTracker(void);
  10. ~HandTracker(void);
  11. bool init(Mat frame, Rect &trackBox);
  12. bool processFrame(Mat frame, Rect &trackBox);
  13. bool detectFist(Mat frame, Rect trackBox);
  14. private:
  15. bool isHand(const Mat frame);
  16. void detectPalm( Mat img, Rect &box);
  17. void frameDiff(const Mat image, Mat &diff);
  18. void calSkinPro(Mat frame);
  19. void getSkinModel(const Mat img, Rect rect);
  20. private:
  21. Mat backProject;
  22. MatND hist;
  23. CascadeClassifier palmCascade;
  24. CascadeClassifier fistCascade;
  25. Mat preGray;
  26. int successiveDetect;
  27. };

handTracker.cpp

[cpp] view plaincopy

  1. // Hand tracking algorithm using meanshift and mouse control
  2. // Author : zouxy
  3. // Date   : 2014-01-06
  4. // HomePage : http://blog.csdn.net/zouxy09
  5. // Email  : [email protected]
  6. #include "handTracker.h"
  7. HandTracker::HandTracker()
  8. {
  9. successiveDetect = 0;
  10. const char *palmCascadeName = "palmCascadeClassifier.xml";
  11. const char *fistCascadeName = "fistCascadeClassifier.xml";
  12. if (!palmCascade.load(palmCascadeName) || !fistCascade.load(fistCascadeName))
  13. {
  14. cout<<"Can not load cascade!"<<endl;
  15. }
  16. }
  17. HandTracker::~HandTracker()
  18. {
  19. }
  20. // init function: detect hand region and init meanshift
  21. bool HandTracker::init(Mat frame, Rect &trackBox)
  22. {
  23. trackBox = Rect(0, 0, 0, 0);
  24. // detect hand
  25. detectPalm(frame, trackBox);
  26. // The detected box should large enough and not near the boundary of image
  27. if (trackBox.area() > 900 && 0.3 * frame.cols < trackBox.x + 0.5 * trackBox.width
  28. && trackBox.x + 0.5 * trackBox.width < 0.7 * frame.cols
  29. && 0.3 * frame.rows < trackBox.y + 0.5 * trackBox.height
  30. && trackBox.y + 0.5 * trackBox.height < 0.7 * frame.rows)
  31. {
  32. // Check skin area of the detected box to make sure it is a hand
  33. if (isHand(frame(trackBox)))
  34. {
  35. // To avoid detecting error, need to successive detect twice successfully
  36. successiveDetect++;
  37. if (successiveDetect > 2)
  38. {
  39. // Calculate skin probability model for meanshift
  40. getSkinModel(frame, trackBox);
  41. successiveDetect = 0;
  42. return true;
  43. }
  44. }
  45. }
  46. return false;
  47. }
  48. // detect hands and return the biggest hand
  49. void HandTracker::detectPalm( Mat img, Rect &box)
  50. {
  51. double scale = 1.3;
  52. Mat small_img, gray;
  53. vector<Rect> boxs;
  54. gray.create(img.rows, img.cols, CV_8UC1);
  55. small_img.create( cvRound (gray.rows/scale), cvRound(gray.cols/scale), CV_8UC1 );
  56. cvtColor( img, gray, CV_BGR2GRAY );
  57. resize( gray, small_img, small_img.size(), 0, 0, INTER_LINEAR );
  58. equalizeHist( small_img, small_img );
  59. palmCascade.detectMultiScale( small_img, boxs, 1.1, 2, CV_HAAR_SCALE_IMAGE, Size(30, 30) );
  60. //Get the bigest face
  61. Rect maxBox(0, 0, 0, 0);
  62. for (vector<Rect>::const_iterator r = boxs.begin(); r != boxs.end(); r++)
  63. {
  64. if (r->area() > maxBox.area())
  65. maxBox = *r;
  66. }
  67. if (boxs.size() > 0)
  68. {
  69. box.x = cvRound(maxBox.x * scale);
  70. box.y = cvRound(maxBox.y * scale);
  71. box.width = cvRound(maxBox.width * scale);
  72. box.height = cvRound(maxBox.height * scale);
  73. }
  74. }
  75. // check skin area of our tracking box to make sure it is a hand
  76. bool HandTracker::isHand(const Mat frame)
  77. {
  78. Mat YCbCr;
  79. vector<Mat> planes;
  80. int count = 0;
  81. cvtColor(frame, YCbCr, CV_RGB2YCrCb);
  82. split(YCbCr, planes);
  83. MatIterator_<uchar> it_Cb = planes[1].begin<uchar>(),
  84. it_Cb_end = planes[1].end<uchar>();
  85. MatIterator_<uchar> it_Cr = planes[2].begin<uchar>();
  86. // skin satisfy: 138 <= Cr <= 170 and 100 <= Cb <= 127 (empirical value)
  87. for( ; it_Cb != it_Cb_end; ++it_Cr, ++it_Cb)
  88. {
  89. if (138 <= *it_Cr &&  *it_Cr <= 170 && 100 <= *it_Cb &&  *it_Cb <= 127)
  90. count++;
  91. }
  92. // It is a hand when contains large enough skin area
  93. return (count > 0.4 * frame.cols * frame.rows);
  94. }
  95. // Calculate skin probability model (histogram) for meanshift
  96. void HandTracker::getSkinModel(const Mat img, Rect rect)
  97. {
  98. int hue_Bins = 50;
  99. float hue_Ranges[] = { 0, 180 };
  100. const float *ranges= hue_Ranges;
  101. Mat HSV, hue, mask;
  102. cvtColor(img, HSV, CV_RGB2HSV);
  103. inRange(HSV, Scalar(0, 30, 10), Scalar(180, 256, 256), mask);
  104. vector<Mat> planes;
  105. split(HSV, planes);
  106. hue = planes[0];
  107. Mat roi(hue, rect), maskroi(mask, rect);
  108. calcHist(&roi, 1, 0, maskroi, hist, 1, &hue_Bins, &ranges);
  109. normalize(hist, hist, 0, 255, CV_MINMAX);
  110. }
  111. // Calculate skin probability image (back project map) for meanshift
  112. void HandTracker::calSkinPro(Mat frame)
  113. {
  114. Mat mask, hue, HSV;
  115. cvtColor(frame, HSV, CV_RGB2HSV);
  116. inRange(HSV, Scalar(0, 30, 10), Scalar(180, 256, 256), mask);
  117. vector<Mat> planes;
  118. split(HSV, planes);
  119. hue = planes[0];
  120. // hue varies from 0 to 179, see cvtColor
  121. float hue_Ranges[] = { 0, 180 };
  122. const float *ranges= hue_Ranges;
  123. calcBackProject(&hue, 1, 0, hist, backProject, &ranges, 1.0, true);
  124. backProject &= mask;
  125. }
  126. //  Detect motion using frame differece
  127. void HandTracker::frameDiff(const Mat image, Mat &diff)
  128. {
  129. int thresValue = 20;
  130. Mat curGray;
  131. cvtColor(image, curGray, CV_RGB2GRAY);
  132. if (preGray.size != curGray.size)
  133. curGray.copyTo(preGray);
  134. absdiff(preGray, curGray, diff);
  135. threshold(diff, diff, thresValue, 255, CV_THRESH_BINARY);
  136. erode(diff, diff, Mat(3, 3, CV_8UC1), Point(-1,-1));
  137. dilate(diff, diff, Mat(3, 3, CV_8UC1), Point(-1,-1));
  138. curGray.copyTo(preGray);
  139. }
  140. // Tracking hand using meanshift
  141. bool HandTracker::processFrame(Mat frame, Rect &trackBox)
  142. {
  143. float rate = 0.9;
  144. Mat diff;
  145. // tracking hand
  146. calSkinPro(frame);        // skin information
  147. frameDiff(frame, diff);   // motion information
  148. // fusing skin and motion information using a weighted rate
  149. Mat handProMap = backProject * rate + (1 - rate) * diff;
  150. meanShift(handProMap, trackBox, TermCriteria( CV_TERMCRIT_EPS | CV_TERMCRIT_ITER, 10, 1 ));
  151. // ensure the tracking result is a hand
  152. Mat skin = backProject(trackBox) > 100;
  153. return countNonZero(skin) > 0.4 * trackBox.area();
  154. }
  155. // Detect fist for the command: click the mouse
  156. bool HandTracker::detectFist(Mat frame, Rect palmBox)
  157. {
  158. Rect detectFistBox;
  159. detectFistBox.x = (palmBox.x - 40) > 0 ? (palmBox.x - 40) : 0;
  160. detectFistBox.y = (palmBox.y - 20) > 0 ? (palmBox.y - 20) : 0;
  161. detectFistBox.width = palmBox.width + 80;
  162. detectFistBox.height = palmBox.height + 40;
  163. detectFistBox &= Rect(0, 0, frame.cols, frame.rows);
  164. Mat gray;
  165. cvtColor(frame, gray, CV_BGR2GRAY );
  166. Mat tmp = gray(detectFistBox);
  167. vector<Rect> fists;
  168. fistCascade.detectMultiScale(tmp, fists, 1.1, 2, CV_HAAR_SCALE_IMAGE, Size(30, 30) );
  169. return fists.size();
  170. }

四、总结

人机交互是技术发展的一个趋势。包括语音交互、体感交互、增强现实等等。例如之前的kinect、siri和科大讯飞等等,还有当下沸沸扬扬的可穿戴设备。无数创业新星的诞生也都瞄准了这块蛋糕。无愧为创业的十大方向之一。当然了,就当下的技术而言,语音交互还是蛮成熟的了,至少从应用来讲,已经遍及大地了。这也得益于语音识别技术的提升和云计算的支撑。但对于2D摄像头的手势控制来说,做得比较好的就是以色列的公司了,包括三星智能电视所采用的PointGrab。还有目前国内彩电巨头,创维、康佳、TCL等,在今年都纷纷与这些手势控制的公司合作,以将其嵌入到他们的智能电视中去。然而,手势交互虽然是未来的趋势,但目前的手势操作使用起来还是很疲劳的,手势交互还没上升到真正自然和轻松的交互,这也是它暂时无法取代鼠标的原因之一。如果能像科幻片所刻画的那样,那才是它造福人类之日了。

人工智能的目标也是让计算机能听、能说、能看、能感觉、能做和“听话”,用的越多越听话。可以像人一样,感知这个美好的世界。通过模拟人类五个感官:视觉(手势)、声音(语音)、触觉(触屏)、味觉和嗅觉),为用户提供了包括光、声、力、嗅、味等全方位、多角度的真实感觉与优秀的体验。

人工智能的未来在哪里?在那里!

五、参考文献

[1]反向投影图

[2] Meanshift,聚类算法

[3] mean-shift算法概述

[4] meanShift算法介绍

[5]mouse_event函数说明

时间: 2024-08-05 02:38:36

基于meanshift的手势跟踪与电脑鼠标控制(手势交互系统)的相关文章

基于MeanShift的目标跟踪算法及实现

一.简介 首先扯扯无参密度估计理论,无参密度估计也叫做非参数估计,属于数理统计的一个分支,和参数密度估计共同构成了概率密度估计方法.参数密度估计方法要求特征空间服从一个已知的概率密度函数,在实际的应用中这个条件很难达到.而无参数密度估计方法对先验知识要求最少,完全依靠训练数据进行估计,并且可以用于任意形状的密度估计.所以依靠无参密度估计方法,即不事先规定概率密度函数的结构形式,在某一连续点处的密度函数值可由该点邻域中的若干样本点估计得出.常用的无参密度估计方法有:直方图法.最近邻域法和核密度估计

手势跟踪论文学习:Realtime and Robust Hand Tracking from Depth

本文介绍的方法主要是用到了深度信息.提出了一种新的手指检测以及手型初始化的方法.具有很好的鲁棒性.在不使用GPU的情况下,速度就可以达到25FPS.准确率还相当的高.可以说是现在手势识别中最好的方法了. 当前的很多方法要不就是很慢,要不就是使用了GPU,再或者就是需要非常复杂的初始化.而本文提出的方法重新定义了手势的模型,结合了现在通用的两种方法的优势,并且加上一个约束方程,得到了很好的效果. 1.模型的重新定义 每一只手,定义了一个自由度(DOF)为26 的手的模型,其中的6个自由度代表全局的

浏览器禁用Cookie,基于Cookie的会话跟踪机制失效的解决办法

当浏览器禁用Cookies时,基于Cookie的会话跟踪机制就会失效,解决办法是利用URL重写机制跟踪用户会话. 在使用URL重写机制的时候需要注意,为了保证会话跟踪的正确性,所有的链接和重定向语句中的URL都需要调用encodeURL()或encodeRedirectURL()方法进行编码.另外,由于附加在URL中的SessionID是动态产生的,对于每一个用户都是不同的,所欲对于静态页面的相互跳转,URL重写机制就无能为力了,但是,我们也可以通过将静态页面转换为动态页面来解决这个问题. 在w

学习OpenCV——hand tracking手势跟踪

这几日,岛上风云突变,我这个倒霉孩子终究木有躲过感冒的魔掌,中枪鸟~~~ 这几天只写了个简单的手势跟踪的代码. 原理是:背景差分+肤色检测. 背景差分:取前30帧图像取平均值,计算前30帧之差的和,再求均值.在背景平均值上下浮动的阈值之外的被检测出来. 肤色检测:利用YCrCb空间. 两个结果相与操作. 这种方式的优点:1.有效解决了肤色检测结果中总是检测到人脸的情况: 2.解决背景差分检测结果杂乱的情况: 缺点:背景要求相对稳定,反差越大越好,鲁棒性差. 注意事项:差分法由于涉及到累加图像,编

电脑鼠标的简单分类

之所以要查电脑鼠标分类,是因为鼠标有点小毛病,所以要对症下药....哈哈哈哈 机械鼠标(mechanical mouse)又名滚轮鼠标,主要由滚球.辊柱和光栅信号传感器组成.鼠标通过 ps/2 口或串口与主机相连.接口中一般使用四根线,分别是电源 ,地,时钟和数据.把鼠标翻转过来,如果下面有个小圆球, 则是机械鼠标.目前已被逐渐淘汰. 光电鼠标器是通过红外线或激光检测鼠标器的位移,将位移信号转换为电脉冲信号,再通过程序的处理和转换来控制屏幕上的光标箭头的移动的一种硬件设备.在市场较为常见,光电鼠

电脑鼠标指针样式更改

电脑鼠标指针样式更改 前言: 今天在办公室,看到坐在隔壁的吴老师的电脑,发现她的桌面的鼠标指针是动态的, 不像平时看到的呆呆的那种鼠标指针,出于好奇,易安就偷了她的电脑嘿嘿嘿~ 爱美之心人皆有之,其实大多数人都会的啦,但是写出来给一些不会的人看吧,别忘了关注我吖 步骤: 一.    下载好指针文件,易安已经帮你下好款比较美观的鼠标指针文件啦~(需要的可以关注公众号获取哦) 二.    复制文件(也可以复制文件夹哦) 三.    打开我们的系统盘(一般都是C盘啦~) 我的系统盘是C盘,所以这里打开

基于SkyWalking的分布式跟踪系统 - 微服务监控

上一篇文章我们搭建了基于SkyWalking分布式跟踪环境,今天聊聊使用SkyWalking监控我们的微服务(DUBBO) 服务案例 假设你有个订单微服务,包含以下组件 MySQL数据库分表分库(2台) 生产者(2台) dubbo-provider 消费者 dubbo-consumer 网络拓扑图如下 生产者的关键代码 @Service public class OrderServiceImpl implements OrderService { @Autowired protected Ord

基于SkyWalking的分布式跟踪系统 - 异常告警

通过前面2篇文章我们搭建了SW的基础环境,监控了微服务,能了解所有服务的运行情况.但是当出现服务响应慢,接口耗时严重时我们需要立即定位到问题,这就需要我们今天的主角--监控告警,同时此篇也是SW系列的最后一篇. UI参数 首先我们认识一下SW DashBoard上的几个关键参数,如下图所示 告警配置 告警流程 skywalking发送告警的基本原理是每隔一段时间轮询skywalking-collector收集到的链路追踪的数据,再根据所配置的告警规则(如服务响应时间.服务响应时间百分比)等,如果

Unity3D鼠标控制角色移动

一直都有一颗文学逗比的心,很中二和玛丽苏的想写那种龙傲天的小说.所以这个寒假就非常想敲出个RPG游戏来抒发心中的这份狂热.一开始是想用Three.js来做,后来转用Unity3D来做了,毕竟相对简单一点.好多东西不用自己去写,也可以避免心中这份狂热不至于还没把基础框架搭建好就降为0度了. 角色移动的例子 控制角色移动,对于PC端而言就是键盘或者鼠标.其中键盘控制角色移动的是经典的fps游戏中wasd四个方向按键.而鼠标控制角色移动一般常见于MMORPG.我比较倾向于MMORPG风格控制角色移动,