OpenCV C++案例实战二十二《手势识别》

  • 前言
  • 一、手部关键点检测
    • 1.1 功能源码
    • 1.2 功能效果
  • 二、手势识别
    • 2.1算法原理
    • 2.2功能源码
  • 三、结果显示
    • 3.1功能源码
    • 3.2效果显示
  • 四、源码
  • 总结

前言

本文将使用OpenCV C++ 实现手势识别效果。本案例主要可以分为以下几个步骤:
1、手部关键点检测
2、手势识别
3、效果显示
接下来就来看看本案例具体是怎么实现的吧!!!

一、手部关键点检测

如图所示,为我们的手部关键点所在位置。第一步,我们需要检测手部21个关键点。我们使用深度神经网络DNN模块来完成这件事。通过使用DNN模块可以检测出手部21个关键点作为结果输出,具体请看源码。

1.1 功能源码

//手部关键点检测
bool HandKeypoints_Detect(Mat src, vector<Point>&HandKeypoints)
{//模型尺寸大小int width = src.cols;int height = src.rows;float ratio = width / (float)height;int modelHeight = 368;  //由模型输入维度决定int modelWidth = int(ratio*modelHeight);//模型文件string model_file = "pose_deploy.prototxt";  //网络模型string model_weight = "pose_iter_102000.caffemodel";//网络训练权重//加载caffe模型Net net = readNetFromCaffe(model_file, model_weight);//将输入图像转成blob形式Mat blob = blobFromImage(src, 1.0 / 255, Size(modelWidth, modelHeight), Scalar(0, 0, 0));//将图像转换的blob数据输入到网络的第一层“image”层,见deploy.protxt文件net.setInput(blob, "image");//结果输出Mat output = net.forward();int H = output.size[2];int W = output.size[3];for (int i = 0; i < nPoints; i++){//结果预测Mat probMap(H, W, CV_32F, output.ptr(0, i)); resize(probMap, probMap, Size(width, height));Point keypoint; //最大可能性手部关键点位置double classProb;  //最大可能性概率值minMaxLoc(probMap, NULL, &classProb, NULL, &keypoint);HandKeypoints[i] = keypoint; //结果输出,即手部关键点所在坐标}return true;
}

1.2 功能效果


如图所示,我们已经通过DNN检测出21个手部关键点所在位置。接下来,我们需要使用这些关键点进行简单的手势识别。

二、手势识别

2.1算法原理

本案例实现手势识别是通过比较关键点位置确定的。首先拿出每个手指尖关键点索引(即4、8、12、16、20)。接下来,对比每个手指其它关键点与其指尖所在位置。
例如我们想确定大拇指现在的状态是张开的还是闭合的。如下图所示,由于OpenCV是以左上角为起点建立坐标系的。当大拇指处于张开状态时(掌心向内),我们可以发现,对比关键点4、关键点3所在位置。当4的x坐标大于3的x坐标时,拇指处于张开状态;当4的x坐标小于3的x坐标时,拇指处于闭合状态。
同理,其余四个手指,以食指为例。当关键点8的y坐标小于关键点6的y坐标时,此时食指处于张开状态;当关键点8的y坐标大于关键点6的y坐标时,此时食指处于闭合状态。
当手指处于张开状态时,我们计数1。通过统计手指的张开数达到手势识别的目的。具体请看源码。

2.2功能源码

//手势识别
bool Handpose_Recognition(vector<Point>&HandKeypoints, int& count)
{vector<int>fingers;//拇指if (HandKeypoints[tipIds[0]].x > HandKeypoints[tipIds[0] - 1].x){   //如果关键点'4'的x坐标大于关键点'3'的x坐标,则说明大拇指是张开的。计数1fingers.push_back(1);}else{fingers.push_back(0);}//其余的4个手指for (int i = 1; i < 5; i++){if (HandKeypoints[tipIds[i]].y < HandKeypoints[tipIds[i] - 2].y){//例:如果关键点'8'的y坐标小于关键点'6'的y坐标,则说明食指是张开的。计数1fingers.push_back(1);}else{fingers.push_back(0);}}//结果统计for (int i = 0; i < fingers.size(); i++){if (fingers[i] == 1){count++;}}return true;
}

