PrimeSense Grab Detector编程示例
上篇文章介绍过,PrimeSense有推出一能检测手部“握拳”(grab)和“松开”(release)动作的函数库Grab Detector。虽然这两个库的使用有一些限制,但是作为动作捕捉程序开发爱好者,值得看看。 下载GrabDetector库,里面PrimeSense提供一个名为“GrabViewer”的示例可参考(在 \Samples\GrabViewer),程序架构和OpenNI 2或 NiTE 2 的示例程序架构大致相同,所以如果之前有做过这方面的程序,很好上手。 OpenNI 与 NiTE初始化由于 GrabDetector除了需要用到深度和彩色图像,也需要使用 NiTE 的 HandTracker 来定位手的位置,所以在使用 GrabDetector之前,需要针对 OpenNI 2 和 NiTE 2,配置好各参量。 以 OpenNI的部分为例,主要就是建立出深度和彩色图像的VideoStream,完成参数配置,代码如下: // Initial OpenNI 因为 GrabDetector使用彩色图像辅助深度图像来检测用户手部动作,所以一定要把彩色图像和深度图像,通过Device的setImageRegistrationMode()来做矫正处理,毕竟两个摄像头之间还是有几厘米的距离,采集到的图像信息有一定的位置偏差。 另外,官方文档要求一定要使用setDepthColorSyncEnabled()开启彩色图像和深度图像的同步,不过实际上如果不打开也一样能使用,可能会有点监测偏差。 NiTE 的部分要建立HandTracker对象以检测手部位置,还是老流程依据 click和 wave 两种手势让摄像头找到运动中的手。 // Initial NiTE
NiTE::initialize();
// create hand tracker
HandTracker mHandTracker;
mHandTracker.create( &devDevice );
// set gesture
mHandTracker.startGestureDetection( GESTURE_WAVE );
mHandTracker.startGestureDetection( GESTURE_CLICK );
GrabDetector 只是要手部的位置,所以理论上如果使用UserTracker追踪到人体骨骼中的左右手位置,应该也还是可以使用的,不过没试过,有兴趣的兄弟可以搞搞看。 GrabDetector 初始化在前一篇文章已经提过,基本上要使用 GrabDetector,主要是要使用PSLabs::CreateGrabDetector()函数,建立出PSLabs::IGrabDetector的对象pGrabDetector来进一步处理。 在建立时,除了需要输入使用的Device外(OpenNI支持多摄像头同时检测),也需要指定数据文档(Redistributable\Common\Data\grab_gesture.dat)所在的位置;我是直接将该文档复制到了VC++项目工程中: PSLabs::IGrabDetector* pGrabDetector
= PSLabs::CreateGrabDetector( devDevice, "GrabDetector/" );
if( pGrabDetector == NULL
|| pGrabDetector->GetLastEvent( NULL ) != openni::STATUS_OK)
{
cerr << "Can't initialize grab detector: "
<< pGrabDetector->GetLastEvent( NULL ) << endl;
return -1;
}
到了这一步,准备工作就基本完成了。 主程序主程序的部分,使用HandTracker中当前手的世界坐标xyz值检测该位置的手掌张开或握紧的状态,所以开始还是一样,通过检测手势,开始手部的追踪(process gestures的部分): vsDepthStream.start();
vsColorStream.start();
PSLabs::IGrabEventListener::GrabEventType eLastEvent
= PSLabs::IGrabEventListener::NO_EVENT;
HandId mHandID = 0;
for( int t = 0; t < 500; ++ t )
{
// get new frame
HandTrackerFrameRef mHandFrame;
if( mHandTracker.readFrame( &mHandFrame ) == nite::STATUS_OK )
{
// process gestures
const nite::Array<GestureData>& aGestures = mHandFrame.getGestures();
for( int i = 0; i < aGestures.getSize(); ++ i )
{
const GestureData& rGesture = aGestures[i];
const Point3f& rPos =rGesture.getCurrentPosition();
cout << "Get gesture: " << rGesture.getType() << " at " ;
cout << rPos.x << ", " << rPos.y << ", " << rPos.z << endl;
mHandTracker.startHandTracking( rPos, &mHandID );
}
// process hands
// ...
}
}
通过mHandID变量,记录最后一个被追踪的手,给 GrabDetector做手部状态的分析。 而“process hand”的部分,开始是和一般的HandTracker的用法相同,需要先使用getHands()取得手部的位置;之后,如果找到我们要处理的手的ID的话,就进入处理程序部分。 // process hands
const nite::Array<HandData>& aHands = mHandFrame.getHands();
for( int i = 0; i < aHands.getSize(); ++ i )
{
const HandData& rHand = aHands[i];
if( rHand.getId() == mHandID )
{
if( rHand.isLost() )
{
cout << "Hand Lost";
mHandID = 0;
}
if( rHand.isTracking() )
{
// update hand position
const Point3f& rPos =rHand.getPosition();
pGrabDetector->SetHandPosition( rPos.x, rPos.y, rPos.z );
// read color frame
VideoFrameRef mColor;
vsColorStream.readFrame( &mColor );
// update depth and color image
pGrabDetector->UpdateFrame( mHandFrame.getDepthFrame(), mColor );
// check last event if not using listener
PSLabs::IGrabEventListener::EventParams mEvent;
if( pGrabDetector->GetLastEvent( &mEvent ) == openni::STATUS_OK )
{
// if status changed
if( mEvent.Type != eLastEvent )
{
switch( mEvent.Type )
{
case PSLabs::IGrabEventListener::GRAB_EVENT:
cout << "Grab" << endl;
break;
case PSLabs::IGrabEventListener::RELEASE_EVENT:
cout << "Release" << endl;
break;
case PSLabs::IGrabEventListener::NO_EVENT:
break;
}
}
eLastEvent = mEvent.Type;
}
}
break;
}
}
接下来,要把新的数据传递给pGrabDetector,让它来分析。首先调用setHandPosition()方法,把手的位置传入,之后调用UpdateFrame(),把深度、彩色的VideoFrameRef作为参数传入,以便分析。 之后,pGrabDetector就开始分析,并根据输入的资料,在内部产生对应的事件(event)。 以官方的示例使用的是 Listener的架构(callback function)来处理信息,不过也可以使用GetLastEvent(),来取得手的最后状态。状态信息在PSLabs::IGrabEventListener::EventParams中,包含了pGrabDetector最后一次产生的事件内容;一般可直接通过其Type,来判断为哪一种事件。 而目前 GrabDetector的事件有三种,分别为GRAB_EVENT、RELEASE_EVENT以及NO_EVENT。 如果每一个图像帧都去进行手部状态监测,并输出当前的手部状态,输出会很乱,因此在本程序中,使用了另外一个eLastEvent来记录之前的手部状态,只有在当前手掌状态和上一次不同时,才有输出。 Listener 模式而如果是要使用 listener模式,就是常见的事件响应模式,只有当有事件、也就是手的状态发生改变,才触发事件。其实OpenNI 2和 NiTE 2 也是支持事件响应模式的。 以 GrabDetector为例,如果要使用 listener 模式的话,是要先继承PSLabs::IGrabEventListener,创建一个自己的 Listener对象,并在里面重载ProcessGrabEvent()方法即可;如下: class GrabEventListener : public PSLabs::IGrabEventListener
{
public:
GrabEventListener(){}
virtual void DLL_CALL ProcessGrabEvent( const EventParams& params )
{
switch( params.Type )
{
case PSLabs::IGrabEventListener::GRAB_EVENT:
cout << "Grab" << endl;
break;
case PSLabs::IGrabEventListener::RELEASE_EVENT:
cout << "Release" << endl;
break;
case PSLabs::IGrabEventListener::NO_EVENT:
break;
}
}
};
可以看到,ProcessGrabEvent()的工作,和上面、放在主程序里面的代码是相同的。 定义完GrabEventListener对象后,如果要使用,就是要在完成 GrabDetector的初始化后,调用AddListener()函数,来指定这个方法处理 GrabDetector产生的事件。代码如下: GrabEventListener mEventListener;
pGrabDetector->AddListener( &mEventListener );
如果按照上面的流程,那上面主程序里面,本来用GetLastEvent()来分析事件的“// check last event if not using listener”整段代码就可以删除了~因为使用pGrabDetector的setHandPosition()和UpdateFrame()这两个方法更新手部坐标后,如果有触发新的事件,pGrabDetector就自动调用mEventListener的ProcessGrabEvent()方法~
结合OpenCV测试检测手及其动作
2014.4.1
|
|
|
|