在上一篇博客中,我们提到了对手部进行识别的方法,接下来,我又选择了三种不同模式的,基于肤色进行识别的方法,这三种算法各有不同,都进行了实现。并且通过实际操作时不断地调整比较,最终将选择效果较好的识别手段。
(1)重心距离法:
主要思路:找到手掌的重心,并找出识别出的手的边缘点,对每一个点进行遍历,依次找出五个距离重心最远的点,可以把他们看作是手指点。
1、对图像做高斯模糊;
2、肤色分割(背景不要有类肤色,如果有,就需要加其他信息来排除干扰);
3、找到手轮廓;
4、计算矩,即重心;
5、寻找指尖。
/*
* 方法一
*/
// Point temp;
// double max = 0;
// int count = 0;
// int notice = 0;
// for (int i = 0; i < conPoint.Count; i++) {
// temp = conPoint [i];
// double dist = (temp.x - center.x) * (temp.x - center.x) + (temp.y - center.y) * (temp.y - center.y);
// if (dist > max) {
// max = dist;
// notice = i;
// }
// if (dist != max) {
// count++;
// if (count > 40) {
// count = 0;
// max = 0;
// bool flag = false;
// if (center.y < conPoint [notice].y)
// continue;
// for (int j = 0; j < fingerTips.Count; j++) {
// if (Mathf.Abs ((float)conPoint [notice].x - (float)fingerTips [j].x) < 20) {
// flag = true;
// break;
// }
// }
// if (flag)
// continue;
// fingerTips.Add (conPoint [notice]);
// Imgproc.circle (rgbaMat, conPoint [notice], 6, new Scalar (0, 0, 255, 255), -1);
// Imgproc.line (rgbaMat, center, conPoint [notice], new Scalar (0, 0, 255, 255), 2);
// }
//
// }
// }
(2)方法二:曲率分析法
在手上,指尖和两指之间的凹槽的曲率是最大的。假设手的轮廓是通过一系列点{Pi}来保存的,那么我们可以通过 [Pi, Pi-k] 和 [Pi, Pi+k] 两个向量的内积(点乘)来寻找高曲率的点,如下图的Φ和β的计算结果,因为他们的两个向量的夹角趋向于90度,也就是它们的内积趋向于0,所以内积结果越小,曲率越大,我们只需要设置一个阈值,就可以把手的指尖和两指之间的凹槽都可以找到。
那么如何分辨指尖和两指之间的凹槽呢?我们可以通过两个向量的叉乘来计算,我们想象下手处于3D空间中,那么就还有一个z方向,β中两个向量的叉乘是大于0的(叉乘方向垂直纸面朝向你,z轴正方向),而Φ处的叉乘是小于0的(叉乘方向垂直纸面远离你,z轴负方向)。
综上,通过在每个点上构造两个向量,比较他们的点乘和叉乘就可以确定指尖了。
1、对图像做高斯模糊;
2、肤色分割(背景不要有类肤色,如果有,就需要加其他信息来排除干扰);
3、找到手轮廓;
4、寻找指尖。
/*
* 方法三
*/
// int max = 0;
// int count = 0;
// int notice = 0;
// Point p1, q, r;
// for (int i = 5; (i < (conPoint.Count - 5)); i++) {
// q = conPoint [i - 5];
// p1 = conPoint [i];
// r = conPoint [i + 5];
// double dot = (q.x - p1.x) * (q.y - p1.y) + (r.y - p1.y) * (r.x - p1.x);
// if (dot < 20 && dot > -20)
// {
// double crossnum = (q.x - p1.x) * (r.y - p1.y) - (r.x - p1.x)*(q.y - p1.y);
// if (crossnum > 0) {
// fingerTips.Add (p1);
// Imgproc.circle (rgbaMat, p1, 6, new Scalar (0, 0, 255, 255), -1);
// Imgproc.line (rgbaMat, center, p1, new Scalar (0, 0, 255, 255), 2);
// }
// }
// }
(3)基于手掌平均位置的算法:同样也是计算出质心之后,对手掌轮廓上的各个点进行遍历。首先计算出轮廓上各个点到手掌质心位置的平均距离,然后根据调试经验,在这个平均距离的基础上乘上一个系数,假设该系数为2,也就是标记出所有手掌上到质心位置大于平均距离两倍的点。这里需要对手指附近进行处理,把位置相近的点合并。考虑到最终进行传输的是点的数据,所以传输的时候只需要筛选掉位置相近的点即可。
/*
* 方法二
*/
Point temp;
double max = 0;
int count = 0;
int notice = 0;
double sum = 0;
for (int i = 0; i < conPoint.Count; i++) {
temp = conPoint [i];
double dist = (temp.x - center.x) * (temp.x - center.x) + (temp.y - center.y) * (temp.y - center.y);
sum += dist;
}
//平均距离
double average = sum / conPoint.Count;
for (int i = 0; i < conPoint.Count; i++) {
temp = conPoint [i];
double dist = (temp.x - center.x) * (temp.x - center.x) + (temp.y - center.y) * (temp.y - center.y);
if (dist > average * 1.6f) {
fingerTips.Add (temp);
Imgproc.circle (rgbaMat, temp, 6, new Scalar (0, 0, 255, 255), -1);
Imgproc.line (rgbaMat, center, temp, new Scalar (0, 0, 255, 255), 2);
}
}
最终通过比较之后发现,加入了质心之后的手掌识别,在识别单个或者两指伸出时效果比较好。从理论上也可以理解,在如果单纯利用凸包来进行寻找的话,只伸出两指的情况,手掌小指出的转角很容易也被识别为一个凸点。而考虑到距离,则不会有这种情况。
但是在五指识别的时候,明显是加入了凸包元素的识别结果更好。
见下图,玫红色点为凸包识别,蓝色点为距离识别。