三、结果显示

通过以上步骤,我们已经有了手部关键点所在坐标位置以及对应的手势结果,接下来就进行效果展示。
在这里,为了逼格高一点,我们将下面的手势模板图像作为输出结果放进我们的测试图中。具体操作请看源码。

3.1功能源码

//识别效果显示
bool ShowResult(Mat& src, vector<Point>&HandKeypoints, int& count)
{//画出关键点所在位置for (int i = 0; i < nPoints; i++){circle(src, HandKeypoints[i], 3, Scalar(0, 0, 255), -1);putText(src, to_string(i), HandKeypoints[i], FONT_HERSHEY_COMPLEX, 0.8, Scalar(0, 255, 0), 2);}//为了显示骚操作,读取模板图片,作为识别结果vector<string>imageList;string filename = "images/";glob(filename, imageList);vector<Mat>Temp;for (int i = 0; i < imageList.size(); i++){Mat temp = imread(imageList[i]);resize(temp, temp, Size(100, 100), 1, 1, INTER_AREA);Temp.push_back(temp);}//将识别结果显示在原图中Temp[count].copyTo(src(Rect(0, src.rows- Temp[count].rows, Temp[count].cols, Temp[count].rows)));putText(src, to_string(count), Point(20, 60), FONT_HERSHEY_COMPLEX, 2, Scalar(0, 0, 128), 3);return true;
}

3.2效果显示

除此之外,我们还可以将所有的图片整合成一张图,具体请看源码吧。

//将所有图片整合成一张图片
bool Stitching_Image(vector<Mat>images)
{Mat canvas = Mat::zeros(Size(1200, 1000), CV_8UC3);int width = 400;int height = 500;for (int i = 0; i < images.size(); i++){resize(images[i], images[i], Size(width, height), 1, 1, INTER_LINEAR);}int col = canvas.cols / width;int row = canvas.rows / height;for (int i = 0; i < row; i++){for (int j = 0; j < col; j++){int index = i * col + j;images[index].copyTo(canvas(Rect(j*width, i*height, width, height)));}}namedWindow("result", WINDOW_NORMAL);imshow("result", canvas);waitKey(0);return true;
}

最终结果如图所示。以上就是整个案例的流程啦。。。

四、源码

