服务器之家:专注于VPS、云服务器配置技术及软件下载分享
分类导航

PHP教程|ASP.NET教程|Java教程|ASP教程|编程技术|正则表达式|C/C++|IOS|C#|Swift|Android|VB|R语言|JavaScript|易语言|vb.net|

服务器之家 - 编程语言 - C/C++ - C++ OpenCV实战之手势识别

C++ OpenCV实战之手势识别

2022-11-03 14:35Zero___Chen C/C++

这篇文章主要介绍了如何利用C++ OpenCV实现手势识别,文中的示例代码讲解详细,对我们学习OpenCV有一定帮助,感兴趣的小伙伴可以了解一下

前言

本文将使用OpenCV C++ 实现手势识别效果。本案例主要可以分为以下几个步骤:

1、手部关键点检测

2、手势识别

3、效果显示

接下来就来看看本案例具体是怎么实现的吧!!!

一、手部关键点检测

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

C++ OpenCV实战之手势识别

 

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 功能效果

C++ OpenCV实战之手势识别

如图所示,我们已经通过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。通过统计手指的张开数达到手势识别的目的。具体请看源码。

C++ OpenCV实战之手势识别

 

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坐标,则说明大拇指是张开的。计数1
		fingers.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坐标,则说明食指是张开的。计数1
			fingers.push_back(1);
		}
		else
		{
			fingers.push_back(0);
		}
	}

	//结果统计
	for (int i = 0; i < fingers.size(); i++)
	{
		if (fingers[i] == 1)
		{
			count++;
		}
	}

	return true;
}

 

三、结果显示

通过以上步骤,我们已经有了手部关键点所在坐标位置以及对应的手势结果,接下来就进行效果展示。

在这里,为了逼格高一点,我们将下面的手势模板图像作为输出结果放进我们的测试图中。具体操作请看源码。

C++ OpenCV实战之手势识别

 

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效果显示

C++ OpenCV实战之手势识别

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

//将所有图片整合成一张图片
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;
}

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

C++ OpenCV实战之手势识别

 

四、源码

#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坐标,则说明大拇指是张开的。计数1
		fingers.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坐标,则说明食指是张开的。计数1
			fingers.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、效果显示(仅为了实现效果演示,可以省略)

以上就是C++ OpenCV实战之手势识别的详细内容,更多关于OpenCV手势识别的资料请关注服务器之家其它相关文章!

原文地址:https://blog.csdn.net/Zero___Chen/article/details/123914808

延伸 · 阅读

精彩推荐
  • C/C++Cmemcpy和strncpy以及snprintf 的性能比较

    Cmemcpy和strncpy以及snprintf 的性能比较

    以下是对字符串拷贝函数memcpy和strncpy以及snprintf它们之间的性能进行了比较,需要的朋友可以过来参考下...

    C语言教程网5072020-12-20
  • C/C++c语言double类型默认输出小数几位

    c语言double类型默认输出小数几位

    在本篇文章里小编给大家分享的是关于c语言double类型默认输出小数几位的相关知识点,需要的朋友们可以学习下。...

    青灯夜游12172021-09-01
  • C/C++VC++植物大战僵尸中文版修改器实现代码

    VC++植物大战僵尸中文版修改器实现代码

    这篇文章主要介绍了VC++植物大战僵尸中文版修改器实现代码,可实现植物大战僵尸中的无限阳光与无冷却时间功能,需要的朋友可以参考下...

    C++教程网6322021-02-25
  • C/C++浅谈C++模板元编程

    浅谈C++模板元编程

    本篇文章主要介绍了浅谈C++模板元编程,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧...

    D_Guco8862021-06-11
  • C/C++C++变量和基本类型详解

    C++变量和基本类型详解

    这篇文章主要介绍了C++变量和基本类型,,一定要注意局部变量与全局变量的作用范围,需要的朋友可以参考下,希望能够给你带来帮助...

    Binary_of11902022-01-21
  • C/C++浅谈C语言的字节对齐 #pragma pack(n)2

    浅谈C语言的字节对齐 #pragma pack(n)2

    下面小编就为大家带来一篇浅谈C语言的字节对齐 #pragma pack(n)2。小编觉得挺不错的现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧...

    C语言教程网10832021-04-26
  • C/C++MFC程序对文件的处理方法

    MFC程序对文件的处理方法

    这篇文章主要介绍了MFC程序对文件的处理方法,需要的朋友可以参考下...

    C语言程序设计11352021-01-30
  • C/C++C语言面试常见考点排序总结

    C语言面试常见考点排序总结

    深处开发岗,其实排序也是绕不开的环节,其中冒泡排序,选择排序,插入排序,归并排序,快速排序,堆排序也是我在秋招以来频繁问到的技术点,今天...

    悟道xn6932022-03-01