C++ OpenCV實(shí)戰(zhàn)之手勢(shì)識(shí)別
前言
本文將使用OpenCV C++ 實(shí)現(xiàn)手勢(shì)識(shí)別效果。本案例主要可以分為以下幾個(gè)步驟:
1、手部關(guān)鍵點(diǎn)檢測(cè)
2、手勢(shì)識(shí)別
3、效果顯示
接下來就來看看本案例具體是怎么實(shí)現(xiàn)的吧?。。?/p>
一、手部關(guān)鍵點(diǎn)檢測(cè)
如圖所示,為我們的手部關(guān)鍵點(diǎn)所在位置。第一步,我們需要檢測(cè)手部21個(gè)關(guān)鍵點(diǎn)。我們使用深度神經(jīng)網(wǎng)絡(luò)DNN模塊來完成這件事。通過使用DNN模塊可以檢測(cè)出手部21個(gè)關(guān)鍵點(diǎn)作為結(jié)果輸出,具體請(qǐng)看源碼。
1.1 功能源碼
//手部關(guān)鍵點(diǎn)檢測(cè) 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ù)測(cè) Mat probMap(H, W, CV_32F, output.ptr(0, i)); resize(probMap, probMap, Size(width, height)); Point keypoint; //最大可能性手部關(guān)鍵點(diǎn)位置 double classProb; //最大可能性概率值 minMaxLoc(probMap, NULL, &classProb, NULL, &keypoint); HandKeypoints[i] = keypoint; //結(jié)果輸出,即手部關(guān)鍵點(diǎn)所在坐標(biāo) } return true; }
1.2 功能效果
如圖所示,我們已經(jīng)通過DNN檢測(cè)出21個(gè)手部關(guān)鍵點(diǎn)所在位置。接下來,我們需要使用這些關(guān)鍵點(diǎn)進(jìn)行簡(jiǎn)單的手勢(shì)識(shí)別。
二、手勢(shì)識(shí)別
2.1算法原理
本案例實(shí)現(xiàn)手勢(shì)識(shí)別是通過比較關(guān)鍵點(diǎn)位置確定的。首先拿出每個(gè)手指尖關(guān)鍵點(diǎn)索引(即4、8、12、16、20)。接下來,對(duì)比每個(gè)手指其它關(guān)鍵點(diǎn)與其指尖所在位置。
例如我們想確定大拇指現(xiàn)在的狀態(tài)是張開的還是閉合的。如下圖所示,由于OpenCV是以左上角為起點(diǎn)建立坐標(biāo)系的。當(dāng)大拇指處于張開狀態(tài)時(shí)(掌心向內(nèi)),我們可以發(fā)現(xiàn),對(duì)比關(guān)鍵點(diǎn)4、關(guān)鍵點(diǎn)3所在位置。當(dāng)4的x坐標(biāo)大于3的x坐標(biāo)時(shí),拇指處于張開狀態(tài);當(dāng)4的x坐標(biāo)小于3的x坐標(biāo)時(shí),拇指處于閉合狀態(tài)。
同理,其余四個(gè)手指,以食指為例。當(dāng)關(guān)鍵點(diǎn)8的y坐標(biāo)小于關(guān)鍵點(diǎn)6的y坐標(biāo)時(shí),此時(shí)食指處于張開狀態(tài);當(dāng)關(guān)鍵點(diǎn)8的y坐標(biāo)大于關(guān)鍵點(diǎn)6的y坐標(biāo)時(shí),此時(shí)食指處于閉合狀態(tài)。
當(dāng)手指處于張開狀態(tài)時(shí),我們計(jì)數(shù)1。通過統(tǒng)計(jì)手指的張開數(shù)達(dá)到手勢(shì)識(shí)別的目的。具體請(qǐng)看源碼。
2.2功能源碼
//手勢(shì)識(shí)別 bool Handpose_Recognition(vector<Point>&HandKeypoints, int& count) { vector<int>fingers; //拇指 if (HandKeypoints[tipIds[0]].x > HandKeypoints[tipIds[0] - 1].x) { //如果關(guān)鍵點(diǎn)'4'的x坐標(biāo)大于關(guān)鍵點(diǎn)'3'的x坐標(biāo),則說明大拇指是張開的。計(jì)數(shù)1 fingers.push_back(1); } else { fingers.push_back(0); } //其余的4個(gè)手指 for (int i = 1; i < 5; i++) { if (HandKeypoints[tipIds[i]].y < HandKeypoints[tipIds[i] - 2].y) { //例:如果關(guān)鍵點(diǎn)'8'的y坐標(biāo)小于關(guān)鍵點(diǎn)'6'的y坐標(biāo),則說明食指是張開的。計(jì)數(shù)1 fingers.push_back(1); } else { fingers.push_back(0); } } //結(jié)果統(tǒng)計(jì) for (int i = 0; i < fingers.size(); i++) { if (fingers[i] == 1) { count++; } } return true; }
三、結(jié)果顯示
通過以上步驟,我們已經(jīng)有了手部關(guān)鍵點(diǎn)所在坐標(biāo)位置以及對(duì)應(yīng)的手勢(shì)結(jié)果,接下來就進(jìn)行效果展示。
在這里,為了逼格高一點(diǎn),我們將下面的手勢(shì)模板圖像作為輸出結(jié)果放進(jìn)我們的測(cè)試圖中。具體操作請(qǐng)看源碼。
3.1功能源碼
//識(shí)別效果顯示 bool ShowResult(Mat& src, vector<Point>&HandKeypoints, int& count) { //畫出關(guān)鍵點(diǎ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); } //為了顯示騷操作,讀取模板圖片,作為識(shí)別結(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); } //將識(shí)別結(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效果顯示
除此之外,我們還可以將所有的圖片整合成一張圖,具體請(qǐng)看源碼吧。
//將所有圖片整合成一張圖片 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é)果如圖所示。以上就是整個(gè)案例的流程啦。。。
四、源碼
#include<iostream> #include<opencv2/opencv.hpp> #include<opencv2/dnn.hpp> using namespace std; using namespace cv; using namespace cv::dnn; //手部關(guān)鍵點(diǎn)數(shù)目 const int nPoints = 21; //手指索引 const int tipIds[] = { 4,8,12,16,20 }; //手部關(guān)鍵點(diǎn)檢測(cè) 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ù)測(cè) Mat probMap(H, W, CV_32F, output.ptr(0, i)); resize(probMap, probMap, Size(width, height)); Point keypoint; //最大可能性手部關(guān)鍵點(diǎn)位置 double classProb; //最大可能性概率值 minMaxLoc(probMap, NULL, &classProb, NULL, &keypoint); HandKeypoints[i] = keypoint; //結(jié)果輸出,即手部關(guān)鍵點(diǎn)所在坐標(biāo) } return true; } //手勢(shì)識(shí)別 bool Handpose_Recognition(vector<Point>&HandKeypoints, int& count) { vector<int>fingers; //拇指 if (HandKeypoints[tipIds[0]].x > HandKeypoints[tipIds[0] - 1].x) { //如果關(guān)鍵點(diǎn)'4'的x坐標(biāo)大于關(guān)鍵點(diǎn)'3'的x坐標(biāo),則說明大拇指是張開的。計(jì)數(shù)1 fingers.push_back(1); } else { fingers.push_back(0); } //其余的4個(gè)手指 for (int i = 1; i < 5; i++) { if (HandKeypoints[tipIds[i]].y < HandKeypoints[tipIds[i] - 2].y) { //例:如果關(guān)鍵點(diǎn)'8'的y坐標(biāo)小于關(guān)鍵點(diǎn)'6'的y坐標(biāo),則說明食指是張開的。計(jì)數(shù)1 fingers.push_back(1); } else { fingers.push_back(0); } } //結(jié)果統(tǒng)計(jì) for (int i = 0; i < fingers.size(); i++) { if (fingers[i] == 1) { count++; } } return true; } //識(shí)別效果顯示 bool ShowResult(Mat& src, vector<Point>&HandKeypoints, int& count) { //畫出關(guān)鍵點(diǎ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); } //為了顯示騷操作,讀取模板圖片,作為識(shí)別結(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); } //將識(shí)別結(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++實(shí)現(xiàn)一些簡(jiǎn)單的手勢(shì)識(shí)別,在這里僅為了提供一個(gè)算法思想,理解了算法思想自己想實(shí)現(xiàn)什么功能都會(huì)很簡(jiǎn)單。主要操作有以下幾點(diǎn)。
1、使用DNN模塊實(shí)現(xiàn)手部關(guān)鍵點(diǎn)檢測(cè)
2、利用各關(guān)鍵點(diǎn)所在位置來判定手指的張合狀態(tài)。
3、效果顯示(僅為了實(shí)現(xiàn)效果演示,可以省略)
以上就是C++ OpenCV實(shí)戰(zhàn)之手勢(shì)識(shí)別的詳細(xì)內(nèi)容,更多關(guān)于OpenCV手勢(shì)識(shí)別的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
用C++實(shí)現(xiàn)strcpy(),返回一個(gè)char*類型的深入分析
本篇文章是對(duì)用C++實(shí)現(xiàn)strcpy(),返回一個(gè)char*類型進(jìn)行了詳細(xì)的分析介紹,需要的朋友參考下2013-05-05linux下C/C++學(xué)生信息管理系統(tǒng)
這篇文章主要為大家詳細(xì)介紹了linux下c/c++學(xué)生信息管理系統(tǒng),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-01-01C語言實(shí)現(xiàn)進(jìn)制轉(zhuǎn)換函數(shù)的實(shí)例詳解
這篇文章主要介紹了C語言實(shí)現(xiàn)進(jìn)制轉(zhuǎn)換函數(shù)的實(shí)例詳解的相關(guān)資料,這里提供實(shí)現(xiàn)實(shí)例幫助大家實(shí)現(xiàn)改功能,需要的朋友可以參考下2017-08-08C++實(shí)現(xiàn)LeetCode(101.判斷對(duì)稱樹)
這篇文章主要介紹了C++實(shí)現(xiàn)LeetCode(101.判斷對(duì)稱樹),本篇文章通過簡(jiǎn)要的案例,講解了該項(xiàng)技術(shù)的了解與使用,以下就是詳細(xì)內(nèi)容,需要的朋友可以參考下2021-07-07C/C++中不同數(shù)據(jù)類型之間的轉(zhuǎn)換詳解
這篇文章主要介紹了C/C++中不同數(shù)據(jù)類型之間的轉(zhuǎn)換詳解,數(shù)據(jù)類型轉(zhuǎn)換是計(jì)算機(jī)編程中常見的操作,用于將一個(gè)數(shù)據(jù)類型轉(zhuǎn)換為另一個(gè)數(shù)據(jù)類型,本文將對(duì)不同數(shù)據(jù)類型之間的轉(zhuǎn)換作出說明,需要的朋友可以參考下2023-10-10