Kinect+OpenNI學習筆記之9(不需要骨骼跟蹤的人體手部分割)


  

  前言

  手勢識別非常重要的一個特點是要體驗要好,即需要以用戶為核心。而手勢的定位一般在手勢識別過程的前面,在上一篇博文Kinect+OpenNI學習筆記之8(Robert Walter手部提取代碼的分析) 中已經介紹過怎樣獲取手勢區域,且取得了不錯的效果,但是那個手勢部位的提取有一個大的缺點,即需要人站立起來,當站立起來后才能夠分隔出手。而手勢在人之間的交流時,並不一定要處於站立狀態,所以這不是一個好的HCI。因此本文介紹的手勢部位的提取並不需要人處於站立狀態,同樣取得了不錯的效果。

 

  實驗說明

  其實,本實驗實現的過程非常簡單。首先通過手部的跟蹤來獲取手所在的坐標,手部跟蹤可以參考本人前面的博文:Kinect+OpenNI學習筆記之7(OpenNI自帶的類實現手部跟蹤)。當定位到手所在的坐標后,因為該坐標是3D的,因此在該坐標領域的3維空間領域內提取出手的部位即可,整個過程的大概流程圖如下:

  

 

  OpenCV知識點總結:

  調用Mat::copyTo()函數時,如果需要有mask操作,則不管源圖像是多少通道的,其mask矩陣都要定義為單通道,另外可以對一個mask矩陣畫一個填充的矩形來達到使mask矩陣中對應ROI的位置的值為設定值,這樣就不需要去一一掃描賦值了。

  在使用OpenCV的Mat矩陣且需要對該矩陣進行掃描時,一定要注意其取值順序,比如說列和行的順序,如果弄反了,則經常會報內存錯誤。

 

 

  實驗結果

  本實驗並不要求人的手一定要放在人體的前面,且也不需要人一定是處在比較簡單的背景環境中,本實驗結果允許人處在復雜的背景環境下,且手可以到處隨便移動。當然了,環境差時有時候效果就不太好。

  下面是3張實驗結果的截圖,手勢分隔圖1:

  

 

  手勢分隔圖2:

  

 

  手勢分隔圖3:

  

 

  實驗主要部分代碼即注釋(附錄有工程code下載鏈接):

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

int XRES = 640;
int YRES = 480;
#define DEPTH_SEGMENT_THRESH 5

using namespace cv;
using namespace xn;
using namespace std;


int main (int argc, char **argv)
{
    COpenNI openni;
    int hand_depth;
    Rect roi;
    roi.x = XRES/2;
    roi.y = YRES/2;
    roi.width = ROI_HAND_WIDTH;
    roi.height = ROI_HAND_HEIGHT;
    if(!openni.Initial())
        return 1;

    namedWindow("color image", CV_WINDOW_AUTOSIZE);
    namedWindow("depth image", CV_WINDOW_AUTOSIZE);
    namedWindow("hand_segment", CV_WINDOW_AUTOSIZE);//顯示分割出來的手的區域

    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);
        circle(color_image, Point(hand_point.X, hand_point.Y), 5, Scalar(255, 0, 0), 3, 8);
        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);

        /*下面的代碼是提取手的輪廓部分*/
        hand_depth = hand_point.Z * DEPTH_SCALE_FACTOR;
        roi.x = hand_point.X - ROI_HAND_WIDTH/2;
        roi.y = hand_point.Y - ROI_HAND_HEIGHT/2;
        if(roi.x <= 0)
            roi.x = 0;
        if(roi.x >= XRES)
            roi.x = XRES;
        if(roi.y <=0 )
            roi.y = 0;
        if(roi.y >= YRES)
            roi.y = YRES;

        //取出手的mask部分
        //不管原圖像時多少通道的,mask矩陣聲明為單通道就ok
        Mat hand_segment_mask(depth_image.size(), CV_8UC1, Scalar::all(0));
        for(int i = roi.x; i < std::min(roi.x+roi.width, XRES); i++)
            for(int j = roi.y; j < std::min(roi.y+roi.height, YRES); j++) {
                hand_segment_mask.at<unsigned char>(j, i) = ((hand_depth-DEPTH_SEGMENT_THRESH) < depth_image.at<unsigned char>(j, i))
                                                            & ((hand_depth+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(20);
    }

}

 

copenni,cpp:

#ifndef COPENNI_CLASS
#define COPENNI_CLASS

#include <XnCppWrapper.h>
#include <iostream>
#include <map>

using namespace xn;
using namespace std;

static DepthGenerator  depth_generator;
static HandsGenerator  hands_generator;
static XnPoint3D hand_point;
static std::map<XnUserID, vector<XnPoint3D>> hands_track_points;

class COpenNI
{
public:
    ~COpenNI() {
        context.Release();//釋放空間
    }
    bool 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 = hands_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, NULL, gesture_cb);
        //設置於手部有關的回調函數
        XnCallbackHandle hands_cb;
        hands_generator.RegisterHandCallbacks(HandCreate, HandUpdate, HandDestroy, NULL, 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, NULL, calibration_complete);
        return true;
    }

    bool Start() {
        status = context.StartGeneratingAll();
        if(CheckError("Start generating error!")) {
            return false;
        }
        return true;
    }

    bool UpdateData() {
        status = context.WaitNoneUpdateAll();
        if(CheckError("Update date error!")) {
            return false;
        }
        //獲取數據
        image_generator.GetMetaData(image_metadata);
        depth_generator.GetMetaData(depth_metadata);

        return true;
    }
    //得到色彩圖像的node
    ImageGenerator& getImageGenerator() {
        return image_generator;
    }
    //得到深度圖像的node
    DepthGenerator& getDepthGenerator() {
        return depth_generator;
    }
    //得到人體的node
    UserGenerator& getUserGenerator() {
        return user_generator;
    }
    //得到手勢姿勢node
    GestureGenerator& getGestureGenerator() {
        return gesture_generator;
    }

