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

      

      前言

      手势识别非常重要的一个特点是要体验要好,即需要以用户为核心。而手势的定位一般在手势识别过程的前面,在上一篇博文Kinect+OpenNI学习笔记之8(Robert Walter手部提取代码的分析) 中已经介绍过怎样获取手势区域,且取得了不错的效果,但是那个手势部位的提取有一个大的缺点,即需要人站立起来,当站立起来后才能够分隔出手。而手势在人之间的交流时,并不一定要处于站立状态,所以这不是一个好的HCI。因此本文介绍的手势部位的提取并不需要人处于站立状态,同样取得了不错的效果。

      实验说明

      其实,本实验实现的过程非常简单。首先通过手部的跟踪来获取手所在的坐标,手部跟踪可以参考本人前面的博文:Kinect+OpenNI学习笔记之7(OpenNI自带的类实现手部跟踪)。当定位到手所在的坐标后,因为该坐标是3D的,因此在该坐标领域的3维空间领域内提取出手的部位即可,整个过程的大概流程图如下:

      

      OpenCV知识点总结:

      调用Mat::copyTo()函数时,如果需要有mask操作,则不管源图像是多少通道的,其mask矩阵都要定义为单通道,另外可以对一个mask矩阵画一个填充的矩形来达到使mask矩阵中对应ROI的位置的值为设定值,这样就不需要去一一扫描赋值了。

      在使用OpenCV的Mat矩阵且需要对该矩阵进行扫描时,一定要注意其取值顺序,比如说列和行的顺序,如果弄反了,则经常会报内存错误。

      实验结果

      本实验并不要求人的手一定要放在人体的前面,且也不需要人一定是处在比较简单的背景环境中,本实验结果允许人处在复杂的背景环境下,且手可以到处随便移动。当然了,环境差时有时候效果就不太好。

      下面是3张实验结果的截图,手势分隔图1:

      

      手势分隔图2:

      

      手势分隔图3:

      

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

    main.cpp:

    #include <iostream>
    
    #include "opencv2/highgui/highgui.hpp"
    #include "opencv2/imgproc/imgproc.hpp"
    #include <opencv2/core/core.hpp>
    #include "copenni.cpp"
    
    #include <iostream>
    
    #define DEPTH_SCALE_FACTOR 255./4096.
    #define ROI_HAND_WIDTH 140
    #define ROI_HAND_HEIGHT 140
    #define MEDIAN_BLUR_K 5
    
    int XRES = 640;
    int YRES = 480;
    #define DEPTH_SEGMENT_THRESH 5
    
    using namespace cv;
    using namespace xn;
    using namespace std;
    
    
    int main (int argc, char **argv)
    {
        COpenNI openni;
        int hand_depth;
        Rect roi;
        roi.x = XRES/2;
        roi.y = YRES/2;
        roi.width = ROI_HAND_WIDTH;
        roi.height = ROI_HAND_HEIGHT;
        if(!openni.Initial())
            return 1;
    
        namedWindow("color image", CV_WINDOW_AUTOSIZE);
        namedWindow("depth image", CV_WINDOW_AUTOSIZE);
        namedWindow("hand_segment", CV_WINDOW_AUTOSIZE);//显示分割出来的手的区域
    
        if(!openni.Start())
            return 1;
        while(1) {
            if(!openni.UpdateData()) {
                return 1;
            }
            /*获取并显示色彩图像*/
            Mat color_image_src(openni.image_metadata.YRes(), openni.image_metadata.XRes(),
                                CV_8UC3, (char *)openni.image_metadata.Data());
            Mat color_image;
            cvtColor(color_image_src, color_image, CV_RGB2BGR);
            circle(color_image, Point(hand_point.X, hand_point.Y), 5, Scalar(255, 0, 0), 3, 8);
            imshow("color image", color_image);
    
    
            /*获取并显示深度图像*/
            Mat depth_image_src(openni.depth_metadata.YRes(), openni.depth_metadata.XRes(),
                                CV_16UC1, (char *)openni.depth_metadata.Data());//因为kinect获取到的深度图像实际上是无符号的16位数据
            Mat depth_image;
            depth_image_src.convertTo(depth_image, CV_8U, DEPTH_SCALE_FACTOR);
            imshow("depth image", depth_image);
    
            /*下面的代码是提取手的轮廓部分*/
            hand_depth = hand_point.Z * DEPTH_SCALE_FACTOR;
            roi.x = hand_point.X - ROI_HAND_WIDTH/2;
            roi.y = hand_point.Y - ROI_HAND_HEIGHT/2;
            if(roi.x <= 0)
                roi.x = 0;
            if(roi.x >= XRES)
                roi.x = XRES;
            if(roi.y <=0 )
                roi.y = 0;
            if(roi.y >= YRES)
                roi.y = YRES;
    
            //取出手的mask部分
            //不管原图像时多少通道的,mask矩阵声明为单通道就ok
            Mat hand_segment_mask(depth_image.size(), CV_8UC1, Scalar::all(0));
            for(int i = roi.x; i < std::min(roi.x+roi.width, XRES); i++)
                for(int j = roi.y; j < std::min(roi.y+roi.height, YRES); j++) {
                    hand_segment_mask.at<unsigned char>(j, i) = ((hand_depth-DEPTH_SEGMENT_THRESH) < depth_image.at<unsigned char>(j, i))
                                                                & ((hand_depth+DEPTH_SEGMENT_THRESH) > depth_image.at<unsigned char>(j,i));
                }
            medianBlur(hand_segment_mask, hand_segment_mask, MEDIAN_BLUR_K);
            Mat hand_segment(color_image.size(), CV_8UC3);
            color_image.copyTo(hand_segment, hand_segment_mask);
    
            imshow("hand_segment", hand_segment);
            waitKey(20);
        }
    
    }

    copenni,cpp:

    #ifndef COPENNI_CLASS
    #define COPENNI_CLASS
    
    #include <XnCppWrapper.h>
    #include <iostream>
    #include <map>
    
    using namespace xn;
    using namespace std;
    
    static DepthGenerator  depth_generator;
    static HandsGenerator  hands_generator;
    static XnPoint3D hand_point;
    static std::map<XnUserID, vector<XnPoint3D>> hands_track_points;
    
    class COpenNI
    {
    public:
        ~COpenNI() {
            context.Release();//释放空间
        }
        bool Initial() {
            //初始化
            status = context.Init();
            if(CheckError("Context initial failed!")) {
                return false;
            }
            context.SetGlobalMirror(true);//设置镜像
            xmode.nXRes = 640;
            xmode.nYRes = 480;
            xmode.nFPS = 30;
            //产生颜色node
            status = image_generator.Create(context);
            if(CheckError("Create image generator  error!")) {
                return false;
            }
            //设置颜色图片输出模式
            status = image_generator.SetMapOutputMode(xmode);
            if(CheckError("SetMapOutputMdoe error!")) {
                return false;
            }
            //产生深度node
            status = depth_generator.Create(context);
            if(CheckError("Create depth generator  error!")) {
                return false;
            }
            //设置深度图片输出模式
            status = depth_generator.SetMapOutputMode(xmode);
            if(CheckError("SetMapOutputMdoe error!")) {
                return false;
            }
            //产生手势node
            status = gesture_generator.Create(context);
            if(CheckError("Create gesture generator error!")) {
                return false;
            }
            /*添加手势识别的种类*/
            gesture_generator.AddGesture("Wave", NULL);
            gesture_generator.AddGesture("click", NULL);
            gesture_generator.AddGesture("RaiseHand", NULL);
            gesture_generator.AddGesture("MovingHand", NULL);
            //产生手部的node
            status = hands_generator.Create(context);
            if(CheckError("Create hand generaotr error!")) {
                return false;
            }
            //产生人体node
            status = user_generator.Create(context);
            if(CheckError("Create gesturen generator error!")) {
                return false;
            }
            //视角校正
            status = depth_generator.GetAlternativeViewPointCap().SetViewPoint(image_generator);
            if(CheckError("Can't set the alternative view point on depth generator!")) {
                return false;
            }
            //设置与手势有关的回调函数
            XnCallbackHandle gesture_cb;
            gesture_generator.RegisterGestureCallbacks(CBGestureRecognized, CBGestureProgress, NULL, gesture_cb);
            //设置于手部有关的回调函数
            XnCallbackHandle hands_cb;
            hands_generator.RegisterHandCallbacks(HandCreate, HandUpdate, HandDestroy, NULL, hands_cb);
            //设置有人进入视野的回调函数
            XnCallbackHandle new_user_handle;
            user_generator.RegisterUserCallbacks(CBNewUser, NULL, NULL, new_user_handle);
            user_generator.GetSkeletonCap().SetSkeletonProfile(XN_SKEL_PROFILE_ALL);//设定使用所有关节(共15个)
            //设置骨骼校正完成的回调函数
            XnCallbackHandle calibration_complete;
            user_generator.GetSkeletonCap().RegisterToCalibrationComplete(CBCalibrationComplete, NULL, calibration_complete);
            return true;
        }
    
        bool Start() {
            status = context.StartGeneratingAll();
            if(CheckError("Start generating error!")) {
                return false;
            }
            return true;
        }
    
        bool UpdateData() {
            status = context.WaitNoneUpdateAll();
            if(CheckError("Update date error!")) {
                return false;
            }
            //获取数据
            image_generator.GetMetaData(image_metadata);
            depth_generator.GetMetaData(depth_metadata);
    
            return true;
        }
        //得到色彩图像的node
        ImageGenerator& getImageGenerator() {
            return image_generator;
        }
        //得到深度图像的node
        DepthGenerator& getDepthGenerator() {
            return depth_generator;
        }
        //得到人体的node
        UserGenerator& getUserGenerator() {
            return user_generator;
        }
        //得到手势姿势node
        GestureGenerator& getGestureGenerator() {
            return gesture_generator;
        }
    
    public:
        DepthMetaData depth_metadata;
        ImageMetaData image_metadata;
    //    static std::map<XnUserID, vector<XnPoint3D>> hands_track_points;
    
    private:
        //该函数返回真代表出现了错误,返回假代表正确
        bool CheckError(const char* error) {
            if(status != XN_STATUS_OK ) {
                //QMessageBox::critical(NULL, error, xnGetStatusString(status));
                cerr << error << ": " << xnGetStatusString( status ) << endl;
                return true;
            }
            return false;
        }
        //手势某个动作已经完成检测的回调函数
        static void XN_CALLBACK_TYPE  CBGestureRecognized(xn::GestureGenerator &generator, const XnChar *strGesture, const XnPoint3D *pIDPosition,
                                                          const XnPoint3D *pEndPosition, void *pCookie) {
       //     COpenNI *openni = (COpenNI*)pCookie;
        //    openni->hands_generator.StartTracking(*pIDPosition);
            hands_generator.StartTracking(*pIDPosition);
        }
        //手势开始检测的回调函数
        static void XN_CALLBACK_TYPE CBGestureProgress(xn::GestureGenerator &generator, const XnChar *strGesture, const XnPoint3D *pPosition,
                                                       XnFloat fProgress, void *pCookie) {
       //     COpenNI *openni = (COpenNI*)pCookie;
       //     openni->hands_generator.StartTracking(*pPosition);
            hands_generator.StartTracking(*pPosition);
        }
        //手部开始建立的回调函数
        static void XN_CALLBACK_TYPE HandCreate(HandsGenerator& rHands, XnUserID xUID, const XnPoint3D* pPosition,
                                                XnFloat fTime, void* pCookie) {
       //     COpenNI *openni = (COpenNI*)pCookie;
            XnPoint3D project_pos;
            depth_generator.ConvertRealWorldToProjective(1, pPosition, &project_pos);
     //       openni->hand_point = project_pos;   //返回手部所在点的位置
            hand_point = project_pos;
            pair<XnUserID, vector<XnPoint3D>> hand_track_point(xUID, vector<XnPoint3D>());
            hand_track_point.second.push_back(project_pos);
            hands_track_points.insert(hand_track_point);
        }
        //手部开始更新的回调函数
        static void XN_CALLBACK_TYPE HandUpdate(HandsGenerator& rHands, XnUserID xUID, const XnPoint3D* pPosition, XnFloat fTime,
                                                void* pCookie) {
       //     COpenNI *openni = (COpenNI*)pCookie;
            XnPoint3D project_pos;
            depth_generator.ConvertRealWorldToProjective(1, pPosition, &project_pos);
         //   openni->hand_point = project_pos;   //返回手部所在点的位置
            hand_point = project_pos;
            hands_track_points.find(xUID)->second.push_back(project_pos);
        }
        //销毁手部的回调函数
        static void XN_CALLBACK_TYPE HandDestroy(HandsGenerator& rHands, XnUserID xUID, XnFloat fTime,
                                                  void* pCookie) {
      //      COpenNI *openni = (COpenNI*)pCookie;
            //openni->hand_point.clear();   //返回手部所在点的位置
            hands_track_points.erase(hands_track_points.find(xUID));
        }
        //有人进入视野时的回调函数
        static void XN_CALLBACK_TYPE CBNewUser(UserGenerator &generator, XnUserID user, void *p_cookie) {
            //得到skeleton的capability,并调用RequestCalibration函数设置对新检测到的人进行骨骼校正
            generator.GetSkeletonCap().RequestCalibration(user, true);
        }
        //完成骨骼校正的回调函数
        static void XN_CALLBACK_TYPE CBCalibrationComplete(SkeletonCapability &skeleton,
                                                           XnUserID user, XnCalibrationStatus calibration_error, void *p_cookie) {
            if(calibration_error == XN_CALIBRATION_STATUS_OK) {
                skeleton.StartTracking(user);//骨骼校正完成后就开始进行人体跟踪了
            }
            else {
                UserGenerator *p_user = (UserGenerator*)p_cookie;
                skeleton.RequestCalibration(user, true);//骨骼校正失败时重新设置对人体骨骼继续进行校正
            }
        }
    
    private:
        XnStatus    status;
        Context     context;
        ImageGenerator  image_generator;
    //    DepthGenerator depth_generator;
        UserGenerator user_generator;
        GestureGenerator gesture_generator;
    //    HandsGenerator  hands_generator;
    //    map<XnUserID, vector<XnPoint3D>> hands_track_points;
        XnMapOutputMode xmode;
    
    public:
      // static XnPoint3D hand_point;
    };
    
    #endif

      实验总结:

      本次实验简单的利用OpenNI的手部跟踪功能提实时分隔出了人体手所在的部位。但是该分隔效果并不是特别好,以后可以改进手利用色彩信息来分隔出手的区域,或者计算出自适应手部位的区域。另外,本程序只是暂时分隔出一个手,以后可以扩展到分隔出多个手的部位.

      参考资料:

         Kinect+OpenNI学习笔记之8(Robert Walter手部提取代码的分析)

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

      附录:实验工程code下载

  • 相关阅读:
    VSCode中按ESLint规则格式化Javascript代码
    VSCode设置资源管理器字体大小
    Windows下利用安装压缩包安装MySQL
    Windows部署Apache 2.4.46及PHP 8.0.3
    npm设置国内镜像
    IDEA运行Tomcat输出信息乱码
    深入理解jvm虚拟机读书笔记-Java内存区域与内存溢出异常
    Navicat Premium
    mysql安装
    ElasticSearch 基础
  • 原文地址:https://www.cnblogs.com/tornadomeet/p/2730891.html
Copyright © 2011-2022 走看看