[转载]Kinect+OpenNI学习笔记之6(获取人体骨架并在Qt中显示) - tornadomeet - 博客园

mikel阅读(944)

[转载]Kinect+OpenNI学习笔记之6(获取人体骨架并在Qt中显示) – tornadomeet – 博客园.

前言

MS的kinec SDK和OpenNI都提供了人体骨骼跟踪的算法,人体骨骼跟踪算法在kinect人体行为识别中非常重要,该识别过程通常被用来作为行为识别的第一步, 比如说,通过定位人体中的骨骼支架,可以提取出人手的部位,从而可以把手的部分单独拿出来分析,这样就达到了手势的定位,而后面的手势识别则可以在刚刚定 位出的领域进行处理。总而言之,一套有效的人体骨架追踪算法在kinect的一系列应用中非常有用,不过MS SDK和OpenNI虽然都提供了该算法类的直调用,但是其源码并没有开放,毕竟这是人家最核心的东东。

开发环境:QtCreator2.5.1+OpenNI1.5.4.0+Qt4.8.2

 

实验说明

在老版本的OpenNI中,要对人进行骨架追踪,需要人先摆出PSI的姿势,然后系统根据该姿势进行骨骼校正,待校正完成后才进行骨骼的跟踪,其流程图可以参考下面的图:

由图可以看出,其完成骨骼跟踪主要分为3个部分,首先需检测到人体,然后需要固定的PSI姿势来对人体的姿势进行校正,待姿势校正完成后,才能进行人体骨骼的追踪。如果程序开发者用代码实现该过程则可以参考hersey的文章透过OpenNI / NITE 分析人体骨架(上)透过OpenNI / NITE 分析人体骨架(下),作者在这2篇文章详细介绍了老版本的人体骨架的OpenNI实现。

在新版本OpenNI1.5以后,人体骨架追踪算法更改了不少,其中最大的特点就是骨架跟踪过程中少了姿势校正的那一步骤,新版本中只需要人体 站起来就可以进行跟踪了,使用起来方便很多,程序开发也简单不少。另外人体骨骼跟踪的效果也提高了不少,一旦骨骼追踪成功后,即使人体没有保持站立姿势有 时候也还是可以继续跟踪的。新版本的人体骨骼跟踪算法使用流程图如下:

 

下面来看看程序中的Capability,它不同于前面文章的generator:

在进行骨架的判断和姿态检测是需要用到OpenNI延伸的功能,与这种延伸功能相关的类可以称作为Capability。在进行人体骨骼分析时,user generator需要有支援Skeleton和Pose Detection这2个的capability。

在程序中需要绘制骨骼节点之间的连线,而节点的坐标和法向都有函数可以获得,获得的坐标为真实世界中的坐标,画图时需要在平面上绘制,因此需要这2个坐标系的转换,转换过程用到下面的函数:

XnStatus xn::DepthGenerator::ConvertRealWorldToProjective(XnUInt32 nCount, const XnPoint3D aRealWorld[], XnPoint3D aProjective[])

该函数表示将深度图获取的真实坐标系转换成平面图形显示的投影坐标系上。第1个参数表示转换坐标点的个数,第2个参数表示真实坐标系中的坐标,第3个参数表示投影坐标系下的坐标。

本实验的程序分为3个类和一个主函数,其中2个类的基本部分在前面的文章中已有介绍,只需要更新其部分功能。下面是本实验中这3个类的设计。当然这都是参考heresy的博客使用Qt 显示OpenNI 的人体骨架

COpenNI类的更新:

因为需要对人体进行骨骼跟踪,所以需要用到OpenNI的UserGenerator这个类。在private变量一栏增加这个类对象的声明。然后在类的Init()函数中使用Create方法产生人体的node。同上一篇博客Kinect+OpenNI学习笔记之5(使用OpenNI自带的类进行简单手势识别)中 类似,这里的人体骨架校正,跟踪等都是通过回调函数的形式进行的,因此还需要在Init()函数中设置这个node的检测到有新人进入和骨骼校正完成的回 调函数(其实还有旧人体目标离去,骨骼校正开始这2个也可以设置回调函数,但在本程序中因为不需要使用它们,因此可以省略不写,老版本的OpenNI是不 允许省略的)。另外,由于色彩节点,深度节点,以及人体检测节点都是私有变量,如果该类的对象需要获取该变量的话不方便,因此在共有函数部分分别设置了3 个共有函数来获取这3个变量。具体该类的全部代码参加本文后面的代码部分。

CSkeletonItem类的设计:

CSkeletonItem这个类主要来完成骨架节点位置的获取,以及画出item中节点之间的连线,同时也在节点位置处画出圆圈代表对应节点的位置。

在构造函数中,设计了一个二维的连接表矩阵,矩阵的大小为14*2,即有14条边,每条边有2个顶点,矩阵中对应位置的值表示的是对应边的节点骨架的标号,在OpenNI中人体的骨架节点共分为15个,手脚共12个,头部2个,躯干1个。如下图所示:

程序中对这15个点编了序号,头部为0, 颈部为1, 躯干为2, 左肩膀为3, 左手肘为4, 左手腕5,右肩膀为6,右手肘为7,右手腕为8,左臀为9,左膝盖为10,左脚跟为11,右臀为12,右膝盖为13,右脚跟为14。

该类中需要重写的boundingRect()函数,函数中设置了一个包含15个节点的最小矩形,因为后面的绘图区域需要在这个矩形内进行,很明显,获得的这个矩形不是固定大小的,而是根据人体骨架的位置在不断变化。大小和位置同时都会发生改变。

重写的paint()函数则需要完成2个部分的功能, 第一是画出骨骼中节点的位置,用圆圈显示;第二是画出2个节点之间的连线,共14条,这样通过画出的连线就可以大概看出人的位置和区域了。本文是参考的heresy文章不用校正姿势的NITE 1.5 ,heresy在设计该类的构造函数时,设计了个15*2的连接表,个人感觉设置为14*2比较合理,因为15个点刚好由14条线可以连接起来,并不是heresy所说的15条线,其实它有2条线是重合的。

CKinectReader类的更新:

  该类是在前面的文章Kinect+OpenNI学习笔记之3(获取kinect的数据并在Qt中显示的类的设计)中 对应类的更新,前面博文中的该类只是完成了深度图像和颜色图像的显示,而在本实验中,需要完成显示骨架节点之间的连线图,因此该类需要继续更新。其实现过 程主要是获取视野中人体的个数,对检测到的每个人体然后调用CSkeletonItem类中的方法UpdateSkeleton()来更新读取的节点坐 标,因为一旦坐标值发生了改变,CSkeletonItem类中的boundingRect()内容也会更改,从而其Item所在区域的矩形也会变化,最 后导致paint()函数的执行,在paint()函数中完成骨骼节点连线和骨骼节点的绘图。

  实验结果

  试验效果的截图:

  

  蓝色的线表示骨骼节点之间的连线,黄色的圈表示骨骼节点。

  实验主要部分代码及注释(附录有实验工程code下载链接地址):

copenni.cpp:

复制代码
#ifndef COPENNI_CLASS
#define COPENNI_CLASS

#include <XnCppWrapper.h>
#include <QtGui/QtGui>
#include <iostream>

using namespace xn;
using namespace std;

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 = 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 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;
    }

public:
    DepthMetaData depth_metadata;
    ImageMetaData image_metadata;
    GestureGenerator gesture_generator;//外部要对其进行回调函数的设置,因此将它设为public类型

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 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;
    DepthGenerator  depth_generator;
    ImageGenerator  image_generator;
    UserGenerator user_generator;
    XnMapOutputMode xmode;
};

#endif
复制代码

 

cskeletonitem.cpp:

复制代码
#ifndef CSKELETONITEM_CLASS
#define CSKELETONITEM_CLASS

#include <QtGui>
#include <XnCppWrapper.h>
#include "copenni.cpp"

