修改 User Generator 用來分析的深度影像


在之前的一系列文章裡,其實 Heresy 自己是覺得應該已經把大部分、在寫 OpenNI 應用程式的基礎部分都講完了(聲音除外),其他的東西,在 Heresy 來看都是比較偏應用、或是偏進階的東西;雖然其實現在有些部分會想改版重寫,像是現在會想把早期的範例改成用 Map metadta 當範例這類的,不過暫時應該還不會真的下去做這件事。

而這篇的內容,基本上也算是一個特殊的進階使用,那就是:

要怎麼修改深度資料、讓 User Generator 來分析?

對於這個問題,Heresy 最初的想法,是自己去寫一個新的 Depth Generator,在裡面去讀取原始的 Depth Generator 後、做完修改再來讓 User Generator 來用。這個方法理論上應該也是可以的,但是實際上真的要自己寫一個 OpenNI 的 production node 其實還是滿麻煩的…

而後來,想到的方法,就是在撥放 ONI 檔時、拿來操作的虛擬 Production node、也就是「Mock Node」來用了!

OpenNI 的 Mock Node 基本上是一種沒有真正對應到資料來源的虛擬原始資料的 Production node,包括了下面四種:

  • xn::MockDepthGenerator
  • xn::MockImageGenerator
  • xn::MockIRGenerator
  • xn::MockAudioGenerator

基本上,就是對應到 OpenNI 裡面,對應到硬體、去讀取原始資料的四種 generator 了~而這四種 Mock Node 基本上都是繼承自本來的 Generator,所以可以和本來的 generator 做一樣的操作。不過,由於他們本身不會去讀取硬體的資料,所以必須要靠程式透過 SetData() 來把資料餵進去,然後再給 OpenNI 的其他模組使用。

而在 OpenNI 官方的「NiRecordSynthetic」這個範例裡,就有用到 xn::MockDepthGenerator 了~有興趣的話,可以參考看看官方範例的寫法;而這邊,Heresy 基本上也是以 xn::MockDepthGenerator 為例子來做說明了。


初始化

在建立的部分,基本概念就是:

// OpenNI Context
xn::Context xContext;
xContext.Init();

// Depth generator
xn::DepthGenerator xDepthGen;
xDepthGen.Create( xContext );

// mock depth generator
xn::MockDepthGenerator xMockDepth;
xMockDepth.CreateBasedOn( xDepthGen, "mock-depth" );

// create user generator using mock depth generator
xn::Query xQuery;
xQuery.AddNeededNode( "mock-depth" );
xn::UserGenerator xUser;
xUser.Create( xContext, &xQuery );

在上面的程式碼裡,首先還是先建立 OpenNI 的 context xContext、並對他進行初始化。接下來,用一般的方法,建立一個 depth generator xDepthGen,用來讀取感應器的資料。

再來,則是建立一個 xn::MockDepthGenerator xMockDepth,用來當作虛擬的 depth generator;這邊是使用 CreateBasedOn() 這個函示來做建立的動作,如此可以把 xDepthGen 的基本資訊直接拿來給 xMockDepth 用。而第二個參數「"mock-depth"」則是一個字串,用來指定建立出來的 node 的名稱。

(注意!Node 名稱似乎有可能會導致錯誤!建議不要改掉字串、或是用不包含「depth」這類可能是內部定義的單字來命名。)

而在建立 user generator 的時候,則就需要透過 xn::Query 來做限制,來要求 OpenNI 去建立一個使用 xMockDepth 的資料的 user generator 了~而這邊限制條件的方法,基本上就是先建立一個 xn::Query 的物件 xQuery,然後透過他的 AddNeededNode() 這個函式來做條件的指定;而指定的條件,這邊就是給他 xMockDepth 的名稱,也就是 "mock-depth" 了~

在設定好 xQuery 後,只要在 user generator 建立的時候,把他的位址當作 Create() 的第二個參數傳進去,這樣 OpenNI 就會建立一個使用 xMockDepth 的 User generator 出來了~


資料更新

初始化完成後,接下來的設定基本上都和本來的用法一樣了~不一樣的地方,在於每次在主迴圈內、進行資料更新的時候,會需要做特殊的處理。

首先,本來在主迴圈裡面、每次都需要呼叫 Context 的 WaitAndUpdateAll() 這個函式,來要求 OpenNI Context 針對所有的 Node 進行更新、直到都更新好了再繼續執行;但是現在由於 mock node 本身不會自己產生資料,所以如果使用 WaitAndUpdateAll(),會因為等不到 xMockDepth 的更新,而讓程式卡住(不會完全不動,只是更新很慢)。所以,這邊要修改一下,變成呼叫 WaitOneUpdateAll(),然後把 xDepthGen 傳進去,讓他等到 depth generator 的資料準備好了,就繼續執行。

而等到 xDepthGen 取得新的深度資料之後呢,就要手動把新的 depth map 讀取出來、經過修改(看自己要怎麼改)、透過 xn::MockDepthGeneratorSetData() 這個函式,把修改後的結果餵給 xMockDepth 了~如此一來,之後 user generator 就會使用這個被修改過的資料,拿來做分析了~

實際上寫的話,大概就會像下面這樣:

// update data
xContext.WaitOneUpdateAll( xDepthGen );

// get original depth map
xn::DepthMetaData xDepthData;
xDepthGen.GetMetaData( xDepthData );

// make data writable and modify
xDepthData.MakeDataWritable();
// modify data....

// set data
xMockDepth.SetData( xDepthData );

這邊基本上就是在 xDepthGen 的資料更新後,透過 GetMetaData() 把讀取到的資料(xDepthData)拿出來,然後透過 xn::DepthMetaDataMakeDataWritable() 這個函式,在內部建立一份額外的記憶體空間,讓資料可以被修改(本來是唯獨的)

而之後,就可以針對 xDepthData 進行修改了~看是要套用什麼影像處理的演算法,就是看各自的需求了。修改資料時,還需要先取出可寫入的資料,最簡單的方法應該就是使用 WritableDepthMap() 這個函式,下面就是一個把所想像素都填 0 的範例:

xn::DepthMap& rDepthMap = xDepthData.WritableDepthMap();
for( unsigned int y = 0; y < rDepthMap.YRes(); ++ y )
  for( unsigned int x = 0; x < rDepthMap.XRes(); ++ x )
    rDepthMap(  x, y ) = 0;

而如果需要的話,也可以直接透過 WritableData() 這個函式,來取得 XnDepthPixel 的指標,對整個陣列進行修改。

當修改完成後,只要透過 SetData() 這個函式,就可以把修改過的 xDepthData 送給 xMockDepth;而當資料送給 xMockDepth 後,對應的 user generator、也就是之前建立的 xUser 就會得到有新資料的通知,並且向 xMockDepth 來要這筆修改過的深度資料、並進行分析了~


這篇大概就先寫道這了。基本上,只是著重於概念上的說明,實際上要應用,主要還是看是要做什麼處理了;基本上,Heresy 想到比較可能會需要的,應該會是以影像處理或電腦視覺的 denoise、也就是減少深度的雜訊為主吧~另外,有需要的話,應該也是有可能輔以彩色的資料,來把深度感應器上的洞、閃爍的效果降低,這樣或許可以加強後續分析的穩定性。

不過,由於 OpenNI 互動程式基本上都是互動式的,如果在這部分影像處理花了太多的 CPU 計算能力,有可能會產生效能上的問題…這時候,勢必會需要靠 OpenMP 這類的平行化方法來針對多核心處理器做最佳化,甚至是採用 nVIDIA CUDAOpenCL 這類的 GPGPU 技術,把計算丟到顯示卡去了~這點就是要自己斟酌的了。


OpenNI / Kinect 相關文章目錄

廣告

對「修改 User Generator 用來分析的深度影像」的想法

  1. Herecy,我用的设备是Xtion2,OpenNI是Xtion官网下的1.5.7版本。修改后的深度数据老是无法创建用户, 提示错误Couldn’t get max shift value,请问是什么原因

    • 代码如下

      #define CHECK_RC(rc, what) \
      if (rc != XN_STATUS_OK) \
      { \
      printf(“%s failed: %s\n", what, xnGetStatusString(rc)); \
      return rc; \
      }

      using namespace xn;
      using namespace cv;
      using namespace std;

      // callback function of user generator: new user
      void XN_CALLBACK_TYPE NewUser(xn::UserGenerator& generator,
      XnUserID user,
      void* pCookie)
      {
      cout << "New user identified: " << user << endl;
      generator.GetSkeletonCap().RequestCalibration(user, true);
      }

      // callback function of skeleton: calibration end
      void XN_CALLBACK_TYPE CalibrationEnd(xn::SkeletonCapability& skeleton,
      XnUserID user,
      XnCalibrationStatus eStatus,
      void* pCookie)
      {
      cout << "Calibration complete for user " << user << ", ";
      if (eStatus == XN_CALIBRATION_STATUS_OK)
      {
      cout << "Success" << endl;
      skeleton.StartTracking(user);
      }
      else
      {
      cout << "Failure" << endl;
      skeleton.RequestCalibration(user, true);
      }
      }

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

      XnStatus nRetVal = XN_STATUS_OK;
      Context xContext;
      nRetVal = xContext.Init();
      CHECK_RC(nRetVal, "Init");

      XnMapOutputMode mapMode;
      mapMode.nXRes = 640;
      mapMode.nYRes = 480;
      mapMode.nFPS = 30;

      DepthGenerator xDepthGen;
      nRetVal = xDepthGen.Create(xContext);
      CHECK_RC(nRetVal, "xDepthGen");

      xDepthGen.SetMapOutputMode(mapMode);

      MockDepthGenerator xMockDepth;
      nRetVal = xMockDepth.CreateBasedOn(xDepthGen, "mock-depth");
      CHECK_RC(nRetVal, "xMockDepth");

      xn::Query xQuery;
      nRetVal = xQuery.AddNeededNode("mock-depth");
      CHECK_RC(nRetVal, "mock-depth");

      xn::UserGenerator xUser;
      nRetVal = xUser.Create(xContext, &xQuery);
      CHECK_RC(nRetVal, "xUser");

      XnCallbackHandle hUserCB;
      xUser.RegisterUserCallbacks(NewUser, NULL, NULL, hUserCB);

      xn::SkeletonCapability mSC = xUser.GetSkeletonCap();
      mSC.SetSkeletonProfile(XN_SKEL_PROFILE_ALL);
      XnCallbackHandle hCalibCB;
      mSC.RegisterToCalibrationComplete(CalibrationEnd, &xUser, hCalibCB);

      xContext.StartGeneratingAll();

      for (int i = 1; i < 100; i++)
      {

      // update data
      xContext.WaitOneUpdateAll(xDepthGen);

      // get original depth map
      DepthMetaData xDepthData;
      xDepthGen.GetMetaData(xDepthData);

      //depth
      DepthMetaData depthMD;

      xDepthData.AllocateData(640, 480); //openni里的depthMD必须为指定size大小
      //depthMD.AllocateData(depth_img.cols, depth_img.rows);

      nRetVal = xDepthData.MakeDataWritable();
      CHECK_RC(nRetVal, "Make depth data writable");

      //DepthMap& depthMap = depthMD.WritableDepthMap();
      DepthMap& rDepthMap = xDepthData.WritableDepthMap();
      // modify data….
      for (XnUInt32 y = 0; y < rDepthMap.YRes(); y++)
      {
      for (XnUInt32 x = 0; x 0)
      {
      // 8. get users
      XnUserID* aUserID = new XnUserID[nUsers];
      xUser.GetUsers(aUserID, nUsers);

      // 9. check each user
      for (int i = 0; i < nUsers; ++i)
      {
      // 10. if is tracking skeleton
      if (mSC.IsTracking(aUserID[i]))
      {
      // 11. get skeleton joint data
      XnSkeletonJointTransformation mJointTran;
      mSC.GetSkeletonJoint(aUserID[i], XN_SKEL_HEAD, mJointTran);

      // 12. output information
      cout << "The head of user " << aUserID[i] << " is at (";
      cout << mJointTran.position.position.X << ", ";
      cout << mJointTran.position.position.Y << ", ";
      cout << mJointTran.position.position.Z << ")" << endl;
      }
      }
      delete[] aUserID;
      }

      }

      xContext.StopGeneratingAll();
      xContext.Release();
      printf("\n");
      return 0;
      }

  2. […] 而另一方面,這也可以用來模擬類似 OpenN1 時提供的「Mock Node」、也就是可以用來修改取得道的深度資料、讓之後的 middleware(PrimeSense NiTE)可以使用。(參考《修改 User Generator 用來分析的深度影像》) […]

  3. 請問 Heresy 大大:這樣的方法還是需要接上一個 Kinect ,是否有其他方法可以不用到Kinect,而是接上自製的 stereo camera 然後使用 Nite 的 Skeleton SDK function 呢?

    • 技術上可行(不過很麻煩),不過 NiTE 的授權不允許這件事。
      NiTE 的授權是:只可以用在他自家的晶片產品上。

  4. Heresy 您好
    我把 http://goo.gl/G5fcS 這個網址的main.cpp整個複製到程式去執行,執行錯誤
    Find user generator failed: Can’t create any node of the requested type!
    我把nRetVal = g_UserGenerator.Create( g_Context ,&xQuery);其中的&xQuery拿掉之後,
    執行到g_MockDepth.SetData(depthMD);也是發生錯誤,我把這行註解掉之後,程式便正常執行 了,我想請問的是,g_MockDepth.SetData(depthMD);一定要執行嘛!?照理來說好像要,但是我執行之後就掛掉了,請問heresy知道為什麼嘛!?

      • 我知道,但是我加上這兩個部分,就掛了…
        沒加 反而正常…正常是指 你有修改的部分都有顯示出來!!

        • 確認了一下。
          你是使用 1.5.4.0 unstable 嗎?如果是的話,請換回 stable 版。
          這次的 unstable 版的 MockDepthGenerator 似乎在 CreateBasedOn() 上有問題。

          • 我原本是用1.5.4.0 unstable,
            但我改成openni-win64-1.5.2.23-dev stable之後
            還是有一樣的問題….

          • 請問Heresy,

            我現在改回stable版

            Depth也正常了,但是換成UserGenerator 無法使用,
            所有的Callback都沒有反應…
            請問您有遇過這問題嘛!?

          • 不好意思,我找到問題了!

            我忘記SetData給g_MockDepth了!!

  5. Heresy大 請問這樣可以在修改xDepthData的時候 把全部的pixel 換成另一張depth(也就是換一張圖,source是kinect抓的),然後丟給user generator畫出另一張深度圖的skeleton嗎 感謝~!

  6. Heresy大大你好,
    我試著把文章的內容加到
    “使用 Qt 顯示 OpenNI 的人體骨架"文中的Initial()和UpdateData()的部份
    可是我怎麼是就是只能改掉Qt的顯示部分,判別骨架的部份還是無法更改

    我在想是不是因為我使用的影像是錄下來的.oni檔來分析
    所以程式無法讀取新的xMockDepth去更改新的判別內容
    而是讀取.oni檔舊有的m_Depth node
    如果是這樣的話,那請問要如何解決這個問題呢?
    還是就只能重錄一途阿??

    期待你的回答

    謝謝你的分享

    • 我後來又試過直接接上kinect測試
      我也是把rDepthMap( x, y ) = 0直接用;
      Qt的畫面都是只有彩色圖
      但人走進去還是有paint畫出來的人骨架耶
      是要怎麼強迫他讀取新的xMockDepth啊

      謝謝你分享了

    • 播放 ONI 的時候,本身就是使用 mockDepthGenerator 了,再加上一層並不會有問題。
      實際上 Heresy 自己在測試的時候,就是採用 ONI 檔來做測試的。

      請確定你有把該修改的部分修改完。
      主要是四個部分:
      1. 建立 mockDepthGenerator
      2. 修改 User Generator 的建立方法
      3. 修改 OpenNI context 更新資料的方法
      4. 讀取深度修改後丟給 mockDepthGenerator

      • 我把原本的Initial()改成如下
        bool Initial()
        {
        // Initial OpenNI Context

        m_eResult = m_Context.Init();
        ifstream inFile(“E:\\Q\\depth_data\\path.txt");
        string file_record;
        getline(inFile,file_record);

        play_buttom.SetPlaybackSpeed(speed_for_replay);

        play_buttom.SetRepeat(0);

        if( CheckError( “Context Initial failed" ) )
        return false;

        // create image node
        m_eResult = m_Image.Create( m_Context );
        if( CheckError( “Create Image Generator Error" ) )
        return false;

        // create depth node
        m_eResult = m_Depth.Create( m_Context );
        if( CheckError( “Create Depth Generator Error" ) )
        return false;

        //更改
        xMockDepth.CreateBasedOn(m_Depth,"MockDepth");

        xQuery.AddNeededNode(“MockDepth");

        xUser.Create(m_Context, &xQuery);

        // set nodes 更改
        m_eResult = m_Depth.GetAlternativeViewPointCap().SetViewPoint( m_Image );
        CheckError( “Can’t set the alternative view point on depth generator" );

        XnCallbackHandle hUserCB;
        xUser.RegisterUserCallbacks( CB_NewUser, NULL, NULL, hUserCB );

        xUser.GetSkeletonCap().SetSkeletonProfile( XN_SKEL_PROFILE_ALL );
        //m_User.GetSkeletonCap().SetSkeletonProfile( XN_SKEL_PROFILE_HEAD_HANDS );

        XnCallbackHandle hCalibCB;
        xUser.GetSkeletonCap().RegisterToCalibrationComplete( CB_CalibrationComplete, NULL, hCalibCB );

        return true;
        }

        • UpdateData()修改如下,

          bool UpdateData()
          {

          // update
          m_eResult = m_Context.WaitOneUpdateAll(m_Depth);
          if( CheckError( “Update Data" ) )
          return false;

          // get new data

          m_Depth.GetMetaData( m_DepthMD );

          m_DepthMD.MakeDataWritable();
          xn::DepthMap& rDepthMap = m_DepthMD.WritableDepthMap();
          for( unsigned int y = 0; y < rDepthMap.YRes(); ++ y )
          for( unsigned int x = 0; x < rDepthMap.XRes(); ++ x )
          rDepthMap( x, y ) = 0;

          //transformDepthMD(m_DepthMD);

          xMockDepth.SetData(m_DepthMD);

          m_Image.GetMetaData( m_ImageMD );
          pImageMap=m_Image.GetRGB24ImageMap();

          return true;
          }

          • 而新加入的宣告則放在 class COpenNI 的
            private:
            xn::MockDepthGenerator xMockDepth;
            xn::Query xQuery;
            xn::UserGenerator xUser;

            只是不知道為何就是不能去更改骨架的結果耶
            試了好幾次都無法

            麻煩Heresy解惑了
            謝謝

      • 你好,Heresy 這邊也重現你的問題了。
        花了好一段時間,最後是發現…
        應該是 mock depth generator 的名稱命名不太好,導致在根據名稱做 query 的時候找錯 dpeth generator 造成的…
        似乎某些名稱(例如這邊的「MockDepth」)、在某些狀況下,似乎會出問題去用到其他的 depth generator…
        Heresy 這邊只要換一個名字(「mock-depth」)就可以了。
        麻煩你再試試看。

        • WOW~~~
          真的可以用了耶~~
          謝謝Heresy~~

          不過跑起來就真的會有點lag~~

          • 建議:
            1. 如果是使用 ASUS Xtion 的話,可以考慮用 QVGA 解析度
            2. 想辦法平行化!GPGPU 難度很高,但是應該至少可以透過 OpenMP 來針對多核心做平行化加速~

  7. Hi Heresy,
    Great post , but i am having run time errors while

    xDepthData.MakeDataWritable ();
    / / Modify the data ….
    so when i try to modify it or even access it , it says , “read access violation error", i dont know whats the problem any idea ? :)
    thanks

    • nRetVal = g_Context.FindExistingNode(XN_NODE_TYPE_DEPTH,
      g_DepthGenerator);
      CHECK_RC(nRetVal, “Find depth generator");
      nRetVal =
      g_mockdepth.CreateBasedOn(g_DepthGenerator,"MockDepth");
      CHECK_RC(nRetVal, “Create mock depth node");
      xn::Query xQuery;
      xQuery.AddNeededNode(“MockDepth");
      // Adding the Skeletal and Pose detection capabilities
      xQuery.AddSupportedCapability(XN_CAPABILITY_SKELETON);
      xQuery.AddSupportedCapability(XN_CAPABILITY_POSE_DETECTION);
      nRetVal = g_Context.FindExistingNode(XN_NODE_TYPE_USER,
      g_UserGenerator);
      if (nRetVal != XN_STATUS_OK) {
      nRetVal = g_UserGenerator.Create(g_Context,&xQuery);
      CHECK_RC(nRetVal, “Find user generator");
      }
      This is what is done in the while loop
      {
      g_Context.WaitOneUpdateAll(g_DepthGenerator);
      g_DepthGenerator.GetMetaData(depthMD);
      nRetVal = depthMD.MakeDataWritable();
      transformDepthMD(depthMD); // we could use this to modify the
      depth map but for the time being i have commented it out
      nRetVal = g_mockdepth.SetData(depthMD);
      …..
      …..
      …..
      …..
      …..
      …..
      }

      so when it accesses the depthMD in transform function it gives violation error

      • 首先,你應該是使用 XML 來做初始化?
        不過這邊這種 mock depth generator 的方法,應該沒辦法透過 XML 來做建立;但是你的寫法似乎有去考慮到有去找以建立的 user generator,沒找到再透過 query 條件來建立?
        這樣的話…如果在 XML 裡面有建立 user generator 的話,應該會導致 mock node 的機制失效…這點可能是要注意的地方。

        再來,可以麻煩你檢查一下 MakeDataWritable() 回傳的結果嗎?
        另外,不知道你的 transformDepthMD() 這個含式是怎麼做處理的?
        是按照官方範例的做法嗎?

        • creation of user generator does not give any error.
          I am using the xml files to do the initialization.
          depthMD.MakeDataWritable() returns XN_STATUS_OK
          transformDepthMD () contains the same function as the official sample , i am not doing anything to modify the depth map, it gives error while reading theDepthMap (x, y)
          in the fist iteration.
          I do not know where anything is getting wrong.

          • 可以麻煩確認一下,原始的 NiRecordSynthetic 範例是否可以正確執行嗎?

            另外,Heresy 有試著修改 OpenNI 的「NiUserTracker」這個範例,讓他使用 mock depth generator 來做處理。
            修改後的 main.cpp 在: http://goo.gl/G5fcS
            他會在深度影像上,每隔五個像素、加上一條橫線。

            可以比對原來的版本,或是搜尋「by Heresy」找到程式有修改的地方。
            這樣的程式在 Heresy 這邊是可以正確執行的。

            麻煩試試看。

          • Thank you so much for the help :) . I figured out my error
            I forgot to update the depthMD after context data generation.

            g_DepthGenerator.GetMetaData(depthMD) // previously i was only doing this
            g_Context.WaitOneUpdateAll (g_DepthGenerator);

            g_DepthGenerator.GetMetaData(depthMD) // somehow this line got deleted
            now its working fine.
            Thanks a lot Heresy :)

  8. 嚇!!!原來還有這招……
    我之前還以為只能把他轉OpenCV格式才能改深度資訊勒> <

發表迴響

在下方填入你的資料或按右方圖示以社群網站登入:

WordPress.com 標誌

您的留言將使用 WordPress.com 帳號。 登出 /  變更 )

Facebook照片

您的留言將使用 Facebook 帳號。 登出 /  變更 )

連結到 %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.