前言:
-
关于文章
最近两三天在学习Opencv相关的计算机视觉知识,为了做项目的手势识别交互,在网上看了许多相关资料。
想把一些学习心得分享给有需要的人,如果有更好的方法欢迎探讨交流。 -
关于这个方法
这个方法是基于肤色二值化的图片后再进行手掌的识别追踪,所以决定了这个方法的鲁棒性不会太好,虽然可以通过背景消除等方法降低背景噪音提升识别率,但是如果是需要做项目的话,还是不建议使用这种方法,如果是想了解一下手势识别的思路或者计算机视觉入门的话,可以当成小demo看看,或许会有一些思路的启发。有项目需求的话,推荐使用opencv3的Adaboost级联分类器进行手掌手势的识别,然后使用本篇文章提到的一些方法来消除误检率。当然,也可以使用OpenCV+TensorFlow来进行手势识别检测等等。
心得分享:
首先我的这个demo的部分肤色检测和简单的指尖检测是来至博主@zouxy09的这片分享博文https://blog.csdn.net/zouxy09/article/details/8711461
给了我很大的启发,也让我在短期内对计算机视觉有一点点的了解,推荐大家有时间可以看看这位博主的其他计算机视觉相关的文章,特别感谢这位博主的分享。搜索过手势识别相关资料的人会发现,很多文章都是13年左右的,比较老了,我也在这希望有更多人分享相关文章,虽然手势识别目前已经算比较成熟(跟语音识别方面相比还是相差甚远,虽然微软在最近推出了一个很科幻的手势交互产品,但是不知道实用性怎么样……),但是更多的资料分享,会让这方面的圈子成长的更快,也能让我们这些小白不至于那么迷茫,少走弯路。
- 手势识别大体思路
1.摄像头捕捉视图。
2.记录第一帧视图(该视图仅包含场景背景,不允许出现手掌等物体)。
3.第二帧开始处理,与第一帧视图比较,在最大程度上消除背景噪音。
4.根据肤色(一定颜色值范围内),查找手掌轮廓,取最大轮廓作为近似的手掌轮廓。
5.计算物体轮廓质心,作为判断手掌移动的标识(因为质心一般很稳)。
6.使用重心距离法来查找近似的指尖,使用权重的方法来判断手掌方向,消除误检的指尖。
7.使用距离变化函数distanceTransform,查找掌心及计算近似的掌心大小。
8.以指尖离掌心的距离和掌心大小的半径之比作为数据,用于判断手掌弯曲度。计算手掌弯曲度,判断手掌动作是舒张还是抓取。
主要是需要判断手掌的位置,以及手掌的两种状态,即舒张或抓取。(当然也可以自己拓展判断识别手势)
这个思路是完全不包含机器学习(深度学习)方面的功能的,精确度有限,但是速度较快。
改良思路
主要是对网络上一些能找到的基于肤色检测的手势识别的三方面进行改良
- 指尖检测的误检消除
指尖检测的误检主要是出现在当手掌进行肤色检测后,由于手掌轮廓的缺陷,部分地方被判断成指尖。部分文章使用判断指尖点的y值来判断是否是正确的指尖,当指尖的y值在质心(或者是掌心)之下的时候,则判断不是。该问题的缺陷是在于当手指是横向的时候,就无法使用这个方法。所以需要先计算手掌朝向,通过计算每个找到的指尖在上下左右四个方向的权重来判断手掌朝向,再计算指尖的与质心(或者是掌心)的位置关系来消除大部分误检的指尖。
代码如下:
for (int i = 0; i < fingerTips.size(); i++)
{
int x = plam_c.x - fingerTips[i].x;
int y = plam_c.y - fingerTips[i].y;
if (y > 0)
{
num[0]++;
w[0] += y * (w[0] / num[0]);
}
if (x < 0){
num[1]++;
w[1] += -x * (w[1] / num[1]);
}
if (y < 0){
num[2]++;
w[2] += -y * (w[2] / num[2]);
}
if (x > 0){
num[3]++;
w[3] += x * (w[3] / num[3]);
}
}
该部分为计算上左下右四个方向的权重,最终权重最大的那个方向被判断为手掌方向。
for (int i = 0; i < fingerTips.size(); i++)
{
int x = plam_c.x - fingerTips[i].x;
int y = plam_c.y - fingerTips[i].y;
if (index == 0)
{
if (y < -R)fingerTips[i].x = fingerTips[i].y = -1;
//cout << "手掌朝向:上方" << endl;
}
if (index == 1)
{
if (x > R)fingerTips[i].x = fingerTips[i].y = -1;
//cout << "手掌朝向:左方" << endl;
}
if (index == 2)
{
if (y > R)fingerTips[i].x = fingerTips[i].y = -1;
//cout << "手掌朝向:下方" << endl;
}
if (index == 3)
{
if (x < -R)fingerTips[i].x = fingerTips[i].y = -1;
//cout << "手掌朝向:右方" << endl;
}
}
手掌方向判断后,将其他方向上的手指按约定条件删除(值设置为-1)。例如手掌向左则判断当前的手指指尖x值是不是大于r,即在相反方向。
- 背景噪音的消除
有些文章提出用帧差法来消除背景,但实际上absdiff这个函数的效果并不好,建议使用opencv3自带的高斯背景建模函数createBackgroundSubtractorMOG2来消除背景,当然opencv3还有一些其他的自带方法来消除背景,例如K邻近方法等。
在这里主要提及一下createBackgroundSubtractorMOG2,
构造方法:createBackgroundSubtractorMOG2(使用迭代的背景帧个数, 对光照的敏感度, 是否检测阴影);
前两个参数为整型,第三个为布尔型。一般第一个设置为500,第二个设置为25(如果光照越明显,例如水面,就最好设置大一点,减小对光照的敏感度),第三个看项目需求,不检测阴影有助于方法的速度提升。
使用方法:pMOG2->apply(欲处理的视图, 接收处理后的视图, 是否自动更新背景帧);
第一个参数和第二个类型为Mat,这里就不过多解释了。第三个为整型,可取0或-1,默认是-1,及自动更新背景帧。当设置为-1时,新物体进入视图等待些许时间后也会被当做背景,慢慢消失。设置为0后,新物体进入背景视图后不会消失。
关于createBackgroundSubtractorMOG2这个方法的详细使用方式可以参考这篇文章:https://blog.csdn.net/m0_37901643/article/details/72841289
代码如下:
- 掌心的位置及大小的计算
大多时候,基于肤色识别的算法都使用质心当成掌心,在手掌舒张时,这个质点会计算在掌心偏上,手掌收紧后会靠近下部。与真正的掌心相差甚远。所以使用distanceTransform这个函数计算最佳的掌心近似点,该函数的功能是计算一块区域非零点与离他最近的零点。常规情况下,掌心的点一定是整个手掌中离零点最远的点,而他到离他最近的一个零点,我们把他近似的看成手掌的掌心半径。
关于distanceTransform这个距离变换函数可以祥看这片文章:https://blog.csdn.net/u012566751/article/details/54286391
代码如下:
最终效果如下:
随便截了一帧,凑合看吧……
完整代码下载
https://download.csdn.net/download/justpeanut/11080178
环境:
VS 2013
Opencv 3.1.0
调试运行方式 Release x64