C++ OpenCV實戰(zhàn)之手勢識別
前言
本文將使用OpenCV C++ 實現(xiàn)手勢識別效果。本案例主要可以分為以下幾個步驟:
1、手部關(guān)鍵點檢測
2、手勢識別
3、效果顯示
接下來就來看看本案例具體是怎么實現(xiàn)的吧?。?!
一、手部關(guān)鍵點檢測
如圖所示,為我們的手部關(guān)鍵點所在位置。第一步,我們需要檢測手部21個關(guān)鍵點。我們使用深度神經(jīng)網(wǎng)絡(luò)DNN模塊來完成這件事。通過使用DNN模塊可以檢測出手部21個關(guān)鍵點作為結(jié)果輸出,具體請看源碼。

1.1 功能源碼
//手部關(guān)鍵點檢測
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"; //網(wǎng)絡(luò)模型
string model_weight = "pose_iter_102000.caffemodel";//網(wǎng)絡(luò)訓(xùn)練權(quán)重
//加載caffe模型
Net net = readNetFromCaffe(model_file, model_weight);
//將輸入圖像轉(zhuǎn)成blob形式
Mat blob = blobFromImage(src, 1.0 / 255, Size(modelWidth, modelHeight), Scalar(0, 0, 0));
//將圖像轉(zhuǎn)換的blob數(shù)據(jù)輸入到網(wǎng)絡(luò)的第一層“image”層,見deploy.protxt文件
net.setInput(blob, "image");
//結(jié)果輸出
Mat output = net.forward();
int H = output.size[2];
int W = output.size[3];
for (int i = 0; i < nPoints; i++)
{
//結(jié)果預(yù)測
Mat probMap(H, W, CV_32F, output.ptr(0, i));
resize(probMap, probMap, Size(width, height));
Point keypoint; //最大可能性手部關(guān)鍵點位置
double classProb; //最大可能性概率值
minMaxLoc(probMap, NULL, &classProb, NULL, &keypoint);
HandKeypoints[i] = keypoint; //結(jié)果輸出,即手部關(guān)鍵點所在坐標(biāo)
}
return true;
}
1.2 功能效果

如圖所示,我們已經(jīng)通過DNN檢測出21個手部關(guān)鍵點所在位置。接下來,我們需要使用這些關(guān)鍵點進(jìn)行簡單的手勢識別。
二、手勢識別
2.1算法原理
本案例實現(xiàn)手勢識別是通過比較關(guān)鍵點位置確定的。首先拿出每個手指尖關(guān)鍵點索引(即4、8、12、16、20)。接下來,對比每個手指其它關(guān)鍵點與其指尖所在位置。
例如我們想確定大拇指現(xiàn)在的狀態(tài)是張開的還是閉合的。如下圖所示,由于OpenCV是以左上角為起點建立坐標(biāo)系的。當(dāng)大拇指處于張開狀態(tài)時(掌心向內(nèi)),我們可以發(fā)現(xiàn),對比關(guān)鍵點4、關(guān)鍵點3所在位置。當(dāng)4的x坐標(biāo)大于3的x坐標(biāo)時,拇指處于張開狀態(tài);當(dāng)4的x坐標(biāo)小于3的x坐標(biāo)時,拇指處于閉合狀態(tài)。
同理,其余四個手指,以食指為例。當(dāng)關(guān)鍵點8的y坐標(biāo)小于關(guān)鍵點6的y坐標(biāo)時,此時食指處于張開狀態(tài);當(dāng)關(guān)鍵點8的y坐標(biāo)大于關(guān)鍵點6的y坐標(biāo)時,此時食指處于閉合狀態(tài)。
當(dāng)手指處于張開狀態(tài)時,我們計數(shù)1。通過統(tǒng)計手指的張開數(shù)達(dá)到手勢識別的目的。具體請看源碼。

2.2功能源碼
//手勢識別
bool Handpose_Recognition(vector<Point>&HandKeypoints, int& count)
{
vector<int>fingers;
//拇指
if (HandKeypoints[tipIds[0]].x > HandKeypoints[tipIds[0] - 1].x)
{
//如果關(guān)鍵點'4'的x坐標(biāo)大于關(guān)鍵點'3'的x坐標(biāo),則說明大拇指是張開的。計數(shù)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)
{
//例:如果關(guān)鍵點'8'的y坐標(biāo)小于關(guān)鍵點'6'的y坐標(biāo),則說明食指是張開的。計數(shù)1
fingers.push_back(1);
}
else
{
fingers.push_back(0);
}
}
//結(jié)果統(tǒng)計
for (int i = 0; i < fingers.size(); i++)
{
if (fingers[i] == 1)
{
count++;
}
}
return true;
}
三、結(jié)果顯示
通過以上步驟,我們已經(jīng)有了手部關(guān)鍵點所在坐標(biāo)位置以及對應(yīng)的手勢結(jié)果,接下來就進(jìn)行效果展示。
在這里,為了逼格高一點,我們將下面的手勢模板圖像作為輸出結(jié)果放進(jìn)我們的測試圖中。具體操作請看源碼。

3.1功能源碼
//識別效果顯示
bool ShowResult(Mat& src, vector<Point>&HandKeypoints, int& count)
{
//畫出關(guān)鍵點所在位置
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);
}
//為了顯示騷操作,讀取模板圖片,作為識別結(jié)果
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);
}
//將識別結(jié)果顯示在原圖中
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;
}
最終結(jié)果如圖所示。以上就是整個案例的流程啦。。。

