C++ OpenCV实战之手势识别

 更新时间:2022年04月02日 14:29:43   作者:Zero___Chen  
这篇文章主要介绍了如何利用C++ OpenCV实现手势识别,文中的示例代码讲解详细,对我们学习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手势识别的资料请关注脚本之家其它相关文章!

蓄力AI

微信公众号搜索 “ 脚本之家 ” ,选择关注

程序猿的那些事、送书等活动等着你

原文链接:https://blog.csdn.net/Zero___Chen/article/details/123914808

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若内容造成侵权/违法违规/事实不符,请将相关资料发送至 reterry123@163.com 进行投诉反馈,一经查实,立即处理!

相关文章

  • C++使用循环计算标准差的代码实现

    C++使用循环计算标准差的代码实现

    在C++中,计算标准差可以使用循环来实现,本文给大家介绍了一个示例代码,演示了如何使用循环计算标准差,文中示例代码介绍的非常详细,具有一定的参考价值,需要的朋友可以参考下
    2023-12-12
  • 用纯C语言实现贪吃蛇游戏

    用纯C语言实现贪吃蛇游戏

    这篇文章主要为大家详细介绍了用纯C语言实现贪吃蛇游戏,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2021-07-07
  • 关于PCL出现"无法找到 pcl_commond.dll 文件程序无法执行"的问题及解决方法

    关于PCL出现"无法找到 pcl_commond.dll 文件程序无法执行"的问题及解决方法

    这篇文章主要介绍了PCL出现"无法找到 pcl_commond.dll 文件程序无法执行"的问题,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2022-07-07
  • VC编程控件类HTControl之CHTGDIManager GDI资源管理类用法解析

    VC编程控件类HTControl之CHTGDIManager GDI资源管理类用法解析

    这篇文章主要介绍了VC编程控件类HTControl之CHTGDIManager GDI资源管理类用法解析,需要的朋友可以参考下
    2014-08-08
  • C++ Template应用详解

    C++ Template应用详解

    本篇文章主要介绍了C++ Template应用详解,模板(Template)指C++程序设计设计语言中采用类型作为参数的程序设计,支持通用程序设计。
    2016-12-12
  • MFC对话框自定义消息映射的方法

    MFC对话框自定义消息映射的方法

    这篇文章主要介绍了MFC对话框自定义消息映射的方法,实例分析了MFC自定义消息映射的消息定义、响应、声明及实现消息映射的相关技巧,需要的朋友可以参考下
    2015-07-07
  • C语言实现学生信息管理系统(单链表)

    C语言实现学生信息管理系统(单链表)

    这篇文章主要为大家详细介绍了C语言实现学生信息管理系统,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2018-01-01
  • C语言实现数组的循环移位的方法示例

    C语言实现数组的循环移位的方法示例

    这篇文章主要介绍了C语言实现数组的循环移位的方法示例,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2019-08-08
  • C++的try块与异常处理及调试技术实例解析

    C++的try块与异常处理及调试技术实例解析

    这篇文章主要介绍了C++的try块与异常处理及调试技术实例解析,有助于读者加深对try块调试技术的认识,需要的朋友可以参考下
    2014-07-07
  • C语言与C++中关于字符串使用的比较

    C语言与C++中关于字符串使用的比较

    字符串是我们再熟悉不过的东西了,任何语言中字符串都是基础都要经常用到,那么在不同语言中字符串的用法一样吗?下面我们来看看C语言与C++中字符串使用的比较
    2022-05-05

最新评论