class CSkeletonItem : public QGraphicsItem
{
public:
    /*构造函数*/
    CSkeletonItem(XnUserID &user_id, COpenNI& openni) : QGraphicsItem(), user_id(user_id),
                    openni(openni) {
        /*创建关节相连的二维表
        connections[i]表示第i条线(2个节点之间表示一条线),connections[i][0]和connections[i][1]分别表示
        第i条线的2个端点*/
        //头部和身体的2条线
        {
            connections[0][0] = 0;
            connections[0][1] = 1;
            connections[1][0] = 1;
            connections[1][1] = 2;
        }
        //左手的3条线
        {
            connections[2][0] = 1;
            connections[2][1] = 3;
            connections[3][0] = 3;
            connections[3][1] = 4;
            connections[4][0] = 4;
            connections[4][1] = 5;
        }
        //右手的3条线
        {
            connections[5][0] = 1;
            connections[5][1] = 6;
            connections[6][0] = 6;
            connections[6][1] = 7;
            connections[7][0] = 7;
            connections[7][1] = 8;
        }
        //左腿的3条线
        {
            connections[8][0] = 2;
            connections[8][1] = 9;
            connections[9][0] = 9;
            connections[9][1] = 10;
            connections[10][0] = 10;
            connections[10][1] = 11;
        }
        //右腿的3条线
        {
            connections[11][0] = 2;
            connections[11][1] = 12;
            connections[12][0] = 12;
            connections[12][1] = 13;
            connections[13][0] = 13;
            connections[13][1] = 14;
        }

    }
    /*更新skeleton里面的数据,分别获得15个节点的世界坐标,并转换成投影坐标*/
    void UpdateSkeleton() {
        XnPoint3D joints_realworld[15];
        joints_realworld[0] = getSkeletonPos(XN_SKEL_HEAD);
        joints_realworld[1] = getSkeletonPos(XN_SKEL_NECK);
        joints_realworld[2] = getSkeletonPos(XN_SKEL_TORSO);
        joints_realworld[3] = getSkeletonPos(XN_SKEL_LEFT_SHOULDER);
        joints_realworld[4] = getSkeletonPos(XN_SKEL_LEFT_ELBOW);
        joints_realworld[5] = getSkeletonPos(XN_SKEL_LEFT_HAND);
        joints_realworld[6] = getSkeletonPos(XN_SKEL_RIGHT_SHOULDER);
        joints_realworld[7] = getSkeletonPos(XN_SKEL_RIGHT_ELBOW);
        joints_realworld[8] = getSkeletonPos(XN_SKEL_RIGHT_HAND);
        joints_realworld[9] = getSkeletonPos(XN_SKEL_LEFT_HIP);
        joints_realworld[10] = getSkeletonPos(XN_SKEL_LEFT_KNEE);
        joints_realworld[11] = getSkeletonPos(XN_SKEL_LEFT_FOOT);
        joints_realworld[12] = getSkeletonPos(XN_SKEL_RIGHT_HIP);
        joints_realworld[13] = getSkeletonPos(XN_SKEL_RIGHT_KNEE);
        joints_realworld[14] = getSkeletonPos(XN_SKEL_RIGHT_FOOT);
        //将世界坐标系转换成投影坐标系,一定要使用深度信息的节点
        openni.getDepthGenerator().ConvertRealWorldToProjective(15, joints_realworld, joints_project);
    }
public:
    COpenNI& openni;
    XnUserID& user_id;//每个CSkeletonItem对应一个人体
    XnPoint3D joints_project[15];//15个关节点的坐标
    int connections[14][2];
 //   int connections[15][2];

private:
    XnPoint3D getSkeletonPos(XnSkeletonJoint joint_name) {
        XnSkeletonJointPosition pos;//关节点的坐标
        //得到指定关节名称的节点的坐标,保存在pos中
        openni.getUserGenerator().GetSkeletonCap().GetSkeletonJointPosition(user_id, joint_name, pos);
        return xnCreatePoint3D(pos.position.X, pos.position.Y, pos.position.Z);//以3维坐标的形式返回节点的坐标
    }
    //boudintRect函数的重写
    QRectF boundingRect() const {
        QRectF rect(joints_project[0].X, joints_project[0].Y, 0, 0);//定义一个矩形外围框,其长和宽都为0
        for(int i = 1; i < 15; i++) {
            //下面的代码是找出能够围住15个节点的最小矩形框
            //rect.left()等返回的是一个实数
            if(joints_project[i].X < rect.left()) { //小于矩形框左边点的横坐标时
                rect.setLeft(joints_project[i].X);
            }
            if(joints_project[i].X > rect.right()) {
                rect.setRight(joints_project[i].X);
            }
            if(joints_project[i].Y < rect.top()) {
                rect.setTop(joints_project[i].Y);
            }
            if(joints_project[i].Y > rect.bottom()) {
                rect.setBottom(joints_project[i].Y);
            }
        }
        return rect;
    }
    //重绘函数的重写
    void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) { //固定的参数形式
        //后面要画骨骼直接的连线,首先需要设置画笔
        QPen pen(QColor::fromRgb(0, 0, 255));//设置蓝色的画笔
        pen.setWidth(3);
        painter->setPen(pen);
        //画骨骼的线,总共是14条线
        for(unsigned int i = 0; i < 14; i++) {
            XnPoint3D &p1 = joints_project[connections[i][0]];
            XnPoint3D &p2 = joints_project[connections[i][1]];
            painter->drawLine(p1.X, p1.Y, p2.X, p2.Y);
        }
        painter->setPen(QPen(Qt::yellow, 3));
        //每个节点处画个小圆圈
        for(unsigned int  i = 0; i < 15; i++ ) {
            painter->drawEllipse(QPoint(joints_project[i].X, joints_project[i].Y), 5, 5);
        }
    }
};

#endif
复制代码

 

ckinectreader.cpp:

复制代码
#include <QtGui>
#include <QDebug>
#include <XnCppWrapper.h>
#include "copenni.cpp"  //要包含cpp文件,不能直接包含类
#include "cskeletonitem.cpp"
#include <iostream>

using namespace std;

class CKinectReader: public QObject
{
public:
    //构造函数,用构造函数中的变量给类的私有成员赋值
    CKinectReader(COpenNI &openni, QGraphicsScene &scene) : openni(openni), scene(scene) {
    }
    ~CKinectReader() {
        scene.removeItem(image_item);
        scene.removeItem(depth_item);
        delete [] p_depth_argb;
    }
    bool Start(int interval = 33) {
        openni.Start();//因为在调用CKinectReader这个类的之前会初始化好的,所以这里直接调用Start了
        image_item = scene.addPixmap(QPixmap());
        image_item->setZValue(1);
        depth_item = scene.addPixmap(QPixmap());
        depth_item->setZValue(2);
        openni.UpdateData();
        p_depth_argb = new uchar[4*openni.depth_metadata.XRes()*openni.depth_metadata.YRes()];
        startTimer(interval);//这里是继承QObject类,因此可以调用该函数
        return true;
    }
private:
    COpenNI &openni;    //定义引用同时没有初始化,因为在构造函数的时候用冒号来初始化
    QGraphicsScene &scene;
    QGraphicsPixmapItem *image_item;
    QGraphicsPixmapItem *depth_item;
    uchar *p_depth_argb;
    vector<CSkeletonItem*> skeletons;//CSkeletonItem类的使用在此处得到了体现

private:
    void timerEvent(QTimerEvent *) {

        openni.UpdateData();
        //这里使用const,是因为右边的函数返回的值就是const类型的
        const XnDepthPixel *p_depth_pixpel = openni.depth_metadata.Data();
        unsigned int size = openni.depth_metadata.XRes()*openni.depth_metadata.YRes();

        //找深度最大值点
        XnDepthPixel max_depth = *p_depth_pixpel;
        for(unsigned int i = 1; i < size; ++i)
            if(p_depth_pixpel[i] > max_depth )
                max_depth = p_depth_pixpel[i];

        //将深度图像格式归一化到0~255
        int idx = 0;
        for(unsigned int i = 1; i < size; ++i) {
            //一定要使用1.0f相乘,转换成float类型,否则该工程的结果会有错误,因为这个要么是0,要么是1,0的概率要大很多
            float fscale = 1.0f*(*p_depth_pixpel)/max_depth;
            if((*p_depth_pixpel) != 0) {
                p_depth_argb[idx++] = 255*(1-fscale);    //蓝色分量
                p_depth_argb[idx++] = 0; //绿色分量
                p_depth_argb[idx++] = 255*fscale;   //红色分量,越远越红
                p_depth_argb[idx++] = 255*(1-fscale); //距离越近,越不透明
            }
            else {
                p_depth_argb[idx++] = 0;
                p_depth_argb[idx++] = 0;
                p_depth_argb[idx++] = 0;
                p_depth_argb[idx++] = 255;
            }
            ++p_depth_pixpel;//此处的++p_depth_pixpel和p_depth_pixpel++是一样的
        }
        //往item中设置图像色彩数据
        image_item->setPixmap(QPixmap::fromImage(
                              QImage(openni.image_metadata.Data(), openni.image_metadata.XRes(), openni.image_metadata.YRes(),
                              QImage::Format_RGB888)));
        //往item中设置深度数据
        depth_item->setPixmap(QPixmap::fromImage(
                              QImage(p_depth_argb, openni.depth_metadata.XRes(), openni.depth_metadata.YRes()
                              , QImage::Format_ARGB32)));
        //读取骨骼信息
        UserGenerator &user_generator = openni.getUserGenerator();
        XnUInt16 users_num = user_generator.GetNumberOfUsers();//得到视野中人体的个数
        if(users_num > 0) {
            XnUserID *user_id = new XnUserID[users_num];//开辟users_num个XnUserID类型的内存空间,XnUserID其实就是一个XnUInt32类型
            user_generator.GetUsers(user_id, users_num);//将获取到的userid放入user_id指向的内存中
            unsigned int counter = 0;
            SkeletonCapability &skeleton_capability = user_generator.GetSkeletonCap();//获取骨骼的capability
            for(int i = 0; i < users_num; i++) {
                if(skeleton_capability.IsTracking(user_id[i])) {
                    ++counter;
                    if(counter > skeletons.size()) { //跟踪中人体的数目大于视野中人体的数量时
                        CSkeletonItem *p_skeleton = new CSkeletonItem(user_id[i], openni);//重新创建一个骨架对象,并加入到骨架vector中
                        scene.addItem(p_skeleton);//在场景中显示该骨架
                        p_skeleton->setZValue(10);
                        skeletons.push_back(p_skeleton);
                    }
                    else
                        skeletons[counter-1]->user_id = user_id[i];
                    //更新对应人体的骨架信息
                    skeletons[counter-1]->UpdateSkeleton();
                   //调用该函数后boundingRect()函数就会一直在更新,所以paint()函数也在不断变化
                    skeletons[counter-1]->setVisible(true);
                }
            }
            //将其他没有使用的item设置为不显示
            for(unsigned int i = counter; i < skeletons.size(); ++i) {
                skeletons[i]->setVisible(false);
            }
            delete [] user_id;
        }
    }
};
复制代码

 

main.cpp:

复制代码
#include <QtGui>
#include <QtCore>
#include "copenni.cpp"
#include "cskeletonitem.cpp"
#include "ckinectreader.cpp"

using namespace xn;

int main(int argc, char **argv)
{
    COpenNI openni;
    if(!openni.Initial())
        return 1;//返回1表示不正常返回

    QApplication app(argc, argv);
    QGraphicsScene scene;
    QGraphicsView view(&scene);
    view.resize(650, 540);//view的尺寸比图片的输出尺寸稍微大一点
    view.show();

    CKinectReader kinect_reader(openni, scene);
    kinect_reader.Start();

    return app.exec();
}
复制代码

 

  实验总结:

  通过本实验学会了简单使用OpenNI的库来获取人体的骨骼节点并在Qt中显示出来。

  参考资料:

透过OpenNI / NITE 分析人体骨架(上)

透过OpenNI / NITE 分析人体骨架(下)

不用校正姿势的NITE 1.5

使用Qt 显示OpenNI 的人体骨架

 

 

附录:实验工程code下载

 

 

 

作者:tornadomeet 出处:http://www.cnblogs.com/tornadomeet 欢迎转载或分享,但请务必声明文章出处。

[转载]Kinect+OpenNI学习笔记之5(使用OpenNI自带的类进行简单手势识别) - tornadomeet - 博客园

mikel阅读(961)

[转载]Kinect+OpenNI学习笔记之5(使用OpenNI自带的类进行简单手势识别) – tornadomeet – 博客园.

前言

因为OpenNI可以获取到kinect的深度信息,而深度信息在手势识别中有很大用处,因此本文就来使用OpenNI自带的类来做简单的手势识别。识别的动作为4种,挥手,手移动,举手,往前推手。通过后面的实验可以发现,其实提供的类的效果非常不好。

开发环境:QtCreator2.5.1+OpenNI1.5.4.0+Qt4.8.2+OpenCV2.4.2

 

实验说明

  跟手势相关的是GestureGenerator这个类,它的初始化过程和 depth_metadata,image_metadata都一样,因此首先在上2篇文章的COpenNI类中增加一个public类对象 GestureGenerator gesture_generator;为什么不放在private里呢?因为我们的COpenNI对象需要调用这个变量来设置手势获取的一些属性,比如手 势识别的种类等,总之就是这个变量外部需要能够访问得到,因此这里我将其放在public里面。另外在COpenNI类的Init()函数中需要加入下面 的代码:

复制代码
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);
复制代码

  OpenNI进行手势识别的方式是采用函数回调,即如果一个手势发生了或者正在发生时可以触发相应的回调函数,从而去执行回调函数,这有点类似于Qt中的信号与槽的关系。在OpenNI中设置回调函数的原型为:

  XnStatus RegisterGestureCallbacks(GestureRecognized RecognizedCB, GestureProgress ProgressCB, void* pCookie, XnCallbackHandle& hCallback);

  其中前2个参数为回调函数,第一个回调函数表示手部某个动作已经执行完毕,第二个参数表示收部某个动作正在执行;参 数三为一个空指针,即可以指向任何数据类型的指针,其作用为给回调函数当额外的参数使用;参数四为回调函数的处理函数,用来记录和管理回调函数的。参数三 在本实验中设置为NULL,参数四实际上本实验中也没有用到。

上面2个回调函数的名称可以自定义,但是这2个函数参数的个数和类型不能改变,这2个回调函数的参数个数都为5,但是其类型有些不同,具体的可以参考后面提供的代码。

  由于在程序中添加了4种动作的捕捉,所以打算在检测到某个手势动作时,在窗口显示栏的图片上添加相应的手势动作文字 提示。很明显,只有当手势检测到时才能在图片上添加文字,该部分在回调函数中实现。但是如果我们单独在回调函数中给图片添加相应的文字,然后在主程序中显 示图片,则因为回调函数一结束完就回到了主函数的while循环中,而这时图片的内容已经更新了(即有文字的图片被重新覆盖了),因此人眼一瞬间看不到有 文字提示的图片。最后个人的解决方法是用一个标志来表示检测到了某个手势动作,如果检测到了则显示存储下来的有文字的图片,反正,显示正常的图片。本程序 提供的图片为深度图。

  实验结果

  举手的显示结果如下:

  

   其实从本人的实验过程来看,大部分的手势动作都被检测为举手RaiseHand,少部分为挥手Wave,其它的基本上没出现过。说明OpenNI自带的手势识别类的功能不是很强。

  实验主要部分代码及注释(附录有实验工程code下载链接):

copenni.cpp:

复制代码
#include <XnCppWrapper.h>
#include <QtGui/QtGui>
#include <iostream>

using namespace xn;
using namespace std;

class COpenNI
{
public:
    ~COpenNI() {
        context.Release();//释放空间
    }
    bool Initial() {
        //初始化
        status = context.Init();
        if(CheckError("Context initial failed!")) {
            return false;
        }
        context.SetGlobalMirror(true);//设置镜像
        //产生图片node
        status = image_generator.Create(context);
        if(CheckError("Create image generator  error!")) {
            return false;
        }
        //产生深度node
        status = depth_generator.Create(context);
        if(CheckError("Create depth 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;
        }
        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);
        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;
    }

public:
    DepthMetaData depth_metadata;
    ImageMetaData image_metadata;
    GestureGenerator gesture_generator;//外部要对其进行回调函数的设置,因此将它设为public类型

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;
    }

private:
    XnStatus    status;
    Context     context;
    DepthGenerator  depth_generator;
    ImageGenerator  image_generator;
};
复制代码

 

main.cpp:

复制代码
#include <QCoreApplication>

#include "opencv2/highgui/highgui.hpp"
#include "opencv2/imgproc/imgproc.hpp"
#include <opencv2/core/core.hpp>
#include "copenni.cpp"

#include <iostream>

using namespace cv;
using namespace xn;

Mat depth_image;
Mat depth_image_result;//深度结果图,且在该图上显示手势动作的类型
COpenNI openni;
bool test_flag = false;

// callback function for gesture recognized
//回调函数,该函数的函数名字可以随便取,但是其参数的格式必须不能改变
//这里该函数的作用是表示上面4种手势发生完成后调用
void  XN_CALLBACK_TYPE  GRecognized ( xn::GestureGenerator &generator,
                                    const XnChar *strGesture,
                                    const  XnPoint3D *pIDPosition,
                                    const  XnPoint3D *pEndPosition,
                                    void *pCookie )
{
    depth_image_result = depth_image.clone();
    putText(depth_image_result, strGesture, Point(50, 150), 3, 0.8, Scalar(255, 0, 0), 2 );
    test_flag = true;
}

// callback function for gesture progress
//该函数表示上面4种手势某一种正在发生时调用
void  XN_CALLBACK_TYPE  GProgress ( xn::GestureGenerator &generator,
                                  const XnChar *strGesture,
                                  const  XnPoint3D *pPosition,
                                 XnFloat fProgress,
                                 void *pCookie )
{
    ;
}

int main (int argc, char **argv)
{

    if(!openni.Initial())
        return 1;
    XnCallbackHandle handle;
    openni.gesture_generator.RegisterGestureCallbacks(GRecognized, GProgress, NULL, handle);
    if(!openni.Start())
        return 1;
    namedWindow("depth image", CV_WINDOW_AUTOSIZE);
    putText(depth_image, "YES!", Point(50, 150), 3, 0.8, Scalar(255, 0, 0), 2 );
    while(1) {
        if(!openni.UpdateData()) {
            return 1;
        }
        /*获取并显示深度图像,且这2句代码不能放在回调函数中调用,否则后面的imshow函数会因为执行时找不到图片(因为此时回调函数不一定执行了)而报错*/
        Mat depth_image_src(openni.depth_metadata.YRes(), openni.depth_metadata.XRes(),
                            CV_16UC1, (char *)openni.depth_metadata.Data());//因为kinect获取到的深度图像实际上是无符号的16位数据
        depth_image_src.convertTo(depth_image, CV_8U, 255.0/8000);

        if(!test_flag)
            imshow("depth image", depth_image);
        else
            imshow("depth image", depth_image_result);
        waitKey(30);
        test_flag = false;
    }

}
复制代码

 

  错误总结

  如果用Qt的控制台建立程序,运行程序时出现下面的错误提示:

  

  这是因为控制台程序不能使用Qt的界面(本程序中使用了QMessageBox),因此需要在工程pro的代码中把QT –  gui给去掉,否则会报类似的这种错误。

  如果是在OpenCV中出现如下错误:

  

  则表示是imshow函数需要还来不及显示完成就被其它的函数给中断了,这可能在回调函数中出现这种情况。

  实验总结

  通过本次实验对OpenNI自带的手势识别类的使用有了初步的了解。

  参考资料:

    OpenNI 的手势侦测

  附录:实验工程code下载

作者:tornadomeet 出处:http://www.cnblogs.com/tornadomeet 欢迎转载或分享,但请务必声明文章出处。

[转载]Kinect+OpenNI学习笔记之4(OpenNI获取的图像结合OpenCV显示) - tornadomeet - 博客园

mikel阅读(925)

[转载]Kinect+OpenNI学习笔记之4(OpenNI获取的图像结合OpenCV显示) – tornadomeet – 博客园.

前言

本文来结合下opencv的highgui功能显示kinect采集得来的颜色图和深度图。本来在opencv中自带了VideoCapture类的,使用该类可以直接驱动kinect设备,具体的可以参考下面的文章:http://blog.csdn.net/moc062066/article/details/6949910,也可以参考opencv提供的官方文档:http://docs.opencv.org/doc/user_guide/ug_highgui.html。 这种方法用起来非常简单,不需要考虑OpenNI的驱动过程,有点像傻瓜式的操作。不过本人在使用该种方法时kinect一直驱动不成功,即使用 opencv的VideoCapture类来捕捉Kinect设备的数据,一直是打不开的,即驱动不成功。但是kinect设备已经连接上了,且能运行 openni中的sample,说明kinect的硬件驱动是没问题的,应该是opencv这个类的bug,网上很多人都碰到了该情况。

所以还是使用前面2篇博文介绍的,自己用OpenNI写驱动,这样可以更深刻的对OpenNI这个库灵活运用。

开发环境:QtCreator2.5.1+OpenNI1.5.4.0+Qt4.8.2+OpenCV2.4.2

 

实验说明

在用OpenCV显示OpenNI的数据时,先来了解下Kinect获取到的深度信息的一些特点,Heresy的文章:Kinect + OpenNI 的深度值 介绍得比较通俗易懂。下面是个人觉得kinect深度信息比较重要的地方:Kinect官方推荐的使用距离为1.2m到3.6m之间。其中1.2m时的精 度为3mm,3.2米的时候精度越为3cm。随着距离越来越远,其检测到的精度也越来越小。精度最小为1mm,大概是距离50cm时,不过此时的点数量比 较少,也不稳定,因此官方没有推荐使用这个时候的距离。

另外还需要注意OpenNI中表示深度像素的格式为XnDepthPixel,实际上是单一channel的16位正整数,因此如果使用 OpenCV来存储时,需要设定格式为CV_16UC1。因此其范围是0~65536,不过期最大的深度只能感应到10000,所以我们需要将其归一化到 一个比较好的范围内。

