CSK.Blog--个人原创Weblog

Oculus Rift 虚拟现实眼镜的介绍、体验和应用畅想

本文最早发布于ifanr,当时地址为:http://www.ifanr.com/288886
先转回我自己的Blog
内容提要,请点击文章标题浏览全文

1. 简介

去年8月的时候,我经好友介绍,了解到了当时正在Kickstarter网站上募资的Oculus Rift虚拟现实眼镜项目(后文简称Rift)。虽然那个时候Kickstarter上的项目页面中并没看到Oculus Rift眼镜的实物,不过看过五分钟不到的演示视频的后,我便迫不及待的想预定一台了。

图: Oculus Rift在Kickstarter上的募资页面

...

图:Oculus Rift宣传视频中宣称的110度视角

经过了半年多的等待,Oculus团队不负众望陆续将Rift开发套件送到了全球各地的赞助者手上。我也在今年4月底收到了等候多时的Rift。

那么Rift到底用起来感觉如何?接下来我将向大家分享我的使用体验。并且由于我本身对电子制作和机器人有浓厚兴趣,因此本文也将花一定篇幅探讨Rift背后的技术实现以及针对他进行扩展应用。

2. 开箱照

我的Rift是从香港寄来的,快递使用的是EMS。打开纸箱后就会看到Rift定制的黑色箱子,感觉很专业:


做工上我认为只能算中上水平,不过要知道对于一个之前没有涉足硬件制造的团队来说,半年内生产出上万的批量合格产品绝非易事。
在早先的Oculus进度通报邮件中提及,目前所有的开发者套件均由深圳的工厂负责代工。

3. 使用过程和体验

...

如果将Rift眼镜上的透镜拆掉,就可以看到藏在眼镜底部的LCD显示器。它只是简单的复制了电脑显示器的画面而已。

...

很容易猜到这两幅画面其实分别对应了Rift中左右眼应该看到的部分。而之所以扭曲,这便是Rift能实现110度视角的奥秘所在:采用凸透镜将画面放大。

图:利用凸透镜将原先长度为x的画面放大至x’
来源:Oculus SDK文档

图: Rift在LCD屏幕前安装了凸透镜,用于扩大视角

由于凸透镜在放大画面的同时会产生扭曲,因此就需要先将PC的原始输出画面做扭曲的处理,就可以抵消扩大视角范围造成的画面扭曲。

为了给大家直观的感受,这里将Rift左右眼位置的凸透镜拆除,可以看到背后的LCD显示的对应画面:

虽然这样远距离看上去画面很清晰,但是如果不安装凸透镜,直接将rift带上,双眼就会看到模糊一片的景象,并且也会感觉画面很小。

那么带上Rift看到的画面是怎样的呢?这里我将相机仿造人肉眼那样直接贴紧Rift拍摄,此时的画面就和亲身的体验很接近。不过亲身的体验是全三维的,很有现场感。这种感觉靠本文难以带给读者。

图:相机尚未完全紧贴Rift时的效果,此时基本可以看到画面。

...

视角切换体验

作为虚拟现实眼镜另一个很重要的指标就是在人扭头,视角切换时候,所看到画面随着切换的响应速度了。如果传感器或者软件处理的较慢,则会感受到所看到的画面有所滞后。从而产生不真实感,更是会令人晕眩。

对此Oculus Rift在宣传中申明采用了1000hz刷新率的惯性导航定位算法,可以实现很低的滞后率。

从我的感受看,前文提到的官方演示程序的确表现很出色。读者不妨先看这段视频感受一下。

佩戴舒适度与晕眩感

...

支持Rift的应用和游戏

...

4. 内部构造和简要分析

...

实现移动感知的探讨

下图为我团队所开发的0-6米全向激光测距雷达系统。他可以实时的扫描环境四周(360度)的物体相对于自身的距离,并且可以提供毫米级别的定位精度。用它来结合Rift进行虚拟现实世界中人物移动的判断,当属不错的选择。

图:使用激光雷达进行Rift使用者的跟踪

应用的展望

...

图:Rift代表的虚拟现实技术与机器人技术的融合

低成本激光投射虚拟键盘的设计制作-上(原理和硬件)

姗姗来迟的文章...先前在《无线电》杂志2012年9-10月刊登过,近期转到我blog上
转载注意:
来源连接:
目前DFRobot正协助制作本设计的开源套件,感兴趣的朋友可以前去购买。详情见:
在制作3D激光扫描仪的过程中,我偶然在网络上发现有人销售可以投影出键盘画面的激光器组件,并且价格低廉。仔细想来其实激光投影键盘的原理和3D扫描仪一致,于是便产生了DIY一个低成本激光键盘的想法。最终,本作品在我完成3D扫描仪的3天内完成了,可见硬件部分的制作不复杂。虽然完成的时间短,但其中也涉及不少的设计细节需要考虑,于是便写了此文向大家分享我的制作过程。

简介

激光投射键盘相信大家之前也有所听说,他通过光学手段,将计算机键盘的画面通过激光投影到任意的平面上(如桌面)上,并且允许操作者像使用真实键盘那样进行输入操作。

图:Celluon公司出品的Magic Cube激光投影键盘

目前市面上能买到的激光键盘产品是由韩国公司Celluon生产销售的。这类产品目前也可以在国内买到,不过因为价格高昂,目前仍旧在千元水平。我之前并没有购买过,具体使用效果也不得而知。有人评价说因为缺少物理反馈,这样的键盘使用上手感可能不好。

但无论如何,激光虚拟键盘还是非常能吸引眼球的,在投射出的键盘影像上输入非常有科技感并且足够科幻。如果能自己以较低成本DIY一台出来,那岂不是更好?

本文提出的DIY激光投影键盘方案可以保证使用较低的成本(百元左右),并提供较强的性能:30hz的输入频率、支持多键输入,并且还有额外的功能:把他当作多点输入设备来使用,比如可以作为绘图板,并能感应手指的压力大小。

在完成本制作后我拍摄了一段演示视频展现它的性能,可以访问如下地址观看:

原理分析


在具体介绍实现过程前,我们首先需要分析这类激光投影键盘的工作原理以及给出解决问题的思路,这样也可方便大家举一反三。首先需要解决的核心问题有这么两个:

  1. 如何产生键盘的画面?
  2. 如何检测键盘输入事件?

产生键盘画面


对于产生键盘画面,可能很多人认为这种画面是通过激光+高速光学振镜来得到的。这种方式虽然在技术上是完全可行的,但由于需要采用精密的机械部件,成本非常高,并且也难以做成轻便的产品。
 
图:通过光学振镜扫描产生的激光投影画面。图片来自[1]

实际上在激光投影键盘产品中,这类画面往往是通过全息投影技术得到的。激光器通过照射先前保存有键盘画面的全息镜片的方式在目标平面上产生相应的画面。这种方式的成本非常低廉,市面销售的激光笔常配备的投影图案的镜头也是用这种原理产生的。

 
图:可以投射出图案的激光笔镜头

不过这类全息投影方式对于DIY来说仍旧不现实,幸好得益于目前网络的便利——通过网购可以直接买到用于产生激光键盘画面的全息投影设备了,且成本在¥50以内。


 
图:可以购买到的投影键盘画面的激光器模组

识别键盘输入事件


在解决了产生键盘画面的问题后,这里分析另一个看似简单的问题:如何判断键盘输入事件?

由于键盘画面可以投射在任意的表面上,因此传统的靠物理按钮的手段自然是不可能的(否则也称不上虚拟键盘)。需要非接触的手段来检测。这里给出了几种途径,他们在技术上都是可行的:

1) 通过计算机视觉的方式,通过图像来识别
通过摄像头捕捉键盘区域的画面并进行分析,判断出键盘输入事件。
2) 通过检测按键发出的声音来判断
这里假设使用者在按键时会碰触桌面,产生一定的敲击声。通过检测该声音传播时间,可以进行定位。该方案在国外的一些研究机构已经实现。
3) 通过超声波雷达手段来判断
通过发射超声波并检测反射波的传播时间查来检测目标物体(手指)的位置。

这3种方案国内外均有文献表明可以实现,不过相对来说计算机视觉的硬件较为简单,仅仅需要一个摄像头,因此这里我们采用这种方式。

下图给出了本制作早期阶段,摄像头所拍摄的使用过程的画面,基于这类画面进行计算机视觉的运算,可以得到我们需要的键盘事件:

图:通过计算机视觉的方式识别并判断键盘输入事件

其实这里涉及到了2个子问题:
  1. 判断手指按下的是哪个键?
  2. 如何判断手指已经“按到”了对应的“按钮”?

由于我们人类就是通过视觉来理解外部世界的,因此很直观的可以想到,只要能够识别并定位画面中手指的位置,第一个问题就可以解决了。这里先不讨论定位本身该如何实现,假设我们的算法已经可以和人脑一样轻松在一副画面中找到手指的位置并用相对于图像的坐标来表示(x,y)。

接下来就要考虑第二个问题,如何判断手指已经“按下按钮”?这里一个办法是通过捕捉声音:即像前文提到的通过捕捉手指碰触桌面产生的敲击声来判断。但这样会带来额外的问题:

  1. 需要额外的硬件和电路,增加了复杂性
  2. 如何将敲击声与画面中真正敲击的手指对应?就像上图中的5个手指都可能是在敲击状态,此时难以进行匹配
  3. 其他的噪音也会被当作键盘敲击

因此这里还是依靠视觉的手段来进行判断。在分析可行方案前,需要明确“按下按钮”的具体指标。我们可以定义当手指碰触桌面,或者距离桌面足够接近为“按下”。那么其实问题的实质就是我们需要检测出手指距离桌面的距离z。在求出该数值后,我们只需简单的判断它小于某一个值,就认为手指已经“按下按键”。

综合起来看,我们需要设计一种视觉处理算法:它可以在一副画面中找出每个手指相对画面的位置(x,y),并且手指距离桌面的高度z。事实上我们就是在检测手指的三维空间坐标了(x,y,z)了。
 

图:需要设计的视觉检测算法和期望的检测结果

如果之前有阅读我的3D激光扫描仪制作的话,相信大家就能猜到具体的实现方式了,其实算法本身就是做三维的激光测距。不过这里我们先按照前文的思路继续分析下去:

做三维测量有很多种方式,比如现在可以购买微软的KINECT深度传感器。如果不考虑成本,这的确是一种非常有效的方法,可以非常好的解决本文提出的这些问题。另一种类似的办法是使用双摄像头来做双目视觉处理,提取目标画面物体的深度信息。不过目前这类处理比较消耗处理资源,且校正过程比较复杂,并不适合这里的应用。

 
图:RoboPeak团队曾进行的双目视觉进行深度提取的实验

这里我们采用基于三角测距原理的激光测距仪[2]一致的办法,通过主动投射激光来做目标物体的三维坐标检测。在[2]中我使用一束线型激光照射目标物体,在目标物体的反射光被摄像头捕捉到,利用三角测距原理,可以求出目标物体中被线激光照亮部分的坐标信息。
 
在[2]中提到的利用线激光和单摄像头的3D测距原理

这里我们将线激光所产生的光线平面与桌面平行并紧贴在桌面之上,将摄像头放置于激光发射器上方并俯视桌面,如下图所示:

 
图:利用激光三角测距原理用于手指空间坐标检测

此时若手指接近桌面,则会阻挡住激光的通路,产生反射,反射的光点画面会被图中摄像头拍摄到。这是一个标准的三角测距的结构设置。细心的读者可能已经发现在前文给出的本制作摄像头拍摄到的画面中手指尖部的白色光斑,这正是安装了线激光器后被手指遮挡产生的反射效果。

 
图:本制作采用的线激光测距方案

对于这种基于线激光的测距的方式[2],我们可以通过三角关系求出被激光照亮部分相对于激光发射口为原点、位于线激光组成平面内的坐标P(x,y)。而又因为线激光组成的平面与桌面平行,且在设计上这两个平面是紧贴着的,所以可以近似的认为坐标点P(x,y)是位于桌面平面的。

这样的设置具有几个优点:

方便检测指尖
当手指遮挡激光平面后产生了反射,因此会在摄像头画面中出现较为明亮的光斑,可以通过简单的视觉算法来提取指尖部分

方便检测按键事件
由于只有当手指靠近或者碰触到桌面才会遮挡住激光产生反光,而在距离桌面较高的位置则不会被检测算法察觉。因此这里检测按键事件可以做出简化,不需要通过检测手指距离桌面高度来判断。而只要当检测算法检测到手指反光,则可认为出现了按键事件,且可直接用当前检测到的坐标来进行后续的处理。

值得一提的是,事实上这样的方式对从事多点触摸领域朋友来说应该不陌生,有一种称为LLP(Laser Light Plane)的技术[4]和我们的方式很类似。

图:LLP方式的多点触摸技术,图片来源自[5]

如上图所示,LLP技术也通过发射一束线型激光构造出一个光平面,并捕捉手指反光来做多点触摸定位。而摄像头的安装位置也并非一定位于上边示意图的底下,同样也可以安装在本制作一样的位置。事实上这里采用的激光测距方案的前期处理是和LLP一致的:提取手指指尖的兴趣点,并转换成相对于摄像头画面的坐标。不过对于多点触摸而言,不需要得到手指在桌面上的精确距离,就好比我们用的鼠标只需要知道一个相对位移的程度,而不需得到具体鼠标移动了多少毫米。但由于我们需要将手指的坐标转换成具体按键的信息,因此还有必要进行更进一步的处理。

不过相信有人会说这样的方法岂不是和之前通过求出指尖的三维坐标的方式的思路不同吗?的确目前的方式无法直接求得手指距离桌面的高度z。并且接触主动发射线激光的特性,通过求取z来判断是否存在按键事件也显得不必要了。但这并不表示我们无法求解手指距离桌面的高度,并且在后文我将指出,求出手指距离桌面的高度仍旧是有意义的。

判断并产生对应的按键事件


前面我们已经解决了如何检测手指当碰触到桌面时指尖在桌面平面上的坐标P(x,y)。但这与我们最终要产生对应的按键事件还有一定距离。我们需要建立一个映射机制,通过桌面坐标P(x,y)找到对应按键键值,并最终通知操作系统触发一个对应键值的按键事件。

这里的做法与GUI系统进行UI元素碰撞判断的过程类似。在图形系统中,所有UI元素均保存有他们相对于屏幕的坐标值。GUI系统不停地判断当前鼠标指针位置是否落入了某一个按钮或者选择框的坐标范围内。

图:GUI系统通过对比鼠标坐标和按钮UI元素的坐标来判断鼠标是否存在于按钮元素上方

与GUI系统类似,我们首先需要建立投射键盘图案中每个按键的坐标信息。然后将指尖相对于桌面平面上的坐标P(x,y)映射到键盘图案的坐标系内进行按键的判断。
 
图:将基于桌面平面以激光器为原点的指尖坐标P(x,y)映射到键盘坐标平面并进行按键判断

上述过程涉及到两个步骤:

1) 将以激光发射器为原点的指尖坐标P(x,y)映射到以键盘图案左上角为原点坐标的平面内,得到映射点P’(x,y)
该过程用数学表达记作:
P' (x,y)=fprojection (P(x,y))

2) 将映射点P’(x,y)与事先记录的每个按键的坐标值比对,求得当前对应按下的按键值。
该过程记作:
Key[n]=fmapping (P' (x,y))

可能有人会问为何不直接按照以激光发射器原点的坐标来表示每个按键的坐标?这样第一步的映射f_projection就可以省略了。这的确也可以达到同样的效果。但是由于在组装上会存在误差,不能保证每次制作出来的成品激光发射器与键盘图案投射的位置都完全一致,并且单个成品在使用过程中也会因为热胀冷缩等原因,键盘图案会发生偏移。一旦键盘图案发生了移动,则先前以这种方式记录的所有按键坐标都需要重新测量。但如果一开始就以键盘左上角为原点的方式记录坐标,则每次组装完成品或者发生图案偏移后,则只需进行简单的矫正,求出一个新的f_projection函数即可。

对于步骤2)中的函数f_mapping,一种简单的实现是依次按照前文例图中的判断代码对每个按键轮流计算,判断是否被“按下”。这种方式实现简单直观,但是性能较差。对于这类问题,可以使用Kd-Tree的数据结构[3]进行快速的查找。

系统框图


前面我们解决了制作激光投影键盘的两个关键问题,这里将我们最终采用方案的大致原理做出总结,方便大家理解。在实现过程部分,将具体就其中的实现细节做出介绍。

 

框图中还包含了本制作的另一个功能:多点触摸绘图板。该部分也将在具体制作过程中介绍。

实现过程

器件选择


有前面的原理分析可知,本激光键盘至少需要摄像头、投射键盘画面的激光器以及一字线激光。如下是我选用的器件和参数

元件

核心参数

摄像头

广角镜头,视角>120

投射键盘画面的激光器

无特殊要求

一字线激光

红外激光器,>50mW

红外带通滤光片

800nm左右光谱带通


对于摄像头,这里一个比较关键的特性是需要采用广角镜头。目前的USB摄像头一般视角在90度左右,这样的摄像头需要在很高处俯视才能拍摄到完整的激光键盘图案,如果想把本投影键盘制作的小巧,则需要采用广角镜头,视角最好在120度以上。

 
图:窄视角的摄像头需要安装在较高的位置

