对大学期间做的一些设计的汇总之一
原理和实现流程
激光投影虚拟键盘是基于OpenCV结合一字红外激光、图像摄像头、红外滤光片组成。
使用摄像头获取到原始图像、对原始图像进行二值化、查找图像轮廓、获取中心坐标,进而检测出由于手指遮挡所引起的一字红外激光反射生成的光点,通过对光点中心位置的检测映射到键盘位置,从而实现了对应的按键行为。
使用OpenCV视觉库可以很快捷查找由图像摄像头获取到的手指头轮廓和定位手指头的位置以及校正由图像摄像头引起的图像曲面失真。
硬件系统
硬件:一个摄像头(视角尽可能大一点),一块arduino板,一个一字红外发射器,一个滤波片,一个键盘投影,一台电脑
注意:用于装在摄像头前的滤波片要能正好过滤可见光并不能过滤红外光,但尽管是这样,在白天自然光的时候效果依然做不到很好,所以就在晚上关灯调试。
我这里的摄像头因为视角太小就只能装的比较高。自上而下的三个硬件分别为摄像头、键盘投影器、一字红外发射器。
效果图
因为图像读取是有一定延迟的,多按键被识别为按下,输出结果可能会出错。
我这里的处理是二值图的中心点所在按键位置,所以比如同时按A和L,可能处处结果为G。
部分代码
#include <iostream>
#include "opencv2/opencv.hpp"
#include "opencv2/imgproc/imgproc.hpp"
#include "opencv2/highgui/highgui.hpp"
#include <cv.h>
#include <highgui.h>
#include <stack>
#include <opencv2/video.hpp>
#include <stdio.h>
#include <stdlib.h>
#include <string>
#include<ostream>
#include<fstream>
#include<Windows.h>
using namespace cv;
using namespace std;
void refresh(String a) {//保存到txt
ofstream fout;
fout.open("data.txt",std::ios::out|std::ios::app);
fout << a;
fout.close();
}
int main()
{
VideoCapture capture(0);
Mat frame;
capture >> frame;
if (!capture.isOpened())
{
printf("can not open ...\n");
return -1;
} //获取视频的第一帧,并框选目标
while (1)
{
capture.read(frame);
if (!frame.empty())
{
namedWindow("output", WINDOW_AUTOSIZE);
imshow("output", frame);
//setMouseCallback("output", draw_rectangle, 0);
waitKey(250);
}
Mat img, imgGray, two;
img = frame;
cvtColor(img, imgGray, CV_BGR2GRAY);//先对彩色图像进行灰度化
threshold(imgGray, two, 10, 255, CV_THRESH_BINARY);//对灰度图进行二值化
// vector<vector<Point>> contours;
// vector<Vec4i> hierarchy;
// findContours(two, contours, hierarchy, RETR_CCOMP, CHAIN_APPROX_SIMPLE);
// cvNamedWindow("二值化");// 创建一个名为窗口
// imshow("二值化", two);// 在窗口中显示
Mat ele = getStructuringElement(MORPH_RECT, Size(2, 2));//图像侵蚀
Mat des;
erode(two, des, ele);
vector<Point2f> point;
int count = 0;
int x = 0, y = 0;
RNG& rng = theRNG();
Mat image(600, 600, CV_8UC3);
for (int j = 0; j < des.rows; j++) {//求二值图的白点的点集
for (int i = 0; i < des.cols; i++) {
if (des.at<uchar>(j, i) == 255) {
point.push_back(Point2f(i, j));
count++;
x += i;
y += j;
}
}
}
if (count != 0) {
//Sleep(200);
Point2f center;//寻找最小面积的包围圆
float radius = 0;
try {
minEnclosingCircle(Mat(point), center, radius);
}
catch (Exception) {}
//测位置代码:
/*int ox=0,oy=0;
ox=center.x;
oy=center.y;
cout<<"ox:"<<ox<<" oy:"<<oy<<endl;
Sleep(100);
*/
String o="";
//根据键盘实际位置来调整
if (center.y > 198 && center.y <= 234) {//数字显示
if (center.x > 90 && center.x <= 132) {
o = "1";
}
else if (center.x <= 174) {
o = "2";
}
...//以下类似
}
else if (center.y > 234 && center.y <= 270) {//字母第一行
...
}
else if (center.y > 270 && center.y < 306) {//字母第二行
...
}
else if (center.y > 306 && center.y < 342) {//字母第三行
...
}
else if (center.y > 342 && center.y < 378) {//空格行
if (center.x > 265 && center.x <= 445) {
o = " ";
}
}
else {
cout << "wrong" << endl;
}
cout << o ;
refresh(o);
Sleep(200);//消抖
}
cvNamedWindow("轮廓图");// 创建一个名为窗口
imshow("轮廓图", des);// 在窗口中显示
}
return 0;
}