C++ OpenCV实战之手势识别
脚本之家 / 编程助手:解决程序员“几乎”所有问题!
脚本之家官方知识库 → 点击立即使用
前言
本文将使用OpenCV C++ 实现手势识别效果。本案例主要可以分为以下几个步骤:
1、手部关键点检测
2、手势识别
3、效果显示
接下来就来看看本案例具体是怎么实现的吧!!!
一、手部关键点检测
如图所示,为我们的手部关键点所在位置。第一步,我们需要检测手部21个关键点。我们使用深度神经网络DNN模块来完成这件事。通过使用DNN模块可以检测出手部21个关键点作为结果输出,具体请看源码。
1.1 功能源码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 | //手部关键点检测 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功能源码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 | //手势识别 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功能源码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | //识别效果显示 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效果显示
除此之外,我们还可以将所有的图片整合成一张图,具体请看源码吧。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | //将所有图片整合成一张图片 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 ; } |
最终结果如图所示。以上就是整个案例的流程啦。。。
四、源码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 | #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手势识别的资料请关注脚本之家其它相关文章!
微信公众号搜索 “ 脚本之家 ” ,选择关注
程序猿的那些事、送书等活动等着你
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若内容造成侵权/违法违规/事实不符,请将相关资料发送至 reterry123@163.com 进行投诉反馈,一经查实,立即处理!
相关文章
关于PCL出现"无法找到 pcl_commond.dll 文件程序无法执行"的问题及解决方法
这篇文章主要介绍了PCL出现"无法找到 pcl_commond.dll 文件程序无法执行"的问题,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下2022-07-07VC编程控件类HTControl之CHTGDIManager GDI资源管理类用法解析
这篇文章主要介绍了VC编程控件类HTControl之CHTGDIManager GDI资源管理类用法解析,需要的朋友可以参考下2014-08-08
最新评论