除了镜头外,摄像头本身没有特殊要求,一般市面VGA画质的普通USB摄像头即可满足要求。下图为我采用的镜头和摄像头照片:
 
图:两种不同视角的摄像头镜头,左边为窄视角镜头,右边为广角镜头

  
图:USB摄像头模组

对投射键盘图案的激光发射器模组前文已经提到,且没有特殊要求。

 
图:选用的激光键盘图案投射模组

对用于测距的激光发射器,只需要使用红外波段的一字线激光器,功率在50mW左右即可。为了安全考虑,不要使用过大功率的激光器。采用红外波段主要为了过滤可见光干扰,简化视觉处理中的兴趣点提取,同时也不会因为手指反射对投射的键盘图案产生干扰。
 
图:选用的红外线型激光器

摄像头改装


在我之前的3D测距仪文章[2]中提到过使用红外激光器配合红外滤光片进行测距可有效避免可见光干扰的技巧,在这里也仍旧适用。对此我们需要对摄像头镜头做一些改造,首先拆除镜头上的红外光截至滤光片,并在镜头头部安装红外带通滤光片:

 
图:拆除镜头内的红外截止滤光片

 
图:在镜头表面安装红外带通滤光片

电子系统


本制作的电子系统比较简单,仅仅涉及给两个激光发射器供电以及通过USB电缆连接摄像头至主机。并没有采用任何单片机,这样也意味着之前原理部分介绍的算法将完全在PC上实现。这样的优点是大幅降低了制作成本以及制作的难度。如果使用的是免驱动USB摄像头,在PC上使用本激光投影键盘只需要额外运行处理程序即可。不过缺点就是这样的设计无法给手机等不支持USB的设备使用。

由于摄像头已经使用了一条USB电缆,因此这里我们直接通过usb的5V供电来驱动另外2个激光器。电路很简单,如下图所示:

 

可以看到电路的核心就是一个3.3V的LDO芯片而已,可以裁剪一个洞洞板制作:

 
图:使用小块洞洞板制作简易的驱动电路

总体安装


由前面的分析可以得到如下的安装结构:
 
其中线型激光器需要安装在设备的最底下并紧靠底部,这样可以保证产生的激光平面能够尽可能的靠近桌面平面。在激光器上方安装投射键盘图案的激光模组,他的高度以能够投射出与常规键盘尺寸大小类似的图案来决定。在最顶端安装摄像头,其高度取决于摄像头能够完整拍摄整个键盘图案画面为宜。

我使用一块轻质木材作为安装这3个部件的整体支架:

 
图:将激光器安装在木板最底部,使用热熔胶固定
 
图:安装图案投射模块和摄像头并点亮
 
图:在木板背面安装稳压电路板和所有供电连线
 
图:将木板装入一个牢固的纸盒

 
图:完成并通电
 
图:顶部加装一个“保护罩”

最终本制作的实体部分组装完毕,接下来将介绍本制作的重点:视觉算法部分。

低成本激光投射虚拟键盘的设计制作-下(算法与实现)

姗姗来迟的文章...先前在《无线电》杂志2012年9-10月刊登过,近期转到我blog上
转载注意:
请保留作者和出处。
目前DFRobot正协助制作本设计的开源套件,感兴趣的朋友可以前去购买。详情见:

3.5. 前期视觉处理


由于本制作的视觉处理部分都在PC上进行,因此我使用了OpenCV[6]库来加速视觉运算代码的开发。在进行原理部分提到的手指尖提取和坐标求解前,需要对摄像头捕获的原始画面做一些前期的处理,方便后续的计算。

3.5.1. 镜头扭曲矫正

由于我们使用了广角镜头,因此摄像头拍摄到的画面会存在比较明显的扭曲,就如下图所示:

这样扭曲的画面难以直接进行处理计算出后续需要的坐标信息,因为这种扭曲变换是非线性的。因此这里首先需要对摄像头镜头进行校正。在之前的文章[2]中有对摄像头矫正技术的介绍,这里我仍旧使用matlab的摄像头校正工具[7]进行校正。在OpenCV中也提供了相关函数和工具可以对摄像头校正,不过很多人反映相比matlab的算法,OpenCV函数校正得到的结果略差。

在使用Chessboard图案完整校正后,可使用OpenCV的cvInitUndistortRectifyMap/ cvRemap函数通过校正数据将扭曲的画面重新修正。

