使用OpenNI 控制Kinect 的马达

2012.02.4 No Comments

虽然应该有很多人是用Microsoft Kinect for Xbox 360来进行OpenNI程式的开发,但是到目前为止,市面上OpenNI真正的相容装置,其实还是只有ASUS的Xtion Pro系列,要使用Kinect是需要特殊修改过的驱动程式的。而在驱动程式的部分,目前大部分的开发应该都还是使用avin2所提供SensorKinect吧~

而如果是要使用Kinect进行程式开发的话,使用跨平台、开放原始码的OpenNI和使用微软官方的Kinect for Windows SDK相比,一个很大的缺点,就是功能上的不完整;这包括了声音、以及Kinect上的马达。

声音的部分,OpenNI本身有提供API来做控制,目前的问题应该是SensorKinect还没有支援(有列在todo list);而马达的部分,OpenNI本来的API并没有支援,不过目前看来也已经是打算透过xn::GeneralIntCapability的形式来做支援了,只是现在还是无法使用就是了。

不过,后来才发现,在官方论坛上,已经有强者透过OpenNI比较低阶的USB控制介面(XnUSB.h),来做到马达的控制了!原文可以参考《Easy way to control Kinect motor through OpenNI》一文;而目前OpenNI也已经把他的程式码放到github上的Samples里(连结)了!

他基本上是把相关功能封包成一个KinectMotors的类别,把相关的控制写成它的成员函式、以方便操作;其内容如下:

class KinectMotors
{
public :
   enum { MaxDevs = 16 };

public :
  KinectMotors()
  {
    m_isOpen = false ;
  }

  virtual ~KinectMotors()
  {
    Close();
  }

  bool Open()
  {
    const  XnUSBConnectionString *paths;
     XnUInt32 count;
     XnStatus res;

    // Init OpenNI USB
    res = xnUSBInit ();
     if (res != XN_STATUS_OK )
    {
      xnPrintError(res, "xnUSBInit failed" );
       return  false ;
    }

    // Open all "Kinect motor" USB devices
    res = xnUSBEnumerateDevices (0x045E /* VendorID */ , 0x02B0 /*ProductID*/ , &paths, &count);
     if (res != XN_STATUS_OK)
    {
      xnPrintError(res, "xnUSBEnumerateDevices failed" );
       return  false ;
    }

    // Open devices
    for (XnUInt32 index = 0; index < count; ++index)
    {
      res = xnUSBOpenDeviceByPath (paths[index], &m_devs[index]);
       if (res != XN_STATUS_OK)
      {
        xnPrintError(res, "xnUSBOpenDeviceByPath failed" );
         return  false ;
      }
    }

    m_num = count;

    XnUChar buf[1];   // output buffer

    // Init motors
    for (XnUInt32 index = 0; index < m_num; ++index)
    {
      res = xnUSBSendControl (m_devs[index], (XnUSBControlType) 0xc0, 0x10, 0x00, 0x00, buf, sizeof (buf), 0);
       if (res != XN_STATUS_OK)
      {
        xnPrintError(res, "xnUSBSendControl failed" );
        Close();
        return  false ;
      }

      res = xnUSBSendControl (m_devs[index], XN_USB_CONTROL_TYPE_VENDOR, 0x06, 0x01, 0x00, NULL, 0, 0);
       if (res != XN_STATUS_OK)
      {
        xnPrintError(res, "xnUSBSendControl failed" );
        Close();
        return  false ;
      }
    }
    m_isOpen = true ;
     return  true ;
  }

  void Close()
  {
    if (m_isOpen)
    {
      for (XnUInt32 index = 0; index < m_num; ++index)
      {
        xnUSBCloseDevice (m_devs[index]);
      }
      m_isOpen = false ;
    }
  }

  bool Move( int angle)
  {
    XnStatus res;
    // Send move control requests
    for (XnUInt32 index = 0; index < m_num; ++index)
    {
      res = xnUSBSendControl (m_devs[index], XN_USB_CONTROL_TYPE_VENDOR, 0x31, angle, 0x00, NULL, 0, 0);
       if (res != XN_STATUS_OK)
      {
        xnPrintError(res, "xnUSBSendControl failed" );
         return  false ;
      }
    }
    return  true ;
  }

private :
   XN_USB_DEV_HANDLE m_devs[MaxDevs];
  XnUInt32 m_num;
  bool m_isOpen;
};

而在程式里建立一个KinectMotors的物件后,就可以透过他的Move()这个函式,来进行Kinect仰角的调整了!简单的操作范例,大致上如下:

KinectMotors motors;
 if (!motors.Open())
   return 1;

motors.Move(31);

不过基本上,这个方法应该只算是一个雏形,和OpenNI提供的一般操作介面不完全一致;而且在这个类别里,他基本上会根据Vender ID和Product ID把系统里的Kinect马达装置都找出来(透过xnUSBEnumerateDevices()),然后都开启来控制;所以如果有使用多台Kinect的话,他会同时对所有的Kinect来进行操作如果要个别控制的话,则是需要改写这个类别,并针对得到的XnUSBConnectionString来和xn::Device的资讯(应该是「Create Info」、参考)做比对、以找出要控制的是哪一个Kinect 。

另外要注意的是,由于Kinect内部其实还有一个加速度感应器,可以判断自身的倾角,所以实际上这边透过Move()设定的角度,会是空间中绝对的角度也就是说,0度的时候,Kinect会是水平的,正值会往上抬、负值则会往下倾,直到到达目标角度为止;这是在使用时可能要注意的地方。

总之,如果有必要的话,透过这个方法应该是可以用来操作Kinect 的马达了~不过,还是希望OpenNI 能赶快把Kinect 纳入正式支援吧~ ^^"

本文转自http://kheresy.wordpress.com/2012/02/03/control_kinect_motor_via_openni/

Leave a Reply
You must be logged in to post a comment.