• 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开发入门(四):景深数据处理 上

  • 相关阅读:
    【C++ OpenGL ES 2.0编程笔记】8: 使用VBO和IBO绘制立方体 【转】
    顶点缓存对象(VBO)【转】
    CompileGLShader
    VR虚拟现实的工作原理,你知道多少?【转】
    VR/AR工作原理、目前存在的技术问题
    Got fatal error 1236 from master when reading data from binary log: 'Could not find first log file name in binary log index file'
    nginx配置用户认证
    恢复阿里云RDS云数据库MySQL的备份文件到自建数据库
    阿里云rds linux平台使用wget 工具下载备份与日志文件
    screen 命令使用及示例
  • 原文地址:https://www.cnblogs.com/tornadomeet/p/2772681.html
Copyright © 2020-2023  润新知