前言
本文所設計的類主要是和人體的手部打交道的,與人體的檢測,姿勢校正,骨架跟蹤沒有關系,所以本次類的設計中是在前面的OpenNI+Kinect系列博文基礎上去掉那些與手勢無關的驅動,較小代碼量負擔。類中保留下來有手勢識別,手部跟蹤,以及手部跟蹤的軌跡和多個手部的位置坐標等信息。本類的設計也開始慢慢遵循一些C/C++編程規范,這里采用的是google的編程規范。
本文測試設計出的類的功能是與博文不需要骨骼跟蹤的人體多個手部分割 一樣,進行人體多個手部跟蹤和分割。
開發環境:開發環境:QtCreator2.5.1+OpenNI1.5.4.0+Qt4.8.2+OpenCV2.4.2
Google編程規范節選
類的設計的規范性,首先是遵循一定的編程風格,這里本人采用的是google C++編程風格。Google編程規范具體內容可以參考網頁:Google C++ Style Guide。 這次類的設計是在前面的博文使用OpenNI自帶的類進行簡單手勢識別 和博文不需要骨骼跟蹤的人體多個手部分割 的基礎上從下面幾個地方做了編程風格規范:
1. 將類的設計分開為頭文件和源文件,前面類的設計是在一個cpp文件中。分開設計的目的是使代碼看起來結構更加清晰。
2. 在類中使用特定的聲明次序,即public在前,隨后依次為protected, private.且在其內部是成員函數在成員變量之前。比如在public的內部,一般的聲明次序為:typedef和enums,常量,構造函數,析構函數,成員函數(包括靜態成員函數),數據成員(包括靜態數據成員)。
3. 類中的函數體要盡量短小,緊湊,功能單一。
4. 類的成員函數命名以大寫字母開頭,每個單詞首字母大寫,沒有下划線。內聯函數命名除外。
5. 類的成員變量名一徇小寫,單詞間以下划線相還,並以下划線結尾。
6. Typedef類型命名時,其類型名字每個單詞以大寫字母開頭,不包含下划線。
openNI知識點總結
從OpenNI源碼的下面2句代碼中可以看出,XnUserID是unsigned int類型。
typedef unsigned int XnUInt32; typedef XnUInt32 XnUserID;
openni有些回調函數中會有傳入參數類型為XnUserID的數據,比如在本文講類的設計中,XnUserID類型就代表檢測到的不同手部的id號,因為該id理論上是用來表明不同的手部的,因此當檢測到新的手部時,系統中XnUserID類型對應變量將會加1。總的來看,XnUserID類型對應的變量值一直在遞增,剛剛上面有講到XnUserID是unsigned int類型的,所以其表示的范圍能力可以是很大,即使由於視頻中出現有很多的誤檢導致其值增加很多,這還是足夠用了的。誤檢主要是某些情況下,人的一只手由於運動速度過快或者其背景的干擾,在手勢檢測過程中有可能同時被當做是幾十只手,這時候手部的id值就會一下子增加很多。所以在程序的后續的跟蹤過程中,id值(通過輸出到后台來觀察)有可能很大。這時如果要遍歷id的話,會影響程序的速度和內存開銷。但是這也沒有辦法,因為我們的工作是基於OpenNI的手部跟蹤的基礎上的,只能依靠手部跟蹤的性能了。
雖然說手部的id值會很大,但是我們在一個視野中出現的手的個數並不是特別多,頂多幾十只手。所以在程序中如果要給不同的手部不同的顏色跟蹤,或者對不同的手部都采用不同的掩膜區域,此時設置為顏色的個數和掩膜區域的個數就不需要最大的id值那么大了,因為雖然id值高,但是有些手部退出了視野,其內存中對應存的id那一項被刪掉了,實際的手的數目不會特別大。因此在程序中,我們可以先不考慮id的最大值,而設置一個手部最大的個數,比如說20。然后用id值對最大個數20取掩膜來進行遍歷。如果程序中不用它來求默認個數的掩膜的話,隨着id值的增加,程序運行時有可能會連續出現下面3個錯誤:


