目录
实验原理
在 OpenCV 中存在鼠标的操作,比如左键单击、双击等。对于 OpenCV 来讲,用户的鼠标操作被认为发生了一个鼠标事件,需要对这个鼠标事件进行处理,这就是事件的响应。下面我们来介绍一下鼠标事件。
鼠标事件包括左键按下、左键松开、左键双击、鼠标移动等。当鼠标事件发生时,OpenCV 会让一个鼠标响应函数自动被调用,相当于一个回调函数,这个回调函数就是鼠标事件处理函数。 OpenCV 提供了 setMousecallback 来预先设置回调函数(相当于告诉系统鼠标处理的回调函数已经设置好了,有鼠标事件发生时,系统调用这个回调函数即可),注意是系统调用,而不是开发者调用,因此称为回调函数。函数 setMousecallback 声明如下:
cpp
void setMousecallback(const string& winname, MouseCallback onMouse, void*userdata=0);
其中参数
winname 表示窗口的名字,
onMouse 是鼠标事件响应的回调函数指针,
userdate 是传给回调函数的参数。
这个函数名也比较形象,一看就知道是用来设置鼠标回调函数的。
鼠标事件回调函数类型 MouseCallback 定义如下:
cpp
typedef void(* cv::MouseCallback)(int event,int x,int y,int flags,void*useradata);
其中参数
event 表示鼠标事件,
x 表示鼠标事件的 x 坐标,
y 表示鼠标事件的 y 坐标,
flags 表示鼠标事件的标志,
userdata 是可选的参数。
鼠标事件 event 主要有以下几种:
cpp
enum
{
EVENT_MOUSEMOVE =0, //滑动
EVENT_LBUTTONDOWN =1, //左键单击
EVENT_RBUTTONDOWN=2,//右键单击
EVENT_MBUTTONDOWN=3,//中键单击
EVENT_LBUTTONUP=4,//左键放开
EVENT_RBUTTONUP=5,//右键放开
EVENT_MBUTTONUP=6,//中键放开
EVENT_LBUTTONDBLCLK=7,//左键双击
EVENT_RBUTTONDBLCLK=8,//右键双击
EVENT_MBUTTONDBLCLK=9,//中键双击
EVENT_MOUSEWHEEL=10,//正值表示向前滚动,负值表示向后滚动
EVENT_MOUSEHWHEEL=11//正值表示向左滚动,负值表示向右滚动
};
鼠标事件标志 flags 主要有以下几种:
cpp
enum {
EVENT_FLAG_LBUTTON = 1,//按住左键拖曳
EVENT_FLAG_RBUTTON = 2,//按住右键拖曳
EVENT_FLAG_MBUTTON = 4,//按住中键拖曳
EVENT_FLAG_CTRLKEY = 8,//按 Ctrl
EVENT_FLAG_SHIFTKEY = 16,//按 Shift
EVENT_FLAG_ALTKEY = 32//按 Alt
};
通过 event 和 flags 就能清楚地了解当前鼠标发生了哪种操作。
line()函数来对直线的绘制。
cpp
//https://www.opencv.org.cn/opencvdoc/2.3.2/html/modules/core/doc/drawing_functions.html?highlight=line#cv.Line
void line(Mat& img, Point pt1, Point pt2, const Scalar& color, int thickness=1, int lineType=8, int shift=0);
参数:
img: 要绘制线段的图像。
pt1: 线段的起点。
pt2: 线段的终点。
color: 线段的颜色,通过一个Scalar对象定义。
thickness: 线条的宽度。
lineType: 线段的类型。可以取值8, 4, 和CV_AA,
分别代表8邻接连接线,4邻接连接线和反锯齿连接线。
默认值为8邻接。为了获得更好地效果可以选用CV_AA(采用了高斯滤波)。
shift: 坐标点小数点位数。
实验代码
cpp
#include "pch.h"
#include <opencv2/highgui/highgui_c.h>
#include <opencv2\opencv.hpp>
//#pragma comment(lib, "opencv_world450d.lib") //引用引入库
using namespace cv;
#include<iostream>
using namespace std;
#define WINDOW "原图"
Mat g_srcImage, g_dstImage;
Point previousPoint;
bool P = false;
void on_mouse(int event, int x, int y, int flags, void*);
int main()
{
g_srcImage = imread("200.jpg", 1);
imshow(WINDOW, g_srcImage);
setMouseCallback(WINDOW, on_mouse, 0);
waitKey(0);
return 0;
}
void on_mouse(int event, int x, int y, int flags, void*)
{
if (event == EVENT_LBUTTONDOWN)
{
previousPoint = Point(x, y);
}
else if (event == EVENT_MOUSEMOVE && (flags&EVENT_FLAG_LBUTTON))
{
Point pt(x, y);
line(g_srcImage, previousPoint, pt, Scalar(0, 0, 255), 2, 5, 0);
//https://blog.csdn.net/hk121/article/details/81085213
previousPoint = pt;
imshow(WINDOW, g_srcImage);
}
}
在代码中,on_mouse 就是用来处理鼠标事件的回调函数,当鼠标有动作产生时,on_mouse 会被系统调用,然后在 on_mouse 中,判断发生了哪种动作,进而进行相应的处理。本例我们关心的是按下鼠标左键,然后按住左键时的鼠标移动。一旦按下鼠标左键,就记录下当时的位置(也就是画线的开始点),位置记录在 previousPoint 中,接着如果继续有鼠标移动事件发生,我们就开始调用函数 line 画线。最后把画了线的图像数据 g_srcImage 用函数 imshow 显示出来。
运行结果
文章参考
程序中如果鼠标右键被按下,则会提示"点击鼠标左键才可以绘制轨迹",点击左键会输出当前鼠标的坐标,并将该点坐标定义为某段轨迹的起始位置。之后按住左键移动鼠标,会进入到第三个逻辑判断,绘制鼠标的移动轨迹。
示例程序中提供了两种绘制轨迹的方法,
第一种是每次调用回调函数获得鼠标位置时更改周围的图像像素值,这种方式比较直观,但是由于回调函数有一定的执行时间,因此当鼠标移动较快时绘制的图像轨迹会出现断点。
第二种绘制轨迹的方式是在前一时刻和当前时刻鼠标位置间绘制直线,这种方式可以避免因鼠标移动过快而带来的轨迹出现断点的问题。
cpp
// myMouse.cpp绘制鼠标移动轨迹
#include "pch.h"
#include <opencv2/opencv.hpp>
#include <iostream>
using namespace std;
using namespace cv;
Mat img, imgPoint; //全局的图像
Point prePoint; //前一时刻鼠标的坐标,用于绘制直线
void mouse(int event, int x, int y, int flags, void*);
int main()
{
img = imread("200.jpg");
if (!img.data)
{
cout << "请确认输入图像名称是否正确!" << endl;
system("pause");
return -1;
}
img.copyTo(imgPoint);
imshow("图像窗口1", img);
imshow("图像窗口2", imgPoint);
setMouseCallback("图像窗口1", mouse, 0); //鼠标事件
waitKey(0);
system("pause");
return 0;
}
void mouse(int event, int x, int y, int flags, void*)
{
if (event == EVENT_RBUTTONDOWN) //单击右键
{
cout << "点击鼠标左键才可以绘制轨迹" << endl;
}
if (event == EVENT_LBUTTONDOWN) //单击左键,输出坐标
{
prePoint = Point(x, y);
cout << "轨迹起使坐标" << prePoint << endl;
}
if (event == EVENT_MOUSEMOVE && (flags & EVENT_FLAG_LBUTTON)) //鼠标按住左键移动
{
//通过改变图像像素显示鼠标移动轨迹
imgPoint.at<Vec3b>(y, x) = Vec3b(0, 0, 255);
imgPoint.at<Vec3b>(y, x - 1) = Vec3b(0, 0, 255);
imgPoint.at<Vec3b>(y, x + 1) = Vec3b(0, 0, 255);
imgPoint.at<Vec3b>(y + 1, x) = Vec3b(0, 0, 255);
imgPoint.at<Vec3b>(y + 1, x) = Vec3b(0, 0, 255);
imshow("图像窗口2", imgPoint);
//通过绘制直线显示鼠标移动轨迹
Point pt(x, y);
line(img, prePoint, pt, Scalar(0, 0, 255), 2, 5, 0);
prePoint = pt;
imshow("图像窗口1", img);
}
}