本文使用的是上一篇博文:Kinect+OpenNI学习笔记之3(获取kinect的数据并在Qt中显示的类的设计) 中用到的类COpenNI,该类可以方便的驱动kinect,并将获得的色彩信息和深度信息保存在共有变量中,提供其对象来调用。主函数中使用 OpenCV库来创建窗口,且将Kinect获到的数据转换成OpenCV的格式,然后在创建的窗口中显示。同时也对深度图像和颜色图像进行了canny 边缘检测并显示出来比较。

 

实验结果

本实验显示4幅图像,分别为颜色原图及其canny边缘检测图,深度原图及其canny边缘检测图。结果截图部分图如下:

 

 

实验主要部分代码及注释(附录有实验工程code下载链接地址):

copenni.cpp:

复制代码
#include <XnCppWrapper.h>
#include <QtGui/QtGui>
#include <iostream>

using namespace xn;
using namespace std;

class COpenNI
{
public:
    ~COpenNI() {
        context.Release();//释放空间
    }
    bool Initial() {
        //初始化
        status = context.Init();
        if(CheckError("Context initial failed!")) {
            return false;
        }
        context.SetGlobalMirror(true);//设置镜像
        //产生图片node
        status = image_generator.Create(context);
        if(CheckError("Create image generator  error!")) {
            return false;
        }
        //产生深度node
        status = depth_generator.Create(context);
        if(CheckError("Create depth 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;
        }

        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;
    }

public:
    DepthMetaData depth_metadata;
    ImageMetaData image_metadata;

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;
    }

private:
    XnStatus    status;
    Context     context;
    DepthGenerator  depth_generator;
    ImageGenerator  image_generator;
};
复制代码

 

main.cpp:

复制代码
#include <QCoreApplication>

#include "opencv2/highgui/highgui.hpp"
#include "opencv2/imgproc/imgproc.hpp"
#include <opencv2/core/core.hpp>
#include "copenni.cpp"

#include <iostream>

using namespace cv;
using namespace xn;

int main (int argc, char **argv)
{
    COpenNI openni;
    if(!openni.Initial())
        return 1;

    namedWindow("color image", CV_WINDOW_AUTOSIZE);
    namedWindow("color edge detect", CV_WINDOW_AUTOSIZE);
    namedWindow("depth image", CV_WINDOW_AUTOSIZE);
    namedWindow("depth edge detect", 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);
        imshow("color image", color_image);

        /*对色彩图像进行canny边缘检测并显示*/
        Mat color_image_gray, color_image_edge;
        cvtColor(color_image_src, color_image_gray, CV_RGB2GRAY);//因为在进行边缘检测的时候只能使用灰度图像
        Canny(color_image_gray, color_image_edge, 5, 100);
        imshow("color edge detect", color_image_edge);

        /*获取并显示深度图像*/
        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_edge;
        depth_image_src.convertTo(depth_image, CV_8U, 255.0/8000);
        imshow("depth image", depth_image);

        /*计算深度图像的canny边缘并显示*/
        Canny(depth_image, depth_image_edge, 5, 100);
        imshow("depth edge detect", depth_image_edge);
        waitKey(30);

    }

}
复制代码

 

 

总结:本实验将OpenNI驱动的Kinect数据转换成OpenCV中方便处理的格式,达到了将OpenNI和OpenCV两者相结合的目的。

 

 

参考资料:

Kinect+OpenNI学习笔记之3(获取kinect的数据并在Qt中显示的类的设计)

opencv2.3读取kinect深度信息和彩色图像

Kinect + OpenNI 的深度值

OpenNI + OpenCV

 

 

附录: 实验工程code下载

 

 

 

作者:tornadomeet 出处:http://www.cnblogs.com/tornadomeet 欢迎转载或分享,但请务必声明文章出处。

[转载]Kinect+OpenNI学习笔记之3(获取kinect的数据并在Qt中显示的类的设计) - tornadomeet - 博客园

mikel阅读(774)

[转载]Kinect+OpenNI学习笔记之3(获取kinect的数据并在Qt中显示的类的设计) – tornadomeet – 博客园.

 

前言

在上一篇文章Kinect+OpenNI学习笔记之2(获取kinect的颜色图像和深度图像) 中, 已经介绍了怎样使用OpenNI来获取Kinect的深度数据和颜色数据,并将获取到的结果在Qt中显示,不过那个代码是写在同一个cpp文件中,以后用 到的时候不能讲这些显示的基本过程单独拿出来,比较麻烦。所以这节主要是将OpenNI获取图像的流程以及Qt显示这些图像的结果分开为了2个类来写,方 便以后工程的直接拷贝。

开发环境:QtCreator2.5.1+OpenNI1.5.4.0+Qt4.8.2

 

实验说明

COpenNI这个类主要是初始化kinect设备,并获取深度图像和颜色图像,参加上一篇博客的初始化过程步骤,如果遇到错误,则有相应的错 误处理过程。CKinectReader类是将COpenNI这个类读取到的结果显示在Qt的界面上的。因此一个类是负责与硬件Kinect打交道,一个 类是负责与人(界面显示)打交道的。具体的过程见上篇文章的分析和后面的代码。

这里发现一个小问题,与kinect有关的工程如果改变了代码,则在每次编译前最好clean一下,因为有可能是与硬件设备相关,没有clean的工程和clean后的工程效果有时会不同。

 

C/C++知识点总结:

在构造函数中可以使用冒号给类中的数据成员赋值,这样的好处就是可以给常量和引用变量赋值初始化赋值的效果。

类的私有成员只能是类内部的函数调用,连类的对象都不能去调用私有成员变量。

在类的内部使用qDebug(), cout等函数输出调试时是不行的。

隐式数据类型转换,如果是同种类型的数据进行四则运算,则得出的结果也是那种类型,如果其中有常数类型的数据常数参与,则得出的结果会自动转换成跟常数类型相同的类型。

如果一个类以单独一个cpp文件出现,在使用到该类的时候,直接include该cpp文件.

 

实验结果

在程序中设置了镜像和视觉校正,且将kinect感应不到深度信息的地方全部显示为不透明的黑色,因此你在图中看到的黑色部分就是kinect的深度盲区。

效果如下:

 

 

实验主要部分代码及注释(附录有工程code下载链接):

copenni.cpp:

复制代码
#include <XnCppWrapper.h>
#include <QtGui>
#include <iostream>

using namespace xn;
using namespace std;

class COpenNI
{
public:
    ~COpenNI() {
        context.Release();//释放空间
    }
    bool Initial() {
        //初始化
        status = context.Init();
        if(CheckError("Context initial failed!")) {
            return false;
        }
        context.SetGlobalMirror(true);//设置镜像
        //产生图片node
        status = image_generator.Create(context);
        if(CheckError("Create image generator  error!")) {
            return false;
        }
        //产生深度node
        status = depth_generator.Create(context);
        if(CheckError("Create depth 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;
        }

        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;
    }

public:
    DepthMetaData depth_metadata;
    ImageMetaData image_metadata;

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;
    }

private:
    XnStatus    status;
    Context     context;
    DepthGenerator  depth_generator;
    ImageGenerator  image_generator;
};
复制代码

 

ckinectreader.cpp:

复制代码
#include <QtGui>
#include <QDebug>
#include <XnCppWrapper.h>
#include "copenni.cpp"  //要包含cpp文件,不能直接包含类
#include <iostream>

using namespace std;

class CKinectReader: public QObject
{
public:
    //构造函数,用构造函数中的变量给类的私有成员赋值
    CKinectReader(COpenNI &openni, QGraphicsScene &scene) : openni(openni), scene(scene) {
        test = 0.0;
    }
    ~CKinectReader() {
        scene.removeItem(image_item);
        scene.removeItem(depth_item);
        delete [] p_depth_argb;
    }
    bool Start(int interval = 33) {
        openni.Start();//因为在调用CKinectReader这个类的之前会初始化好的,所以这里直接调用Start了
        image_item = scene.addPixmap(QPixmap());
        image_item->setZValue(1);
        depth_item = scene.addPixmap(QPixmap());
        depth_item->setZValue(2);
        openni.UpdateData();
        p_depth_argb = new uchar[4*openni.depth_metadata.XRes()*openni.depth_metadata.YRes()];
        startTimer(interval);//这里是继承QObject类,因此可以调用该函数
        return true;
    }
    float test ;
private:
    COpenNI &openni;    //定义引用同时没有初始化,因为在构造函数的时候用冒号来初始化
    QGraphicsScene &scene;
    QGraphicsPixmapItem *image_item;
    QGraphicsPixmapItem *depth_item;
    uchar *p_depth_argb;

private:
    void timerEvent(QTimerEvent *) {

        openni.UpdateData();
        //这里使用const,是因为右边的函数返回的值就是const类型的
        const XnDepthPixel *p_depth_pixpel = openni.depth_metadata.Data();
        unsigned int size = openni.depth_metadata.XRes()*openni.depth_metadata.YRes();

        //找深度最大值点
        XnDepthPixel max_depth = *p_depth_pixpel;
        for(unsigned int i = 1; i < size; ++i)
            if(p_depth_pixpel[i] > max_depth )
                max_depth = p_depth_pixpel[i];
        test = max_depth;

        //将深度图像格式归一化到0~255
        int idx = 0;
        for(unsigned int i = 1; i < size; ++i) {
            //一定要使用1.0f相乘,转换成float类型,否则该工程的结果会有错误,因为这个要么是0,要么是1,0的概率要大很多
            float fscale = 1.0f*(*p_depth_pixpel)/max_depth;
            if((*p_depth_pixpel) != 0) {
                p_depth_argb[idx++] = 255*(1-fscale);    //蓝色分量
                p_depth_argb[idx++] = 0; //绿色分量
                p_depth_argb[idx++] = 255*fscale;   //红色分量,越远越红
                p_depth_argb[idx++] = 255*(1-fscale); //距离越近,越不透明
            }
            else {
                p_depth_argb[idx++] = 0;
                p_depth_argb[idx++] = 0;
                p_depth_argb[idx++] = 0;
                p_depth_argb[idx++] = 255;
            }
            ++p_depth_pixpel;//此处的++p_depth_pixpel和p_depth_pixpel++是一样的
        }
        //往item中设置图像色彩数据
        image_item->setPixmap(QPixmap::fromImage(
                              QImage(openni.image_metadata.Data(), openni.image_metadata.XRes(), openni.image_metadata.YRes(),
                              QImage::Format_RGB888)));
        //往item中设置深度数据
        depth_item->setPixmap(QPixmap::fromImage(
                              QImage(p_depth_argb, openni.depth_metadata.XRes(), openni.depth_metadata.YRes()
                              , QImage::Format_ARGB32)));
    }
};
复制代码

 

main.cpp:

复制代码
#include <QtGui/QtGui>
#include <QDebug>
#include "ckinectreader.cpp"

int main(int argc, char **argv)
{
    COpenNI openni;
    if(!openni.Initial())//初始化返回1表示初始化成功
        return 1;

    QApplication app(argc, argv);

    QGraphicsScene scene;
    QGraphicsView view;
    view.setScene(&scene);
    view.resize(650, 540);
    view.show();

    CKinectReader kinect_reader(openni, scene);
    kinect_reader.Start();//启动,读取数据
    qDebug() << kinect_reader.test;
    return app.exec();
}
复制代码

 

 

总结:这次实验的目的主要是将相互稍微独立的代码用单独的类来写,方便以后的代码重复利用。

 

 

  参考资料:http://kheresy.wordpress.com/2011/08/18/show_maps_of_openni_via_qt_graphicsview/

 

 

附录:实验工程code下载

 

 

 

作者:tornadomeet 出处:http://www.cnblogs.com/tornadomeet 欢迎转载或分享,但请务必声明文章出处。

[转载]Kinect+OpenNI学习笔记之2(获取kinect的颜色图像和深度图像) - tornadomeet - 博客园

mikel阅读(864)

[转载]Kinect+OpenNI学习笔记之2(获取kinect的颜色图像和深度图像) – tornadomeet – 博客园.

前言

网上有不少使用Qt做界面,OpenNI为库来开发kinect。或许大家的第一个问题就是询问该怎样使用Kinect来获取颜色信息图和深度信息图呢?这一节就是简单来回答这个问题的。

开发环境:QtCreator2.5.1+OpenNI1.5.4.0+Qt4.8.2

 

实验说明:

在使用OpenNI来驱动读取kinect数据时,我们需要了解context object这个名词。查看了下OpenNI UserGuide文档,简单翻译下这个名词的意思:

Context是openNI中一个主要的object,它掌握了OpenNI使用过程中应用程序的全部状态,以及这些状态的 prodection chains,一个应用程序有多个context,但是这些context之间不能共享信息。例如一个中间件节点不能使用另一个context的驱动节 点。Context在使用前必须被立即初始化,因此此时所有嵌入的模块被下载和分析。为了释放context的内存,应用程序需调用shutdown程 序。

虽然翻译得不准确,但是它的大概意思就是告诉我们在驱动kinect时,需要用到context这个类,且我们需要安装一定顺序去使用,这与一 些常见的库驱动差不多,比如opengl,这些都需要什么初始化啊,设置属性啊等。因此我们只需要直接去看懂他人的一个工程实例就ok了。

好了,本文参考Heresy的教程中的源码写的。

在新建好工程文件后,需要包含XnCppWrapper头文件,且需在Qt工程中设置好头文件目录和库文件目录。

使用OpenNI读取颜色图和深度图的步骤如下(这个是程序的核心部分):

1. 定义一个Context对象,并 调用该对象的Init()方法来进行初始化。

2. 定义一个XnMapOutputMode格式对象,设置好分图像分辨率和帧率。

3. 定义颜色图和深度图的节点对象,并用其Create()方法来创建,参数为Context对象.

4. 设置颜色和深度图的输出模式,调用的方法是SetMapOutputMode();参数为步骤2中定义和设置好了的XnMapOutputMode对象。

6. 如果深度图和颜色图在一张图上显示,则必须对深度图像进行校正,校正的方法是调用深度图的如下方法:.GetAlternativeViewPointCap().SetViewPoint();

7. 调用context对象的StartGeneratingAll()来开启设备读取数据开关。

8. 调用context对象的更新数据方法,比如WaitAndupdateAll()方法。

9. 定义颜色图和色彩图的ImageMetaData对象,并利用对应的节点对象的方法GetMetaData(),将获取到的数据保存到对应的ImageMetaData对象中。

10. 如果需要将深度图转换成灰度图来显示,则需要自己将深度值转换成0~255的单通道或者多通道数据,然后直接用来显示。

注意如果没有设置视觉校正,则深度图的显示与颜色图的显示会出现对应不上的情况,后面的实验可以看出这2者的区别,另外对于是否需要设置镜像就要看自己的具体应用场合了。

 

实验结果:

下面分别分是否设置图像镜像,是否对深度图像进行校正来给出实验结果.

无镜像无校正:

 

无镜像有校正:

 

有镜像无校正:

 

有镜像有校正:

 

从有无镜像可以看出,设置镜像的效果与字面的理解是一样的,即有镜像时就相当于取镜子中的图像。有无校正可以看出,没有校正时,深度图片和颜色 图片同一个物体都对应不起来,可以看下天花板上的吊灯就可以发现,没校正,2者不重合,且相差不少。有校正时效果就好多了,只是此时的深度图像显示的范围 要稍小些。

 

实验主要部分代码及注释(附录有工程code下载链接):

首先来个最小工程,即去掉那些错误处理代码:

  main.cpp:

复制代码
#include <QtGui>
#include <XnCppWrapper.h> //包含OpenNI的头文件

using namespace xn;//使用OpenNI库中的命名空间

//全局的OpenNI object
Context g_context;
ImageGenerator g_image_generator;
DepthGenerator g_depth_generator;

//全局的Qt Object
QGraphicsPixmapItem *g_image_map;
QGraphicsPixmapItem *g_depth_map;

//CTimer类的定义
class CTimer : public QObject
{
public:
    void start() {        
        g_context.StartGeneratingAll();//开启设备读取数据的开关
        startTimer(33);//使用startTimer()启动定时器,每当时间到时会自动调用timerEvent()函数
    }
private:
    void timerEvent(QTimerEvent *) {
        g_context.WaitAndUpdateAll();//更新数据

        //颜色数据
        ImageMetaData image_map;
        g_image_generator.GetMetaData(image_map);
        //为g_image_map设置图片,图片的数据来源于外部硬件设备
        g_image_map->setPixmap(QPixmap::fromImage(QImage(image_map.Data(), image_map.XRes(),
                                                         image_map.YRes(), QImage::Format_RGB888)));
        //深度数据
        DepthMetaData depth_map;
        g_depth_generator.GetMetaData(depth_map);
        XnDepthPixel max_depth_value = depth_map.ZRes();
        QImage depth_img(depth_map.XRes(), depth_map.YRes(), QImage::Format_ARGB32);//格式为ARGB32型的
        for(unsigned int i = 0; i < depth_map.XRes(); i++)
            for(unsigned int j = 0; j < depth_map.YRes(); j++)
            {
                XnDepthPixel depth_value_ij = depth_map(i, j);//获取x,y处的坐标值
                if(depth_value_ij == 0) {
                    depth_img.setPixel(i, j, qRgba(0, 0, 0, 0));
                }//如果捕捉不到深度信息,则将其设置为0
                else {
                    float fscale = 1.0f*depth_value_ij/max_depth_value;//当前深度的比例因子
                    depth_img.setPixel(i, j, qRgba(255*(1-fscale), 0, 255*fscale, 255*(1-fscale)));
                }
            }
        g_depth_map->setPixmap(QPixmap::fromImage(depth_img));
    }
};

int  main(int argc, char **argv)
{
    QApplication app(argc, argv);

    g_context.Init();//context初始化
    g_context.SetGlobalMirror(true);//设置全局镜像,就像照镜子一样,与设置为false时的2张图片镜像
    XnMapOutputMode xmode;//定义图像的输出模式
    xmode.nXRes = 640;//x方向分辨率
    xmode.nYRes = 480;//y方向分辨率
    xmode.nFPS = 30;//帧率
    //设置颜色节点属性
    g_image_generator.Create(g_context);
    g_image_generator.SetMapOutputMode(xmode);
    //设置深度节点属性
    g_depth_generator.Create(g_context);
    g_depth_generator.SetMapOutputMode(xmode);

    //视觉校正,否则深度图和颜色图感应到的区域不能一一对应
    g_depth_generator.GetAlternativeViewPointCap().SetViewPoint(g_image_generator);

    //Qt场景设置
    QGraphicsScene scene;
    g_image_map = scene.addPixmap(QPixmap());
    g_image_map->setZValue(1);//设置为z方向上的第1层
    g_depth_map = scene.addPixmap(QPixmap());
    g_depth_map->setZValue(2);//设置为z方向上的第2层

    //Qt视图创建
    QGraphicsView view(&scene);
    view.resize(660, 500);

    //设置定时器,每隔一段时间读取kinect的颜色信息和深度信息
    CTimer timer;
    timer.start();
    view.show();

    return app.exec();
}
复制代码

 

  加入错误处理部分后的完整main.cpp:

复制代码
#include <QtGui>
#include <XnCppWrapper.h> //包含OpenNI的头文件

using namespace xn;//使用OpenNI库中的命名空间

//全局的OpenNI object
XnStatus g_status;
Context g_context;
ImageGenerator g_image_generator;
DepthGenerator g_depth_generator;
bool g_has_image_generator = true;

//全局的Qt Object
QGraphicsPixmapItem *g_image_map;
QGraphicsPixmapItem *g_depth_map;