#include<iostream>
#include<opencv2/opencv.hpp>
#include<opencv2/dnn.hpp>
using namespace std;
using namespace cv;
using namespace cv::dnn;//手部关键点数目
const int nPoints = 21;
//手指索引
const int tipIds[] = { 4,8,12,16,20 };//手部关键点检测
bool HandKeypoints_Detect(Mat src, vector<Point>&HandKeypoints)
{//模型尺寸大小int width = src.cols;int height = src.rows;float ratio = width / (float)height;int modelHeight = 368;  //由模型输入维度决定int modelWidth = int(ratio*modelHeight);//模型文件string model_file = "pose_deploy.prototxt";  //网络模型string model_weight = "pose_iter_102000.caffemodel";//网络训练权重//加载caffe模型Net net = readNetFromCaffe(model_file, model_weight);//将输入图像转成blob形式Mat blob = blobFromImage(src, 1.0 / 255, Size(modelWidth, modelHeight), Scalar(0, 0, 0));//将图像转换的blob数据输入到网络的第一层“image”层,见deploy.protxt文件net.setInput(blob, "image");//结果输出Mat output = net.forward();int H = output.size[2];int W = output.size[3];for (int i = 0; i < nPoints; i++){//结果预测Mat probMap(H, W, CV_32F, output.ptr(0, i)); resize(probMap, probMap, Size(width, height));Point keypoint; //最大可能性手部关键点位置double classProb;  //最大可能性概率值minMaxLoc(probMap, NULL, &classProb, NULL, &keypoint);HandKeypoints[i] = keypoint; //结果输出,即手部关键点所在坐标}return true;
}//手势识别
bool Handpose_Recognition(vector<Point>&HandKeypoints, int& count)
{vector<int>fingers;//拇指if (HandKeypoints[tipIds[0]].x > HandKeypoints[tipIds[0] - 1].x){   //如果关键点'4'的x坐标大于关键点'3'的x坐标,则说明大拇指是张开的。计数1fingers.push_back(1);}else{fingers.push_back(0);}//其余的4个手指for (int i = 1; i < 5; i++){if (HandKeypoints[tipIds[i]].y < HandKeypoints[tipIds[i] - 2].y){//例:如果关键点'8'的y坐标小于关键点'6'的y坐标,则说明食指是张开的。计数1fingers.push_back(1);}else{fingers.push_back(0);}}//结果统计for (int i = 0; i < fingers.size(); i++){if (fingers[i] == 1){count++;}}return true;
}//识别效果显示
bool ShowResult(Mat& src, vector<Point>&HandKeypoints, int& count)
{//画出关键点所在位置for (int i = 0; i < nPoints; i++){circle(src, HandKeypoints[i], 3, Scalar(0, 0, 255), -1);putText(src, to_string(i), HandKeypoints[i], FONT_HERSHEY_COMPLEX, 0.8, Scalar(0, 255, 0), 2);}//为了显示骚操作,读取模板图片,作为识别结果vector<string>imageList;string filename = "images/";glob(filename, imageList);vector<Mat>Temp;for (int i = 0; i < imageList.size(); i++){Mat temp = imread(imageList[i]);resize(temp, temp, Size(100, 100), 1, 1, INTER_AREA);Temp.push_back(temp);}//将识别结果显示在原图中Temp[count].copyTo(src(Rect(0, src.rows- Temp[count].rows, Temp[count].cols, Temp[count].rows)));putText(src, to_string(count), Point(20, 60), FONT_HERSHEY_COMPLEX, 2, Scalar(0, 0, 128), 3);return true;
}//将所有图片整合成一张图片
bool Stitching_Image(vector<Mat>images)
{Mat canvas = Mat::zeros(Size(1200, 1000), CV_8UC3);int width = 400;int height = 500;for (int i = 0; i < images.size(); i++){resize(images[i], images[i], Size(width, height), 1, 1, INTER_LINEAR);}int col = canvas.cols / width;int row = canvas.rows / height;for (int i = 0; i < row; i++){for (int j = 0; j < col; j++){int index = i * col + j;images[index].copyTo(canvas(Rect(j*width, i*height, width, height)));}}namedWindow("result", WINDOW_NORMAL);imshow("result", canvas);waitKey(0);return true;
}int main()
{vector<string>imageList;string filename = "test/";glob(filename, imageList);vector<Mat>images;for (int i = 0; i < imageList.size(); i++){Mat src = imread(imageList[i]);vector<Point>HandKeypoints(nPoints);HandKeypoints_Detect(src, HandKeypoints);int count = 0;Handpose_Recognition(HandKeypoints, count);ShowResult(src, HandKeypoints, count);images.push_back(src);imshow("Demo", src);waitKey(0);}Stitching_Image(images);system("pause");return 0;
}

总结

本文使用OpenCV C++实现一些简单的手势识别,在这里仅为了提供一个算法思想,理解了算法思想自己想实现什么功能都会很简单。主要操作有以下几点。
1、使用DNN模块实现手部关键点检测
2、利用各关键点所在位置来判定手指的张合状态。
3、效果显示(仅为了实现效果演示,可以省略)

OpenCV C++案例实战二十二《手势识别》相关推荐

  1. OpenCV C++案例实战三十二《字符识别》

    OpenCV C++案例实战三十二<字符识别> 前言 一.结果演示 二.制作数据集 三.字符识别 四.源码 总结 前言 本案例将使用OpenCV C++ 进行字符识别.主要包括制作数据集. ...

  2. OpenCV C++案例实战三《二维码检测》

    OpenCV C++案例实战三<二维码检测> 前言 一.二维码检测 二.二维码识别 1.通过findContours找到轮廓层级关系 三.二维码绘制 四.源码 总结 前言 本文将使用Ope ...

  3. OpenCV C++案例实战二十九《遥感图像分割》

    OpenCV C++案例实战二十九<遥感图像分割> 前言 一.准备数据 二.K-Means分类 三.效果显示 四.源码 总结 前言 本案例基于k-means机器学习算法进行遥感图像分割.主 ...

  4. OpenCV C++案例实战十二《图像全景拼接》

    OpenCV C++案例实战十二<图像全景拼接> 前言 一.OpenCV Stitcher 1.功能源码 2.效果 二.图像全景拼接 1.特征检测 2.计算单应性矩阵 3.透视变换 4.图 ...

  5. OpenCV C++案例实战二《生成蒙太奇图像》

    OpenCV C++案例实战二<生成蒙太奇图像> 前言 一.输入模板图像 二.读取素材图像 三.生成蒙太奇模板 四.生成蒙太奇图像 五.源码 总结 前言 本文将使用OpenCV C++ 生 ...

  6. OpenCV C++案例实战二十一《制作视频播放器》

    OpenCV C++案例实战二十一<制作视频播放器> 前言 一.源码 二.效果 总结 前言 本文将使用OpenCV C++ 制作简易视频播放器,用于实现视频播放基本功能. 1.通过创建滑动 ...

  7. OpenCV C++案例实战二十七《角度测量》

    OpenCV C++案例实战二十七<角度测量> 前言 一.鼠标响应事件 1.1功能源码 1.2功能效果 二.计算直线角度 2.1 计算直线斜率 2.2计算直线角度 2.3功能源码 三.绘制 ...

  8. OpenCV C++案例实战十八《抖音特效——“蓝线挑战”》

    OpenCV C++案例实战十八<抖音特效--"蓝线挑战"> 前言 一.图像扫描 二.生成定格图像 三.图像混合 四.效果显示 五.源码 总结 前言 本文将使用Open ...

  9. OpenCV C++案例实战十九《制作电子相册查看器》

    OpenCV C++案例实战十九<制作电子相册查看器> 前言 一.图片读取 二.图片展示 三.键盘控制 四.效果显示 五.源码 总结 前言 本文将使用OpenCV C++ 制作电子相册查看 ...

最新文章

  1. 点击按钮,缩放图片(img.width、img.style.width、img.offsetWidth)
  2. 计算机丢失first,求大神解答硬盘驱动丢失怎么办
  3. C语言 typedef - C语言零基础入门教程
  4. 华图砖题库php文件怎么打印_事业单位招聘考试《工会基础知识》试题库及答案1380题...
  5. javascript单元测试
  6. 服务器启动jupyter
  7. SQL:postgresql中为查询结果增加一个自增序列之ROW_NUMBER () OVER ()的使用
  8. vs2010中使用Nunit测试c#代码结果的正确性
  9. 【数据采集与数据清洗】课堂笔记
  10. 磁场消灭癌细胞,是一种新的抗癌方案吗?
  11. Mobileye自动驾驶汽车在纽约市开跑
  12. Easyx-----c语言实现图形化打砖块
  13. 【CSS】同色系背景
  14. 设计图片转换html5,在HTML5中翻转图片
  15. JQuery验证手机号电话号码
  16. # Python+SQLite## 医院住院信息管理软件
  17. 普元 AppServer在window2019中无法启动server,也没有报错信息
  18. 第九节:点云PointCloud(第2部分,CloudCompare 处理点云)【Three.js整理】
  19. 用开源中国(oschina)Git管理代码(整合IntelliJ 13.1.5)
  20. 魔兽世界 Mangos Trinity TrinityCore 数据库 结构 大纲

热门文章

  1. UGUI InputField 无法唤起键盘
  2. 方舟生存进化服务器Linux,Linux CentOS 方舟生存进化开服教程[转]
  3. python html文本 转义字符,python处理html转义字符的方法详解
  4. Java如何获取今天周几
  5. go-grpc环境配置(win10)
  6. [蓝桥杯python] 秘密行动:小D接到一项任务,要求他爬到一座n层大厦的顶端与神秘人物会面。这座大厦有一个神奇的特点,每层的高度都不一样,同时,小D也拥有一项特殊能力,可以一次向上跳跃一层或两层
  7. 良好的编程习惯(注释)
  8. i2c总线协议的工作原理详解
  9. iOS集成微信支付--Swift
  10. 银行数字化转型导师坚鹏:数字化时代如何提升银行信用卡员工绩效