為了解決這個錯誤都弄了大半天了。其原因是程序中id的值一直在增加,且人為修改它的值起不到任何效果,它是由OpenNI內部決定的。
C/C++知識點總結
define和typedef使用時恰恰相反,簡單一點來理解:define是將它后面的第1個來代替第2個使用,而typedef是將它后面的第2個來代替第1個使用。
類的源碼
copennihand.h:
#ifndef COpenniHand_H #define COpenniHand_H #include <XnCppWrapper.h> #include <iostream> #include <vector> #include <map> using namespace xn; using namespace std; class COpenniHand { public: COpenniHand(); ~COpenniHand(); /*OpenNI的內部初始化,屬性設置*/ bool Initial(); /*啟動OpenNI讀取Kinect數據*/ bool Start(); /*更新OpenNI讀取到的數據*/ bool UpdateData(); /*得到色彩圖像的node*/ ImageGenerator& getImageGenerator(); /*得到深度圖像的node*/ DepthGenerator& getDepthGenerator(); /*得到手勢姿勢的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 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_; ImageGenerator image_generator_; DepthGenerator depth_generator_; GestureGenerator gesture_generator_; HandsGenerator hand_generator_; }; #endif // COpenniHand_H
copennihand.cpp:
#include "copennihand.h" #include <XnCppWrapper.h> #include <iostream> #include <map> using namespace xn; using namespace std; COpenniHand::COpenniHand() { } COpenniHand::~COpenniHand() { } bool COpenniHand::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; } //視角校正 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); return true; } bool COpenniHand::Start() { status_ = context_.StartGeneratingAll(); if(CheckError("Start generating error!")) { return false; } return true; } bool COpenniHand::UpdateData() { status_ = context_.WaitNoneUpdateAll(); if(CheckError("Update date error!")) { return false; } //獲取數據 image_generator_.GetMetaData(image_metadata_); depth_generator_.GetMetaData(depth_metadata_); return true; } ImageGenerator &COpenniHand::getImageGenerator() { return image_generator_; } DepthGenerator &COpenniHand::getDepthGenerator() { return depth_generator_; } GestureGenerator &COpenniHand::getGestureGenerator() { return gesture_generator_; } HandsGenerator &COpenniHand::getHandGenerator() { return hand_generator_; } bool COpenniHand::CheckError(const char *error) { if(status_ != XN_STATUS_OK) { cerr << error << ": " << xnGetStatusString( status_ ) << endl; return true; } return false; } void COpenniHand::CBGestureRecognized(GestureGenerator &generator, const XnChar *strGesture, const XnPoint3D *pIDPosition, const XnPoint3D *pEndPosition, void *pCookie) { COpenniHand *openni = (COpenniHand*)pCookie; openni->hand_generator_.StartTracking(*pEndPosition); } void COpenniHand::CBGestureProgress(GestureGenerator &generator, const XnChar *strGesture, const XnPoint3D *pPosition, XnFloat fProgress, void *pCookie) { } void COpenniHand::HandCreate(HandsGenerator &rHands, XnUserID xUID, const XnPoint3D *pPosition, XnFloat fTime, void *pCookie) { COpenniHand *openni = (COpenniHand*)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 COpenniHand::HandUpdate(HandsGenerator &rHands, XnUserID xUID, const XnPoint3D *pPosition, XnFloat fTime, void *pCookie) { COpenniHand *openni = (COpenniHand*)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 COpenniHand::HandDestroy(HandsGenerator &rHands, XnUserID xUID, XnFloat fTime, void *pCookie) { COpenniHand *openni = (COpenniHand*)pCookie; openni->hand_points_.erase(openni->hand_points_.find(xUID)); openni->hands_track_points_.erase(openni->hands_track_points_.find(xUID )); }
main.cpp:
#include <iostream> #include "opencv2/highgui/highgui.hpp" #include "opencv2/imgproc/imgproc.hpp" #include <opencv2/core/core.hpp> #include "copennihand.h" #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 using namespace cv; using namespace xn; using namespace std; int main (int argc, char **argv) { 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);//顯示分割出來的手的區域 COpenniHand 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); for(auto itUser = openni.hand_points_.cbegin(); itUser != openni.hand_points_.cend(); ++itUser) { 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 Mat hand_segment_mask(depth_image.size(), CV_8UC1, Scalar::all(0)); 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); imshow("hand_segment", hand_segment); waitKey(30); } }
參考文獻:
