{半路出家学OpenCV}-1. OpenCV的鼠标事件

预先声明:javascript

本人并不是计算机或软件专业科班出身,只是我的研究中用到了OpenCV,所以该blog只是我的笔记性质,不能保证正确。
同时可能有一些C++、计算机理论乃至图像处理科学上的低级错误,欢迎指出。java

本笔记主要包括以下内容ios

  • setMouseCallback()的基本内容
  • 举例并给出注意点

1. setMouseCallback()基本介绍

setMouseCallback()为OpenCV中的鼠标回调函数,具体在highgui.hpp的提供的简单说明为:git

/** @brief Sets mouse handler for the specified window
@param winname Name of the window.
@param onMouse Mouse callback. See OpenCV samples, such as https://github.com/opencv/opencv/tree/master/samples/cpp/ffilldemo.cpp, on how to specify and use the callback.
@param userdata The optional parameter passed to the callback.github

该函数的基本格式为:web

void setMouseCallback(const String& winname, MouseCallback onMouse, void* userdata = 0);

下面是各个参数的具体含义:小程序

1) const String& winname

@param winname Name of the window.svg

简单说,就是制定了鼠标事件的具体窗口。winname应该为具体的窗口的标示;函数

2) MouseCallback onMouse

@param onMouse Mouse callback. ui

用于设置鼠标的回调函数,简单说,被调用的函数原型为:

void onMouse(int event, int x, int y, int flags, void*)

具体的各参数解释详见第2部分。

3) void* userdata = 0

@param userdata The optional parameter passed to the callback.

我的理解,这是定义传输到回调函数的用户数据。若是无特殊需求,通常不用定义便可。

2. void onMouse()的使用

使用下面的定义进行说明。

void onMouse(int event, int x, int y, int flags, void*)

1) event包括的事件:

event是鼠标的回调事件,当有鼠标动做时,将回传信息到onMouse中,同时返回鼠标的坐标(也就是x和y的值)。
下面是highgui.hpp中的内容:

//! Mouse Events see cv::MouseCallback
enum MouseEventTypes {
       EVENT_MOUSEMOVE = 0, //!< indicates that the mouse pointer has moved over the window.
       EVENT_LBUTTONDOWN = 1, //!< indicates that the left mouse button is pressed.
       EVENT_RBUTTONDOWN = 2, //!< indicates that the right mouse button is pressed.
       EVENT_MBUTTONDOWN = 3, //!< indicates that the middle mouse button is pressed.
       EVENT_LBUTTONUP = 4, //!< indicates that left mouse button is released.
       EVENT_RBUTTONUP = 5, //!< indicates that right mouse button is released.
       EVENT_MBUTTONUP = 6, //!< indicates that middle mouse button is released.
       EVENT_LBUTTONDBLCLK = 7, //!< indicates that left mouse button is double clicked.
       EVENT_RBUTTONDBLCLK = 8, //!< indicates that right mouse button is double clicked.
       EVENT_MBUTTONDBLCLK = 9, //!< indicates that middle mouse button is double clicked.
       EVENT_MOUSEWHEEL = 10,//!< positive and negative values mean forward and backward scrolling, respectively.
       EVENT_MOUSEHWHEEL = 11 //!< positive and negative values mean right and left scrolling, respectively.
     };

这是highgui.hpp中指定的类型,具体的意义上面的代码解释的已经基本比较清楚了,event的事件主要有11种。
同时,实际使用中可使用highgui_c.h中指定的CV_EVENT_XXXX的事件。

2) flags中包括的内容

简单说,flags就是鼠标的拖动事件。具体的定义详见下面highgui.hpp中指定的类型。

//! Mouse Event Flags see cv::MouseCallback
enum MouseEventFlags {
       EVENT_FLAG_LBUTTON   = 1, //!< indicates that the left mouse button is down.
       EVENT_FLAG_RBUTTON   = 2, //!< indicates that the right mouse button is down.
       EVENT_FLAG_MBUTTON   = 4, //!< indicates that the middle mouse button is down.
       EVENT_FLAG_CTRLKEY   = 8, //!< indicates that CTRL Key is pressed.
       EVENT_FLAG_SHIFTKEY  = 16,//!< indicates that SHIFT Key is pressed.
       EVENT_FLAG_ALTKEY    = 32 //!< indicates that ALT Key is pressed.
     };

3) int x; int y;

此时鼠标所在点的位置。

3. 实际举例及说明

参考其余博客及例程,编写了小程序,实如今图像上画直线的功能:

  1. 能够在画直线前给出鼠标所在点的坐标供选择;
  2. 能够在肯定起点后(左键单击)给出该点的坐标,并使用圆点标示;
  3. 肯定起点后,拖动鼠标,能够给出备选的直线及鼠标所在点的坐标;
  4. 再次点击鼠标,能够画出直线,并在终点给出该点的坐标,并使用圆点表示。

1) 程序代码

//该程序实如今图像上画直线的功能;

#include "stdafx.h"
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <iostream>
#include <stdio.h>

using namespace cv;
using namespace std;

#define WINDOW "图像"

//定义全局变量
Mat g_srcImage,g_dstImage; //初始化两个图像矩阵
Point previousPoint;//在图像中定义2维点,分别具备x和y两个坐标
bool dbFlag = false; //定义dbFlag为划线的标志

//声明鼠标回调函数
void MouseEvent(int event, int xMouse, int yMouse, int flags, void*);

//main函数
int main(int argc, char** argv)
{
    // 读取图像为g_srcImage
    g_srcImage = imread("c:\\images\\flower4.jpg");
    if (g_srcImage.empty())
        return -1;

    //将g_srcImage克隆(clone)给g_dstImage
    g_dstImage = g_srcImage.clone(); //注(1):clone与直接赋值的区别

    //显示初始图像
    imshow(WINDOW, g_srcImage);

    //回调函数响应
    setMouseCallback(WINDOW, MouseEvent, 0);
    waitKey(0);
    return 0;
}


void MouseEvent(int event, int xMouse, int yMouse, int flags, void*)
{
    char temp[30]; //定义char型变量存储显示的文字

//当没有任何键按下(或起始点还未肯定时),显示鼠标所在点的坐标
    if (event == EVENT_MOUSEMOVE && (dbFlag == 0)) //dbFlag == 0保证了第一次按下左键时才为真
    {
        g_srcImage = g_dstImage.clone(); //将g_dstImage克隆给g_srcImage,以后再g_srcImage上作处理
        Point ptTempnoline(xMouse, yMouse); //定义二维点ptTempnoline,也就是无划线的时候的点,赋值给鼠标所在点

        //在鼠标所在点输出坐标(左上角为(0,0))(操做对象为g_srcImage)
        sprintf_s(temp, "(%d,%d)", xMouse, yMouse);
        putText(g_srcImage, temp, ptTempnoline, FONT_HERSHEY_PLAIN, 1, Scalar(0, 0, 255)); //将坐标点显示到g_srcImage上

        //显示g_srcImage
        imshow(WINDOW, g_srcImage);
    }

//当按下第一次左键时,肯定直线起点坐标,同时画一个圆点,并显示此时坐标
    else if (event == EVENT_LBUTTONDOWN &&(dbFlag == 0)) //dbFlag == 0保证了第一次按下左键时为真
    {
        g_srcImage = g_dstImage.clone(); //将dstImage克隆给srcImage
        previousPoint = Point(xMouse, yMouse);//定义二维点previousPoint,也就是划线的起始点

        //在鼠标所在点(起始点)输出坐标(操做对象为g_srcImage)
        sprintf_s(temp, "(%d,%d)", xMouse, yMouse);
        putText(g_srcImage, temp, previousPoint, FONT_HERSHEY_PLAIN,1, Scalar(0, 0, 255));

        //在鼠标所在点(起始点)画实心圆点(操做对象为g_srcImage)
        circle(g_srcImage, previousPoint, 3, cvScalar(255, 0, 0, 0), CV_FILLED, CV_AA, 0);

        //输出g_srcImage
        imshow(WINDOW, g_srcImage);

        //将操做完成后的g_srcImage克隆给g_dstImage
        g_dstImage = g_srcImage.clone();

        //将标识符dbFlag赋值为1,标识此时已经有了第一次鼠标按下了
        dbFlag = 1;
    }

//当按下第一次左键后,此时移动鼠标,应在起始点和当前点之间具备备选直线(有且仅有这一条)
//为了保证只有一条曲线,须要在g_srcImage上作操做,同事不停的把原有的图像g_dstImage克隆给g_srcImage
//换句话说,只要不是画的咱们想要的直线,不该该将其给g_dstImage
    else if (event == EVENT_MOUSEMOVE && (dbFlag == 1)) //dbFlag == 1保证了此时为左键按下后的状态
    {
        g_srcImage = g_dstImage.clone(); //将g_dstImage克隆给g_srcImage,保证了每次移动鼠标后,都是在以前的图像上画线,避免有其余备选直线的存在

        Point ptTemp(xMouse, yMouse); //定义二维点ptTemp,是此时鼠标所在点

        //在鼠标所在点(起始点)输出坐标(操做对象为g_srcImage)
        sprintf_s(temp, "(%d,%d)", xMouse, yMouse); 
        putText(g_srcImage, temp, ptTemp, FONT_HERSHEY_PLAIN, 1, Scalar(0, 0, 255));

        //在鼠标所在点和起始点之间画备选直线(操做对象为g_srcImage)。
        line(g_srcImage, previousPoint, ptTemp, Scalar(0, 0, 255), 2, 5, 0);
        imshow(WINDOW, g_srcImage);
    }

//按下第一次左键后,再次按下左键,应在起始点和当前点之间画直线,并在终点画圆点并标明坐标
    else if (event == EVENT_LBUTTONDOWN && (dbFlag == 1)) //dbFlag == 1保证了此时为第二次左键按下的状态
    {
        g_srcImage = g_dstImage.clone(); //将g_dstImage克隆给g_srcImage,保证了每次移动鼠标后,都是在以前的图像上画线

        Point ptDst(xMouse, yMouse);//定义二维点ptDst,是终点

        //在鼠标所在点(终止点)输出坐标(操做对象为g_srcImage)
        sprintf_s(temp, "(%d,%d)", xMouse, yMouse);
        putText(g_srcImage, temp, ptDst, FONT_HERSHEY_PLAIN, 1, Scalar(0, 0, 255));

        //在鼠标所在点(终止点)画实心圆点(操做对象为g_srcImage)
        circle(g_srcImage, ptDst, 3, cvScalar(255, 0, 0, 0), CV_FILLED, CV_AA, 0);

        //在鼠标所在点(终止点)和起始点之间画直线(操做对象为g_srcImage)。
        line(g_srcImage, previousPoint, ptDst, Scalar(0, 0, 255), 2, 5, 0);

        imshow(WINDOW, g_srcImage);

        //将操做完成后的g_srcImage克隆给g_dstImage
        g_dstImage = g_srcImage.clone(); 

        //将dbFlag复位为0,从新开始画下一条直线。
        dbFlag = 0; 
    }
}

2) 须要注意的地方

  • 注(1):定义了两个矩阵g_srcImageg_dstImage,用于在不一样的状况下(如画线和备选直线时)进行操做。可是刚开始时,直接使用了g_dstImage = g_srcImage进行赋值,这致使错误。缘由为:调用拷贝构造函数或赋值操做符时,只会拷贝Mat的头部,并增长reference count,并不会真正拷贝数据。若是要进行数据拷贝,能够调用clone或copyTo函数。
  • 注(2):该程序使用了dbFlag的布尔型变量对是否按下起始点进行表示。其实该程序还能够进行简化…
  • 须要注意putText(), line()circle()函数的应用。
  • 其余须要注意的地方,都放到了代码中。

3) 运行效果图

程序的运行效果