//CTimer类的定义
class CTimer : public QObject
{
public:
    void start() {
        g_status = g_context.StartGeneratingAll();//开启设备读取数据的开关
        if(g_status == XN_STATUS_OK) {
            startTimer(33);//使用startTimer()启动定时器,每当时间到时会自动调用timerEvent()函数
        }
        else {
            QMessageBox::critical(NULL, "Create Data Error!", xnGetStatusString(g_status));//显示创建数据失败,该消息框没有父窗口
        }
    }
private:
    void timerEvent(QTimerEvent *) {
        g_context.WaitAndUpdateAll();//更新数据

        //颜色数据
        if(g_has_image_generator) {
            ImageMetaData image_map;
            g_image_generator.GetMetaData(image_map);
            //为g_image_map设置图片,图片的数据来源于外部硬件设备
            g_image_map->setPixmap(QPixmap::fromImage(QImage(image_map.Data(), image_map.XRes(),
                                                         image_map.YRes(), QImage::Format_RGB888)));
        }
        //深度数据
        DepthMetaData depth_map;
        g_depth_generator.GetMetaData(depth_map);
        XnDepthPixel max_depth_value = depth_map.ZRes();
        QImage depth_img(depth_map.XRes(), depth_map.YRes(), QImage::Format_ARGB32);//格式为ARGB32型的
        for(unsigned int i = 0; i < depth_map.XRes(); i++)
            for(unsigned int j = 0; j < depth_map.YRes(); j++)
            {
                XnDepthPixel depth_value_ij = depth_map(i, j);//获取x,y处的坐标值
                if(depth_value_ij == 0) {
                    depth_img.setPixel(i, j, qRgba(0, 0, 0, 0));
                }//如果捕捉不到深度信息,则将其设置为0
                else {
                    float fscale = 1.0f*depth_value_ij/max_depth_value;//当前深度的比例因子
                    depth_img.setPixel(i, j, qRgba(255*(1-fscale), 0, 255*fscale, 255*(1-fscale)));
                }
            }
        g_depth_map->setPixmap(QPixmap::fromImage(depth_img));
    }
};

int  main(int argc, char **argv)
{
    QApplication app(argc, argv);

    g_status = g_context.Init();//context初始化
    if(g_status != XN_STATUS_OK) {
        QMessageBox::critical(NULL, "Context Initial Error!", xnGetStatusString(g_status));
        return -1;
    }
   // g_context.SetGlobalMirror(true);//设置全局镜像,就像照镜子一样,与设置为false时的2张图片镜像
    XnMapOutputMode xmode;//定义图像的输出模式
    xmode.nXRes = 640;//x方向分辨率
    xmode.nYRes = 480;//y方向分辨率
    xmode.nFPS = 30;//帧率
    //设置颜色节点属性
    g_status = g_image_generator.Create(g_context);
    if(g_status != XN_STATUS_OK) {
        QMessageBox::critical(NULL, "Image map create failed", xnGetStatusString(g_status));
        g_has_image_generator = false;
    }
    if( g_has_image_generator ) {
        g_status = g_image_generator.SetMapOutputMode(xmode);
        if(g_status != XN_STATUS_OK) {
            QMessageBox::critical(NULL, "Image map output mode error!", xnGetStatusString(g_status));
            return -1;
        }
    }
    //设置深度节点属性
    g_status = g_depth_generator.Create(g_context);
    if(g_status != XN_STATUS_OK) {
        QMessageBox::critical(NULL, "Depth map create failed", xnGetStatusString(g_status));
        return -1;
    }
    g_status = g_depth_generator.SetMapOutputMode(xmode);
    if(g_status != XN_STATUS_OK) {
        QMessageBox::critical(NULL, "Depth map output mode error!", xnGetStatusString(g_status));
        return -1;
    }

    if(g_has_image_generator)//视觉校正,否则深度图和颜色图感应到的区域不能一一对应
        ;//g_depth_generator.GetAlternativeViewPointCap().SetViewPoint(g_image_generator);
    //Qt场景设置
    QGraphicsScene scene;
    g_image_map = scene.addPixmap(QPixmap());
    g_image_map->setZValue(1);//设置为z方向上的第1层
    g_depth_map = scene.addPixmap(QPixmap());
    g_depth_map->setZValue(2);//设置为z方向上的第2层

    //Qt视图创建
    QGraphicsView view(&scene);
    view.resize(660, 500);

    //设置定时器,每隔一段时间读取kinect的颜色信息和深度信息
    CTimer timer;
    timer.start();
    view.show();

    return app.exec();
}
复制代码

 

 

总结:通过本次实验,了解了怎样使用OpenNI来显示kinect的颜色图像和深度图像了。

 

 

参考资料:http://kheresy.wordpress.com/index_of_openni_and_kinect/comment-page-5/

 

 

附录:实验工程code下载

 

 

 

作者:tornadomeet 出处:http://www.cnblogs.com/tornadomeet 欢迎转载或分享,但请务必声明文章出处。

[转载]Kinect+OpenNI学习笔记之1(开发环境的建立) - tornadomeet - 博客园

mikel阅读(1179)

[转载]Kinect+OpenNI学习笔记之1(开发环境的建立) – tornadomeet – 博客园.

前言

一般的普通摄像机获取的是RGB彩色图像,在计算机视觉领域,很大一部分都是基于颜色图像来做处理的,取得了不少研究成果。最近几年的深度图研 究给计算机视觉和图像处理带来了更多的信息,因为所获取的图像多了一个维度的,通常也称之为RGBD图。比较物美价廉的深度信息获取传感器有MS的 Kinect,同时还有ASUS的Xtion。这2种传感器都能较为准确的获得一定范围内物体的深度信息。比如说Kinect的参数信息图如下:

要想在视觉上达到AI的水平还有很长的路要走,深度信息的提供无疑给视觉处理多带来了一份希望,多了深度这一维的信息肯定是有用的,所以本人打算以后结合色彩信息和深度信息来做人体行为识别方面的研究。

废话不说,实验室有Kinect设备,因此需要搭建Kinect的开发环境,这里开发Kinect的驱动不是用的MS的SDK,而是采用开源的OpenNI(个人比较喜欢开源的东西)。

环境的搭建可以参考著名网友Heresy的系列文章,其中关于安装环境的搭建有一篇为: 2012 OpenNI环境/Kinect安装教学 。该文章中写的非常详细,赞!

  Heresy的文章列表为:http://kheresy.wordpress.com/index_of_openni_and_kinect/comment-page-5/

   安装步骤

     另外这里是我个人的WinXP+Kinect+OpenNI+NITE+Qt开发环境搭建过程:

     1. 采用OpenNI开发套件。OpenNI分为3个组件,OpenNI的本身框架,算法分析的中间件,以及硬件模块组。下载OpenNI的官网为:http://openni.org/Downloads/OpenNIModules.aspx

根据自己电脑的类型和步骤2的SensorKinect版本,选择那3个下拉列表,出现文件后下载即可。我这里选的是OpenNI Binarys+Unstable和32位的开发版本。其中OpenNI文件名为:

openni-win32-1.5.4.0-dev.msi, NITE文件名为:nite-win32-1.5.2.21-dev.msi,Sensor文件名为:sensor-win32-5.1.2.1- redist.msi。依次安装OpenNI,NITE,Sensor这3个文件。

     2. 安装SensorKinect,这个为Kinect驱动的破解版,因为OpenNI的驱动只是针对ASUS的,这个破解版是网友自己弄的,下载网站为:https://github.com/avin2/SensorKinect/tree/unstable/Bin

我选的版本名为:SensorKinect093-Bin-Win32-v5.1.2.1.msi

     非常需要注意的几点是:

     1. 安装过程中把各种杀毒软件都关掉,比如360那些玩意。

     2. 安装过程中不要将Kinect连接到电脑上。

     3. 上面的安装步骤顺序不要乱,且步骤1和步骤2中的文件版本一定要对应起来,其版本对应的说明在sensorkinect下载网https://github.com/avin2/SensorKinect的readme文件中有。

     安装成功后,插上kinect到电脑,开启电源,在电脑的设备管理器中看是否能够识别kinect的3个驱动,如果没有成功,手动更新驱动程序到指定的安装目录。

     驱动安装成功后的设备管理器处会显示如下:

  

     如果上面的步骤都完成,那么可以打开openNI安装目录下的samples子目录,进入bin目录下的Debug或者release,连接好 kinect,双击其中的一个sample,看能否运行,我这里是双击的UserTracker.net.exe,为人体骨骼提取,图像为深度图,其运行 结果如下所示:

  

 

 

安装总结

     但如果是用Qt来开发Kinect的话,则OpenNI的安装目录不能采用默认的目录,因为默认的目录中会含有Program Files中间有空格。所以在安装步骤1和2共4个文件时,其目录最好都安装在非中文非空格下面。如果不小心安装在默认路径下,卸载后又重新安装 OpenNI及其组件,很容易识别,一般是安装每个文件到最后,然后进度条又回去,且会提示比如:Primesense-NTE1.5.2.21for windows setup wizard ended prematurely because of an error.Your system has not been modified.To install this program at a later time,run Setup Wizard again,Click the finish button to exit the Setup Wizard.即显示如下:

  

  这是因为前面安装的注册表信息没有clean干净,一般的非专业的清理工具是弄不干净的,比如说360安全卫士什么的。后面我也在网上尝试了好多种注册表清理工具都不行。到最后找到了一个专业的小工具:Eusing Free Registry Cleaner,下载完清理一遍再安装,一步成功,很感谢这个注册表清理小工具!

     另外,在用Qt开发时,需要在.pro工程文件添加如下关于OpenNI头文件和库文件:

INCLUDEPATH += C:\Qt\OpenNI\Include

LIBS += C:\Qt\OpenNI\Lib\NiSampleExtensionModule.lib C:\Qt\OpenNI\Lib\NiSampleModule.lib \
        C:\Qt\OpenNI\Lib\OpenNI.jni.lib C:\Qt\OpenNI\Lib\openNI.lib

  此处我的OpenNI和Qt安装在一个目录,都放在c盘。当然,如果使用了OpenCVS库,也同样需要添加。

     到此为止,WinXP+Kinect+OpenNI+NITE+Qt+OpenCV的开发环境就建立好了,祝大家好运!

  参考资料

   下面是这2天找到的一些比较好的Kinect相关资料:

  Heresy系列文章:

     http://kheresy.wordpress.com/index_of_openni_and_kinect/comment-page-5/

     kinect和openNI学习资料汇总

     OpenCV 计算机视觉 kinect的博客:

     http://blog.csdn.net/moc062066/article/category/871261

      2011微软亚洲研究院Kinect for Windows研讨会

     http://www.msra.cn/Articles/ArticleItem.aspx?Guid=f3dfa208-bc88-4d8b-82a8-63e597315cf8

     Kinect介绍入门视频教程:

     http://www.youtube.com/watch?v=yQg4yhCL1xI

     OpenNI官网:

     http://openni.org/

     OpenNI安装教程:

     http://viml.nchc.org.tw/blog/paper_info.php?CLASS_ID=1&SUB_ID=1&PAPER_ID=346

