学习OpenCV 06 鼠标事件
与键盘事件不同,鼠标事件是通过更加传统的回调(callback)函数机制完成的,即我们必须先写好一个回调程序,使OpenCV在发送任何鼠标事件时都可以调用这个回调程序。当我们完成回调程序后,需要在OpenCV中注册(register)这个函数,即告诉OpenCV这是一个正常的回调程序,用户使用鼠标在特定窗口完成一些操作后可以得到一定的响应。
回调callback
回调其实就是任何以一个参数集(the correct set of arguments)并返回一个正确的类型(the correct type)的函数。需要明确告诉程序使用的回调函数发生的事件类型和发生地点。若用户按下鼠标同时还按下Shift键或Alt键,也需明确告诉回调函数。指向这样的函数的指针是cv::MouseCallback。以下是定义回调函数必须匹配的接口协议:
void your_mouse_callback(
int event, //事件类型
int x, //鼠标事件的X坐标
int y, //鼠标事件的Y坐标
int flags, //事件的更多细节
void* param, //cv::setMouseCallback()里的参数
)
其中,第一个参数会取下表的一个值
事件 | 数值 |
cv::EVENT_MOUSEMOVE | 0 |
cv::EVENT_LBUTTONDOWN | 1 |
cv::EVENT_RBUTTONDOWN | 2 |
cv::EVENT_MBUTTONDOWN | 3 |
cv::EVENT_LBUTTONUP | 4 |
cv::EVENT_RBUTTONUP | 5 |
cv::EVENT_MBUTTONUP | 6 |
cv::EVENT_LBUTTONDBLCLK | 7 |
cv::EVENT_RBUTTONDBLCLK | 8 |
cv::EVENT_MBUTTONDBLCLK | 9 |
第二和第三个参数将被设置为鼠标事件的X坐标和Y坐标。!!!需要注意:这里的坐标是图像上的像素坐标,独立于窗口其他细节的。
第四个参数为标志值,是一个bit位,每个bit位都代表在事件发生时的不同条件。如cv::EVENT_FLAG_SHIFTKEYD的数值是16(即第四个bit,也就是10000即1<<4[1左移4位]);因此,我们若想测试Shift键是否按下,可以计算按位与(AND)flags&cv:: EVENT_FLAG_SHIFTKEYD。下表为完整的标志列表
标志 | 数值 |
cv::EVENT_FLAG_LBUTTON | 1 |
cv::EVENT_FLAG_RBUTTON | 2 |
cv::EVENT_FLAG_MBUTTON | 4 |
cv::EVENT_FLAG_CTRLKEY | 8 |
cv::EVENT_FLAG_SHIFTKEY | 16 |
cv::EVENT_FLAG_ALTKEY | 32 |
最后一个参数是一个void类型指针,基于这种类型指针,OpenCV可以用来传递额外信息到任何类型的结构。
注册回调函数
接下来,就需要一个函数来注册这个回调函数,即cv::setMouseCallback(),它需要三个参数
void cv::setMouseCallback(
const string& windowName, //标志窗口的句柄
cv::MouseCallbacn on_mouse, //回调函数
void* param=NULL //回调函数的附加参数
)
第一个参数是回调函数作用的窗口名称,即只有事件在这个特定窗口中时才能出发回调。
第二个参数是要注册的回调函数。
第三个即刚才提到的回调函数接口协议的定义里面的param,可以让我们在回调函数执行时给回调函数传递特定的信息。
实例:使用鼠标回调完成绘制方框的小程序
#include <opencv2/opencv.hpp>
#include <iostream>
using namespace std;
using namespace cv;
void my_mouse_callback(int event, int x, int y, int flags, void* param);//回调函数定义
Rect box;
bool drawing_box = false;
void draw_box(Mat &img, Rect box) {
rectangle(img, box.tl(), box.br(), Scalar(0, 0, 255));//tl()提取左上角,br()提取右下角,Scalar(0,0,255)为红色。即在img上绘图一个红色的从左上角到右下角的矩形
}
void help() {
cout << "show how to use a mouse to draw regions in an image." << endl;
}
int main(int argc, char**argv) {
help();
box = Rect(-1, -1, 0, 0);//Rect(x,y,w,h),(x,y)为左上角,第3、4参数为width和height
Mat image(200, 200, CV_8UC3), temp;
image.copyTo(temp);
box = Rect(-1, -1, 0, 0);
image = Scalar::all(0);
namedWindow("Box Example");
setMouseCallback("Box Example", my_mouse_callback, (void*)&image);
for (;;) {
image.copyTo(temp);
if (drawing_box) draw_box(temp, box);
imshow("Box Example", temp);
if (waitKey(15)==27) break;
}
return 0;
}
void my_mouse_callback(int event, int x, int y, int flags, void* param) {
Mat& image = *(Mat *)param;
switch (event)
{
case EVENT_MOUSEMOVE: {
if (drawing_box) {
box.width = x - box.x;
box.height = y - box.y;
}
}
break;
case EVENT_LBUTTONDOWN: {
drawing_box = true;
box = Rect(x, y, 0, 0);
}
break;
case EVENT_LBUTTONUP: {
drawing_box = false;
if (box.width < 0) {
box.x += box.width;
box.width *= -1;
}
if (box.height < 0) {
box.y += box.height;
box.height *= -1;
}
draw_box(image, box);
}
break;
}
}
程序结果:
关于cv::waitKey()函数的说明
waitKey()除了设定自动返回之前等待按键的事件量(ms为单位)外,还有个功能是让任何OpenCV窗口进行更新。这意味着如果不调用waitKey(),图像可能永远不会在窗口中绘制,或者窗口在移动、调整大小、从覆盖中显示出来等时回很奇怪甚至严重异常。