#include <iostream> #include <iomanip> #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}, {'<',98, 114,149,166}, {'>',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; j<imgSize.height; j++) for (unsigned int i=0; i<imgSize.width; i++) { CvScalar c = cvGet2D(frame, j, i); double b = ((double)c.val[0])/255.; double g = ((double)c.val[1])/255.; double r = ((double)c.val[2])/255.; // unsigned char f = 255*((r>0.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)); else cvSet2D(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 key for (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; i<g_key_num; i++) { if(xx > g_keymap.x0 && xx < g_keymap[i].x1 && yy > g_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') << blobNumber << ".png"; cvSaveImageBlob(filename.str().c_str(), img, it->second); blobNumber++; std::cout << filename.str() << " saved!" << std::endl; } break; } cvReleaseBlobs(blobs); //frameNumber++; } cvReleaseStructuringElement(&morphKernel); cvReleaseImage(&frame); cvDestroyWindow("red_object_tracking"); return 0; }
我第一次看到这玩意儿就觉得特别新奇,后来看到淘宝上棒子的产品,要900多米,实在是宰人啊。于是就一直有想法做一个。
不久前在淘宝看到了投射键盘图案的激光模组,果断买了一个,开始筹划制作一个。
taobao上买的650nm虚拟键盘激光组件:
虚拟键盘激光组件
而且现在有了强大的opencv图像处理库,实现这样的虚拟激光投射键盘变得易如反掌。
先说说投影键盘的基本原理。键盘由三个主要部件组成:摄像头、键盘图案投射器、一字线性感应激光头。
见下图:
投影键盘的基本原理
图上从上到下分别是键盘图案投射器、摄像头、一字线性感应激光头。
当然,摄像头放在键盘图案投射器上面也是可以的,比如我就是这么做的。
1. 键盘图案投射器在平坦的桌面投出清晰键盘图案
2. 最底下的一字线性激光(一般采用红外线的,这样眼睛不可见)发出一字型激光,平行于桌面射出,这样如果手指有按键活动,会在手指上形成激光光斑
3. 摄像头捕获激光光斑,对应于键盘图案映射的位置,就可以知道哪些键被按下
OK,原理很简单,是不是。有了这些模块,剩下的关键就是摄像头的图像处理算法了,而且现在有了opencv,实现也不是难事。
这里说一下我的实现方法。
可见光谱
由于人眼对激光的反应不一样,780nm-808nm的激光人眼不敏感,可看到微弱的一丝红光。850nm至1064nm波长人眼不可见,通过红外感光仪器等专业设备可以看到,其中808-850nm通过摄像头可以看到。980-1064nm通过倍频片可以看到。
所以我在网上买了一个808nm-810nm 红外一字线激光器。这样配上滤光片,可以滤去绝大多数其他波长的杂光,只剩下红外激光的光斑。
这样做的好处是减少干扰,增加键盘的可靠性,而且使算法处理更加简单有效。
加上前面的650nm虚拟键盘激光组件,总共也就花了100块钱左右。
25mw 808nm-810nm 红外一字线激光器 激光头
红外一字线激光器
顺便说一句,本文中的摄像头放的位置只能捕捉到部分键盘图像,所以demo只是演示了部分键盘的按键。
不过丝毫不影响原理介绍。如果要获得全部键盘图像,或者去买一个广角的摄像头,或者把这个摄像头位置提高,不是什么难事。
时间有限,不想折腾了。
激光投影键盘
代码: