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以上的速度,但作为日常使用来说是足够了。
不过由于不是实体键盘,无法提供物理反馈,因此无法进行盲打,输入速度也不够快。这点我相信Celluon公司的产品也有同样的问题。就我们这个DIY作品来说,相信它应该是一个成功的作品。
下一步工作
虽然本制作已经完满达到当初的设计要求,不过还有不少可以改善的地方。这里我就当是抛砖引玉给大家一个借鉴,欢迎大家在此基础上做出更多的扩展。
就装置尺寸上就有很多可以改进的余地,比如大家会发现本制作的体积要比Celluon公司产品大很多,这主要是因为我们采用的摄像头镜头的视角仍旧不够大,需要安装在较高的高度上。
另外就定位算法而言,其实也有不少可以优化的地方,比如其实我们未必需要那么高精度的激光定位数据,如果只是简单的作为键盘输入的话,可以降低测距精度,或者完全使用其他算法来实现按键识别的过程。
另外目前的设计需要在PC上进行所有的视觉运算,这样的好处是大幅降低了制作成本和难度,但缺点也很明显:无法在手机等设备上使用。当然现在的智能手机一般带有USB OTG功能,可以外接usb设备,同时OpenCV也可以在Android、iOS等移动设备上运行,因此这里的实现仍旧可以通过移植运行在这些移动设备上。不过如果将这些算法移植到嵌入式芯片,比如DSP或者ARM内,则可以完全摆脱对PC的依赖。键盘自身就可处理所有视觉计算并直接输出按键事件,也可以加装WIFI或者蓝牙,做成无线传输。这其实也并不是个遥不可及的改进,不过那样的话,我们的制作就足以与Celluon的产品竞争了
源代码:
请前往RoboPeak团队页面获取:
http://www.robopeak.net/blog/?p=315
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/d ... hnologies_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