四、源碼
#include<iostream>
#include<opencv2/opencv.hpp>
#include<opencv2/dnn.hpp>
using namespace std;
using namespace cv;
using namespace cv::dnn;
//手部關(guān)鍵點數(shù)目
const int nPoints = 21;
//手指索引
const int tipIds[] = { 4,8,12,16,20 };
//手部關(guān)鍵點檢測
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"; //網(wǎng)絡(luò)模型
string model_weight = "pose_iter_102000.caffemodel";//網(wǎng)絡(luò)訓(xùn)練權(quán)重
//加載caffe模型
Net net = readNetFromCaffe(model_file, model_weight);
//將輸入圖像轉(zhuǎn)成blob形式
Mat blob = blobFromImage(src, 1.0 / 255, Size(modelWidth, modelHeight), Scalar(0, 0, 0));
//將圖像轉(zhuǎn)換的blob數(shù)據(jù)輸入到網(wǎng)絡(luò)的第一層“image”層,見deploy.protxt文件
net.setInput(blob, "image");
//結(jié)果輸出
Mat output = net.forward();
int H = output.size[2];
int W = output.size[3];
for (int i = 0; i < nPoints; i++)
{
//結(jié)果預(yù)測
Mat probMap(H, W, CV_32F, output.ptr(0, i));
resize(probMap, probMap, Size(width, height));
Point keypoint; //最大可能性手部關(guān)鍵點位置
double classProb; //最大可能性概率值
minMaxLoc(probMap, NULL, &classProb, NULL, &keypoint);
HandKeypoints[i] = keypoint; //結(jié)果輸出,即手部關(guān)鍵點所在坐標(biāo)
}
return true;
}
//手勢識別
bool Handpose_Recognition(vector<Point>&HandKeypoints, int& count)
{
vector<int>fingers;
//拇指
if (HandKeypoints[tipIds[0]].x > HandKeypoints[tipIds[0] - 1].x)
{
//如果關(guān)鍵點'4'的x坐標(biāo)大于關(guān)鍵點'3'的x坐標(biāo),則說明大拇指是張開的。計數(shù)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)
{
//例:如果關(guān)鍵點'8'的y坐標(biāo)小于關(guān)鍵點'6'的y坐標(biāo),則說明食指是張開的。計數(shù)1
fingers.push_back(1);
}
else
{
fingers.push_back(0);
}
}
//結(jié)果統(tǒng)計
for (int i = 0; i < fingers.size(); i++)
{
if (fingers[i] == 1)
{
count++;
}
}
return true;
}
//識別效果顯示
bool ShowResult(Mat& src, vector<Point>&HandKeypoints, int& count)
{
//畫出關(guān)鍵點所在位置
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);
}
//為了顯示騷操作,讀取模板圖片,作為識別結(jié)果
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);
}
//將識別結(jié)果顯示在原圖中
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;
}
總結(jié)
本文使用OpenCV C++實現(xiàn)一些簡單的手勢識別,在這里僅為了提供一個算法思想,理解了算法思想自己想實現(xiàn)什么功能都會很簡單。主要操作有以下幾點。
1、使用DNN模塊實現(xiàn)手部關(guān)鍵點檢測
2、利用各關(guān)鍵點所在位置來判定手指的張合狀態(tài)。
3、效果顯示(僅為了實現(xiàn)效果演示,可以省略)
以上就是C++ OpenCV實戰(zhàn)之手勢識別的詳細(xì)內(nèi)容,更多關(guān)于OpenCV手勢識別的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
用C++實現(xiàn)strcpy(),返回一個char*類型的深入分析
本篇文章是對用C++實現(xiàn)strcpy(),返回一個char*類型進(jìn)行了詳細(xì)的分析介紹,需要的朋友參考下2013-05-05
linux下C/C++學(xué)生信息管理系統(tǒng)
這篇文章主要為大家詳細(xì)介紹了linux下c/c++學(xué)生信息管理系統(tǒng),具有一定的參考價值,感興趣的小伙伴們可以參考一下2018-01-01
C語言實現(xiàn)進(jìn)制轉(zhuǎn)換函數(shù)的實例詳解
這篇文章主要介紹了C語言實現(xiàn)進(jìn)制轉(zhuǎn)換函數(shù)的實例詳解的相關(guān)資料,這里提供實現(xiàn)實例幫助大家實現(xiàn)改功能,需要的朋友可以參考下2017-08-08
C++實現(xiàn)LeetCode(101.判斷對稱樹)
這篇文章主要介紹了C++實現(xiàn)LeetCode(101.判斷對稱樹),本篇文章通過簡要的案例,講解了該項技術(shù)的了解與使用,以下就是詳細(xì)內(nèi)容,需要的朋友可以參考下2021-07-07
C/C++中不同數(shù)據(jù)類型之間的轉(zhuǎn)換詳解
這篇文章主要介紹了C/C++中不同數(shù)據(jù)類型之間的轉(zhuǎn)換詳解,數(shù)據(jù)類型轉(zhuǎn)換是計算機(jī)編程中常見的操作,用于將一個數(shù)據(jù)類型轉(zhuǎn)換為另一個數(shù)據(jù)類型,本文將對不同數(shù)據(jù)類型之間的轉(zhuǎn)換作出說明,需要的朋友可以參考下2023-10-10