作者:tornadomeet 出处:http://www.cnblogs.com/tornadomeet 欢迎转载或分享,但请务必声明文章出处。

[转载]Kinect 体感应用开发相关资源汇总 - 行业应用 - ITeye资讯

mikel阅读(808)

[转载]Kinect 体感应用开发相关资源汇总 – 行业应用 – ITeye资讯.

Kinect是微软Xbox 360的体感周边外设,它是一种3D体感摄影机,具备即时动态捕捉、影像辨识、麦克风输入、语音辨识、社群互动等功能。玩家可以通过该外设在游戏中开车、与其他玩家互动,以及通过互联网与其他Xbox玩家分享图片和信息等。

 

Kinect一经推出,立即受到了行业的追捧。今年6月和11月,微软相继发布了 Kinect for Windows SDK 

BetaBeta 2,以帮助开发者开发Kinect相关的应用。

 

微软称,开发Kinect应用本质上和开发其他Windows应用一样,不同的是该SDK支持Kinect感应器的相关功能,比如彩色图像、深度图像、音频、骨骼动画数据等。” 

本文整理了Kinect应用开发的相关资源,如果你正在进行Kinect开发或打算进行Kinect开发,这将对你有很大的帮助。

文件/工具/ 示例

文档

视频

项目

注:上述资源都是英文的,但如果想从事Kinect相关开发,绝对值得一看。

Via dzone

[转载]Kinect for Windows SDK开发初体验(三)骨骼追踪 - 马宁 - 博客园

mikel阅读(760)

[转载]Kinect for Windows SDK开发初体验(三)骨骼追踪 – 马宁 – 博客园.

作者:马宁

我们的Kinect SDK开发开始渐入佳境了,Skeleton Tracking(骨骼追踪)是Kinect的核心技术,正因为有了这项技术,很多有趣的功能才得以实现。

首先,我们来看一下骨骼追踪的具体实现。Kinect最多可以追踪20个骨骼点,而且目前只能追踪人体,其他的物体或者动物就无能为力了。下图介绍了Kinect骨骼点的分布情况:

7

初始化代码

接 下来,我们来看一下骨骼追踪的代码是如何编写的。首先,我们要创建一个新的Visual C#的工程,叫做“SkeletonTracking”,添加Kinect程序集和Coding4Fun程序集的工作,可以参考上一篇“Kinect for Windows SDK开发初体验(二)操作Camera”的内容,在这里不再重复。

首先我们还是创建Runtime对象,在初始化时,指定UseSkeletalTracking的RuntimeOptions,然后在SkeletonFrameReady事件中添加处理函数。

Runtime nui; 

private void Window_Loaded(object sender, RoutedEventArgs e)

{

nui = new Runtime();

nui.Initialize(RuntimeOptions.UseSkeletalTracking); 

nui.SkeletonFrameReady += new EventHandler<SkeletonFrameReadyEventArgs>(nui_SkeletonFrameReady);

}

接下来,窗体关闭时的事件处理函数:

private void Window_Closed(object sender, EventArgs e)

{

nui.Uninitialize();

}

如果这个时候,我们在空的nui_SkeletonFrameReady事件处理函数中,添加一个断点:

void nui_SkeletonFrameReady(object sender, SkeletonFrameReadyEventArgs e) 

{

}

正确连接Kinect设备,并且站在Kinect前,让Kinect能够正确识别人体时,SkeletonFrameReady事件将被触发。

我们可以通过下图看到返回的事件处理参数,其中比较重要的是SkeletonFrame和Skeletons两个对象。

image[11]

添加代码

接下来,我们要准备WPF的界面,在界面上添加五个小球,来跟踪头部、双手和膝盖的位置。在MainPage.xaml中,添加下列代码:

<Canvas Name="MainCanvas">
<Ellipse Canvas.Left="0" Canvas.Top="0" Height="50" Name="headEllipse" Stroke="Black" Width="50" Fill="Orange" />
<Ellipse Canvas.Left="50" Canvas.Top="0" Height="50" Name="rightEllipse" Stroke="Black" Width="50" Fill="SlateGray" />
<Ellipse Canvas.Left="100" Canvas.Top="0" Fill="SpringGreen" Height="50" Name="leftEllipse" Stroke="Black" Width="50" />
<Ellipse Canvas.Left="150" Canvas.Top="0" Height="50" Name="KneeRightEllipse" Stroke="Black" Width="50" Fill="Salmon" />
<Ellipse Canvas.Left="200" Canvas.Top="0" Fill="Navy" Height="50" Name="KneeLeftEllipse" Stroke="Black" Width="50" />
</Canvas>

然后,我们在SkeletonFrameReady事件处理函数中添加捕捉SkeletonData的方法:

void nui_SkeletonFrameReady(object sender, SkeletonFrameReadyEventArgs e) 

{

SkeletonFrame allSkeletons = e.SkeletonFrame;

//get the first tracked skeleton

SkeletonData skeleton = (from s in allSkeletons.Skeletons

where s.TrackingState == SkeletonTrackingState.Tracked

select s).FirstOrDefault();

}

我们使用了LINQ来获取TrackingState等于Tracked的SkeletonData数据。在SkeletonData对象的 Joints属性集合中保存了所有骨骼点的信息。每个骨骼点的信息都是一个Joint对象,其中的Position的X、Y、Z表示了三维位置。其中X和 Y的范围都是-1到1,而Z是Kinect到识别物体的距离。

我们可以用下面的代码,将Joint的位置缩放到合适的比例:

Joint j = skeleton.Joints[JointID.HandRight].ScaleTo(640, 480, .5f, .5f);

最后两个参数为原始大小的最大值和最小值,上面的语句相当于将-0.5到0.5的范围扩大为0到640的范围。

我们封装了一个函数,将获取到的SkeletonData数据,转换为屏幕上的某一个圆圈:

private void SetEllipsePosition(FrameworkElement ellipse, Joint joint) 

{ 

var scaledJoint = joint.ScaleTo(640, 480, .5f, .5f);

Canvas.SetLeft(ellipse, scaledJoint.Position.X);

Canvas.SetTop(ellipse, scaledJoint.Position.Y); 

}

最后,我们SkeletonFrameReady事件的处理方法会是这样的:

void nui_SkeletonFrameReady(object sender, SkeletonFrameReadyEventArgs e) 

{

SkeletonFrame allSkeletons = e.SkeletonFrame;

//get the first tracked skeleton

SkeletonData skeleton = (from s in allSkeletons.Skeletons

where s.TrackingState == SkeletonTrackingState.Tracked

select s).FirstOrDefault();

SetEllipsePosition(headEllipse, skeleton.Joints[JointID.Head]); 

SetEllipsePosition(leftEllipse, skeleton.Joints[JointID.HandLeft]); 

SetEllipsePosition(rightEllipse, skeleton.Joints[JointID.HandRight]);

SetEllipsePosition(KneeLeftEllipse, skeleton.Joints[JointID.KneeLeft]);

SetEllipsePosition(KneeRightEllipse, skeleton.Joints[JointID.KneeRight]);

}

最后,程序运行的效果如下,貌似膝盖的识别还是有些问题:

30

程序运行时,我们会发现小球运动时会有跳动的问题,为了减少这种情况,我们要设置SkeletonEngine引擎的 TransformSmooth属性为true,并指定TransformSmoothParameters参数,根据应用的具体情况,该参数也应该被适 当微调。

添加代码后的Load函数,代码如下:

private void Window_Loaded(object sender, RoutedEventArgs e)

{

nui = new Runtime();

nui.Initialize(RuntimeOptions.UseSkeletalTracking); 

//Must set to true and set after call to Initialize

nui.SkeletonEngine.TransformSmooth = true;

//Use to transform and reduce jitter

var parameters = new TransformSmoothParameters

{

Smoothing = 0.75f,

Correction = 0.0f,

Prediction = 0.0f,

JitterRadius = 0.05f,

MaxDeviationRadius = 0.04f

};

nui.SkeletonEngine.SmoothParameters = parameters;

nui.SkeletonFrameReady += new EventHandler<SkeletonFrameReadyEventArgs>(nui_SkeletonFrameReady);

}

写到最后

到这里, Kinect最精华的部分“骨骼追踪”已经介绍给大家了,大家可以去写一些有趣的应用了。接下来,我们会介绍另外一个Kinect的核心功能——Depth Data,景深数据。

 

OpenXLive杯Windows Phone游戏开发大赛

clip_image019

OpenXLive杯Windows Phone游戏开发大赛,是由OpenXLive联合国内知名的开发者社区:DevDiv、智机网、WPMind、Silverlight银光中国和XNA游戏世界,一起举办的针对Windows Phone游戏开发的比赛。

http://www.openxlive.net/posts/news/40

[转载]Kinect for Windows SDK开发初体验(二)操作Camera - 马宁 - 博客园

mikel阅读(798)

[转载]Kinect for Windows SDK开发初体验(二)操作Camera – 马宁 – 博客园.

作者:马宁

Kinect SDK出来之后,不到24小时,很多Geek们已经将自己的示例发布到网上去了。可见,好东西肯定会被大家认可的,不好的东西投入再多的宣传也没用。

这一篇我们就要正式进入Kinect的编程世界了,介绍我们如何从Camera获取图像信息。先来介绍一下Kinect的整体结构,省得大家在后边的介绍中被某些名词弄晕。

image_thumb[3]

