简单粗糙的指尖检测方法(FingerTips Detection)

zouxy09@qq.com

http://blog.csdn.net/zouxy09

在人机交互领域,如果可以比较好的检测指尖,对于交互的丰富度、灵活性来说是有很大提升的。目前指尖检测的方法也很多,我这里稍微尝试了下简单了两种。这两种方法都借助了手的几何特征,简单但比较粗糙,鲁棒性不够。

方法一:重心距离法

见下图,红色点是手的重心,那么手的边缘的所有点与重心点的距离按顺时针方向或者逆时针方向遍历,就会出现五个峰值,分别是五个手指,这样我们就可以简单找到了。如果你是只伸出一两个手指,那么就只有一两个峰值了。

简单的代码如下:

1、对图像做高斯模糊;

2、肤色分割(背景不要有类肤色,如果有,就需要加其他信息来排除干扰);

3、找到手轮廓;

4、计算矩,即重心;

5、寻找指尖。

// Simple FingerTips Detection
// Author : Zouxy
// Date   : 2013-3-23
// HomePage : http://blog.csdn.net/zouxy09
// Email  : zouxy09@qq.com#include "opencv2/opencv.hpp"using namespace cv;
using namespace std;void skinExtract(const Mat &frame, Mat &skinArea);int main(int argc, char* argv[])
{Mat frame, skinArea;VideoCapture capture;capture.open(0);if (!capture.isOpened()){cout<<"No camera!\n"<<endl;return -1;}while (1){capture >> frame;//Mat frame = imread("fingertips(1).jpg");if (frame.empty())break;skinArea.create(frame.rows, frame.cols, CV_8UC1);skinExtract(frame, skinArea);Mat show_img;frame.copyTo(show_img, skinArea);vector<vector<Point> > contours;vector<Vec4i> hierarchy;//寻找轮廓findContours(skinArea, contours, hierarchy, CV_RETR_CCOMP, CV_CHAIN_APPROX_SIMPLE);// 找到最大的轮廓int index;double area, maxArea(0);for (int i=0; i < contours.size(); i++){area = contourArea(Mat(contours[i]));if (area > maxArea){maxArea = area;index = i;}         }//drawContours(frame, contours, index, Scalar(0, 0, 255), 2, 8, hierarchy );Moments moment = moments(skinArea, true);Point center(moment.m10/moment.m00, moment.m01/moment.m00);circle(show_img, center, 8 ,Scalar(0, 0, 255), CV_FILLED);// 寻找指尖vector<Point> couPoint = contours[index];vector<Point> fingerTips;Point tmp;int max(0), count(0), notice(0);for (int i = 0; i < couPoint.size(); i++){tmp = couPoint[i];int dist = (tmp.x - center.x) * (tmp.x - center.x) + (tmp.y - center.y) * (tmp.y - center.y);if (dist > max){max = dist;notice = i;}// 计算最大值保持的点数,如果大于40(这个值需要设置,本来想根据max值来设置,// 但是不成功,不知道为何),那么就认为这个是指尖if (dist != max){count++;if (count > 40){count = 0;max = 0;bool flag = false;// 低于手心的点不算if (center.y < couPoint[notice].y )continue;// 离得太近的不算for (int j = 0; j < fingerTips.size(); j++){if (abs(couPoint[notice].x - fingerTips[j].x) < 20){flag = true;break;}}if (flag) continue;fingerTips.push_back(couPoint[notice]);circle(show_img, couPoint[notice], 6 ,Scalar(0, 255, 0), CV_FILLED);line(show_img, center, couPoint[notice], Scalar(255, 0, 0), 2);                }}}imshow("show_img", show_img);if ( cvWaitKey(20) == 'q' )break;}return 0;
}//肤色提取,skinArea为二值化肤色图像
void skinExtract(const Mat &frame, Mat &skinArea)
{Mat YCbCr;vector<Mat> planes;//转换为YCrCb颜色空间cvtColor(frame, YCbCr, CV_RGB2YCrCb);//将多通道图像分离为多个单通道图像split(YCbCr, planes); //运用迭代器访问矩阵元素MatIterator_<uchar> it_Cb = planes[1].begin<uchar>(),it_Cb_end = planes[1].end<uchar>();MatIterator_<uchar> it_Cr = planes[2].begin<uchar>();MatIterator_<uchar> it_skin = skinArea.begin<uchar>();//人的皮肤颜色在YCbCr色度空间的分布范围:100<=Cb<=127, 138<=Cr<=170for( ; it_Cb != it_Cb_end; ++it_Cr, ++it_Cb, ++it_skin){if (138 <= *it_Cr &&  *it_Cr <= 170 && 100 <= *it_Cb &&  *it_Cb <= 127)*it_skin = 255;else*it_skin = 0;}//膨胀和腐蚀,膨胀可以填补凹洞(将裂缝桥接),腐蚀可以消除细的凸起(“斑点”噪声)dilate(skinArea, skinArea, Mat(5, 5, CV_8UC1), Point(-1, -1));erode(skinArea, skinArea, Mat(5, 5, CV_8UC1), Point(-1, -1));
}

方法二:曲率分析法

见下图,可以看到,在指尖和两指之间的凹槽的曲率是最大的。假设手的轮廓是通过一系列点{Pi}来保存的,那么我们可以通过 [Pi, Pi-k]和 [Pi, Pi+k] 两个向量的内积(点乘)来寻找高曲率的点,如下图的Φ和β的计算结果,因为他们的两个向量的夹角趋向于90度,也就是它们的内积趋向于0,所以内积结果越小,曲率越大,我们只需要设置一个阈值,就可以把手的指尖和两指之间的凹槽都可以找到。

那么如何分辨指尖和两指之间的凹槽呢?我们可以通过两个向量的叉乘来计算,我们想象下手处于3D空间中,那么就还有一个z方向,β中两个向量的叉乘是大于0的(叉乘方向垂直纸面朝向你,z轴正方向),而Φ处的叉乘是小于0的(叉乘方向垂直纸面远离你,z轴负方向)。

综上,通过在每个点上构造两个向量,比较他们的点乘和叉乘就可以确定指尖了。

简单的代码如下:

1、对图像做高斯模糊;

2、肤色分割(背景不要有类肤色,如果有,就需要加其他信息来排除干扰);

3、找到手轮廓;

4、寻找指尖。

// Simple FingerTips Detection Using Curvature Determination
// Author : Zouxy
// Date   : 2013-3-23
// HomePage : http://blog.csdn.net/zouxy09
// Email  : zouxy09@qq.com#include "opencv2/opencv.hpp"using namespace cv;
using namespace std;void skinExtract(const Mat &frame, Mat &skinArea);int main(int argc, char* argv[])
{Mat frame, skinArea;VideoCapture capture;capture.open(0);if (!capture.isOpened()){cout<<"No camera!\n"<<endl;return -1;}while (1){capture >> frame;//Mat frame = imread("fingertips(1).jpg");if (frame.empty())break;GaussianBlur(frame, frame, Size(3, 3), 0);skinArea.create(frame.rows, frame.cols, CV_8UC1);skinExtract(frame, skinArea);Mat show_img;frame.copyTo(show_img, skinArea);vector<vector<Point> > contours;vector<Vec4i> hierarchy;//寻找轮廓findContours(skinArea, contours, hierarchy, CV_RETR_CCOMP, CV_CHAIN_APPROX_SIMPLE);// 找到最大的轮廓int index;double area, maxArea(0);for (int i=0; i < contours.size(); i++){area = contourArea(Mat(contours[i]));if (area > maxArea){maxArea = area;index = i;}           }//drawContours(frame, contours, index, Scalar(0, 0, 255), 2, 8, hierarchy );Moments moment = moments(skinArea, true);Point center(moment.m10/moment.m00, moment.m01/moment.m00);circle(show_img, center, 8 ,Scalar(0, 0, 255), CV_FILLED);// 寻找指尖vector<Point> couPoint = contours[index];int max(0), count(0), notice(0);vector<Point> fingerTips;Point p, q, r;for (int i = 5; (i < (couPoint.size() - 5)) && couPoint.size(); i++){q = couPoint[i - 5];p = couPoint[i];r = couPoint[i + 5];int dot = (q.x - p.x ) * (r.x - p.x) + (q.y - p.y ) * (r.y - p.y);if (dot < 20 && dot > -20){int cross = (q.x - p.x ) * (r.y - p.y) - (r.x - p.x ) * (q.y - p.y);if (cross > 0){fingerTips.push_back(p);circle(show_img, p, 5 ,Scalar(255, 0, 0), CV_FILLED);line(show_img, center, p, Scalar(255, 0, 0), 2);    }}}imshow("show_img", show_img);if ( cvWaitKey(20) == 'q' )break;}return 0;
}//肤色提取,skinArea为二值化肤色图像
void skinExtract(const Mat &frame, Mat &skinArea)
{Mat YCbCr;vector<Mat> planes;//转换为YCrCb颜色空间cvtColor(frame, YCbCr, CV_RGB2YCrCb);//将多通道图像分离为多个单通道图像split(YCbCr, planes); //运用迭代器访问矩阵元素MatIterator_<uchar> it_Cb = planes[1].begin<uchar>(),it_Cb_end = planes[1].end<uchar>();MatIterator_<uchar> it_Cr = planes[2].begin<uchar>();MatIterator_<uchar> it_skin = skinArea.begin<uchar>();//人的皮肤颜色在YCbCr色度空间的分布范围:100<=Cb<=127, 138<=Cr<=170for( ; it_Cb != it_Cb_end; ++it_Cr, ++it_Cb, ++it_skin){if (138 <= *it_Cr &&  *it_Cr <= 170 && 100 <= *it_Cb &&  *it_Cb <= 127)*it_skin = 255;else*it_skin = 0;}//膨胀和腐蚀,膨胀可以填补凹洞(将裂缝桥接),腐蚀可以消除细的凸起(“斑点”噪声)dilate(skinArea, skinArea, Mat(5, 5, CV_8UC1), Point(-1, -1));erode(skinArea, skinArea, Mat(5, 5, CV_8UC1), Point(-1, -1));
}

效果:

如要深入,我们还可以参考以下资源:

finger-detection-and-gesture-recognition [Code]

Hand and Finger Detection using JavaCV[Project]

Hand and fingers detection[Code]

简单粗糙的指尖检测方法(FingerTips Detection)相关推荐

  1. LVC | 一种简单的小样本目标检测方法

      欢迎关注我的公众号 [极智视界],获取我的更多笔记分享   大家好,我是极智视界,本文解读一下 Label, Verify, Correct (LVC):一种简单的小样本目标检测方法.   本文的 ...

  2. 港科夜闻|香港科技大学副校长(研究及发展)叶玉如教授团队成功开发出简单有效的血液检测方法...

    关注并星标 每周阅读港科夜闻 建立新视野 开启新思维 1.香港科技大学副校长(研究及发展)叶玉如教授团队成功开发出简单有效的血液检测方法.由国际知名神经生物学家.香港科技大学副校长(研究及发展)叶玉如 ...

  3. 指尖检测的几种新方法

    指尖检测根据应用可以分为单指尖检测和多指尖检测. 下面是我在工作中想到的方法,希望对你有用或提供点儿灵感. 单指尖检测新方法:重心距离法 找到手的区域,我一般用肤色检测 计算手的区域的重心 在手的区域 ...

  4. 指尖检测的新方法几种

    指尖检测根据应用可以分为单指尖检测和多指尖检测. 下面是我在工作中想到的方法,希望对你有用或提供点儿灵感. 单指尖检测新方法:重心距离法 找到手的区域,我一般用肤色检测 计算手的区域的重心 在手的区域 ...

  5. 一文览尽LiDAR点云目标检测方法

    点击上方"3D视觉工坊",选择"星标" 干货第一时间送达 转载于 :计算机视觉之路,作者:山涧一壶酒 / 导读 / 自动驾驶中的激光雷达点云如何做特征表达,将基 ...

  6. 一文览尽基于激光雷达点云(lidar)的目标检测方法

    点击上方"3D视觉工坊",选择"星标" 干货第一时间送达 本文来源:计算机视觉之路,作者:山涧一壶酒,编辑:智车科技 / 导读 / 上周文章:自动驾驶中的激光雷 ...

  7. ICDM2019|基于多尺度描述神经网络的余震检测方法

    你和"懂AI"之间,只差了一篇论文 很多读者给芯君后台留言,说看多了相对简单的AI科普和AI方法论,想看点有深度.有厚度.有眼界--以及重口味的专业论文. 为此,在多位AI领域的专 ...

  8. 论文笔记-基于代码属性图和Bi-GRU的软件脆弱性检测方法

    一.摘要 ​提出了一种基于代码属性图和Bi-GRU的软件脆弱性检测方法.该方法通过从函数的代码属性图中提取出抽象语法树序列.控制流图序列作为函数表征的表征方式,减少代码表征过程中的信息的损失,并通过选 ...

  9. matlab中facedetector,Matlab人脸检测方法(Face Parts Detection)详解

    今天同学让我帮忙制作一个人脸表情识别的样本库,其中主要是对人脸进行裁剪,这里用到了一个相对较新的Matlab人脸检测方法Face Parts Detection,网上百度了一下发现关于Matlab人脸 ...

最新文章

  1. python3语法错误-关于在python3.7当中的语法错误!
  2. 28.特性trait.rs
  3. L2TP协议笔记1---L2TP概念及协议流程分析
  4. java加载失败是什么原因_这个加载失败是什么问题呢
  5. Linux网络编程——原始套接字编程
  6. python开发之路---第二模块--OS模块
  7. ScheduledExecutorService延时线程池的简单使用
  8. 微电子科学与工程是否属于计算机类专业,微电子科学与工程专业属于什么学科...
  9. 如何提升团队凝聚力,打造高绩效团队?
  10. 矩阵计算与AI革命:可将计算性能提高150倍的异构计算
  11. Android实践:基于聚合数据的手机号码归属地查询
  12. mybatis 源码系列(四) 数据库驱动Driver加载方式
  13. Windows Metro Style颜色色值表
  14. iOS--CFMessagePort实现进程间通信
  15. 链表 - 头节点的意义
  16. R 语言 4.2.2安装 WGCNA
  17. 程序员的520花式绘制爱心代码大全
  18. 重庆智能停车场的无线wifi网络覆盖解决方案
  19. 二路归并排序Python实现-III
  20. [4G5G专题-75]:流程 - 4G LTE无线接入网中运营商标识、基站标识、终端标识大全

热门文章

  1. Java 线程之间通信
  2. 重复类发展手法_正确的护肤手法(动态演示),喜欢就拿去收藏吧!
  3. android layout 层次感,FrameLayout的层次问题
  4. 数据库MySQL基础---JDBC开发步骤--JDBC封装工具类--PreparedStatement实现CRUD操作
  5. mutilprocess模块的用法
  6. 清理神器CleanMyMac X 空间透镜——可视化您的磁盘空间
  7. K-means算法应用:图片压缩
  8. 从Excel读取数据,然后分析相似的数据,多线程处理(多线程比较相似的字符串,统计出相似的数量及字符串)...
  9. 开源通用爬虫框架YayCrawler-页面的抽取规则定义
  10. qt5连接sqlite数据库实例