图:将左侧广角镜头的原始画面进行扭曲修正,得到右侧画面

这里有一点需要注意,在先前的安装阶段我们提到摄像头需要安装红外带通滤光片。但在安装滤光片后,由于可见光都被过滤,因此之后无法再进行上述的镜头校正。因此这部分的校正工作需要先于红外滤光片的安装进行。
3.5.2. 滤波

由于摄像头镜头加装了红外带通滤光片,可见光可以有效地被阻挡,因此在摄像头捕获的画面上基本只含有手指对红外激光的反射。

图:红外带通滤光片有效地过滤了可见光干扰

对于这样的画面基本上是可以直接进行后续的视觉处理的,不过一般我们还需要额外进行几个步骤:灰度化、高斯滤波、阈值化、形态学滤波

灰度化即将原先RGB色彩的彩色图像转化成灰度图,因为后续的视觉算法并不关心色彩信息,但需要反射光亮度,使用灰度表示后可以大幅加速处理速度。高斯滤波(Gauss Filter)、阈值化(Threshold)、形态学滤波(Morphology Filter)用于过滤画面中的噪点并且使得反射光斑变得平滑和连贯。如果不熟悉这部分概念,可以参考Digital Image Processing一书[8]。这几步操作在OpenCV中均有对应函数可以实现。

图:对图像进行各类滤波处理后的效果

上图展现了经过上述滤波算法后,手指尖激光反光光斑处理后的效果。可以看到,原先光斑外围的反射光干扰以及两个比较靠近的指尖之间“黏连”的光斑已经有效地过滤掉了。通过一系列的滤波过程,使得我们可以很精确的求出指尖的坐标。

3.6. 兴趣点提取


可以使用OpenCV提供的cvFindContours对先前与处理得到的光斑画面进行轮廓提取,进而求解出每个光斑区域在图像中的位置。进一步的,我们通过质心法并以光斑亮度作为权重,可以大致求解出每个指尖中心的大致坐标。虽然这个中心坐标未必真的在指尖中心,但相比简单的以光斑区域中心作为指尖中心的方法要精确很多。

图:使用轮廓提取手段提取出来的手指区域坐标

3.7. 手指坐标计算和校正


在得到了手指光斑中心点相对于画面坐标之后,可以通过三角测距的方法,把手指在桌面平面内的坐标P(x,y)求出来。
该过程可以类比在激光3D测距中的算法,如下图所示:

图:将指尖坐标求解问题转化成3D激光测距问题

我们可以把兴趣点画面顺时针旋转90度,即将图像的X/Y轴相互交换,并在兴趣点之间连接起直线,如上图(b)所示。就会发现这样的画面就和[2]中进行3D激光测距出现的激光扫描线类似,如上图(c)。因此,我们可以按照[2]中推导的公式,求出画面中每个兴趣点的真实坐标(即以激光器为原点,桌面所在平面的坐标系坐标)

这部分的具体原理和公式推导请参考文章[2]。为了能够进行精确的激光测距,我们也需要和[2]中描述的那样对本制作进行校正。校正方法与[2]一样,可以在投影键盘前方用直尺做测量并进行曲线拟合。

图:对激光测距部分进行校正

经过本轮的运算,我们可以得到先前监测到的兴趣点在桌面平面坐标下的表示,测试坐标值具有了物理意义,均以毫米为单位表示。

3.8. 按键映射和校正


在得到了以桌面坐标平面表示的指尖坐标P(x,y)后,我们需要再进行校正得到前文提到的映射函数
P' (x,y)=fprojection (P(x,y))
该部分的校正比较容易,可以将手指放置在键盘图案的某几个“按键”上,然后将当前的坐标P(x,y)与所“按”“按键”在键盘图案的坐标P_key (x,y)做一下对比。

理想状态下,每个P(x,y)与对应的P_key (x,y)都偏差了一个固定的位移量D。不过由于校正中会带来误差,每次测量时的偏差量D’与理想的位移量都存在一个误差偏移Derror。即
Pkey (x,y)=P(x,y)+D+ Derror
这里我们就可以采用多次测量,并进行最小二乘拟合的方式,把Derror给消除掉,得到近似于D的一个校正值。因此,这里的映射函数就是:
P' (x,y)=P(x,y)+D
另外这里也需要把键盘图案的坐标事先保存下来。我直接对投影在桌面上的键盘图案进行了测量,并按照如下格式保存在程序当中:

