zoukankan      html  css  js  c++  java
  • Kinect+OpenNI学习笔记之10(不需要骨骼跟踪的人体多个手部分割)

     前言

      这篇文章主要是介绍多个手部的分割,是在前面的博文:不需要骨骼跟踪的人体手部分割 的基础上稍加改进的。因为识别有的一个应用场合就是手势语言识别,而手势一般都需要人的2只手相配合完成,因此很有必要对人体的多个手部来进行分割。

     

     实验说明

      其实本文中使用的还是OpenNI自带的一些算法实现的,因为OpenNI中自己本身就对每个手部有一个UserID的标志,所以我们每当检测到一只手时就可以把手的位置连同他的ID一起存下来,后面进行手势分割时按照检测到的不同手势分别进行分割即可。其程序流程图如下所示:

      

     

      下面是本次实验特别需要注意的一些细节。

      OpenNI知识点总结:

      一般情况下OpenNI的回调函数中都会有一个参数pCookie,该参数就可以解决前面的博文Kinect+OpenNI学习笔记之7(OpenNI自带的类实现手部跟踪)

    中提到的一个当时没有完美解决的问题:即回调函数与类的静态函数,类的静态变量这3者之间使用相互矛盾的问题。那个时候因为在回调函数中需要使用静态成员变量,所以类中普通的非静态成员变量是不能够使用的,否则程序会编译时会报错误。但是如果我们把这些普通变量在类中定义成了静态变量后,这些静态变量就属于类本身了,并不属于类的对象。因此该变量在类的其它成员函数中是不能够被使用的。这样就产生了矛盾,当时的解决方法是将这些变量不放在类中,而放在类外称为整个工程的全局变量。虽然理论上可以解决问题,不过一个跟类有关的变量竟然不能够放在类的内部,听起来就像是个大笑话!这样的封装明显不合理。

      幸运的是,现在因为回调函数传进来时有了pCookie这个变量,这样我们在回调函数中可以间接使用类的非静态成员变量了,使用这些变量既不需要定义为static类型,且也可以在类的成员函数中来进行初始化。具体方法是将某个节点(比如说手部,人体,姿势等节点)的注册函数RegisterGestureCallbacks()中第3个参数设置为this指针,而不是null指针。同时在具体的回调函数中,首先把pCookie指针强制转换成COpenNI这个类的指针,然后用转换过来的指针调用需要用到的类的成员变量。

      C/C++知识点总结:

      pair和map的区别:map是一个容器,容器中的第一个元素表示的是键值key,其它元素分别表示以后用该容器存储数据时的数据类型。因此map中的每一条记录的key值是不能重复的。当map定义的时候只有2个集合的时候,里面的每一条记录可以用pair来存储。因此可以简单的理解一个pair对应的是一条具体的记录,而一个map是一个存放pair的容器,并且map声明了容器的属性。

    当在进行pair数据类型的定义时,如果其元素中的一个已经确定,另一个还不知道,则在定义的同时可以直接传入确定的那个元素,另一个用它的数据类型后面接一个空括号即可。

      在使用vector时,必须是已存在的元素才能用下标操作符进行索引。可以使用at和[]获取指定位置的数据或者给指定位置的数据赋值。

      Qt知识点总结:

      在QtCreator的使用中,有时候会出现两个尖括号在一起的情况,这时候没有语法错误,但是QtCreator这个编辑环境会在你的代码下出现个红色的波纹,让人看起来非常不舒服。例如:

     

      解决方法非常简单,即把两个尖括号中间不要紧挨着,用一个空格号隔开一下即可,这时候红色的波纹警告线就消失了。

     实验结果

      本工程可以对多个手部进行分割,分割效果取决于OpenNI中的手部跟踪效果。其效果截图如下:

      

      实验代码(附录有工程code下载链接)

    copenni.cpp:

    #ifndef COPENNI_CLASS
    #define COPENNI_CLASS
    
    #include <XnCppWrapper.h>
    #include <iostream>
    #include <map>
    
    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 = 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, this, gesture_cb);
            //设置于手部有关的回调函数
            XnCallbackHandle hands_cb;
            hands_generator.RegisterHandCallbacks(HandCreate, HandUpdate, HandDestroy, this, 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;
        DepthGenerator  depth_generator;
        HandsGenerator  hands_generator;
        std::map<XnUserID, XnPoint3D> hand_points;//为了存储不同手的实时点而设置的
        map< XnUserID, vector<XnPoint3D> > hands_track_points;//为了绘画后面不同手部的跟踪轨迹而设定的
    
    private:
        //该函数返回真代表出现了错误,返回假代表正确
        bool CheckError(const char* error) {
            if(status != XN_STATUS_OK ) {
                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);
        }
        //手势开始检测的回调函数
        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);
        }
        //手部开始建立的回调函数
        static void XN_CALLBACK_TYPE HandCreate(HandsGenerator& rHands, XnUserID xUID, const XnPoint3D* pPosition,
                                                XnFloat fTime, void* pCookie) {
            COpenNI *openni = (COpenNI*)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);
        }
        //手部开始更新的回调函数
        static void XN_CALLBACK_TYPE HandUpdate(HandsGenerator& rHands, XnUserID xUID, const XnPoint3D* pPosition, XnFloat fTime,
                                                void* pCookie) {
            COpenNI *openni = (COpenNI*)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);
        }
        //销毁手部的回调函数
        static void XN_CALLBACK_TYPE HandDestroy(HandsGenerator& rHands, XnUserID xUID, XnFloat fTime,
                                                  void* pCookie) {
            COpenNI *openni = (COpenNI*)pCookie;
            openni->hand_points.erase(openni->hand_points.find(xUID));
            openni->hands_track_points.erase(openni->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;
        UserGenerator user_generator;
        GestureGenerator gesture_generator;
        XnMapOutputMode xmode;
    
    };
    
    #endif

    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;
        vector<Scalar> color_array;//采用默认的10种颜色
        {
            color_array.push_back(Scalar(255, 0, 0));
            color_array.push_back(Scalar(0, 255, 0));
            color_array.push_back(Scalar(0, 0, 255));
            color_array.push_back(Scalar(255, 0, 255));
            color_array.push_back(Scalar(255, 255, 0));
            color_array.push_back(Scalar(0, 255, 255));
            color_array.push_back(Scalar(128, 255, 0));
            color_array.push_back(Scalar(0, 128, 255));
            color_array.push_back(Scalar(255, 0, 128));
            color_array.push_back(Scalar(255, 128, 255));
        }
        vector<int> hand_depth(10, 0);
        vector<Rect> hands_roi(10, Rect(320, 240, ROI_HAND_WIDTH, 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);
            for(auto itUser = openni.hand_points.cbegin(); itUser != openni.hand_points.cend(); ++itUser) {
                circle(color_image, Point(itUser->second.X, itUser->second.Y),
                       5, color_array.at(itUser->first % color_array.size()), 3, 8);
                /*设置不同手部的深度*/
                hand_depth.at(itUser->first) = itUser->second.Z* DEPTH_SCALE_FACTOR;
                /*设置不同手部的不同感兴趣区域*/
                hands_roi.at(itUser->first) = Rect(itUser->second.X - ROI_HAND_WIDTH/2, itUser->second.Y - ROI_HAND_HEIGHT/2,
                                                   ROI_HAND_WIDTH, ROI_HAND_HEIGHT);
                hands_roi.at(itUser->first).x =  itUser->second.X - ROI_HAND_WIDTH/2;
                hands_roi.at(itUser->first).y =  itUser->second.Y - ROI_HAND_HEIGHT/2;
                hands_roi.at(itUser->first).width = ROI_HAND_WIDTH;
                hands_roi.at(itUser->first).height = ROI_HAND_HEIGHT;
                if(hands_roi.at(itUser->first).x <= 0)
                    hands_roi.at(itUser->first).x  = 0;
                if(hands_roi.at(itUser->first).x > XRES)
                    hands_roi.at(itUser->first).x =  XRES;
                if(hands_roi.at(itUser->first).y <= 0)
                    hands_roi.at(itUser->first).y = 0;
                if(hands_roi.at(itUser->first).y > YRES)
                    hands_roi.at(itUser->first).y =  YRES;
            }
    
            imshow("color image", color_image);
    
    
            /*获取并显示深度图像*/
            Mat depth_image_src(openni.depth_metadata.YRes(), openni.depth_metadata.XRes(),
                                CV_16UC1, (char *)openni.depth_metadata.Data());//因为kinect获取到的深度图像实际上是无符号的16位数据
            Mat depth_image;
            depth_image_src.convertTo(depth_image, CV_8U, DEPTH_SCALE_FACTOR);
            imshow("depth image", depth_image);
    
            //取出手的mask部分
            //不管原图像时多少通道的,mask矩阵声明为单通道就ok
            Mat hand_segment_mask(depth_image.size(), CV_8UC1, Scalar::all(0));
            for(auto itUser = openni.hand_points.cbegin(); itUser != openni.hand_points.cend(); ++itUser)
                for(int i = hands_roi.at(itUser->first).x; i < std::min(hands_roi.at(itUser->first).x+hands_roi.at(itUser->first).width, XRES); i++)
                    for(int j = hands_roi.at(itUser->first).y; j < std::min(hands_roi.at(itUser->first).y+hands_roi.at(itUser->first).height, YRES); j++) {
                        hand_segment_mask.at<unsigned char>(j, i) = ((hand_depth.at(itUser->first)-DEPTH_SEGMENT_THRESH) < depth_image.at<unsigned char>(j, i))
                                                                    & ((hand_depth.at(itUser->first)+DEPTH_SEGMENT_THRESH) > depth_image.at<unsigned char>(j,i));
                    }
            medianBlur(hand_segment_mask, hand_segment_mask, MEDIAN_BLUR_K);
            Mat hand_segment(color_image.size(), CV_8UC3);
            color_image.copyTo(hand_segment, hand_segment_mask);
    
            imshow("hand_segment", hand_segment);
            waitKey(30);
        }
    
    }

      实验总结:

      本次实验基本上都是基于OpenNI的一些算法的,其分割也是简单的用深度信息进行,没有什么特别的算法,且分割过程中没有引入色彩信息。后续的工作是从将色彩信息和深度信息结合起来做分割,即达到手部分割邻域自适应选择。

      附录:实验工程code

  • 相关阅读:
    采用商业智能提升企业的数字营销策略
    采用商业智能提升企业的数字营销策略
    《PostgreSQL服务器编程》一一1.3 超越简单函数
    《PostgreSQL服务器编程》一一1.3 超越简单函数
    《PostgreSQL服务器编程》一一1.3 超越简单函数
    《PostgreSQL服务器编程》一一1.3 超越简单函数
    2017 全球半导体预估跳增 11.5%,存储器最夯
    2017 全球半导体预估跳增 11.5%,存储器最夯
    如何从零学习PostgreSQL Page结构
    转成json必须是unicdoe字符
  • 原文地址:https://www.cnblogs.com/tornadomeet/p/2748999.html
Copyright © 2011-2022 走看看