OpenCV 实战 | 深入理解回调函数

2020 年 2 月 26 日 计算机视觉life

点击上方“计算机视觉life”,选择“星标”

快速获得最新干货


学弟学妹们开始学opencv了,参看的书籍是毛星云的《oepncv3编程入门》,编程环境是用的VS2017或VS2019,该项目是给他们留的第一次作业,作业内容:

读取一张图片,在该图片上截取一个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);  //不断的用读取的图片更新临时图片tempImageif (g_bDrawingBox) DrawRectangle(tempImage, g_rectangle);//将矩形g_rectangle画到tempImage上    imshow(WINDOW, tempImage);  //展示tempImageif (waitKey(10) == 27) break;//当按下Esc时程序结束  }

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

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


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

附程序源码:

#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); //不断的用读取的图片更新临时图片tempImageif (g_bDrawingBox) DrawRectangle(tempImage, g_rectangle);//将矩形g_rectangle画到tempImage上 imshow(WINDOW, tempImage); //展示tempImageif (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);}

交流群

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

投稿也欢迎联系:simiter@126.com

长按关注计算机视觉life

推荐阅读


实战 | 相机标定

实战 | 图像矫正技术

实战 | Unity下ARKit与OpenCV的结晶

实战 | 基于SegNet和U-Net的遥感图像语义分割

实战 | 文字定位与切割

实战 | 源码入门之Faster RCNN

实战 | 自己实现扫描全能王

实战 | 用OpenCV轻松生成微信国庆版头像

实战 | OpenCV 实现多张图像拼接

实战 | OpenCV相机标定与畸变校正

实战 | 教你自动查找拍糊的图

我用MATLAB撸了一个2D LiDAR SLAM

实战 | 哪个瞬间让你突然觉得CV技术真有用?

干货 | 史上最全 OpenCV 活体检测教程

解决方案 | 如何用普通手机准确测量物体体积?

Mask-RCNN模型的实现自定义对象(无人机)检测

scikit-image图像处理入门

还在用肉眼找不同吗?这个技术轻松搞定

OpenCV测量物体的尺寸技能 get~

超详讲解图像拼接/全景图原理和应用 | 附源码


最新AI干货,我在看  

登录查看更多
0

相关内容

一个跨平台的计算机视觉处理库,全称是Open Source Computer Vision。
【2020新书】实战R语言4,323页pdf
专知会员服务
101+阅读 · 2020年7月1日
【实用书】学习用Python编写代码进行数据分析,103页pdf
专知会员服务
195+阅读 · 2020年6月29日
【2020新书】使用高级C# 提升你的编程技能,412页pdf
专知会员服务
58+阅读 · 2020年6月26日
深度神经网络实时物联网图像处理,241页pdf
专知会员服务
77+阅读 · 2020年3月15日
【模型泛化教程】标签平滑与Keras, TensorFlow,和深度学习
专知会员服务
21+阅读 · 2019年12月31日
【电子书】Flutter实战305页PDF免费下载
专知会员服务
23+阅读 · 2019年11月7日
【初学者系列】tensorboard学习笔记
专知
7+阅读 · 2019年10月4日
实战 | 用Python做图像处理(三)
七月在线实验室
15+阅读 · 2018年5月29日
实战 | 用Python做图像处理(二)
七月在线实验室
17+阅读 · 2018年5月25日
基于TensorFlow的深度学习实战
七月在线实验室
9+阅读 · 2018年4月25日
Python 爬虫实践:《战狼2》豆瓣影评分析
数据库开发
5+阅读 · 2018年3月19日
深度学习中的「卷积层」如何深入理解?
深度学习世界
6+阅读 · 2017年11月30日
OpenCV计算机视觉产品实战
炼数成金订阅号
12+阅读 · 2017年9月22日
时延神经网络(TDNN)原理及其TensorFlow实现
深度学习每日摘要
56+阅读 · 2017年5月19日
Feature Selection Library (MATLAB Toolbox)
Arxiv
7+阅读 · 2018年8月6日
Arxiv
6+阅读 · 2018年1月14日
Arxiv
3+阅读 · 2018年1月10日
VIP会员
相关VIP内容
【2020新书】实战R语言4,323页pdf
专知会员服务
101+阅读 · 2020年7月1日
【实用书】学习用Python编写代码进行数据分析,103页pdf
专知会员服务
195+阅读 · 2020年6月29日
【2020新书】使用高级C# 提升你的编程技能,412页pdf
专知会员服务
58+阅读 · 2020年6月26日
深度神经网络实时物联网图像处理,241页pdf
专知会员服务
77+阅读 · 2020年3月15日
【模型泛化教程】标签平滑与Keras, TensorFlow,和深度学习
专知会员服务
21+阅读 · 2019年12月31日
【电子书】Flutter实战305页PDF免费下载
专知会员服务
23+阅读 · 2019年11月7日
相关资讯
【初学者系列】tensorboard学习笔记
专知
7+阅读 · 2019年10月4日
实战 | 用Python做图像处理(三)
七月在线实验室
15+阅读 · 2018年5月29日
实战 | 用Python做图像处理(二)
七月在线实验室
17+阅读 · 2018年5月25日
基于TensorFlow的深度学习实战
七月在线实验室
9+阅读 · 2018年4月25日
Python 爬虫实践:《战狼2》豆瓣影评分析
数据库开发
5+阅读 · 2018年3月19日
深度学习中的「卷积层」如何深入理解?
深度学习世界
6+阅读 · 2017年11月30日
OpenCV计算机视觉产品实战
炼数成金订阅号
12+阅读 · 2017年9月22日
时延神经网络(TDNN)原理及其TensorFlow实现
深度学习每日摘要
56+阅读 · 2017年5月19日
Top
微信扫码咨询专知VIP会员