前言
這篇文章是本人玩kinect時做的一個小實驗,即不采用機器學習等類似AI的方法來做簡單的手勢數字識別,當然了,該識別的前提是基於本人前面已提取出手部的博文Robert Walter手部提取代碼的分析的基礎上進行的。由於是純數學形狀上來判別手勢,所以只是做了個簡單的0~5的數字識別系統,其手勢的分割部分效果還不錯(因為其核心代碼是由OpenNI提供的),手勢數字識別時容易受干擾,效果一般般,畢竟這只是個簡單的實驗。
實驗基礎
首先來看下本系統的流程圖,如下所示:
其中輪廓的提取,多邊形擬合曲線的求法,凸包集和凹陷集的求法都是采用opencv中自帶的函數。手勢數字的識別是利用凸包點以及凹陷點和手部中心點的幾何關系,簡單的做了下邏輯判別了(可以肯定的是這種方法很爛),具體的做法是先在手部定位出2個中心點坐標,這2個中心點坐標之間的距離閾值由程序設定,其中一個中心點就是利用OpenNI跟蹤得到的手部位置。有了這2個中心點的坐標,在程序中就可以分別計算出在這2個中心點坐標上的凸凹點的個數。當然了,這樣做的前提是用人在做手勢表示數字的同時應該是將手指的方向朝上(因為沒有像機器學習那樣通過樣本來訓練,所以使用時條件要苛刻很多)。利用上面求出的4種點的個數(另外程序中還設置了2個輔助計算點的個數,具體見代碼部分)和簡單的邏輯判斷就可以識別出數字0~5了。其它的數字可以依照具體的邏輯去設計(還可以設計出多位數字的識別),只是數字越多設計起來越復雜,因為要考慮到它們之間的干擾性,且這種不通用的設計方法也沒有太多的實際意義。
OpenCV知識點總結
void convexityDefects(InputArray contour, InputArray convexhull, OutputArray convexityDefects)
這個在函數在前面的博文Robert Walter手部提取代碼的分析中已經介紹過,當時是這么解釋的:
該函數的作用是對輸入的輪廓contour,凸包集合來檢測其輪廓的凸型缺陷,一個凸型缺陷結構體包括4個元素,缺陷起點坐標,缺陷終點坐標,缺陷中離凸包線距離最遠的點的坐標,以及此時最遠的距離。參數3即其輸出的凸型缺陷結構體向量。
其凸型缺陷的示意圖如下所示:
不過這里需要重新對這3個參數做個詳細的說明:
第1個參數雖然寫的是contour,字面意思是輪廓,但是本人實驗過很多次,發現如果該參數為目標通過輪廓檢測得到的原始輪廓的話,則程序運行到onvexityDefects()函數時會報內存錯誤。因此本程序中采用的不是物體原始的輪廓,而是經過多項式曲線擬合后的輪廓,即多項式曲線,這樣程序就會順利地運行得很好。另外由於在手勢識別過程中可能某一幀檢測出來的輪廓非常小(由於某種原因),以致於少到只有1個點,這時候如果程序運行到onvexityDefects()函數時就會報如下的錯誤:
查看opencv源碼中對應錯誤提示的位置,其源碼如下:
表示在1969行的位置出錯,也就是CV_Assert( ptnum > 3 );出錯,說明出錯時此處的ptnum <=3;看上面一行代碼ptnum = points.checkVector(2, CV_32S);所以我們需要了解checkVector()函數的功能,進入opencv中關於checkVector的源碼,如下:
int Mat::checkVector(int _elemChannels, int _depth, bool _requireContinuous) const { return (depth() == _depth || _depth <= 0) && (isContinuous() || !_requireContinuous) && ((dims == 2 && (((rows == 1 || cols == 1) && channels() == _elemChannels) || (cols == _elemChannels))) || (dims == 3 && channels() == 1 && size.p[2] == _elemChannels && (size.p[0] == 1 || size.p[1] == 1) && (isContinuous() || step.p[1] == step.p[2]*size.p[2]))) ? (int)(total()*channels()/_elemChannels) : -1; }
該函數源碼大概意思就是說對應的Mat矩陣如果其深度,連續性,通道數,行列式滿足一定條件的話就返回Mat元素的個數和其通道數的乘積,否則返回-1;而本文是要求其返回值大於3,有得知此處輸入多邊形曲線(即參數1)的通道數為2,所以還需要求其元素的個數大於1.5,即大於2才滿足ptnum > 3。簡單的說就是用convexityDefects()函數來對多邊形曲線進行凹陷檢測時,必須要求參數1曲線本身至少有2個點(也不知道這樣分析對不對)。因此本人在本次程序convexityDefects()函數前加入了if(Mat(approx_poly_curve).checkVector(2, CV_32S) > 3)來判斷,只有滿足該if條件,才會進行后面的凹陷檢測。這樣程序就不會再出現類似的bug了。
第2個參數一般是由opencv中的函數convexHull()獲得的,一般情況下該參數里面存的是凸包集合中的點在多項式曲線點中的位置索引,且該參數以vector的形式存在,因此參數convexhull中其元素的類型為unsigned int。在本次凹陷點檢測函數convexityDefects()里面根據文檔,要求該參數為Mat型。因此在使用convexityDefects()的參數2時,一般將vector直接轉換Mat型。
參數3是一個含有4個元素的結構體的集合,如果在c++的版本中,該參數可以直接用vector<Vec4i>來代替,Vec4i中的4個元素分別表示凹陷曲線段的起始坐標索引,終點坐標索引,離凸包集曲線最遠點的坐標索引以及此時的最遠距離值,這4個值都是整數。在c版本的opencv中一般不是保存的索引,而是坐標值,如下所示:
struct CvConvexityDefect { CvPoint* start; // point of the contour where the defect begins CvPoint* end; // point of the contour where the defect ends CvPoint* depth_point; // the farthest from the convex hull point within the defect float depth; // distance between the farthest point and the convex hull };
C/c++知識點總結:
std::abs()函數中的參數不能為整型,否則編譯的時候會報錯參數不匹配,因此一般情況下可以傳入long型,這樣就不會報錯了。
實驗結果:
這里顯示的是手勢分割后的效果以及其對應的數字識別結果。
數字“0”的識別結果:
數字“1”的識別結果:
數字“2”的識別結果:
數字“3”的識別結果:
數字“4”的識別結果:
數字“5”的識別結果:
實驗主要部分代碼及注釋:
本次實驗程序實現過程和上面的系統流程圖類似,大概過程如下:
1. 求出手部的掩膜
2. 求出掩膜的輪廓
3. 求出輪廓的多變形擬合曲線
4. 求出多邊形擬合曲線的凸包集,找出凸點
5. 求出多變形擬合曲線的凹陷集,找出凹點
6. 利用上面的凸凹點和手部中心點的幾何關系來做簡單的數字手勢識別
copenni類采用前面博文設計的,這里給出主函數代碼部分。
main.cpp:
#include <iostream> #include "opencv2/highgui/highgui.hpp" #include "opencv2/imgproc/imgproc.hpp" #include <opencv2/core/core.hpp> #include "copenni.cpp" #include <iostream> #define DEPTH_SCALE_FACTOR 255./4096. #define ROI_HAND_WIDTH 140 #define ROI_HAND_HEIGHT 140 #define MEDIAN_BLUR_K 5 #define XRES 640 #define YRES 480 #define DEPTH_SEGMENT_THRESH 5 #define MAX_HANDS_COLOR 10 #define MAX_HANDS_NUMBER 10 #define HAND_LIKELY_AREA 2000 #define DELTA_POINT_DISTENCE 25 //手部中心點1和中心點2距離的閾值 #define SEGMENT_POINT1_DISTANCE 27 //凸點與手部中心點1遠近距離的閾值 #define SEGMENT_POINT2_DISTANCE 30 //凸點與手部中心點2遠近距離的閾值 using namespace cv; using namespace xn; using namespace std; int main (int argc, char **argv) { unsigned int convex_number_above_point1 = 0; unsigned int concave_number_above_point1 = 0; unsigned int convex_number_above_point2 = 0; unsigned int concave_number_above_point2 = 0; unsigned int convex_assist_above_point1 = 0; unsigned int convex_assist_above_point2 = 0; unsigned int point_y1 = 0; unsigned int point_y2 = 0; int number_result = -1; bool recognition_flag = false; //開始手部數字識別的標志 vector<Scalar> color_array;//采用默認的10種顏色 { color_array.push_back(Scalar(255, 0, 0)); color_array.push_back(Scalar(0, 255, 0)); color_array.push_back(Scalar(0, 0, 255)); color_array.push_back(Scalar(255, 0, 255)); color_array.push_back(Scalar(255, 255, 0)); color_array.push_back(Scalar(0, 255, 255)); color_array.push_back(Scalar(128, 255, 0)); color_array.push_back(Scalar(0, 128, 255)); color_array.push_back(Scalar(255, 0, 128)); color_array.push_back(Scalar(255, 128, 255)); } vector<unsigned int> hand_depth(MAX_HANDS_NUMBER, 0); vector<Rect> hands_roi(MAX_HANDS_NUMBER, Rect(XRES/2, YRES/2, ROI_HAND_WIDTH, ROI_HAND_HEIGHT)); namedWindow("color image", CV_WINDOW_AUTOSIZE); namedWindow("depth image", CV_WINDOW_AUTOSIZE); namedWindow("hand_segment", CV_WINDOW_AUTOSIZE); //顯示分割出來的手的區域 namedWindow("handrecognition", CV_WINDOW_AUTOSIZE); //顯示0~5數字識別的圖像 COpenNI openni; if(!openni.Initial()) return 1; if(!openni.Start()) return 1; while(1) { if(!openni.UpdateData()) { return 1; } /*獲取並顯示色彩圖像*/ Mat color_image_src(openni.image_metadata_.YRes(), openni.image_metadata_.XRes(), CV_8UC3, (char *)openni.image_metadata_.Data()); Mat color_image; cvtColor(color_image_src, color_image, CV_RGB2BGR); Mat hand_segment_mask(color_image.size(), CV_8UC1, Scalar::all(0)); for(auto itUser = openni.hand_points_.cbegin(); itUser != openni.hand_points_.cend(); ++itUser) { point_y1 = itUser->second.Y; point_y2 = itUser->second.Y + DELTA_POINT_DISTENCE; circle(color_image, Point(itUser->second.X, itUser->second.Y), 5, color_array.at(itUser->first % color_array.size()), 3, 8); /*設置不同手部的深度*/ hand_depth.at(itUser->first % MAX_HANDS_COLOR) = (unsigned int)(itUser->second.Z* DEPTH_SCALE_FACTOR);//itUser->first會導致程序出現bug /*設置不同手部的不同感興趣區域*/ hands_roi.at(itUser->first % MAX_HANDS_NUMBER) = Rect(itUser->second.X - ROI_HAND_WIDTH/2, itUser->second.Y - ROI_HAND_HEIGHT/2, ROI_HAND_WIDTH, ROI_HAND_HEIGHT); hands_roi.at(itUser->first % MAX_HANDS_NUMBER).x = itUser->second.X - ROI_HAND_WIDTH/2; hands_roi.at(itUser->first % MAX_HANDS_NUMBER).y = itUser->second.Y - ROI_HAND_HEIGHT/2; hands_roi.at(itUser->first % MAX_HANDS_NUMBER).width = ROI_HAND_WIDTH; hands_roi.at(itUser->first % MAX_HANDS_NUMBER).height = ROI_HAND_HEIGHT; if(hands_roi.at(itUser->first % MAX_HANDS_NUMBER).x <= 0) hands_roi.at(itUser->first % MAX_HANDS_NUMBER).x = 0; if(hands_roi.at(itUser->first % MAX_HANDS_NUMBER).x > XRES) hands_roi.at(itUser->first % MAX_HANDS_NUMBER).x = XRES; if(hands_roi.at(itUser->first % MAX_HANDS_NUMBER).y <= 0) hands_roi.at(itUser->first % MAX_HANDS_NUMBER).y = 0; if(hands_roi.at(itUser->first % MAX_HANDS_NUMBER).y > YRES) hands_roi.at(itUser->first % MAX_HANDS_NUMBER).y = YRES; } imshow("color image", color_image); /*獲取並顯示深度圖像*/ Mat depth_image_src(openni.depth_metadata_.YRes(), openni.depth_metadata_.XRes(), CV_16UC1, (char *)openni.depth_metadata_.Data());//因為kinect獲取到的深度圖像實際上是無符號的16位數據 Mat depth_image; depth_image_src.convertTo(depth_image, CV_8U, DEPTH_SCALE_FACTOR); imshow("depth image", depth_image); //取出手的mask部分 //不管原圖像時多少通道的,mask矩陣聲明為單通道就ok for(auto itUser = openni.hand_points_.cbegin(); itUser != openni.hand_points_.cend(); ++itUser) { for(int i = hands_roi.at(itUser->first % MAX_HANDS_NUMBER).x; i < std::min(hands_roi.at(itUser->first % MAX_HANDS_NUMBER).x+hands_roi.at(itUser->first % MAX_HANDS_NUMBER).width, XRES); i++) for(int j = hands_roi.at(itUser->first % MAX_HANDS_NUMBER).y; j < std::min(hands_roi.at(itUser->first % MAX_HANDS_NUMBER).y+hands_roi.at(itUser->first % MAX_HANDS_NUMBER).height, YRES); j++) { hand_segment_mask.at<unsigned char>(j, i) = ((hand_depth.at(itUser->first % MAX_HANDS_NUMBER)-DEPTH_SEGMENT_THRESH) < depth_image.at<unsigned char>(j, i)) & ((hand_depth.at(itUser->first % MAX_HANDS_NUMBER)+DEPTH_SEGMENT_THRESH) > depth_image.at<unsigned char>(j,i)); } } medianBlur(hand_segment_mask, hand_segment_mask, MEDIAN_BLUR_K); Mat hand_segment(color_image.size(), CV_8UC3); color_image.copyTo(hand_segment, hand_segment_mask); /*對mask圖像進行輪廓提取,並在手勢識別圖像中畫出來*/ std::vector< std::vector<Point> > contours; findContours(hand_segment_mask, contours, CV_RETR_LIST, CV_CHAIN_APPROX_SIMPLE);//找出mask圖像的輪廓 Mat hand_recognition_image = Mat::zeros(color_image.rows, color_image.cols, CV_8UC3); for(int i = 0; i < contours.size(); i++) { //只有在檢測到輪廓時才會去求它的多邊形,凸包集,凹陷集 recognition_flag = true; /*找出輪廓圖像多邊形擬合曲線*/ Mat contour_mat = Mat(contours[i]); if(contourArea(contour_mat) > HAND_LIKELY_AREA) { //比較有可能像手的區域 std::vector<Point> approx_poly_curve; approxPolyDP(contour_mat, approx_poly_curve, 10, true);//找出輪廓的多邊形擬合曲線 std::vector< std::vector<Point> > approx_poly_curve_debug; approx_poly_curve_debug.push_back(approx_poly_curve); drawContours(hand_recognition_image, contours, i, Scalar(255, 0, 0), 1, 8); //畫出輪廓 // drawContours(hand_recognition_image, approx_poly_curve_debug, 0, Scalar(256, 128, 128), 1, 8); //畫出多邊形擬合曲線 /*對求出的多邊形擬合曲線求出其凸包集*/ vector<int> hull; convexHull(Mat(approx_poly_curve), hull, true); for(int i = 0; i < hull.size(); i++) { circle(hand_recognition_image, approx_poly_curve[hull[i]], 2, Scalar(0, 255, 0), 2, 8); /*統計在中心點1以上凸點的個數*/ if(approx_poly_curve[hull[i]].y <= point_y1) { /*統計凸點與中心點1的y軸距離*/ long dis_point1 = abs(long(point_y1 - approx_poly_curve[hull[i]].y)); int dis1 = point_y1 - approx_poly_curve[hull[i]].y; if(dis_point1 > SEGMENT_POINT1_DISTANCE && dis1 >= 0) { convex_assist_above_point1++; } convex_number_above_point1++; } /*統計在中心點2以上凸點的個數*/ if(approx_poly_curve[hull[i]].y <= point_y2) { /*統計凸點與中心點1的y軸距離*/ long dis_point2 = abs(long(point_y2 - approx_poly_curve[hull[i]].y)); int dis2 = point_y2 - approx_poly_curve[hull[i]].y; if(dis_point2 > SEGMENT_POINT2_DISTANCE && dis2 >= 0) { convex_assist_above_point2++; } convex_number_above_point2++; } } // /*對求出的多邊形擬合曲線求出凹陷集*/ std::vector<Vec4i> convexity_defects; if(Mat(approx_poly_curve).checkVector(2, CV_32S) > 3) convexityDefects(approx_poly_curve, Mat(hull), convexity_defects); for(int i = 0; i < convexity_defects.size(); i++) { circle(hand_recognition_image, approx_poly_curve[convexity_defects[i][2]] , 2, Scalar(0, 0, 255), 2, 8); /*統計在中心點1以上凹陷點的個數*/ if(approx_poly_curve[convexity_defects[i][2]].y <= point_y1) concave_number_above_point1++; /*統計在中心點2以上凹陷點的個數*/ if(approx_poly_curve[convexity_defects[i][2]].y <= point_y2) concave_number_above_point2++; } } } /**畫出手勢的中心點**/ for(auto itUser = openni.hand_points_.cbegin(); itUser != openni.hand_points_.cend(); ++itUser) { circle(hand_recognition_image, Point(itUser->second.X, itUser->second.Y), 3, Scalar(0, 255, 255), 3, 8); circle(hand_recognition_image, Point(itUser->second.X, itUser->second.Y + 25), 3, Scalar(255, 0, 255), 3, 8); } /*手勢數字0~5的識別*/ //"0"的識別 if((convex_assist_above_point1 ==0 && convex_number_above_point2 >= 2 && convex_number_above_point2 <= 3 && concave_number_above_point2 <= 1 && concave_number_above_point1 <= 1) || (concave_number_above_point1 ==0 || concave_number_above_point2 == 0) && recognition_flag == true) number_result = 0; //"1"的識別 if(convex_assist_above_point1 ==1 && convex_number_above_point1 >=1 && convex_number_above_point1 <=2 && convex_number_above_point2 >=2 && convex_assist_above_point2 == 1) number_result = 1; //"2"的識別 if(convex_number_above_point1 == 2 && concave_number_above_point1 == 1 && convex_assist_above_point2 == 2 /*convex_assist_above_point1 <=1*/ && concave_number_above_point2 == 1) number_result = 2; //"3"的識別 if(convex_number_above_point1 == 3 && concave_number_above_point1 <= 3 && concave_number_above_point1 >=1 && convex_number_above_point2 >= 3 && convex_number_above_point2 <= 4 && convex_assist_above_point2 == 3) number_result = 3; //"4"的識別 if(convex_number_above_point1 == 4 && concave_number_above_point1 <=3 && concave_number_above_point1 >=2 && convex_number_above_point2 == 4) number_result = 4; //"5"的識別 if(convex_number_above_point1 >=4 && convex_number_above_point2 == 5 && concave_number_above_point2 >= 3 && convex_number_above_point2 >= 4) number_result = 5; if(number_result !=0 && number_result != 1 && number_result != 2 && number_result != 3 && number_result != 4 && number_result != 5) number_result == -1; /*在手勢識別圖上顯示匹配的數字*/ std::stringstream number_str; number_str << number_result; putText(hand_recognition_image, "Match: ", Point(0, 60), 4, 1, Scalar(0, 255, 0), 2, 0 ); if(number_result == -1) putText(hand_recognition_image, " ", Point(120, 60), 4, 2, Scalar(255, 0 ,0), 2, 0); else putText(hand_recognition_image, number_str.str(), Point(150, 60), 4, 2, Scalar(255, 0 ,0), 2, 0); imshow("handrecognition", hand_recognition_image); imshow("hand_segment", hand_segment); /*一個循環中對有些變量進行初始化操作*/ convex_number_above_point1 = 0; convex_number_above_point2 = 0; concave_number_above_point1 = 0; concave_number_above_point2 = 0; convex_assist_above_point1 = 0; convex_assist_above_point2 = 0; number_result = -1; recognition_flag = false; number_str.clear(); waitKey(20); } }
copenni.h:
#ifndef COPENNI_H #define COPENNI_H #include <XnCppWrapper.h> #include <XnCyclicStackT.h> #include <XnHashT.h> #include <XnListT.h> #include <iostream> #include <map> using namespace xn; using namespace std; class COpenNI { public: COpenNI(); ~COpenNI(); /*OpenNI的內部初始化,屬性設置*/ bool Initial(); /*啟動OpenNI讀取Kinect數據*/ bool Start(); /*更新OpenNI讀取到的數據*/ bool UpdateData(); /*得到色彩圖像的node*/ ImageGenerator& getImageGenerator(); /*得到深度圖像的node*/ DepthGenerator& getDepthGenerator(); /*得到人體的node*/ UserGenerator& getUserGenerator(); /*得到手勢姿勢的node*/ GestureGenerator& getGestureGenerator(); /*得到手部的node*/ HandsGenerator& getHandGenerator(); DepthMetaData depth_metadata_; //返回深度圖像數據 ImageMetaData image_metadata_; //返回彩色圖像數據 std::map<XnUserID, XnPoint3D> hand_points_; //為了存儲不同手的實時點而設置的 std::map< XnUserID, vector<XnPoint3D> > hands_track_points_; //為了繪畫后面不同手部的跟蹤軌跡而設定的 private: /*該函數返回真代表出現了錯誤,返回假代表正確*/ bool CheckError(const char* error); /*表示有人體進入的回調函數*/ static void XN_CALLBACK_TYPE CBNewUser(UserGenerator &generator, XnUserID user, void *p_cookie); /*表示骨骼校正完成的回調函數*/ static void XN_CALLBACK_TYPE CBCalibrationComplete(SkeletonCapability &skeleton, XnUserID user, XnCalibrationStatus calibration_error, void *p_cookie); /*表示某個手勢動作已經完成檢測的回調函數*/ static void XN_CALLBACK_TYPE CBGestureRecognized(xn::GestureGenerator &generator, const XnChar *strGesture, const XnPoint3D *pIDPosition, const XnPoint3D *pEndPosition, void *pCookie); /*表示檢測到某個手勢開始的回調函數*/ static void XN_CALLBACK_TYPE CBGestureProgress(xn::GestureGenerator &generator, const XnChar *strGesture, const XnPoint3D *pPosition, XnFloat fProgress, void *pCookie); /*手部開始建立的回調函數*/ static void XN_CALLBACK_TYPE HandCreate(HandsGenerator& rHands, XnUserID xUID, const XnPoint3D* pPosition, XnFloat fTime, void* pCookie); /*手部開始更新的回調函數*/ static void XN_CALLBACK_TYPE HandUpdate(HandsGenerator& rHands, XnUserID xUID, const XnPoint3D* pPosition, XnFloat fTime, void* pCookie); /*手部銷毀的回調函數*/ static void XN_CALLBACK_TYPE HandDestroy(HandsGenerator& rHands, XnUserID xUID, XnFloat fTime, void* pCookie); XnStatus status_; Context context_; XnMapOutputMode xmode_; UserGenerator user_generator_; ImageGenerator image_generator_; DepthGenerator depth_generator_; GestureGenerator gesture_generator_; HandsGenerator hand_generator_; }; #endif // COPENNI_H
copenni.cpp:
#include "copenni.h" #include <XnCppWrapper.h> #include <iostream> #include <map> using namespace xn; using namespace std; COpenNI::COpenNI() { } COpenNI::~COpenNI() { } bool COpenNI::Initial() { status_ = context_.Init(); if(CheckError("Context initial failed!")) { return false; } context_.SetGlobalMirror(true);//設置鏡像 xmode_.nXRes = 640; xmode_.nYRes = 480; xmode_.nFPS = 30; //產生顏色node status_ = image_generator_.Create(context_); if(CheckError("Create image generator error!")) { return false; } //設置顏色圖片輸出模式 status_ = image_generator_.SetMapOutputMode(xmode_); if(CheckError("SetMapOutputMdoe error!")) { return false; } //產生深度node status_ = depth_generator_.Create(context_); if(CheckError("Create depth generator error!")) { return false; } //設置深度圖片輸出模式 status_ = depth_generator_.SetMapOutputMode(xmode_); if(CheckError("SetMapOutputMdoe error!")) { return false; } //產生手勢node status_ = gesture_generator_.Create(context_); if(CheckError("Create gesture generator error!")) { return false; } /*添加手勢識別的種類*/ gesture_generator_.AddGesture("Wave", NULL); gesture_generator_.AddGesture("click", NULL); gesture_generator_.AddGesture("RaiseHand", NULL); gesture_generator_.AddGesture("MovingHand", NULL); //產生手部的node status_ = hand_generator_.Create(context_); if(CheckError("Create hand generaotr error!")) { return false; } //產生人體node status_ = user_generator_.Create(context_); if(CheckError("Create gesturen generator error!")) { return false; } //視角校正 status_ = depth_generator_.GetAlternativeViewPointCap().SetViewPoint(image_generator_); if(CheckError("Can't set the alternative view point on depth generator!")) { return false; } //設置與手勢有關的回調函數 XnCallbackHandle gesture_cb; gesture_generator_.RegisterGestureCallbacks(CBGestureRecognized, CBGestureProgress, this, gesture_cb); //設置於手部有關的回調函數 XnCallbackHandle hands_cb; hand_generator_.RegisterHandCallbacks(HandCreate, HandUpdate, HandDestroy, this, hands_cb); //設置有人進入視野的回調函數 XnCallbackHandle new_user_handle; user_generator_.RegisterUserCallbacks(CBNewUser, NULL, NULL, new_user_handle); user_generator_.GetSkeletonCap().SetSkeletonProfile(XN_SKEL_PROFILE_ALL);//設定使用所有關節(共15個) //設置骨骼校正完成的回調函數 XnCallbackHandle calibration_complete; user_generator_.GetSkeletonCap().RegisterToCalibrationComplete(CBCalibrationComplete, this, calibration_complete); return true; } bool COpenNI::Start() { status_ = context_.StartGeneratingAll(); if(CheckError("Start generating error!")) { return false; } return true; } bool COpenNI::UpdateData() { status_ = context_.WaitNoneUpdateAll(); if(CheckError("Update date error!")) { return false; } //獲取數據 image_generator_.GetMetaData(image_metadata_); depth_generator_.GetMetaData(depth_metadata_); return true; } ImageGenerator &COpenNI::getImageGenerator() { return image_generator_; } DepthGenerator &COpenNI::getDepthGenerator() { return depth_generator_; } UserGenerator &COpenNI::getUserGenerator() { return user_generator_; } GestureGenerator &COpenNI::getGestureGenerator() { return gesture_generator_; } HandsGenerator &COpenNI::getHandGenerator() { return hand_generator_; } bool COpenNI::CheckError(const char *error) { if(status_ != XN_STATUS_OK) { cerr << error << ": " << xnGetStatusString( status_ ) << endl; return true; } return false; } void COpenNI::CBNewUser(UserGenerator &generator, XnUserID user, void *p_cookie) { //得到skeleton的capability,並調用RequestCalibration函數設置對新檢測到的人進行骨骼校正 generator.GetSkeletonCap().RequestCalibration(user, true); } void COpenNI::CBCalibrationComplete(SkeletonCapability &skeleton, XnUserID user, XnCalibrationStatus calibration_error, void *p_cookie) { if(calibration_error == XN_CALIBRATION_STATUS_OK) { skeleton.StartTracking(user);//骨骼校正完成后就開始進行人體跟蹤了 } else { UserGenerator *p_user = (UserGenerator*)p_cookie; skeleton.RequestCalibration(user, true);//骨骼校正失敗時重新設置對人體骨骼繼續進行校正 } } void COpenNI::CBGestureRecognized(GestureGenerator &generator, const XnChar *strGesture, const XnPoint3D *pIDPosition, const XnPoint3D *pEndPosition, void *pCookie) { COpenNI *openni = (COpenNI*)pCookie; openni->hand_generator_.StartTracking(*pEndPosition); } void COpenNI::CBGestureProgress(GestureGenerator &generator, const XnChar *strGesture, const XnPoint3D *pPosition, XnFloat fProgress, void *pCookie) { } void COpenNI::HandCreate(HandsGenerator &rHands, XnUserID xUID, const XnPoint3D *pPosition, XnFloat fTime, void *pCookie) { COpenNI *openni = (COpenNI*)pCookie; XnPoint3D project_pos; openni->depth_generator_.ConvertRealWorldToProjective(1, pPosition, &project_pos); pair<XnUserID, XnPoint3D> hand_point_pair(xUID, XnPoint3D());//在進行pair類型的定義時,可以將第2個設置為空 hand_point_pair.second = project_pos; openni->hand_points_.insert(hand_point_pair);//將檢測到的手部存入map類型的hand_points_中。 pair<XnUserID, vector<XnPoint3D>> hand_track_point(xUID, vector<XnPoint3D>()); hand_track_point.second.push_back(project_pos); openni->hands_track_points_.insert(hand_track_point); } void COpenNI::HandUpdate(HandsGenerator &rHands, XnUserID xUID, const XnPoint3D *pPosition, XnFloat fTime, void *pCookie) { COpenNI *openni = (COpenNI*)pCookie; XnPoint3D project_pos; openni->depth_generator_.ConvertRealWorldToProjective(1, pPosition, &project_pos); openni->hand_points_.find(xUID)->second = project_pos; openni->hands_track_points_.find(xUID)->second.push_back(project_pos); } void COpenNI::HandDestroy(HandsGenerator &rHands, XnUserID xUID, XnFloat fTime, void *pCookie) { COpenNI *openni = (COpenNI*)pCookie; openni->hand_points_.erase(openni->hand_points_.find(xUID)); openni->hands_track_points_.erase(openni->hands_track_points_.find(xUID )); }
實驗總結:
由本次實驗的操作過程可以看出,識別效果抗干擾能力比較差差。因此后續的工作是建立一個手勢識別的數據庫,尋找一個好的手部特征向量,和一個好的分類器。這有可能將是本人研究生畢業論文的研究方向,加油!
參考文獻: