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 viewpoint
glMatrixMode(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>