OpenCV鼠标事件详解-深入理解回调函数

点击上方“小白学视觉”,选择加"星标"或“置顶”
重磅干货,第一时间送达

读取一张图片,在该图片上截取一个ROI区域,将截取的图片在一个新的窗口内展示,并将该图片保持到工程目录下。

这个题一点也不难,因为书上给的例程已经可以完成大部分工作,只需要自己添加几行代码就可以实现上述功能,但添加这几行代码的过程可以帮助你对鼠标回调函数有一个清楚的理解。

首先我们先看一个回调函数原理:

回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。回调函数不是由该函数的实现方法直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。

如果你学过单片机,那你可以将鼠标回调函数理解为单片机的中断函数

运行机制:

⑴定义一个回调函数;
⑵函数实现的一方在初始化的时候,将回调函数的函数指针注册给调用者;
⑶当特定的事件或条件发生的时候,调用者使用函数指针调用回调函数对事件进行处理。

下面我们看一下opencv自带的鼠标回调函数:

void SetMouseCallback(const char* window_name, MouseCallback on_mouse,
void* param = NULL);
  1. 参数window_name:为窗口的名字

  2. 参数on_mouse:用来指定窗口每次鼠标时候发生的时候,被调用函数指针

  3. 参数则为用户定义的传递到回调函数的参数

鼠标回调函数有很多已经定义的响应标识符,如下:

EVENT_MOUSEMOVE      鼠标移动
EVENT_LBUTTONDOWN    左键按下
EVENT_RBUTTONDOWN    右键按下
EVENT_MBUTTONDOWN    滚轮按下
EVENT_LBUTTONUP      左键抬起
EVENT_RBUTTONUP      右键抬起
EVENT_MBUTTONUP      滚轮抬起
EVENT_LBUTTONDBLCLK  左键双击
EVENT_RBUTTONDBLCLK  右键双击
EVENT_MBUTTONDBLCLK  中间双击 
EVENT_FLAG_LBUTTON  左键拖拽  
EVENT_FLAG_RBUTTON   右键拖拽
EVENT_FLAG_MBUTTON   中键拖拽
EVENT_FLAG_CTRLKEY   按住ctrl不放
EVENT_FLAG_SHIFTKEY   按住shift不放
EVENT_FLAG_ALTKEY  按住alt不放

下面以一学妹的程序来说一下做这个题的大体思路:

首先我们应该读取一张图片并将他显示出来,以供我们对他进行操作:

Mat srcImage;  //定义图片变量
srcImage = imread("1.jpg");  //读取图片

然后我们应该定义一个鼠标回调的函数对象,也就是上面鼠标回调函数的第二个参数。我们在截取图片时的过程是这样的:

鼠标左键先按下,然后拖动鼠标成一个矩形框,然后左键松开,截出一个矩形图像。程序如下(详细注释):

//@event:鼠标事件标志(不需要自己传参)
//@x:鼠标在窗口中的x坐标
//@y:鼠标在窗口中的y坐标
//@flags:自定义的宏,做标记符,Ture时开始绘制矩形,false时不绘制
//@* param:传入的图像
void on_MouseHandle(int event, int x, int y, int flags, void* param) {
  Mat& image = *(Mat*)param;
switch (event) {          //检查鼠标事件
case EVENT_MOUSEMOVE: {   //如果检测到鼠标移动
if (g_bDrawingBox) {  //如果绘制标识符为真,则记录下移动时矩形的宽,高
      g_rectangle.width = x - g_rectangle.x;
      g_rectangle.height = y - g_rectangle.y;
    }
  }
break;
case EVENT_LBUTTONDOWN: {  //检测到鼠标左键按下
    g_bDrawingBox = true;  //将绘制标识符设为真,准备开始绘图
    g_rectangle = Rect(x, y, 0, 0);  //用g_rectangle保存下起点
  }
break;
case EVENT_LBUTTONUP: {  //检测到鼠标左键抬起
    g_bDrawingBox = false; //停止绘图
if (g_rectangle.width < 0) {     //对截取的进行处理(因为向上截取则宽,高为负
      g_rectangle.x += g_rectangle.width;             //向下截取宽高为正)
      g_rectangle.width *= (-1);
    }
if (g_rectangle.height < 0) {
      g_rectangle.y += g_rectangle.height;
      g_rectangle.height *= (-1);
    }
    srcROI = image(g_rectangle);   //用全局变量srcROI将截取的位置保存
    shotScreen(srcROI);  //自定义函数,用来展示截图并保存截图
  }
break;
  }
}

用来展示截图并保存截图的自定义函数:

//@mat:截取的图片
void shotScreen(Mat& mat) {
  imshow("截图", mat);
  imwrite("截取的图片.jpg", mat);
}

然后开始编写主函数,首先是一些自变量的定义,并读取目标图片。

然后我们要对鼠标回调函数进行初始化:

namedWindow(WINDOW);   //定义一个窗口
  setMouseCallback(WINDOW, on_MouseHandle, (void*)&srcImage);//对该窗口进行鼠标检测

之后是while循环:

