OSVR头部追踪数据格式及VRPN数据处理流程
头部追踪器是以一个普通HID设备呈现的,并且不断的以高速率(一般为400次每秒,除了早期的硬件达不到这个速率之外)上报输入数据,但是在设备过渡时(比如在HDMI状态过渡时)会暂时性地挂起追踪器数据的上报。
以下为它的协议(以字节为单位):
第0个字节:
位0~3:上报版本号,当前为3
位4:只存在于版本3中,如果检测到视频数据则为“1”,否则为0
位5:只存在于版本3中,如果检测到为竖屏模式(1080X1920 视频)则为“1”,如果检测到为横屏模式(1920X1080),则为“0”
第1个字节:
消息序列号(8位)
第2个字节:
单位四元素i的低8位
第3个字节:
单位四元素i的高8位
第4个字节:
单位四元素j的低8位
第5个字节:
单位四元数j的高8位
第6个字节:
单位四元素k的低8位
第7个字节:
单位四元素k的高8位
第8个字节:
单位四元素w的低8位
第9个字节
单位四元素w的高8位
第10~31个字节:
版本1中:暂未使用,为未来扩展功能作保留
版本2和3中:
10~11字节:陀螺仪X轴速度,以弧度每秒为单位
12~13字节:陀螺仪Y轴速度,以弧度每秒为单位
14~15字节:陀螺仪Z轴速度,以弧度每秒为单位
16~31:为未来扩展作保留
每个速度表示为有符号,16位定点,2的补码,Q9格式的表示形式。这些速度应该以指数坐标对待,它们是本身相对于本地坐标的,而不是全局的世界相对于房间坐标。
版本2:
添加角速率数据
版本3:
重新定义第0个字节中的高序位为检测视频状态位。
每个四元素都是有符号,16位定点,2的补码(反码+1),Q14格式的数据表示形式(因此,在sink端程序,需要解析它们并转成float格式的)。
定点数与浮点数相对应,定点数为小数点的位置是固定的,而浮点数则小数点位置为浮动的(指数浮动)。定点数表示法如下所示:
纯小数表示法:把小数点固定在数值部分最高位之前
符号 | 小数点 | 数值部分 |
纯整数表示法:把小数点固定在数值部分的最后面
符号 | 数值部分 | 小数点 |
Q格式就是将一个小数放大若干倍后,用整数来表示小数。Q越大,数值范围越小,但精度越高,相反,Q越小,数值范围越大,但精度越低。例如在一个16位定点中,Q0范围为-32768到+32767,其精度为1,而Q15的数值范围为-1到0.9999695,精度为1/32768 = 0.00003051。因此,对定点数而言,其数值范围与精度是一对矛盾,一个变量要想能够表示比较大的数值范围,必须牺牲精度为代码;而想提高精度,则数的表示范围就相应地减小。
浮点数与定点数的转换关系可表示为:
因此,
在单片机端:
将从dmp或其他算法获取到的float型四元数扩大2的14次方的倍数,即16384,再通过USB HID往Sink端传,类似以下片段:
buff[0] = 0x03;buff[1] = cnt++;buff[2] = ((int16_t)(q1*16384))>>0;buff[3] = ((int16_t)(q1*16384))>>8; buff[4] = ((int16_t)(q2*16384))>>0;buff[5] = ((int16_t)(q2*16384))>>8;buff[6] = ((int16_t)(q3*16384))>>0;buff[7] = ((int16_t)(q3*16384))>>8;buff[8] = ((int16_t)(q0*16384))>>0;buff[9] = ((int16_t)(q0*16384))>>8;buff[10] = ((int16_t)(radianX*512))>>0;buff[11] = ((int16_t)(radianX*512))>>8;buff[12] = ((int16_t)(radianY*512))>>0;buff[13] = ((int16_t)(radianY*512))>>8;buff[14] = ((int16_t)(radianZ*512))>>0;buff[15] = ((int16_t)(radianZ*512))>>8;buff[16] = 0;buff[17] = 0;
在PC端:
vrpn_Tracker_OSVRHackerDevKit.C内:
void vrpn_Tracker_OSVRHackerDevKit::on_data_received(std::size_t bytes,vrpn_uint8 *buffer)
{if (bytes != 32 && bytes != 16) {send_text_message(vrpn_TEXT_WARNING)<< "Received a report " << bytes<< " in length, but expected it to be 32 or 16 bytes. Discarding. ""(May indicate issues with HID!)";return;}vrpn_uint8 firstByte = vrpn_unbuffer_from_little_endian<vrpn_uint8>(buffer);vrpn_uint8 version = vrpn_uint8(0x0f) & firstByte;_reportVersion = version;switch (version) {case 1:if (bytes != 32 && bytes != 16) {send_text_message(vrpn_TEXT_WARNING)<< "Received a v1 report " << bytes<< " in length, but expected it to be 32 or 16 bytes. ""Discarding. ""(May indicate issues with HID!)";return;}break;case 2:if (bytes != 16) {send_text_message(vrpn_TEXT_WARNING)<< "Received a v2 report " << bytes<< " in length, but expected it to be 16 bytes. Discarding. ""(May indicate issues with HID!)";return;}break;case 3:/// @todo once this report format is finalized, tighten up the/// requirements.if (bytes < 16) {send_text_message(vrpn_TEXT_WARNING)<< "Received a v3 report " << bytes<< " in length, but expected it to be at least 16 bytes. ""Discarding. ""(May indicate issues with HID!)";return;}break;default:/// Highlight that we don't know this report version well..._knownVersion = false;/// Do a minimal check of it.if (bytes < 16) {send_text_message(vrpn_TEXT_WARNING)<< "Received a report claiming to be version " << int(version)<< " that was " << bytes << " in length, but expected it to be ""at least 16 bytes. Discarding. ""(May indicate issues with HID!)";return;}break;}// Report version as analog channel 0.channel[0] = version;vrpn_uint8 msg_seq = vrpn_unbuffer_from_little_endian<vrpn_uint8>(buffer);// Signed, 16-bit, fixed-point numbers in Q1.14 format.typedef vrpn::FixedPoint<1, 14> FixedPointValue;d_quat[Q_X] =FixedPointValue(vrpn_unbuffer_from_little_endian<vrpn_int16>(buffer)).get<vrpn_float64>();d_quat[Q_Y] =FixedPointValue(vrpn_unbuffer_from_little_endian<vrpn_int16>(buffer)).get<vrpn_float64>();d_quat[Q_Z] =FixedPointValue(vrpn_unbuffer_from_little_endian<vrpn_int16>(buffer)).get<vrpn_float64>();d_quat[Q_W] =FixedPointValue(vrpn_unbuffer_from_little_endian<vrpn_int16>(buffer)).get<vrpn_float64>();vrpn_Tracker::timestamp = _timestamp;{char msgbuf[512];int len = vrpn_Tracker::encode_to(msgbuf);if (d_connection->pack_message(len, _timestamp, position_m_id,d_sender_id, msgbuf,vrpn_CONNECTION_LOW_LATENCY)) {fprintf(stderr, "vrpn_Tracker_OSVRHackerDevKit: cannot write ""message: tossing\n");}}if (version >= 2) {// We've got angular velocity in this message too// Given XYZ radians per second velocity.// Signed Q6.9typedef vrpn::FixedPoint<6, 9> VelFixedPoint;q_vec_type angVel;angVel[0] =VelFixedPoint(vrpn_unbuffer_from_little_endian<vrpn_int16>(buffer)).get<vrpn_float64>();angVel[1] =VelFixedPoint(vrpn_unbuffer_from_little_endian<vrpn_int16>(buffer)).get<vrpn_float64>();angVel[2] =VelFixedPoint(vrpn_unbuffer_from_little_endian<vrpn_int16>(buffer)).get<vrpn_float64>();//==================================================================// Determine the rotational velocity, which is// measured in the rotated coordinate system. We need to rotate the// difference Euler angles back to the canonical orientation, apply// the change, and then rotate back to change our coordinates.// Be sure to scale by the time value vrpn_HDK_DT.q_type forward, inverse;q_copy(forward, d_quat);q_invert(inverse, forward);// Remember that Euler angles in Quatlib have rotation around Z in// the first term. Compute the time-scaled delta transform in// canonical space.q_type delta;{delta[Q_W] = 0;delta[Q_X] = angVel[Q_X] * vrpn_HDK_DT * 0.5;delta[Q_Y] = angVel[Q_Y] * vrpn_HDK_DT * 0.5;delta[Q_Z] = angVel[Q_Z] * vrpn_HDK_DT * 0.5;q_exp(delta, delta);q_normalize(delta, delta);}// Bring the delta back into canonical spaceq_type canonical;q_mult(canonical, delta, inverse);q_mult(vel_quat, forward, canonical);// Send the rotational velocity information.// The dt value was set once, in the constructor.char msgbuf[512];int len = vrpn_Tracker::encode_vel_to(msgbuf);if (d_connection->pack_message(len, _timestamp, velocity_m_id,d_sender_id, msgbuf,vrpn_CONNECTION_LOW_LATENCY)) {fprintf(stderr, "vrpn_Tracker_OSVRHackerDevKit: cannot write ""message: tossing\n");}}if (version < 3) {// No status info hidden in the first byte.channel[1] = STATUS_UNKNOWN;}else {// v3+: We've got status info in the upper nibble of the first byte.bool gotVideo = (firstByte & (0x01 << 4)) != 0; // got video?bool gotPortrait = (firstByte & (0x01 << 5)) != 0; // portrait mode?if (!gotVideo) {channel[1] = STATUS_NO_VIDEO_INPUT;}else {if (gotPortrait) {channel[1] = STATUS_PORTRAIT_VIDEO_INPUT;}else {channel[1] = STATUS_LANDSCAPE_VIDEO_INPUT;}}}if (_messageCount == 0) {// When _messageCount overflows, send a report whether or not there was// a change.vrpn_Analog::report();}else {// otherwise just report if we have a change.vrpn_Analog::report_changes();};_messageCount = (_messageCount + 1) % vrpn_HDK_STATUS_STRIDE;
}
on_data_received方法继承自父类vrpn_HidInterface,同时在父类vrpn_HidInterface中update函数不停的获取hid数据往on_data_received中发送,父类vrpn_HidInterface中关于hid的硬件信息又来自com_osvr_Multiserver.cpp时的枚举操作。而vrpn_Tracker_OSVRHackerDevKit又在mainloop中操作update函数,vrpn_Tracker_OSVRHackerDevKit的update函数是从它的祖父类vrpn_BaseClass继承而来的,类中vrpn_BaseClass对mainloop解释为:
通过每个main循环去操作update函数,远程对象的mainloop()应该先调用client_mainloop(),随后调用d_connection->mainloop()函数,而服务对象mainloop()应该先为设备服务,随后再调用server_mainloop()函数,该函数会不停的给客户程序回应心跳包,让客户端程序知道它仍然与服务端相连,而不应该去调用d_connection->mainloop()函数。
最终的mainloop函数调用都在client端被调用,都被vrpn_python域封装好了。比如button:
static PyMethodDef Button_methods[] = {{"mainloop", (PyCFunction)Button::_definition::mainloop, METH_NOARGS, "Run the mainloop" },{"register_change_handler", (PyCFunction)Button::_definition::register_change_handler, METH_VARARGS, "Register a callback handler to handle a position change" },{"unregister_change_handler", (PyCFunction)Button::_definition::unregister_change_handler, METH_VARARGS, "Unregister a callback handler to handle a position change" },{NULL} /* Sentinel */};
OSVR头部追踪数据格式及VRPN数据处理流程相关推荐
- 医疗大数据处理流程_我们需要数据来大规模改善医疗流程
医疗大数据处理流程 Note: the fictitious examples and diagrams are for illustrative purposes ONLY. They are ma ...
- 宏基因组单个样本数据处理流程笔记
宏基因组单个样本数据处理流程笔记 前言 数据预处理 质量控制 去除接头序列 去除宿主序列 物种注释 Kraken2注释 Krona制图 序列组装 MEGAHIT序列组装 SPAdes序列组装 功能注释 ...
- netflow报文格式与数据处理流程分析_RTK、三维激光扫描、无人机倾斜摄影在大比例尺地形图测绘的对比分析...
[摘 要] 本文研究RTK.三维激光扫描和无人机三种地形图测绘的方法.RTK采用传统单点测图的方法,三维激光扫描采用三维点云测绘图的方法,无人机采用三维实景模型测图的方法.研究结果表明:大场景的地形图 ...
- iPhone如何开启头部追踪功能 iPhone头部追踪功能使用方法
通过支持的设备听歌时,就能感受到空间音频有了动态头部追踪效果,无论怎么转动头部,都会感觉声音环绕在你四周,一起来了解一下吧 如果你订阅了Apple Music,通过支持的设备听歌时,就能感受到空间音频 ...
- 计算机会计数据处理流程是怎样的,实现会计电算化后,会计数据的处理流程依旧和手工..._高级会计师_帮考网...
bangkafan 高分答主 06-16 TA获得超过8931个赞 一.手工与电算化会计信息系统的数据处理流程: 1.相同点: ①数据处理步骤相似: ②会计信息相同且最终目标一致: ③遵守的会计法规和 ...
- 眼球追踪如何预测头部追踪
眼球追踪如何预测头部追踪 我将于本周末访问DC州的神经科学学会会议,而且能够看到一些关于眼球追踪方面的最新进展. 很多时候,人们问为什么眼睛追踪对促进研究和为残疾人提供用户界面是很有帮助的. 一个有趣 ...
- 珞珈一号01星(luojia1-01)的夜间灯光影像数据处理流程
珞珈一号01星(luojia1-01)的夜间灯光影像数据处理流程 书接上回,我们爬取了山东省的珞珈一号夜间灯光影像数据,现在我们来对数据进行预处理,以分区获取区域夜间灯光亮度值. (1)加载珞珈一号夜 ...
- 星星之火-42:LTE空口协议栈、数据处理流程与LTE的调制技术大全
前言: LTE的数据处理,是数字调制.频分复用.时分复用.数模转换.模拟调制的集大成者. 这里汇集了众多的复杂的调制技术,包括高阶QAM正交幅度调制.正交多载波调制OFDM.CPRI传输.IQ双路正交 ...
- Gamit Gloness 数据处理 流程
Gloness 数据处理 流程 一.准备文件 1.简述: 在使用Gamit处理数据的前期,需要自己准备文件,准备的文件在目前的处理策略中有: (1)精密星历文件(sp3文件)(示例文件名如下) igl ...
最新文章
- 由“求最大公约数“引发的思考
- phpcms:八、show.html
- jinja Macros
- Springboot 使用wangEditor3.0上传图片
- SecureCRT安装(5)
- 西南大学计算机应用基础作业答案2020,2018秋【西南大学】[0483]《计算机应用基础》作业(资料)...
- 微信小程序连接无法跳转/ can not navigate to tabBar page错误
- 计算机联锁论文致谢,计算机联锁系统 毕业设计论文 定稿.doc
- XP连接网络计算机未启动服务,XP搜索不到网络提示“请启动Windows零配置(WZC)服务”的解决方法...
- 远程服务器网刻系统,网刻-wu123119-ChinaUnix博客
- SpringBoot yml文件命名规则
- PyQT股票看板软件界面设计
- 单片机 AD/DA数模转换
- 高中数学必修一,集合知识概念运算归纳总结
- 聊一聊C语言位域/位段
- 真Unity3d_梦幻西游无双的引擎是?
- java-IO流-在文件中数据内容的插入问题
- Sparql查询RDF
- 关于SAP十个常见问题集锦
- SpringBoot中starter场景启动器
热门文章
- 采用串口中断方式实现串口通信
- arduino2560 digitalWrite原型分析
- 内推名企实习,就来CSDN超级实习生计划,2022年名企实习内推开始发车
- 鼻炎康片怎么样 功效与作用有哪些?
- 电源选项中的S1,S2,S3,S4,S5
- for的嵌套c语言用星号表示三角形ppt,C语言编程求助!“使用循环语句打印出星号三角形”(如下图)...
- 相机多视角极线约束-小白必备
- CSDN开发者周刊:互联网公司接连上演反腐风暴,GitHub 公布了托管平台与美国贸易管制的相关细节!
- BP神经网络预测模型输入数据表是一个表还是可以多个表
- 美国可以实现几乎对全球无死角的监控,这是可能的吗?