图:使用一维数组,保存每个按键中心点的坐标(x,y),按键的尺寸,以及按键的键值

在有了上述数据后,一方面我们可以使用Kd-tree来快速查找到所按下的按键ID。及前文所说的函数
Key[n]=fmapping (P' (x,y))
这里的f_mapping在我的实现中,就是一个Kd-tree。相比循环查找的O(n)复杂度,Kd-tree可以在O(log(n))的时间复杂度下,找到距离点P’(x,y)最接近的元素,因此非常高效率。

目前OpenCV中提供了Kd-tree的实现,不过我在使用过程中遇到了诸多问题,尤其它可能会导致程序crash,因此我使用了由Martin F. Krafft开发的基于C++模板的Kd-tree实现libkdtree++[9]。

得到键盘图案坐标后的另一个好处是可以在PC上重新绘制出一个软键盘,用于提示当前“按下”的按键,提高使用体验:

图:将坐标映射到对应“按下”的按键,并提供视觉反馈

3.9. 键盘事件摸拟注入


在得到用户按下键盘按键的信息后,就要考虑将这些按键注入到当前的系统中,使得我们的投影键盘能够作为一个真正的键盘来使用。这里我没有采用最直接的编写系统驱动程序的办法,虽然那样性能最好最直接,但会额外增加制作时间。我采用Windows系统提供的SendInput API[10]向系统注入键盘事件。该API允许应用程序向整个系统注入任意的键盘事件,在实际效果上,已经和编写驱动程序的效果没有区别。像系统输入法等特性也可以很好的支持。另外,这样做也可以保证我们所有的处理程序都在用户态模式运行,可以很容易的把我们的代码移植到MacOS或者Linux下运行。

图:本制作可以很好的使用中文输入法
不过前面我们得到的按下按键信息还不能直接注入到系统,还需要额外处理如下两件逻辑:
1) 模拟组合键
2) 模拟连发事件

对于组合键的模拟其实比较容易,一方面我们的设计可以保证多按键同时输入,对于Ctrl+C这样的组合键,其实不需要做额外处理,只要将当前的实际按键传输给OS即可。而对于键盘的功能键,比如Fn+F1这种特殊用途的按键,就需要编写程序特殊实现。
对于连发事件OS就没有提供什么特殊帮助了,需要我们自己来模拟。

3.10. 检测手指对桌面的压力与多点触摸版应用


在本制作的介绍视频中,我演示了本制作可以检测出手指对桌面的“压力”,从而在绘图板应用中能够产生不同粗细画笔的应用。

图:本制作的另一应用,多点绘图板

上图中可以发现绘制的线条的粗细不是固定的,线条的粗细是正是通过估算手指对桌面的“压力”决定的。

在前文原理介绍中已经知道,我们是无法真正得到手指对桌面的压力的。一切的信息都是通过摄像头的画面捕捉得来。这里估算的压力,是通过先前兴趣点提取阶段计算出的光斑面积得出的:

图:通过兴趣点区域面积估算“压力”

如上图(b)所示,程序使用了不同直径的圆圈表示两个投影点对应手指的不同“压力”。而参考他们对应的兴趣点尺寸(a)可以发现,面积比较大的兴趣点区域往往也认为“压力”比较大(注意两个画面是上下颠倒关系)。

这样的估算其实也有一定的现实依据,因为当手指紧压桌面时候,指尖反射激光的面积会变大。不过直接使用面积大小作为压力大小的话就会出现问题。就如上图(c)所示,从兴趣点面积看,画面下侧的兴趣点面积显然比上边的大很多。不过这未必是因为对应的一个手指用力压着桌面而另一个没有用力造成的。大家都知道按照投影变换,距离镜头比较远的物体就算尺寸相同,与会显得比较小。在这里两个兴趣点也是同样的情况,所设计的算法需要考虑到这个问题,得到尽可能真实的“压力”估算,如上图(d)所示,本算法最终估算的两者“压力”几乎类似。

这里采用的手段很简单,因为我们可以通过三角测距原则计算出画面中任意点的真实物理坐标,因此可以摆脱投影变换的干扰。具体做法是除了每个兴趣点中心区域求解对应的物理坐标P(x,y)之外,我们对兴趣区域边框上的某一个点也求出他的真实坐标P2(x,y)。由于P(x,y)和P2(x,y)反应了真实世界的两点坐标,用他们之间的距离就可以比较真实的估算出手指的“压力”

图:将兴趣点和兴趣区域边缘一点分别进行测距求解坐标,并以物理坐标点之间的距离估算“压力”

3.11. 程序总体界面和完成图


完成了上述几个核心功能后,我在用OpenCV的绘图功能做了一个简易的程序界面,前面提到的每个处理状态都会实时的在界面中展现。就此大功告成!

图:程序界面

图:使用照片

4. 讨论和下一步工作


虽然制作成本低廉,但得益于在算法上的优势,本激光投影键盘的性能并不差。我并没有用过Celluon公司销售的激光投影键盘产品,因此无法与它们的产品做出比较,这里就单独按照我自己的使用感受以及性能做出评价。

得益于采用了[2]中描述的测距算法,该激光键盘可以识别出0.1mm的细微手指移动,并且通过校正可以达到2mm的最大误差水平。另外理论上通过视觉定位算法,本制作可以支持任意多个按键同时输入。而在输入响应速度上,由于采用的usb摄像头是30fps的,所以本键盘的最快响应频率就是30Hz,这虽然不敌传统键盘100hz以上的速度,但作为日常使用来说是足够了。

性能指标

描述

反应速度

30hz

最多同时输入的按键(多点触摸点数)

无穷多(软件目前限制在10点)

分辨率

0.1mm

最大测距误差

2mm

传输协议

USB

不过由于不是实体键盘,无法提供物理反馈,因此无法进行盲打,输入速度也不够快。这点我相信Celluon公司的产品也有同样的问题。就我们这个DIY作品来说,相信它应该是一个成功的作品。

下一步工作


虽然本制作已经完满达到当初的设计要求,不过还有不少可以改善的地方。这里我就当是抛砖引玉给大家一个借鉴,欢迎大家在此基础上做出更多的扩展。

就装置尺寸上就有很多可以改进的余地,比如大家会发现本制作的体积要比Celluon公司产品大很多,这主要是因为我们采用的摄像头镜头的视角仍旧不够大,需要安装在较高的高度上。

另外就定位算法而言,其实也有不少可以优化的地方,比如其实我们未必需要那么高精度的激光定位数据,如果只是简单的作为键盘输入的话,可以降低测距精度,或者完全使用其他算法来实现按键识别的过程。

另外目前的设计需要在PC上进行所有的视觉运算,这样的好处是大幅降低了制作成本和难度,但缺点也很明显:无法在手机等设备上使用。当然现在的智能手机一般带有USB OTG功能,可以外接usb设备,同时OpenCV也可以在Android、iOS等移动设备上运行,因此这里的实现仍旧可以通过移植运行在这些移动设备上。不过如果将这些算法移植到嵌入式芯片,比如DSP或者ARM内,则可以完全摆脱对PC的依赖。键盘自身就可处理所有视觉计算并直接输出按键事件,也可以加装WIFI或者蓝牙,做成无线传输。这其实也并不是个遥不可及的改进,不过那样的话,我们的制作就足以与Celluon的产品竞争了

5. 参考文献


[1] RGB laser projector – 0,5W
http://www.edaboard.com/thread230353.html

[2] 自制低成本3D激光扫描测距仪(3D激光雷达),第一部分
http://www.csksoft.net/blog/post/lowcost_3d_laser_ranger_1.html

[3] short for k-dimensional tree
http://en.wikipedia.org/wiki/K-d_tree

[4] Multi-Touch Technologies, NUI Group Authors
http://nuicode.com/attachments/download/115/Multi-Touch_Technologies_v1.01.pdf

[5] Tacchi is SigMusic's 46" fully multitouch table.
http://www.acm.uiuc.edu/sigmusic/tacchi.html

[6] OpenCV (Open Source Computer Vision) is a library of programming functions for real time computer vision.
http://opencv.org/

[7] Camera Calibration Toolbox for Matlab
http://www.vision.caltech.edu/bouguetj/calib_doc/

[8] Digital Image Processing, Second Edition, ISBN: 0-201-18075-8

[9] libkdtree++ is an STL-like C++ template container implementation of k-dimensional space sorting, using a kd-tree
http://libkdtree.alioth.debian.org/

[10] SendInput Win32 API
http://msdn.microsoft.com/en-us/library/windows/desktop/ms646310(v=vs.85).aspx

分页:[«]1[»]

Copyright Shikai Chen 2000-2012. Powered By Z-Blog(CSK Modified)