while (1) {
    srcImage.copyTo(tempImage);  //不断的用读取的图片更新临时图片tempImage
if (g_bDrawingBox) DrawRectangle(tempImage, g_rectangle);//将矩形g_rectangle画到tempImage上
    imshow(WINDOW, tempImage);  //展示tempImage
if (waitKey(10) == 27) break;//当按下Esc时程序结束
  }

本程序的最终运行效果如下(这个过程是无限次可循环的,看视频时声音调小一点啊/):

而我们的工程目录下也有了一张截图:

ab7a7cf774fbf6d62a6e9102210a987f.png

到此我们的小项目就完成啦!学到了什么吗?

附程序源码:

#include<opencv2/opencv.hpp>
using namespace cv;


#define WINDOW "程序窗口"


void on_MouseHandle(int event, int x, int y, int flags, void* param);
void DrawRectangle(Mat& img, Rect& box);
void shotScreen(Mat& mat);


Rect g_rectangle; //用来保存截图的位置信息
bool g_bDrawingBox = false;  //绘制标识符
Mat srcROI; //用来保存截图


int main(int argc, char** argv) {
  g_rectangle = Rect(-1, -1, 0, 0);  
  Mat srcImage, tempImage; 
  srcImage = imread("1.jpg");  //读取一张图片
  srcImage.copyTo(tempImage);


  namedWindow(WINDOW);   //定义一个窗口
  setMouseCallback(WINDOW, on_MouseHandle, (void*)&srcImage);//对该窗口进行鼠标检测


while (1) {
    srcImage.copyTo(tempImage);  //不断的用读取的图片更新临时图片tempImage
if (g_bDrawingBox) DrawRectangle(tempImage, g_rectangle);//将矩形g_rectangle画到tempImage上
    imshow(WINDOW, tempImage);  //展示tempImage
if (waitKey(10) == 27) break;//当按下Esc时程序结束
  }
return 0;
}


void on_MouseHandle(int event, int x, int y, int flags, void* param) {
  Mat& image = *(Mat*)param;
switch (event) {          //检查鼠标事件
case EVENT_MOUSEMOVE: {   //如果检测到鼠标移动
if (g_bDrawingBox) {  //如果绘制标识符为真,则记录下移动时矩形的宽,高
      g_rectangle.width = x - g_rectangle.x;
      g_rectangle.height = y - g_rectangle.y;
    }
  }
break;
case EVENT_LBUTTONDOWN: {  //检测到鼠标左键按下
    g_bDrawingBox = true;  //将绘制标识符设为真,准备开始绘图
    g_rectangle = Rect(x, y, 0, 0);  //用g_rectangle保存下起点
  }
break;
case EVENT_LBUTTONUP: {  //检测到鼠标左键抬起
    g_bDrawingBox = false; //停止绘图
if (g_rectangle.width < 0) {     //对截取的进行处理(因为向上截取则宽,高为负
      g_rectangle.x += g_rectangle.width;             //向下截取宽高为正)
      g_rectangle.width *= (-1);
    }
if (g_rectangle.height < 0) {
      g_rectangle.y += g_rectangle.height;
      g_rectangle.height *= (-1);
    }
    srcROI = image(g_rectangle);   //用全局变量srcROI将截取的位置保存
    shotScreen(srcROI);  //自定义函数,用来展示截图并保存截图
  }
break;
  }
}


void DrawRectangle(Mat& img, Rect& box) {
  rectangle(img, box.tl(), box.br(), Scalar(0, 0, 255));
}


void shotScreen(Mat& mat) {
  imshow("截图", mat);
  imwrite("截取的图片.jpg", mat);
}

好消息!

小白学视觉知识星球

开始面向外开放啦👇👇👇

 
 

66154d3deecf73e6468f7da52a1eab6a.jpeg

下载1:OpenCV-Contrib扩展模块中文版教程

在「小白学视觉」公众号后台回复:扩展模块中文教程,即可下载全网第一份OpenCV扩展模块教程中文版,涵盖扩展模块安装、SFM算法、立体视觉、目标跟踪、生物视觉、超分辨率处理等二十多章内容。


下载2:Python视觉实战项目52讲
在「小白学视觉」公众号后台回复:Python视觉实战项目,即可下载包括图像分割、口罩检测、车道线检测、车辆计数、添加眼线、车牌识别、字符识别、情绪检测、文本内容提取、面部识别等31个视觉实战项目,助力快速学校计算机视觉。


下载3:OpenCV实战项目20讲
在「小白学视觉」公众号后台回复:OpenCV实战项目20讲,即可下载含有20个基于OpenCV实现20个实战项目,实现OpenCV学习进阶。


交流群

欢迎加入公众号读者群一起和同行交流,目前有SLAM、三维视觉、传感器、自动驾驶、计算摄影、检测、分割、识别、医学影像、GAN、算法竞赛等微信群(以后会逐渐细分),请扫描下面微信号加群,备注:”昵称+学校/公司+研究方向“,例如:”张三 + 上海交大 + 视觉SLAM“。请按照格式备注,否则不予通过。添加成功后会根据研究方向邀请进入相关微信群。请勿在群内发送广告,否则会请出群,谢谢理解~
  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值