作用:
该工具可用来对/me/head坐标系进行短期(在osvr server运行期间)校准,将头部的方向校正为正前方。它主要是为只有orientation数据的追踪器准备的;而带有完整pose信息的追踪器就不需要这个工具了,它们一般都会带有外部固定的已知位置的地标或者摄像头,而由于已知位置就能够建立一个不变的期望的坐标系。
如何使用:
在运行OSVR reset yaw之前,OSVR server应该先运行并且能正常接受到追踪数据。应用程序也在运行。
1.开启Reset Yaw应用程序。它会连接上OSVR Server并随后在提醒继续操作之前会获取追踪数据
2.应用程序会提示“将你的设备放置在零点方位并按下enter键”:这时头显应该朝向想要的成为正前方的方位,随后按下enter键
3.该应用程序会记录按下enter键这一刻的方位信息,然后再计算这个方位和一个默认方位间关于Y轴的相对旋转信息,随后给OSVR server发生一条信息以更新它的节点/me/head,该节点路径包含一个变形转换层可对头部坐标系进行转换操作,因此可使新的方位变为/me/head的有效正前方。
4.当所有的计算以及服务通信都完成时,应用程序会提醒再次按下enter键退出reset yaw程序,改变将会立即生效。
若是不满意,可再次运行该程序;新创建的校正数据将会刷掉旧的校正数据,所有正在运行的OSVR应用程序将会立即刷新以显示新校正数据的转换结果。
缺陷:
1.该校正数据只在当前服务进程操作时会被保存,其他任何地方都无法保存,因此当关闭OSVR server时,它们就会丢失。(大多数IMU设备在这一点上设计成在每个阶段都需要这这样的校正操作,这样才是合理的操作模式)
2.该操作只对/me/head生效,而对那些即使在同一坐标系中的追踪器或摄像头都不会起到效果。(这也是为什么说它最适合在仅仅有方位追踪器的系统中使用的原因)
3.正如名字所提示的,它只能校准偏航角yaw/heading:地平面假设正确。如果追踪被混合进它的转换中而不仅仅只是去校正它的yaw角,使用该工具也会出现错误。
代码分析:
{
namespace po = boost::program_options;
// clang-format off
po::options_description desc("Options");
desc.add_options()
("help", "produce help message")
("path", po::value<std::string>()->default_value("/me/head"), "path to reset-yaw on")
("no-wait", "headless mode - immediately resets yaw without waiting")
;
// clang-format on
po::variables_map vm;
po::store(po::command_line_parser(argc, argv).options(desc).run(), vm);
po::notify(vm);
const bool noWait = vm.count("no-wait");
{
/// Deal with command line errors or requests for help
bool usage = false;
if (vm.count("help")) {
cout << "Usage: osvr_reset_yaw [options]" << endl;
cout << desc << "\n";
return 1;
}
}
osvr::clientkit::ClientContext ctx("com.osvr.bundled.resetyaw");
std::string const path = vm["path"].as<std::string>();
// Get the interface associated with the destination route we
// are looking for.
osvr::clientkit::Interface iface = ctx.getInterface(path);
{
ClientMainloopThread client(ctx);
cout << "Running client mainloop briefly to start up..." << endl;
client.loopForDuration(boost::chrono::seconds(2));
cout << "Removing any previous yaw-reset transforms..." << endl;
// Get the alias element corresponding to the desired path, if possible.
auto elt = getAliasElement(ctx, path);
if (!elt) {
// No luck, sorry.
cerr << "Couldn't get the alias at " << path << endl;
return -1;
}
// Get a reference to the source associated with the portion
// of the tree that has this destination. Then clean out
// any prior instance of our meddling by checking for an
// entry that has our flag key in it. Then replace the
// original source tree with the cleaned tree. Send this
// cleaned alias back to the server.
osvr::common::ParsedAlias origAlias{elt->getSource()};
if (!origAlias.isValid()) {
cerr << "Couldn't parse the alias!" << endl;
return -1;
}
cout << "Original transform: "
<< origAlias.getAliasValue().toStyledString() << "\n" << endl;
osvr::common::GeneralizedTransform xforms{origAlias.getAliasValue()};
osvr::common::remove_if(xforms, [](Json::Value const ¤t) {
return current.isMember(FLAG_KEY) && current[FLAG_KEY].isBool() &&
current[FLAG_KEY].asBool();
});
cout << "Cleaned transform: "
<< xforms.get(origAlias.getLeaf()).toStyledString() << "\n"
<< endl;
elt->setSource(
osvr::common::jsonToCompactString(xforms.get(origAlias.getLeaf())));
ctx.get()->sendRoute(createJSONAlias(path, *elt));
cout << "Sent cleaned transform, starting again and waiting a few "
"seconds for startup..."
<< endl;
client.start();
boost::this_thread::sleep(SETTLE_TIME);
if (!noWait) {
cout << "\n\nPlease place your device for " << path
<< " in its 'zero' orientation and press enter." << endl;
std::cin.ignore();
}
OSVR_OrientationState state;
OSVR_TimeValue timestamp;
OSVR_ReturnCode ret;
{
/// briefly interrupt the client mainloop so we can get stuff done
/// with the client state.
ClientMainloopThread::lock_type lock(client.getMutex());
ret = osvrGetOrientationState(iface.get(), ×tamp, &state);
if (ret != OSVR_RETURN_SUCCESS) {
cerr << "Sorry, no orientation state available for this path - "
"are you sure you have a device plugged in and your "
"path correct?"
<< endl;
if (!noWait) {
std::cin.ignore();
}
return -1;
}
auto q = osvr::util::eigen_interop::map(state);
auto yaw = osvr::util::extractYaw(q);
cout << "Correction: " << -yaw << " radians about Y" << endl;
Json::Value newLayer(Json::objectValue);
newLayer["postrotate"]["radians"] = -yaw;
newLayer["postrotate"]["axis"] = "y";
newLayer[FLAG_KEY] = true;
xforms.wrap(newLayer);
cout << "New source: "
<< xforms.get(origAlias.getLeaf()).toStyledString() << endl;
elt->setSource(osvr::common::jsonToCompactString(
xforms.get(origAlias.getLeaf())));
ctx.get()->sendRoute(createJSONAlias(path, *elt));
boost::this_thread::sleep(SETTLE_TIME / 2);
}
boost::this_thread::sleep(SETTLE_TIME);
if (!noWait) {
cout << "Press enter to exit.";
std::cin.ignore();
}
}
return 0;
}
输出: