连笔字网 > 知识库

激光虚拟投影键盘

来源:连笔字网 2024-01-22 05:18:38 作者:连笔君

自制激光虚拟投影键盘

先上一个小视频,是我自制的键盘demo。

点击访问视频

再上一张实物图,够山寨把:

虚拟激光投射键盘在1992年就由IBM发明了。我第一次看到这玩意儿就觉得特别新奇,后来看到淘宝上棒子的产品,要900多米,实在是宰人啊。于是就一直有想法做一个。不久前在淘宝看到了投射键盘图案的激光模组,果断买了一个,开始筹划制作一个。

taobao上买的650nm虚拟键盘激光组件:

而且现在有了强大的opencv图像处理库,实现这样的虚拟激光投射键盘变得易如反掌。

先说说投影键盘的基本原理。键盘由三个主要部件组成:摄像头、键盘图案投射器、一字线性感应激光头。见下图:

图上从上到下分别是键盘图案投射器、摄像头、一字线性感应激光头。当然,摄像头放在键盘图案投射器上面也是可以的,比如我就是这么做的。1. 键盘图案投射器在平坦的桌面投出清晰键盘图案2. 最底下的一字线性激光(一般采用红外线的,这样眼睛不可见)发出一字型激光,平行于桌面射出,这样如果手指有按键活动,会在手指上形成激光光斑3. 摄像头捕获激光光斑,对应于键盘图案映射的位置,就可以知道哪些键被按下OK,原理很简单,是不是。有了这些模块,剩下的关键就是摄像头的图像处理算法了,而且现在有了opencv,实现也不是难事。这里说一下我的实现方法。

由于人眼对激光的反应不一样,780nm-808nm的激光人眼不敏感,可看到微弱的一丝红光。850nm至1064nm波长人眼不可见,通过红外感光仪器等专业设备可以看到,其中808-850nm通过摄像头可以看到。980-1064nm通过倍频片可以看到。所以我在网上买了一个808nm-810nm 红外一字线激光器。这样配上滤光片,可以滤去绝大多数其他波长的杂光,只剩下红外激光的光斑。这样做的好处是减少干扰,增加键盘的可靠性,而且使算法处理更加简单有效。加上前面的650nm虚拟键盘激光组件,总共也就花了100块钱左右。

25mw 808nm-810nm 红外一字线激光器 激光头

直径18mm可见光截止400-750nm滤光片,800-1000nm高透

在摄像头上看到的红外激光光斑投射到手指的图像如下图:

对于光斑的跟踪我找了个现成的opencv扩展库cvblob,具体可以参考它的文档和例子,google code上有这个项目的托管。待会儿会奉上代码。cvblob可以跟踪多个光斑,所以很容易就可以实现ctrl+alt+delete之类的组合键。

再来两张键盘图:

顺便说一句,本文中的摄像头放的位置只能捕捉到部分键盘图像,所以demo只是演示了部分键盘的按键。不过丝毫不影响原理介绍。如果要获得全部键盘图像,或者去买一个广角的摄像头,或者把这个摄像头位置提高,不是什么难事。时间有限,不想折腾了。

代码:

#include#include

#include "opencv/cv.h"#include "opencv/highgui.h"

#include "cvblob.h"using namespace cvb;

typedef struct key{char c;int x0;int y0;int x1;int y1;};

key g_keymap[]={{'4',525,350,588,419},{'5',442,345,504,414},{'6',360,339,422,408},{'7',277,332,342,404},{'8',198,327,259,399},{'9',121,320,174,389},{'0',41, 318,94, 383},{'E',528,274,590,337},{'R',443,267,507,332},{'T',359,263,428,327},{'Y',280,259,344,321},{'U',199,251,261,315},{'I',119,246,179,307},{'O',41, 240,96, 301},{'D',504,203,567,259},{'F',424,199,489,257},{'G',348,194,410,251},{'H',266,187,329,245},{'J',192,183,251,241},{'K',117,178,171,236},{'L',42 ,174,92, 229},{'X',543,144,605,197},{'C',467,139,530,191},{'V',392,135,457,190},{'B',316,128,377,181},{'N',242,124,299,176},{'M',171,118,225,172},{'',26, 108,73, 159},{'_',182,62, 531,127},};

int g_key_num=sizeof(g_keymap)/sizeof(key);

int main(){CvTracks tracks;

cvNamedWindow("red_object_tracking", CV_WINDOW_AUTOSIZE);

CvCapture *capture=cvCaptureFromCAM(0);cvGrabFrame(capture);IplImage *img=cvRetrieveFrame(capture);

CvSize imgSize=cvGetSize(img);

IplImage *frame=cvCreateImage(imgSize, img->depth, img->nChannels);

IplConvKernel* morphKernel=cvCreateStructuringElementEx(5, 5, 1, 1, CV_SHAPE_RECT, NULL);

//unsigned int frameNumber=0;unsigned int blobNumber=0;

bool quit=false;while (!quit&&cvGrabFrame(capture)){IplImage *img=cvRetrieveFrame(capture);

cvConvertScale(img, frame, 1, 0);

IplImage *segmentated=cvCreateImage(imgSize, 8, 1);

// Detecting red pixels:// (This is very slow, use direct access better...)for (unsigned int j=0; jfor (unsigned int i=0; i0.2+g)&&(r>0.2+b));// cvSet2D(segmentated, j, i, CV_RGB(f, f, f));if(b>0.4 || g>0.4 || r>0.4)cvSet2D(segmentated, j, i, CV_RGB(255, 255, 255));elsecvSet2D(segmentated, j, i, CV_RGB(0, 0, 0));}

cvMorphologyEx(segmentated, segmentated, NULL, morphKernel, CV_MOP_OPEN, 1);

cvShowImage("segmentated", segmentated);

IplImage *labelImg=cvCreateImage(cvGetSize(frame), IPL_DEPTH_LABEL, 1);

CvBlobs blobs;unsigned int result=cvLabel(segmentated, labelImg, blobs);cvFilterByArea(blobs, 500, 1000000);cvRenderBlobs(labelImg, blobs, frame, frame, CV_BLOB_RENDER_BOUNDING_BOX);cvUpdateTracks(blobs, tracks, 200., 5);cvRenderTracks(tracks, frame, frame, CV_TRACK_RENDER_ID|CV_TRACK_RENDER_BOUNDING_BOX);

cvShowImage("red_object_tracking", frame);

// print keyfor (CvTracks::const_iterator it=tracks.begin(); it!=tracks.end(); ++it){int xx=(int)it->second->centroid.x;int yy=(int)it->second->centroid.y;//std::cout<< xx<< ','<< yy<< std::endl;

for(int i=0; ig_keymap.x0 &&xxg_keymap[i].y0 &&yy< g_keymap[i].y1){std::cout<< g_keymap[i].c<< std::endl;break;}}}cvReleaseImage(&labelImg);cvReleaseImage(&segmentated);char k=cvWaitKey(10)&0xff;switch (k){case 27:case 'q':case 'Q':quit=true;break;case 's':case 'S':for (CvBlobs::const_iterator it=blobs.begin(); it!=blobs.end(); ++it){std::stringstream filename;filename<< "redobject_blob_"<< std::setw(5)<< std::setfill('0')<< blobNumberblobNumber++;std::cout<< filename.str()<< " saved!"<< std::endl;}break;}cvReleaseBlobs(blobs);//frameNumber++;}cvReleaseStructuringElement(&morphKernel);cvReleaseImage(&frame);cvDestroyWindow("red_object_tracking");return 0;}

上一篇:柳岩代言的护肤品广告

下一篇:没有了