C++ OpenCV实战之手势识别
作者:Zero___Chen
前言
本文将使用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坐标,则说明大拇指是张开的。计数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; }
三、结果显示
通过以上步骤,我们已经有了手部关键点所在坐标位置以及对应的手势结果,接下来就进行效果展示。
在这里,为了逼格高一点,我们将下面的手势模板图像作为输出结果放进我们的测试图中。具体操作请看源码。
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坐标,则说明大拇指是张开的。计数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手势识别的资料请关注脚本之家其它相关文章!