2016,VR元年,oculus、HTC、索尼等这样的重量级厂商纷纷推出或宣布了自己的消费级硬件产品来抢占消费者市场,相信广大VR爱好者们中的很多人都已经入手了一款虚拟现实设备。在这些硬件当中,Oculus Rift CV1(以下简称“CV1”)无疑是最受人瞩目的硬件产品之一,毕竟它有2014年拿了Facebook20亿美金这样的大事件撑腰。

众所周知,Oculus Rift采用的是主动式光学定位技术,那它到底是如何实现的呢?

基本实现流程:

Oculus Rift设备上会隐藏着一些红外灯(即为标记点),这些红外灯可以向外发射红外光,并用红外摄像机实时拍摄。获得红外图像后,将摄像机采集到的图像传输到计算单元中,通过视觉算法过滤掉无用的信息,从而获得红外灯的所在方向,再利用PnP算法,即利用四个不共面的红外灯在设备上的位置信息、四个点获得的图像信息即可最终将设备纳入摄像头坐标系,拟合出设备的三维模型,并以此来实时监控玩家的头部、手部运动。

注:具体可看如下图,注意上面这些红色的小点点。

接下来我将向大家介绍一下我的推理过程,以及算法的一些细节。

头显上的LED灯

2016JUN19 OS EMBEDDED NT 01 68

前文中我提到我们需要利用四个不共面的红外灯在设备上的位置信息来进行定位,而如果想要知道不同的红外灯在设备上的位置信息,就必须能够区分不同的红外灯。

为什么这么说呢,如果不区分红外灯,那么当DK2(注:特指头显)在空间中运动时,摄像机捕捉到光点后,要进行关联(姿态最优匹配的过程)的次数 会非常大,举个列子:

1) 如果有N预测图像点和M <= N观察图像点,则有N!/(N-M)!可能的关联

2) 对于N = 40和M = 20(对DK2 LED的数量),有3.3×1029的关联,所以就算是计算机,也无法快速地得到结果。

很显然,DK2一定是采用了某种先验的方式区分光点。那么问题来了,DK2到底是如何区分的呢?

我曾看到有文章中猜测说DK2是通过LED灯的亮灭来区分的,实际上却并非如此。因为虽然通过LED灯的亮灭来区分比较简单,因为亮灭最容易区分出来,但是这种方法有个缺陷,就是无法区分是姿态改变导致的LED灯被遮挡,还是LED灯本身就熄灭了,所以,DK2没有使用这个方法,而是采用LED灯光信息的强弱来实现的。我们来观察用灰度摄像机拍摄的图:

2016JUN19 OS EMBEDDED NT 01 69

2016JUN19 OS EMBEDDED NT 01 70

对比图1,图2,可以发现亮斑的大小有变化。可以看出红色部分,在图2时光斑更大,蓝色则相反。接下里我们看详细的做法。DK2是使用差分法来判断光斑大小。

什么是差分法呢?即DK2将当前帧的光斑与上一帧同一个光斑做对比,如果比之前大,则为大,反之则为小。那么当一个新的帧到达时,该算法首先提取帧的亮像素斑点,如下图。忽略少于10个像素或不是圆盘状的,最后确保所有的斑点来自前一帧中提取的大圆盘状斑点,然后进行对比。

2016JUN19 OS EMBEDDED NT 01 71

我在进一步查看这些点和位置信息的对应关系后,总结出DK2判断强弱变化的依据是:

1) 如果当前帧的斑点比上一帧斑点大10%,就是0;

2) 如果当前帧的斑点比上一帧斑点小10%,就是1;

3) 否则忽略。

这样的设计非常好,防止了LED灯受到随机干扰。

那么是不是这样呢?如何在DK2中表示这些强弱关系呢?

首先,已知SDK的windows的driver会发送一个开始信息,让头显开始运作;

紧接着,这个driver就会不断接收到下面信息:

2016JUN19 OS EMBEDDED NT 01 72

X1 X2 X3 X4 是1个32位数,是图像分析后得到的空间坐标(原理后面给大家解释),DX则不知道干什么用,但是观察上面的num,换算出来是40,index从1开始,不断递增到40,说明DK2在一个一个的识别LED灯,另外,这些信息每17ms左右发上来一次,和60HZ的拍摄频率差不多,基本上可以认定是利用每10帧确定一个LED的方式。

所以DK2确实是将当前帧的光斑与上一帧同一个光斑做对比,然后根据10帧不同的变化来让摄像机确定LED灯的ID。

不过在实际验证中,发现如果这样提取图像,在头显运动时,还是会有错误出现(比如漏拍了几帧), 那么它是如何判别的呢?这个花了我不少时间去想,最后发现这和汉明距离有关系。

