使用 OpenNI 控制 Kinect 的馬達

雖然應該有很多人是用 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 納入正式支援吧~ ^^"


10 thoughts on “使用 OpenNI 控制 Kinect 的馬達”

  1. to 柴郡猫

    Kinect 的馬達應該在硬體上就沒有左右轉的功能?

  2. 您好!这个xnUSBEnumerateDevices()函式中各个16进制的参数是怎么得来的?具体含义是什么啊?我查了半天没有找到这方面的解释,期待您的回答!

  3. to 自溟

    如果你是問 VendorID 和 ProductID 的話,他基本上只是廠商、硬體的識別碼。
    每家廠商、每款硬體的 ID 都不同。

  4. 哦,谢谢,上面的angle只是上下移动角度,如果要左右移动该怎么设置呢?

  5. to 自溟
    Kinect 的馬達應該在硬體上就沒有左右轉的功能

  6. 嗯,是的,我左右扭了一下,没有听到类似齿轮转动的声音,所以应该还没有。

  7. 你好,我用Easy way to control Kinect motor through opneni上的源码,但总是出现xnUSBOpenDeviceByPath failed:USB device not found 。但USB链接是好的,也可以跑基于微软kinect SDK的程序。不知博主有无解决方法。

發佈回覆給「地平线」的留言 取消回覆

發佈留言必須填寫的電子郵件地址不會公開。