Kinect体感机器人(二)—— 体感识别
By 马冬亮(凝霜 Loki)
一个人的战争(http://blog.csdn.net/MDL13412)
体感技术属于NUI(自然人机界面)的范畴,可以让用户通过肢体语言与周边设备或环境互动,其实现手段主要包括:惯性感测、光学感测以及惯性及光学联合感测。市场上比较成熟的产品主要有:微软的Kinect、索尼的PS Move、任天堂的Vii以及来自华硕的Xtion。由于没有华硕Xtion的实物,我不对其进行评测,下表是对其余三种体感设别的评测:
通过上图的对比,来自微软的Kinect具有压倒性的优势,所以Kinect方案最终被我们采纳。
亲身体验过的成功应用
首先是我们制作的体感机器人,实现了对人体动作的模仿,可以应用到灾后搜救领域;
接下来是香港中文大学的“Improving Communication Ability of the Disabled -Chinese Sign Language Recognition and Translation System”;其实就是手语翻译;
还有来自上海大学的3D影院,其通过Kinect追踪用户的头部,让画面主动适应用户。
操作系统的选择
关于操作系统的选择肯定是Linux了,参加嵌入式的比赛用Windows没有好下场,笑:-)
下表是对常见Linux发行版的评测:
限于开发板的处理速度与图形性能,最终方案为Fedora 16发行版。
体感驱动库的选择
体感驱动库我只找到了两个选择:OpenNI和Kinect SDK,后者只能用在Windows上,果断放弃,其评测如下表所示:
代码——初始化体感设备
// 初始化体感设备XnStatus result;xn::Context context;xn::ScriptNode scriptNode;xn::EnumerationErrors errors;// 使用XML文件配置OpenNI库result = context.InitFromXmlFile(CONFIG_XML_PATH, scriptNode, &errors);if (XN_STATUS_NO_NODE_PRESENT == result){XnChar strError[1024];errors.ToString(strError, 1024);NsLog()->error(strError);return 1;}else if (!NsLib::CheckOpenNIError(result, "Open config XML fialed"))return 1;NsLib::TrackerViewer::createInstance(context, scriptNode);NsLib::TrackerViewer &trackerViewer = NsLib::TrackerViewer::getInstance();if (!trackerViewer.init())return 1;trackerViewer.run();
上述代码中的TrackerViewer是使用OpenGL进行绘制的人体骨骼图像,整个程序的同步操作也在此处理,下面给出上述代码引用到的关键代码:
// 单例模式,只允许一个实例
TrackerViewer *TrackerViewer::pInstance_ = 0;void TrackerViewer::createInstance(xn::Context &context,xn::ScriptNode &scriptNode)
{assert(!pInstance_);pInstance_ = new TrackerViewer(context, scriptNode);
}
// 初始化TrackerViewer
bool TrackerViewer::init()
{if (!initDepthGenerator())return false;if (!initUserGenerator())return false;inited_ = true;return true;
}// 初始化深度传感器
bool TrackerViewer::initDepthGenerator()
{XnStatus result;result = Context.FindExistingNode(XN_NODE_TYPE_DEPTH, DepthGenerator);if (!CheckOpenNIError(result, "No depth generator found. Check your XML"))return false; return true;
}// 初始化骨骼识别引擎
bool TrackerViewer::initUserGenerator()
{XnStatus result;// DepthGenerator.GetMapOutputMode(ImageInfo);result = Context.FindExistingNode(XN_NODE_TYPE_USER, UserGenerator);if (!CheckOpenNIError(result, "Use mock user generator")){result = UserGenerator.Create(Context);if (!CheckOpenNIError(result, "Create mock user generator failed"))return false;}result = UserGenerator.RegisterUserCallbacks(User_NewUser, User_LostUser, NULL, hUserCallbacks_);if (!CheckOpenNIError(result, "Register to user callbacks"))return false;result = UserGenerator.GetSkeletonCap().RegisterToCalibrationStart(UserCalibration_CalibrationStart, NULL, hCalibrationStart_);if (!CheckOpenNIError(result, "Register to calibration start"))return false;result = UserGenerator.GetSkeletonCap().RegisterToCalibrationComplete(UserCalibration_CalibrationComplete, NULL, hCalibrationComplete_);if (!CheckOpenNIError(result, "Register to calibration complete"))return false;if (UserGenerator.GetSkeletonCap().NeedPoseForCalibration()){NeedPose = true;if (!UserGenerator.IsCapabilitySupported(XN_CAPABILITY_POSE_DETECTION)){NsLog()->error("Pose required, but not supported");return false;}result = UserGenerator.GetPoseDetectionCap().RegisterToPoseDetected(UserPose_PoseDetected, NULL, hPoseDetected_);if (!CheckOpenNIError(result, "Register to Pose Detected"))return false;UserGenerator.GetSkeletonCap().GetCalibrationPose(StrPose);}UserGenerator.GetSkeletonCap().SetSkeletonProfile(XN_SKEL_PROFILE_ALL);result = UserGenerator.GetSkeletonCap().RegisterToCalibrationInProgress(MyCalibrationInProgress, NULL, hCalibrationInProgress_);if (!CheckOpenNIError(result, "Register to calibration in progress"))return false;result = UserGenerator.GetPoseDetectionCap().RegisterToPoseInProgress(MyPoseInProgress, NULL, hPoseInProgress_);if (!CheckOpenNIError(result, "Register to pose in progress"))return false;return true;
}
//------------------------------------------------------------------------------
// OpenNI Callbacks
//------------------------------------------------------------------------------
void XN_CALLBACK_TYPE TrackerViewer::User_NewUser(xn::UserGenerator& generator, XnUserID nId, void* pCookie)
{std::cout << "New user: " << nId << std::endl;if (TrackerViewer::getInstance().NeedPose){TrackerViewer::getInstance().UserGenerator.GetPoseDetectionCap().StartPoseDetection(TrackerViewer::getInstance().StrPose, nId);}else{TrackerViewer::getInstance().UserGenerator.GetSkeletonCap().RequestCalibration(nId, TRUE);}
}void XN_CALLBACK_TYPE TrackerViewer::User_LostUser(xn::UserGenerator& generator, XnUserID nId, void* pCookie)
{std::cout << "Lost user: " << nId << std::endl;
}void XN_CALLBACK_TYPE TrackerViewer::UserPose_PoseDetected(xn::PoseDetectionCapability& capability, const XnChar* strPose, XnUserID nId, void* pCookie)
{std::cout << "Pose " << TrackerViewer::getInstance().StrPose<< " detected for user " << nId << std::endl;TrackerViewer::getInstance().UserGenerator.GetPoseDetectionCap().StopPoseDetection(nId);TrackerViewer::getInstance().UserGenerator.GetSkeletonCap().RequestCalibration(nId, TRUE);
}void XN_CALLBACK_TYPE TrackerViewer::UserCalibration_CalibrationStart(xn::SkeletonCapability& capability, XnUserID nId, void* pCookie)
{std::cout << "Calibration started for user " << nId << std::endl;
}void XN_CALLBACK_TYPE TrackerViewer::UserCalibration_CalibrationComplete(xn::SkeletonCapability& capability, XnUserID nId, XnCalibrationStatus eStatus,void* pCookie)
{if (eStatus == XN_CALIBRATION_STATUS_OK){std::cout << "Calibration complete, start tracking user " << nId << std::endl;TrackerViewer::getInstance().UserGenerator.GetSkeletonCap().StartTracking(nId);}else{if (TrackerViewer::getInstance().NeedPose){TrackerViewer::getInstance().UserGenerator.GetPoseDetectionCap().StartPoseDetection(TrackerViewer::getInstance().StrPose, nId);}else{TrackerViewer::getInstance().UserGenerator.GetSkeletonCap().RequestCalibration(nId, TRUE);}}
}
// 开始追踪用户
void TrackerViewer::run()
{assert(inited_);XnStatus result;result = Context.StartGeneratingAll();if (!CheckOpenNIError(result, "Start generating failed"))return;initOpenGL(&NsAppConfig().Argc, NsAppConfig().Argv);glutMainLoop();
}
// 初始化OpenGL
void TrackerViewer::initOpenGL(int *argc, char **argv)
{glutInit(argc, argv);glutInitDisplayMode(GLUT_RGB | GLUT_DOUBLE | GLUT_DEPTH);glutInitWindowSize(ImageInfo.nXRes, ImageInfo.nYRes);glutCreateWindow ("User Tracker Viewer");//glutFullScreen();glutSetCursor(GLUT_CURSOR_NONE);glutKeyboardFunc(glutKeyboard);glutDisplayFunc(glutDisplay);glutIdleFunc(glutIdle);glDisable(GL_DEPTH_TEST);glEnable(GL_TEXTURE_2D);glEnableClientState(GL_VERTEX_ARRAY);glDisableClientState(GL_COLOR_ARRAY);
}
//------------------------------------------------------------------------------
// OpenGL Callbacks
//------------------------------------------------------------------------------
void TrackerViewer::glutDisplay()
{if (TrackerViewer::getInstance().SignalExitApp)exit(0);glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);// Setup the OpenGL viewpointglMatrixMode(GL_PROJECTION);glPushMatrix();glLoadIdentity();static TrackerViewer &trackerViewer = TrackerViewer::getInstance();xn::DepthMetaData depthMD;trackerViewer.DepthGenerator.GetMetaData(depthMD);glOrtho(0, depthMD.XRes(), depthMD.YRes(), 0, -1.0, 1.0);glDisable(GL_TEXTURE_2D);trackerViewer.Context.WaitOneUpdateAll(trackerViewer.UserGenerator);xn::SceneMetaData sceneMD;trackerViewer.DepthGenerator.GetMetaData(depthMD);trackerViewer.UserGenerator.GetUserPixels(0, sceneMD);DrawDepthMap(depthMD, sceneMD);glutSwapBuffers();
}void TrackerViewer::glutIdle()
{if (TrackerViewer::getInstance().SignalExitApp)exit(0);glutPostRedisplay();
}void TrackerViewer::glutKeyboard(unsigned char key, int x, int y)
{switch (key){case 27:TrackerViewer::getInstance().SignalExitApp = true;default:break;}
}
上述代码完成了GUI的逻辑操作,关键说明如下:
// 将Kinect采集到的深度图像映射到OpenGL使用的2D坐标系中xn::DepthMetaData depthMD;trackerViewer.DepthGenerator.GetMetaData(depthMD);glOrtho(0, depthMD.XRes(), depthMD.YRes(), 0, -1.0, 1.0);glDisable(GL_TEXTURE_2D);
// 等待Kinect更新
trackerViewer.Context.WaitOneUpdateAll(trackerViewer.UserGenerator);
// 获取Kinect采集到的深度图像和用户信息,为绘制骨骼点和计算关节角度做准备xn::SceneMetaData sceneMD;trackerViewer.DepthGenerator.GetMetaData(depthMD);trackerViewer.UserGenerator.GetUserPixels(0, sceneMD);
// 绘制人体骨骼图像并计算关节角度,详见“Kinect体感机器人(三)—— 空间向量法计算关节角度”
DrawDepthMap(depthMD, sceneMD);
<OpenNI><Licenses><!-- Add application-specific licenses here <License vendor="vendor" key="key"/>--></Licenses><Log writeToConsole="false" writeToFile="false"><!-- 0 - Verbose, 1 - Info, 2 - Warning, 3 - Error (default) --><LogLevel value="3"/><Masks><Mask name="ALL" on="true"/></Masks><Dumps></Dumps></Log><ProductionNodes><!-- Set global mirror --><GlobalMirror on="true"/><!-- Create a depth node and give it a name alias (useful if referenced ahead in this script) --><Node type="Depth" name="MyDepth"><Query><!-- Uncomment to filter by vendor name, product name, etc.<Vendor>MyVendor inc.</Vendor><Name>MyProduct</Name><MinVersion>1.2.3.4</MinVersion><Capabilities><Capability>Cropping</Capability></Capabilities>--></Query><Configuration><MapOutputMode xRes="640" yRes="480" FPS="30"/> <!-- Uncomment to override global mirror<Mirror on="false" /> --></Configuration></Node></ProductionNodes>
</OpenNI>