交互系統的構建之(一)http://blog.csdn.net/zouxy09/article/details/7919618 中提到我的整個交互系統包含以下部分: TLD系統、TTS語音合成、語音識別、手勢和語音控制鼠標和鍵盤、運行前加入手掌的檢測(這樣就不用鼠標畫目標box了)、拳頭的檢測等等。
目前已完成:
1、TLD系統的介紹與編譯:
http://blog.csdn.net/zouxy09/article/details/7893022
2、TLD系統工作過程分析:
http://blog.csdn.net/zouxy09/article/details/7893026
3、重寫Makefile編譯TLD系統:
http://blog.csdn.net/zouxy09/article/details/7919618
本文將完成:
Linux下鼠標和鍵盤的模擬控制,也就是爲手勢和語音控制鼠標和鍵盤部分服務的。
有關於本系統構建的文章結構都會由三個部分來組織,一是該功能模塊的介紹和在Linux下簡單應用程序的實現;二是將該功能模塊整合到交互系統(先以TLD爲地基)中去;三是分析目前存在的問題與未來的解決構思。
一、input 子系統和模擬程序編寫:
Linux 輸入子系統是 Linux內核用於管理各種輸入設備(鍵盤,鼠標,遙控杆,書寫板等等)的。輸入子系統分爲三塊: input core, drivers和 event handlers。正常的路徑是從底層硬件到驅動,從驅動到 input core,從 input core到 event handler,從 event handler到user space。
這麼說吧:如果是沒有這個input子系統的話,假如我們用鍵盤按下了一個鍵A,鍵盤會有一個linux的設備驅動文件,假設是/dev/keyboard,我們的用戶空間的應用程序就會打開並訪問這個設備文件/dev/keyboard,應用程序會輪詢這個文件,一旦你按下了一個鍵A了,它就會返回給用戶程序說你按下了鍵A。那麼如果有了input子系統的話,我們的用戶空間的應用程序就不是直接打開和訪問鍵盤的設備驅動文件了,而是訪問由鍵盤驅動在input子系統中註冊的event事件文件,例如/dev/input/event3,而對於鍵盤驅動來說,它也是實現由input子系統提供的接口就可以了。按下鍵了,就發送給input子系統。不再直接與用戶空間的應用程序直接面對面了。這樣,就很方便的對驅動和應用程序都統一了接口,而且同一種接口還適合管理多種硬件。好像很簡單的問題給我囉嗦化了,不知道有沒有說錯,呵呵。
這個input子系統可以很容易地讓我們在用戶空間模擬鼠標和鍵盤事件。例如,你可以寫一個應用程序,往input子系統的/dev/input/event3設備文件(假設這個是鍵盤設備文件)寫入A,這樣就相當於你通過鍵盤按下了A,而這個A對系統任意的一個當前活動窗口有效(捕捉)。
要實現這個功能,我們需要回答一下幾個問題:
1、往什麼設備文件寫?
也就是如何查看哪些設備文件是鍵盤的,哪些是鼠標的,找到這些設備文件,我們纔可以通過應用程序來打開和寫入鍵值(或者鼠標的控制信息)。
通過 #cat /proc/bus/input/devices可以查看到當前input子系統下面的所有event設備,我們找到鼠標和鍵盤的即可。
例如:
I: Bus=0003 Vendor=046d Product=c018 Version=0111
N: Name=" USB Optical Mouse"
P: Phys=usb-0000:00:1d.1-2/input0
S: Sysfs=/class/input/input24
U: Uniq=
H: Handlers=mouse1 event2
B: EV=7
B: KEY=70000 0 0 0 0 0 0 0 0
B: REL=103
上面Name處可以看到這個鼠標設備,然後對應的句柄Handlers是event2;
2、怎麼寫入:
大家都知道,Linux下萬物皆文件,所以對於文件操作我們只需要:open()和write()就可以了。
3、要寫入什麼東西(鍵值的編碼):
在/usr/include/linux/input.h中有定義,這個文件定義了event事件的結構體,API和標準按鍵的編碼等;我們需要將要寫入的按鍵編碼填充到結構體中,然後寫入鍵盤或者鼠標的事件設備驅動文件中。
輸入事件的結構體:
struct input_event {
struct timeval time; //按鍵時間
__u16 type; //事件的類型
__u16 code; //要模擬成什麼按鍵
__s32 value; //是按下1還是釋放0
};
標準按鍵的編碼:(只列舉部分)
type:
事件的類型:
EV_KEY, 按鍵事件,如鍵盤的按鍵(按下哪個鍵),鼠標的左鍵右鍵(是非擊下)等;
EV_REL, 相對座標,主要是指鼠標的移動事件(相對位移);
EV_ABS, 絕對座標,主要指觸摸屏的移動事件,但好像這個不能用在鼠標上面,也就是說無法通過這個來獲取鼠標的絕對座標(鼠標是一個相對位移的設備)。
code:
事件的代碼:
如果事件的類型代碼是EV_KEY,該代碼code爲設備鍵盤代碼。代碼植0~127爲鍵盤上的按鍵代碼,0x110~0x116 爲鼠標上按鍵代碼,其中0x110(BTN_ LEFT)爲鼠標左鍵,0x111(BTN_RIGHT)爲鼠標右鍵,0x112(BTN_ MIDDLE)爲鼠標中鍵。其它代碼含義請參看include/linux /input.h文件。該文件中會定義相應的宏來代表不同的按鍵。
如果事件的類型代碼是EV_REL,code值表示軌跡的類型。如指示鼠標的X軸方向REL_X (代碼爲0x00),指示鼠標的Y軸方向REL_Y,指示鼠標中輪子方向REL_WHEEL。
value:
事件的值:
如果事件的類型代碼是EV_KEY,當按鍵按下時值爲1,鬆開時值爲0;
如果事件的類型代碼是EV_ REL,value的正數值和負數值分別代表兩個不同方向的值。例如:如果code是REL_X,value是10的話,就表示鼠標相對於上一次的座標,往x軸向右移動10個像素點。
在Linux下寫的簡單的模擬鼠標和鍵盤事件的程序:
#include <stdio.h>
#include <linux/input.h>
#include <fcntl.h>
#include <sys/time.h>
#include <unistd.h>
//按鍵模擬,按鍵包含按下和鬆開兩個環節
void simulate_key(int fd, int kval)
{
struct input_event event;
gettimeofday(&event.time, 0);
//按下kval鍵
event.type = EV_KEY;
event.value = 1;
event.code = kval;
write(fd, &event, sizeof(event));
//同步,也就是把它報告給系統
event.type = EV_SYN;
event.value = 0;
event.code = SYN_REPORT;
write(fd, &event, sizeof(event));
memset(&event, 0, sizeof(event));
gettimeofday(&event.time, 0);
//鬆開kval鍵
event.type = EV_KEY;
event.value = 0;
event.code = kval;
write(fd, &event, sizeof(event));
//同步,也就是把它報告給系統
event.type = EV_SYN;
event.value = 0;
event.code = SYN_REPORT;
write(fd, &event, sizeof(event));
}
//鼠標移動模擬
void simulate_mouse(int fd, int rel_x, int rel_y)
{
struct input_event event;
gettimeofday(&event.time, 0);
//x軸座標的相對位移
event.type = EV_REL;
event.value = rel_x;
event.code = REL_X;
write(fd, &event, sizeof(event));
//y軸座標的相對位移
event.type = EV_REL;
event.value = rel_y;
event.code = REL_Y;
write(fd, &event, sizeof(event));
//同步
event.type = EV_SYN;
event.value = 0;
event.code = SYN_REPORT;
write(fd, &event, sizeof(event));
}
int main(int argc, char **argv)
{
int fd_mouse = -1;
int fd_kbd = -1;
int i = 0;
fd_kbd = open("/dev/input/event3", O_RDWR);
if(fd_kbd <= 0)
{
printf("Can not open keyboard input file\n");
return -1;
}
fd_mouse = open("/dev/input/event2", O_RDWR);
if(fd_mouse <= 0)
{
printf("Can not open mouse input file\n");
return -1;
}
for (i = 0; i < 50; i++)
{
simulate_key(fd_mouse, BTN_LEFT); //模擬按下鼠標左鍵
//if (i % 3 == 0)
// simulate_key(fd_kbd, KEY_A); //模擬按下鍵盤A鍵
//模擬鼠標相對上次x和y軸相應移動10個像素
//simulate_mouse(fd_mouse, 10, 10);
sleep(3);
}
close(fd_kbd);
close(fd_mouse);
}
那麼如何模擬組合鍵呢?其實和大家平時按鍵盤的過程是一樣的,我們用程序按照這個過程來模擬就可以了。以CTRL + SPACE爲例:
//先發送一個 CTRL 按下去的事件
//再發送一個 SPACE 按下去的事件
//然後發送一個釋放 SPACE 的事件
//再發送一個釋放 CTRL 的事件
得注意每步的發送都需要同步一次。
二、整合到交互系統(先以TLD爲地基)中去
這個因爲不用涉及額外鏈接一些庫,所以整合就變得很簡單了。只需要修改run_tld.cpp:
1、添加run_tld.cpp中沒有的,但模擬按鍵卻需要的頭文件:
#include <linux/input.h>
2、把simulate_key()和simulate_mouse()兩個函數的實現複製到run_tld.cpp的main函數的前面。然後在tld.init(last_gray, box, bb_file); 後面添加:
//xiaoyi added here
int fd_kbd = -1;
int fd_mouse = -1;
int open_success = 1;
fd_kbd = open("/dev/input/event3", O_RDWR);
if(fd_kbd <= 0)
{
printf("Can not open keyboard input file\n");
open_success = 0;
}
fd_mouse = open("/dev/input/event2", O_RDWR);
if(fd_mouse <= 0)
{
printf("Can not open mouse input file\n");
open_success = 0;
}
3、在TLD跟蹤到box後,獲取本幀跟蹤的box和上一幀box的位移,如果位移大於2個像素(避免抖動),鼠標就移動8倍像素距離。
pbox是當前幀跟蹤到的目標box,tbox是我自己定義的,用來存放上一幀跟蹤到的目標box的。
tld.processFrame(last_gray,current_gray,pts1,pts2,pbox,status,tl,bb_file);
//Draw Points
if (status) {
drawPoints(frame,pts1);
drawPoints(frame,pts2,Scalar(0,255,0));
drawBox(frame,pbox);
detections++;
//xiaoyi added here
x_pixel_move =(int)( (tbox.x + tbox.width)/2 - (pbox.x + pbox.width)/2);
y_pixel_move =(int)( (pbox.y + pbox.height)/2 - (tbox.y + tbox.height)/2);
if (norm(x_pixel_move) > 2 || norm(y_pixel_move) > 2 )
simulate_mouse(fd_mouse, 8 * x_pixel_move, 8 * y_pixel_move);
tbox = pbox;
//下面這部分是用來測試當手快速左右上下移動時,向系統發送左右上下的模擬按鍵事件
/*
if (open_success && (x_pixel_move > 8 || x_pixel_move < -8 || y_pixel_move > 8 || y_pixel_move < -8))
{
if (x_pixel_move < -8)
simulate_key(fd_kbd, KEY_RIGHT);
else if (x_pixel_move > 8)
simulate_key(fd_kbd, KEY_LEFT);
else if (y_pixel_move < -8)
simulate_key(fd_kbd, KEY_UP);
else if (y_pixel_move > 8)
simulate_key(fd_kbd, KEY_DOWN);
tbox = pbox;
x_pixel_move = 0;
y_pixel_move = 0;
}
*/
}
三、存在的問題和解決思路:
1、鼠標絕對座標的獲取:據我的瞭解,好像Linux或者c並不提供直接獲取鼠標絕對座標的API,而需要通過第三方的API來獲取。另外,也許也不需要獲取絕對座標,所以暫時擱置;
2、手掌控制鼠標不穩定(漂移)與範圍控制沒處理好:可能通過卡爾曼濾波和速度映射等方法來做改進,後面再處理;
3、代碼結構亂:一旦自己後面加入了很多模塊,這樣代碼就會比較混亂,所以需要後期進行各模塊代碼的整理,已達到內聚性強點,而且代碼容易管理。