public:
    DepthMetaData depth_metadata;
    ImageMetaData image_metadata;
//    static std::map<XnUserID, vector<XnPoint3D>> hands_track_points;

private:
    //該函數返回真代表出現了錯誤,返回假代表正確
    bool CheckError(const char* error) {
        if(status != XN_STATUS_OK ) {
            //QMessageBox::critical(NULL, error, xnGetStatusString(status));
            cerr << error << ": " << xnGetStatusString( status ) << endl;
            return true;
        }
        return false;
    }
    //手勢某個動作已經完成檢測的回調函數
    static void XN_CALLBACK_TYPE  CBGestureRecognized(xn::GestureGenerator &generator, const XnChar *strGesture, const XnPoint3D *pIDPosition,
                                                      const XnPoint3D *pEndPosition, void *pCookie) {
   //     COpenNI *openni = (COpenNI*)pCookie;
    //    openni->hands_generator.StartTracking(*pIDPosition);
        hands_generator.StartTracking(*pIDPosition);
    }
    //手勢開始檢測的回調函數
    static void XN_CALLBACK_TYPE CBGestureProgress(xn::GestureGenerator &generator, const XnChar *strGesture, const XnPoint3D *pPosition,
                                                   XnFloat fProgress, void *pCookie) {
   //     COpenNI *openni = (COpenNI*)pCookie;
   //     openni->hands_generator.StartTracking(*pPosition);
        hands_generator.StartTracking(*pPosition);
    }
    //手部開始建立的回調函數
    static void XN_CALLBACK_TYPE HandCreate(HandsGenerator& rHands, XnUserID xUID, const XnPoint3D* pPosition,
                                            XnFloat fTime, void* pCookie) {
   //     COpenNI *openni = (COpenNI*)pCookie;
        XnPoint3D project_pos;
        depth_generator.ConvertRealWorldToProjective(1, pPosition, &project_pos);
 //       openni->hand_point = project_pos;   //返回手部所在點的位置
        hand_point = project_pos;
        pair<XnUserID, vector<XnPoint3D>> hand_track_point(xUID, vector<XnPoint3D>());
        hand_track_point.second.push_back(project_pos);
        hands_track_points.insert(hand_track_point);
    }
    //手部開始更新的回調函數
    static void XN_CALLBACK_TYPE HandUpdate(HandsGenerator& rHands, XnUserID xUID, const XnPoint3D* pPosition, XnFloat fTime,
                                            void* pCookie) {
   //     COpenNI *openni = (COpenNI*)pCookie;
        XnPoint3D project_pos;
        depth_generator.ConvertRealWorldToProjective(1, pPosition, &project_pos);
     //   openni->hand_point = project_pos;   //返回手部所在點的位置
        hand_point = project_pos;
        hands_track_points.find(xUID)->second.push_back(project_pos);
    }
    //銷毀手部的回調函數
    static void XN_CALLBACK_TYPE HandDestroy(HandsGenerator& rHands, XnUserID xUID, XnFloat fTime,
                                              void* pCookie) {
  //      COpenNI *openni = (COpenNI*)pCookie;
        //openni->hand_point.clear();   //返回手部所在點的位置
        hands_track_points.erase(hands_track_points.find(xUID));
    }
    //有人進入視野時的回調函數
    static void XN_CALLBACK_TYPE CBNewUser(UserGenerator &generator, XnUserID user, void *p_cookie) {
        //得到skeleton的capability,並調用RequestCalibration函數設置對新檢測到的人進行骨骼校正
        generator.GetSkeletonCap().RequestCalibration(user, true);
    }
    //完成骨骼校正的回調函數
    static void XN_CALLBACK_TYPE 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);//骨骼校正失敗時重新設置對人體骨骼繼續進行校正
        }
    }

private:
    XnStatus    status;
    Context     context;
    ImageGenerator  image_generator;
//    DepthGenerator depth_generator;
    UserGenerator user_generator;
    GestureGenerator gesture_generator;
//    HandsGenerator  hands_generator;
//    map<XnUserID, vector<XnPoint3D>> hands_track_points;
    XnMapOutputMode xmode;

public:
  // static XnPoint3D hand_point;
};

#endif

 

 

  實驗總結:

  本次實驗簡單的利用OpenNI的手部跟蹤功能提實時分隔出了人體手所在的部位。但是該分隔效果並不是特別好,以后可以改進手利用色彩信息來分隔出手的區域,或者計算出自適應手部位的區域。另外,本程序只是暫時分隔出一個手,以后可以擴展到分隔出多個手的部位.

 

  參考資料:

     Kinect+OpenNI學習筆記之8(Robert Walter手部提取代碼的分析)

     http://dl.dropbox.com/u/5505209/FingertipTuio3d.zip

 

 

  附錄:實驗工程code下載

 

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM