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

      前言

      本文将使用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手势识别的资料请关注其它相关文章!

      声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。