Kinect 一共有三个Camera,其中中间的一个是RGB Camera,用来获取640×480的彩色图像,每秒钟最多获取30帧图像;两侧是两个景深(3D Depth)传感器,用来检测玩家的相对位置,原理和人眼立体成像是一样的,不过这两个传感器使用的是红外线,所以说奥巴马玩不了Kinect的人一定是 居心叵测。Kinect两侧是麦克风,下边还有一个可移动底座,用来调整Kinect的仰角。

Kinect开发环境

今天我们主要是操作RGB Camera和Depth Sensor,首先,我们要完成Kinect开发环境的配置:

第一步,创建WPF工程

打开Visual Studio 2010,创建一个WPF工程,名叫KinectWpfDemo:

21

当然,由于Kinect SDK中包含基于.NET的程序集,除了WPF外,我们使用.NET WinForm或XNA框架都可以,目前还没有人在Silverlight平台上实验成功。

第二步,添加Kinect程序集的引用

在Solution Explorer中,右键单击KinectWpfDemo,在右键菜单中选择“Add Reference…”。在弹出的对话框中,我们在.NET标签页里,选择“Microsoft.Research.Kinect”程序集。如下图所示:

22

第三步,添加Coding4Fun Kinect Toolkit

这是一个可选项,不过为了之后的编程方便,建议大家添加一个。Coding4Fun Kinect Toolkit的下载地址:

http://c4fkinect.codeplex.com/

解压缩后,一共有五个文件,针对WinForm、WPF平台,还有一个Microsoft.Expression.Drawing.dll。我们通过Add Reference,将Coding4Fun.Kinect.Wpf.dll添加进来。

23

获取RGB Camera数据

第四步,添加控件

双击打开MainWindow.xaml,在设计器中添加两个Image控件,一个用于显示RGB图像,另一个用于显示Depth信息。

第五步,引用命名空间

打开MainWindow.xaml.cs文件,在文件头部添加对于Kinect对象的引用:

using Microsoft.Research.Kinect.Nui;

using Microsoft.Research.Kinect.Audio;

using Coding4Fun.Kinect.Wpf;

回到MainWindow.xaml的设计器中,在属性窗口中选择Event,找到Loaded和Closed两个方法,分别双击,添加两个事件的处理函数:

25

在MainWindow.xaml.cs文件的MainWindow类中,声明Runtime的变量:

Runtime nui;

然后,在Loaded事件的处理函数中添加Runtime初始化的代码:

private void Window_Loaded(object sender, RoutedEventArgs e)

{

nui = new Runtime();

nui.Initialize(RuntimeOptions.UseColor| RuntimeOptions.UseDepth | RuntimeOptions.UseDepthAndPlayerIndex | RuntimeOptions.UseSkeletalTracking);

}

接下来是Closed事件中关闭Runtime的代码:

private void Window_Closed(object sender, EventArgs e)

{

nui.Uninitialize();

}

Runtime对象是Kinect SDK中最主要的一个类,所有针对Kinect的操作都由Runtime类进行了封装。Runtime的构造函数没有接受任何参数,但有一个显式的初始化 函数Initialize,接受RuntimeOptions参数,指定调用Kinect的哪些功能。其中 RuntimeOptions.UseColor表示使用RGB Camera,而RuntimeOptions.UseDepth则表示使用Depth传感器。

初始化工作完成之后,我们要通过RGB Camera来获取实时的图像数据了。我们首先要声明一个事件处理方法,来接收视频数据的信息:

nui.VideoFrameReady += new EventHandler<ImageFrameReadyEventArgs>(nui_VideoFrameReady);

然后是事件处理函数:

void nui_VideoFrameReady(object sender, ImageFrameReadyEventArgs e)

{

PlanarImage imageData = e.ImageFrame.Image;

image1.Source = BitmapSource.Create(imageData.Width, imageData.Height, 96, 96,

PixelFormats.Bgr32, null, imageData.Bits, imageData.Width * imageData.BytesPerPixel);

//image1.Source = e.ImageFrame.ToBitmapSource();

}

提示:Getting Started上提供的Sample Code有误,需要将最后一个参数中的data.Width改为imageData.Width才可以正常运行。

VideoFrameReady事件会传递一个ImageFrameReadyEventArgs参数给事件处理函数,其中的ImageFrame 会包含关于图片的各种信息,比如Type变量指定了图像是来自RGB还是Depth,Resolution变量指定了分辨率,而Image中以 byte[]数组的方式保存了图像的真实数据。

然后的工作就是根据PlanarImage中包括的数据来创建一个Bitmap对象,然后将其传递给Image控件,显示到WPF程序的界面上。

最后,我们还要在构造函数里打开视频流,来获取视频数据:

nui.VideoStream.Open(ImageStreamType.Video, 2, ImageResolution.Resolution640x480, ImageType.Color);

第一个参数是ImageStreamType,用来指定打开的设备流类型;第二个参数是PoolSize,指定缓冲区的数量,至少为2,保证一个Buffer进行绘制,另一个Buffer进行数据填充;第三个参数指定Camera的分辨率;第四个参数则是获取的图片类型。

显示效果如下图所示:

24 - Copy

上面的示例代码,没有使用Coding4Fun的Helper类,如果使用的话,则代码如下:

void nui_VideoFrameReady(object sender, ImageFrameReadyEventArgs e)

{

image1.Source = e.ImageFrame.ToBitmapSource();

e.ImageFrame.ToBitmapSource().Save("catpure.jpg", ImageFormat.Jpeg);

}

Helper类使用了C#的Extension Methods,为ImageFrame增加了一些转换方法。我们还可以将图像保存为文件,考虑到文件系统存储的效率文件,建议大家不用每张都存。

获取Depth信息

接下来我们要获取Depth信息了,过程与RGB Camera类似。首先要确保Runtime对象被初始化时,已经添加了RuntimeOptions.UseDepth的属性,否则设备无法正常打开。

然后,添加获取Depth数据的事件处理,并打开Depth的数据流,这次的分辨率是320×240:

nui.DepthFrameReady += new EventHandler<ImageFrameReadyEventArgs>(nui_DepthFrameReady);

nui.DepthStream.Open(ImageStreamType.Depth, 2, ImageResolution.Resolution320x240, ImageType.Depth);

下面是事件处理函数,在另外一个Image函数里,显示Depth图像:

void nui_DepthFrameReady(object sender, ImageFrameReadyEventArgs e)

{

image2.Source = e.ImageFrame.ToBitmapSource(); 

}

偷懒,所以使用了Coding4Fun的Helper类。程序运行的效果如下:

24

写到最后

这一篇中,我们完成了Kinect开发环境的配置、添加了Coding4Fun Kinect Toolkit、从RGB Camera和Depth Sensor中获取了图像信息。

接下来,我们就要进入Kinect动作捕捉部分了。

[转载]Kinect for Windows SDK开发初体验(一)环境配置 - 马宁 - 博客园

mikel阅读(900)

[转载]Kinect for Windows SDK开发初体验(一)环境配置 – 马宁 – 博客园.

作者:马宁

万众期待的Kinect for Windows SDK终于在广大开发者的千呼万唤中发布了beta版,作为历史上销售最快的消费电子产品,早就有无数人想将其用于其他领域了。微软虽然在硬件接口上制造 了一点小障碍,但并没有对Kinect的输出做任何加密。于是,基于Kinect的各种应用层出不穷,也有开源社区提供了针对Kinect的USB驱动程 序,比如OpenKinect等。

微软从谏如流,推出了官方版的Kinect for Windows SDK,终于让广大开发者可以名正言顺地使用Kinect SDK了。我希望能够在第一时间为国内开发者提供Kinect开发相关的介绍,根据微软官方提供的指南,从浅入深,将自己摸索Kinect SDK 的过程记录下来。

2

安装环境

首先来说,Kinect for Windows SDK的下载地址:

http://research.microsoft.com/en-us/um/redmond/projects/kinectsdk/download.aspx

Kinect SDK目前只支持Windows 7,分为x86和x64两个版本。开发工具方面还需要.NET Framework 4.0和Visual Studio 2010 (最低Express版本)的支持。

Kinect SDK的视频开发教程:

http://channel9.msdn.com/Series/KinectSDKQuickstarts?sort=recent#tab_sortBy_recent

Kinect SDK的开发指南:

http://research.microsoft.com/en-us/um/redmond/projects/kinectsdk/guides.aspx

Kinect SDK的官方论坛:

http://social.msdn.microsoft.com/Forums/en-US/kinectsdk/threads

硬件设备的需求是:Kinect for Xbox 360 sensor和Xbox 360 Kinect AC Adapter/ Power Supply。

如 果您购买的是单独的Kinect,将包含Kinect AC Adapter/ Power Supply。但是如果您购买的是XBOX 360 Slim和Kinect的套装,就需要单独购买Kinect AC Adapter/ Power Supply。下面是亚马逊上的产品链接:

http://www.amazon.com/Xbox-360-Kinect-Adapter-Power-Supply/dp/B004IXRXGY/ref=sr_1_1?ie=UTF8&qid=1308247174&sr=8-1

安装步骤

1. 硬件安装

下面的图片是Kinect和AC Adapter/ Power Supply:

WP_000094

按照接口连接好后,将USB插入到PC上,AC Adapter电源是可以支持220V的,所以不需要转换器直接插到电源上就可以。

2. Kinect SDK安装

Kinect SDK的安装过程非常简单,无需任何设置,直接安装即可。

1

3. 环境测试

Kinect SDK安装完成后,我们将Kinect转接口的USB接口插入到PC中。PC会发现新硬件,并且自动查找驱动程序安装。下图是安装完成后的示意图:

evm1

4. 测试程序

我们打开Kinect SDK中自带的Sample Skeletal Viewer就可以很方便地检测Kinect设备是否与PC已经连接好了。

evm2

为了测试,逼得我不得不闪亮出镜了。以后Kinect调试,说不定还真要请一个人站在那里,实现敏捷开发中的“双人编程”……

写到最后

我们今天关于Kinect的介绍就到这里,在Kinect SDK的发布会上展示了很多有意思的DEMO,可见Kinect的应用前景是无限广阔的。下图就是一个Kinect与虚拟增强现实整合的案例:

4

下一节,我们会深入Kinect真实的开发环境,来编写第一个Kinect的应用程序。