Kinect+OpenNI學習筆記之14(關於Kinect的深度信息)


 

  前言

  由於最近要研究kinect采集到的深度信息的一些統計特征,所以必須先對kinect深度信息做進一步的了解。這些了解包括kinect的深度值精度,深度值的具體代表的距離是指哪個距離以及kinect深度和顏色掃描范圍等。經過查找資料可以解決這些問題,並且后面通過實驗也驗證了這些問題的答案。

  開發環境:開發環境:QtCreator2.5.1+OpenNI1.5.4.0+Qt4.8.2+OpenCV2.4.3

 

  實驗基礎

  首先來看下Kinect性能的基本參數,如下圖所示:

  

   Kinect在使用時,微軟官方推薦的距離為1220mm(4’)~3810mm(12.5’),網友heresy在他的博文Kinect + OpenNI 的深度值中統計過,kinect在距離為1.0m時其精度大概是3mm,而當距離是3.0m時,其精度大概是3cm,因此當kinect在官方推薦的距離范圍內使用是,如果是1.2m時,其精度應該在3mm附近,如果是3.6m時其精度就大於3cm了,因此距離越遠,其深度值精度越低。另外,通過OpenNI獲取到的深度信息(即z坐標)的單位是mm,這一點在程序編程中要注意,且一般的深度值用12bit(其實是可以用13bit表示的)表示,即最大值為4095,也就是代表4.095m,所以平時我們采集到的深度數據如果需要擴展到灰度圖,可以乘以一個因子255/4095(為了加快該結果,一般設置為256/4096,即轉換后的灰度值每變化1,代表kinect采集到的深度值變化了16mm,如果當人的距離為1米左右時,本來精度是3mm,現在經過歸一化后精度確更加下降了,這時候拿這個距離值來做算法不懂會不會有影響,當然了,拿來做灰度圖像的顯示肯定是OK的),最后如果其深度值為0表示該位置處偵測不到像素點的深度。

  Kinect的偵測范圍入下圖所示:

  

  可以看出,kinect的水平偵測范圍為57度(即以sensor為中心,左右各28.5度)。垂直范圍為43度(同理,以sensor為中心,上下各21.5度)。如果人體活動超過了kinect偵測范圍,kinect還會自動追焦27度,即馬達能夠上下旋轉27度(因為涉及到專利的問題,OpenNI驅動沒有這個功能,微軟SDK可以),因此理論上上下掃描的范圍應該為97度(27+27+43)。水平方向上雖然有馬達,但是只能手動去掰動kinect,因為驅動中並沒有對應的水平角度旋轉的API,即使是微軟的SDK也一樣。

  Kinect的傾斜角度如下圖所示:

  

  下面來解釋Kinect采集到的深度值的具體含義:

  Openni的原始驅動類中的depth_metadata_其實也是一副圖像,圖像的坐標表示空間點的投影坐標,圖像坐標里存的值是對應空間點投影坐標的深度值。該深度值並不是指空間中對應像素點到深度sensor點之間的距離(即2點直接的距離),而是指空間中對應像素點到kinect傳感器所在平面的距離(即是一個垂直距離),因為前面已經提到,kinect是可以上下旋轉的。現假設三種情況,第一:我們不上下旋轉kinect,即保持kinect傳感器平面與水平地面垂直,這時像素點X深度值為a;第二:將kinect往上旋轉一個角度(當然了,這個角度值小於27度),這時候同樣一個像素點X的深度值為b;第三:將kinect往下旋轉一個角度Beta角度,這時候X的深度值為c;你會發現,a,b,c這3者不一定相等。

 

  OpenCV知識點總結:

  當Mat中數據的類型為CV_16UC1的時候,這里的16U並不是指unsigned int,而是指的是unsigned short int,因為在OpenCV框架中,int不是16位的,而是32位的。沒想到我使用OpenCV一年了,今天才弄清楚這個。

 

  實驗結果:

  該實驗是測試一個垂直擺放的櫃子,該櫃子一個平面上的點本來與kinect之間的距離是相等的,現在測試kinect在不同上下旋轉角度的情況下,這個櫃子上的點的深度值是否一樣。首先將kinect往上旋轉一個角度,即kinect平面與水平面之間有一個夾角。實驗結果如下:

  

  圖中顯示的數字為鼠標所在位置像素的真實深度值。

 

  櫃子中同一個平面上另一個像素點的深度值結果如下:

  

  由此可以看出,同一個垂直櫃子平面上的點像素值相差30cm以上(其實從圖中深度圖的顏色信息就可以看出,該櫃子深度圖像都是傾斜的,因為kinect本身就有轉角)。

 

  如果把kinect所在的平面擺正,即與水平面之間沒有夾角,則櫃子上某一點的深度值如下圖所示:

  

 

  同一垂直平面上另一個點的如下所示:

  

  由此可以看出,其深度值變化不大。

  上面的實驗結果解釋了在實驗基礎部分中所講的kinect深度值的含義。

 

  實驗主要代碼及注釋:

  copenni.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();

    /*得到給定投影坐標的點,返回其對應真實坐標的深度值*/
    XnFloat GetProjectWorldPixpelDepth(XnPoint3D *project_world_pixpel);

    /*得到手部的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

 

  copenni.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 ));
}

 

  ckinectopencv.h:

#ifndef CKINECTOPENCV_H
#define CKINECTOPENCV_H

#include <opencv2/core/core.hpp>
#include "copennihand.h"
#include "XnCppWrapper.h"

using namespace cv;
using namespace xn;

class CKinectOpenCV
{
public:
    CKinectOpenCV();
    ~CKinectOpenCV();
    void GetAllInformation();   //在返回有用信息前調用該函數,因為openni的數據在不斷更新,信息的處理最好放在一個函數中
    Mat GetColorImage() ;
    Mat GetDepthImage() ;
    Mat GetDepthRealValueImage();
    std::map<XnUserID, XnPoint3D> GetHandPoints();

private:
    COpenniHand openni_hand_;
    std::map<XnUserID, XnPoint3D> hand_points_;  //為了存儲不同手的實時點而設置的
    Mat color_image_;    //顏色圖像
    Mat depth_image_;    //深度圖像
    Mat depth_realvalue_image_;


};

#endif // CKINECTOPENCV_H

 

  ckinectopencv.cpp:

#include "ckinectopencv.h"
#include <opencv2/imgproc/imgproc.hpp>
#include <map>

using namespace cv;
using namespace std;

#define DEPTH_SCALE_FACTOR 255./4096.

CKinectOpenCV::CKinectOpenCV()
{   
    /*初始化openni對應的設備*/
     CV_Assert(openni_hand_.Initial());

    /*啟動openni對應的設備*/
    CV_Assert(openni_hand_.Start());
}

CKinectOpenCV::~CKinectOpenCV()
{
}

void CKinectOpenCV::GetAllInformation()
{
    CV_Assert(openni_hand_.UpdateData());
    /*獲取色彩圖像*/
    Mat color_image_src(openni_hand_.image_metadata_.YRes(), openni_hand_.image_metadata_.XRes(),
                        CV_8UC3, (char *)openni_hand_.image_metadata_.Data());
    cvtColor(color_image_src, color_image_, CV_RGB2BGR);

    /*獲取深度圖像*/
    Mat depth_image_src(openni_hand_.depth_metadata_.YRes(), openni_hand_.depth_metadata_.XRes(),
                        CV_16UC1, (char *)openni_hand_.depth_metadata_.Data());//因為kinect獲取到的深度圖像實際上是無符號的16位數據
    depth_image_src.convertTo(depth_image_, CV_8U, DEPTH_SCALE_FACTOR);

    /*獲取真實深度值圖像,沒有經過深度歸一化的*/
    depth_realvalue_image_ = depth_image_src.clone();

    hand_points_ = openni_hand_.hand_points_;   //返回手部點的位置

    return;
}

Mat CKinectOpenCV::GetColorImage()
{
    return color_image_;
}

Mat CKinectOpenCV::GetDepthImage()
{
    return depth_image_;
}

Mat CKinectOpenCV::GetDepthRealValueImage()
{
    return depth_realvalue_image_;
}

std::map<XnUserID, XnPoint3D> CKinectOpenCV::GetHandPoints()
{
    return hand_points_;
}

 

  main.cpp:

#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include "ckinectopencv.h"
#include "XnCppWrapper.h"

using namespace std;
using namespace cv;

CKinectOpenCV kinect_opencv;
Mat color_image ;
Mat depth_image ;
Mat depth_realvalue_image;
unsigned int  pixpel_depth_value = 0;

Point mouse_pixpel(0, 0);

void on_mouse(int event,int x,int y,int,void*)
{
    if(event == CV_EVENT_MOUSEMOVE) {  //鼠標移動的過程中
        pixpel_depth_value = depth_realvalue_image.at<unsigned short int>(y, x);
        mouse_pixpel = Point(x, y);
    }
}

int main()
{

//    namedWindow("color_image", CV_WINDOW_AUTOSIZE);
    namedWindow("depth_image", CV_WINDOW_AUTOSIZE);
    setMouseCallback("depth_image", on_mouse, 0);   //設置鼠標響應函數

    while(1)
    {
        kinect_opencv.GetAllInformation();
//        color_image = kinect_opencv.GetColorImage();
        depth_image = kinect_opencv.GetDepthImage();
        depth_realvalue_image = kinect_opencv.GetDepthRealValueImage();
        stringstream depth_value_string;
        depth_value_string << pixpel_depth_value/1000. << " m" ;

        putText(depth_image, depth_value_string.str(), mouse_pixpel, 3, 1, Scalar(50, 0, 0), 2, 8);

//        imshow("color_image", color_image);
        imshow("depth_image", depth_image);
        waitKey(30);
    }

    return 0;
}

 

 

  實驗總結: 通過本次資料的查找和實驗的驗證,對kinect的深度值有了更一步的了解了。

 

  參考文獻:

     Kinect + OpenNI 的深度值

     [譯]Kinect for Windows SDK開發入門(四):景深數據處理 上

 

 

 


免責聲明!

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



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