所谓汉明距离,简单地说:如果数字是相同的,它们的汉明距离为0,如果他们在单一的比特不同,它们的距离为1,如果10比特它们彼此逐位否定,它们的汉明距离是10。我发现,DK2已经默认每个拍摄后的图像(提取40个LED灯表示的比特位)和前一帧最多的汉明距离是 3,如果大于这个数字,则认为是错误帧。

最后,给出几个比较有特点的LED的10帧图,大家可以看一下:

2016JUN19 OS EMBEDDED NT 01 73

感知姿态

前面谈了如何探测LED灯,我介绍了DK2的摄像机通过视频流捕获LED灯独特的闪烁模式,拼出10位二进制数来识别ID。

现在来谈如何感知姿态,你可能会很自然地想到,通过每10帧来识别LED,然后利用已知LED的位置来判断姿态。说实话,这个方法也可以,但是这就表示要间隔10个帧才能识别一次姿态,万一动作快一点,就出现麻烦了。

当然,要估计一个三维模型的姿态,需要足够的LED的信息,如果拍摄到的LED点不够,就必须使用DK2中的IMU来进行姿态的感知。另外,IMU本身也会累计误差。

DK2的算法其实是这样的:

1) 初始状态,先读取静止状态的头显,10帧的时间,识别每个LED的ID号;

2) 不断地拍摄,利用前面帧的信息,推测出现在的LED灯的位置;

3) 如果出现错误,就重新通过10帧的时间识别LED;

4) 如果有足够的LED灯信息,就使用识别算法;

5) 如果没有足够的LED灯的信息,就使用IMU识别姿态;

2016JUN19 OS EMBEDDED NT 01 74

三维姿态估计或已知物体相对于2D照相机的角度,求出该物体在相机坐标系的坐标值,称为PNP问题。在Oculus Rift中,感知头显的姿态是利用PnP算法,融合IMU实现的。Oculus Rift没有单纯的采用IMU进行姿态解算是因为IMU本身会产生漂移,无法在长时间内跟踪对象的绝对位置,因此必须外接3D参照系,PnP正可以提供这样一个参照系。说白了,就是要利用PnP来纠正IMU,最后融合成一组比较合理的姿态信息。

那么,什么是PnP算法?

3D姿态估计是一个多维非线性优化问题,摄像机拍摄图像中的一组2D点,例如提取的LED斑点的集合,可以尝试重建相对于相机的坐标模型的未知位置(X,Y,Z)和方向(偏航,俯仰,滚动)。

2016JUN19 OS EMBEDDED NT 01 75

说详细一点,在相机内参数巳知的情况,由特征点与其像点的对应关系求解目标相对于相机的位姿,就是经典的PnP 问题,这些算法大致可分为迭代算法和非迭代算法两类。

非迭代算法采用代数方法直接求出相对位姿,针对P3P 、P4P等问题推导出多种解析算法,该类算法具有运算量小、计算速度快等优点,但受误差影响大且精度不高。Lepetit 提出的一种计算量为O(n)的PnP问题非迭代算法,只需要4个点的信息就可以求解目标相对于相机的姿态信息, Oculus Rift正是利用这种算法实现的。

最后再向大家总结说明一下感知姿态的基本步骤:

1、发出同步信号,等待LED灯亮起后,提取收到的8位灰度视频帧明亮的斑点。

2、创造一种斑点颜色为亮绿色的8位RGB输出视频帧(用于调试/可视化的目的)。

3、匹配大致圆盘形的斑点,并与来自先前帧中的斑点进行比较,比较它们的尺寸差异到差分位解码器中,累积其10位ID和LED关联。排序所有提取或投射二维LED的位置到一个kd树,用在下一帧中快速匹配。

4、如果有四个或更多的识别斑点且没有当前姿势估计,运行从头计算姿态估计方法。

5、如果有四个或多个识别的斑点,且已经识别了先前帧的姿态,采用迭代姿态估计方法。

6、用姿态信息纠正IMU的漂移,并融合IMU的姿态信息。

以上介绍了Oculus Rift的实现过程。

Oculus Rift的主动式红外光学+IMU定位系统精度较高,抗遮挡性强。

由于其所用的摄像机具备很高的拍摄速率,并且该类系统总是能够得到标记点在当前空间的绝对位置坐标,所以不存在累积误差。但是由于摄像头视角有限,因此该产品的可用范围有限,会在很大程度上限制使用者的适用范围,因而无法使用Oculus Rift来玩需要走动等大范围活动的虚拟现实游戏。也因此,虽然Oculus Rift可以支持多个目标物同时定位,但是目标物不可过多,一般不超过两个。