OpenCV マウスイベント情報取得 setMouseCallback (クリックボタン判定、座標取得など)

# 環境
・Windows 7 Professional 64bit
・Visual Studio 2017 Version 15.7.0
・OpenCV 3.4.1

OpenCVの導入方法などは、下記を参照ください。
[Visual Studio 2017 + OpenCV 3.4.1 + CUDA 10.0 + Intel TBB 2018U5 ビルド手順](https://qiita.com/East_san/items/7072257fb12b26c8d215)

# マウスイベント
マウスイベントの情報を取得したりするには、**setMouseCallback**関数を
使用します。今回は、下記のマウスイベントを紹介します。

- クリックボタン判定
    - 左ボタンクリック (EVENT_LBUTTONDOWN, EVENT_LBUTTONUP)
    - 中ボタンクリック (EVENT_MBUTTONDOWN, EVENT_MBUTTONUP)
    - 右ボタンクリック (EVENT_RBUTTONDOWN, EVENT_RBUTTONUP)
    - 左ボタンダブルクリック (EVENT_LBUTTONDBLCLK)
    - 中ボタンダブルクリック (EVENT_MBUTTONDBLCLK)
    - 右ボタンダブルクリック (EVENT_RBUTTONDBLCLK)
    - マウスホイール (EVENT_MOUSEWHEEL, getMouseWheelDelta)
- 座標取得 (EVENT_MOUSEMOVE)
- 応用 (矩形描画時の左上座標と右下座標の取得)

※実行結果をgifでも紹介していますが、ものによってはわかりづらいかもです。。。


### クリックボタン判定
##### 左ボタンクリック (EVENT_LBUTTONDOWN, EVENT_LBUTTONUP)
マウスの左ボタンクリック判定(ボタンを押したとき)には、**EVENT_LBUTTONDOWN**を使用します。

```cpp:LeftMouseClick1.cpp
#include <opencv2\opencv.hpp>
#include <opencv_lib.hpp>

#include <iostream>
using namespace std;
using namespace cv;

void mouse_callback(int event, int x, int y, int flags, void *userdata)
{
	if (event == EVENT_LBUTTONDOWN) {
		cout << "Left mouse button is pressed." << endl;
	}
}

int main()
{
	Mat img = imread("./cpp_img.png");

	imshow("example", img);
	setMouseCallback("example", mouse_callback);
	waitKey();

	return 0;
}
```

![LeftMouseClick1.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/92978/70f2e4fd-31de-40ed-62a3-4cb2b1cc994c.gif)

マウスの左ボタンクリック後判定(ボタンを離したとき)には、**EVENT_LBUTTONUP**を使用します。

```cpp:LeftMouseClick2.cpp
#include <opencv2\opencv.hpp>
#include <opencv_lib.hpp>

#include <iostream>
using namespace std;
using namespace cv;

void mouse_callback(int event, int x, int y, int flags, void *userdata)
{
	if (event == EVENT_LBUTTONUP) {
		cout << "Left mouse button is released." << endl;
	}
}

int main()
{
	Mat img = imread("./cpp_img.png");

	imshow("example", img);
	setMouseCallback("example", mouse_callback);
	waitKey();

	return 0;
}
```

![LeftMouseClick2.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/92978/68310566-6491-1a9d-60fd-a6c7400d5625.gif)

マウスの左ボタンクリック状態判定(ボタンが押されている状態)には、**EVENT_FLAG_LBUTTON**を使用します。<br>EVENT_LBUTTONDOWNとの違いが見せにくいので、特に紹介はしません。。。<br>(使用したい場合は、event == EVENT_FLAG_LBUTTONに変更するだけです)

##### 中ボタンクリック (EVENT_MBUTTONDOWN, EVENT_MBUTTONUP)
マウスの中ボタンクリック判定(ボタンを押したとき)には、**EVENT_MBUTTONDOWN**を使用します。

```cpp:MiddleMouseClick1.cpp
#include <opencv2\opencv.hpp>
#include <opencv_lib.hpp>

#include <iostream>
using namespace std;
using namespace cv;

void mouse_callback(int event, int x, int y, int flags, void *userdata)
{
	if (event == EVENT_MBUTTONDOWN) {
		cout << "Middle mouse button is pressed." << endl;
	}
}

int main()
{
	Mat img = imread("./cpp_img.png");

	imshow("example", img);
	setMouseCallback("example", mouse_callback);
	waitKey();

	return 0;
}
```

![MiddleMouseClick1.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/92978/affd31b3-40b2-6d37-bedc-4d6475b57296.gif)

マウスの中ボタンクリック後判定(ボタンを離したとき)には、**EVENT_MBUTTONUP**を使用します。

```cpp:MiddleMouseClick2.cpp
#include <opencv2\opencv.hpp>
#include <opencv_lib.hpp>

#include <iostream>
using namespace std;
using namespace cv;

void mouse_callback(int event, int x, int y, int flags, void *userdata)
{
	if (event == EVENT_MBUTTONUP) {
		cout << "Middle mouse button is released." << endl;
	}
}

int main()
{
	Mat img = imread("./cpp_img.png");

	imshow("example", img);
	setMouseCallback("example", mouse_callback);
	waitKey();

	return 0;
}
```

![MiddleMouseClick2.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/92978/ae2cacb8-0f28-d214-f70e-a4ee7eb0258a.gif)

マウスの中ボタンクリック状態判定(ボタンが押されている状態)には、**EVENT_FLAG_MBUTTON**を使用します。
左右ボタンと同様、違いがわかりにくいので特に紹介しません。。。


##### 右ボタンクリック (EVENT_RBUTTONDOWN, EVENT_RBUTTONUP)
マウスの右ボタンクリック判定(ボタンを押したとき)には、**EVENT_RBUTTONDOWN**を使用します。

```cpp:RightMouseClick1.cpp
#include <opencv2\opencv.hpp>
#include <opencv_lib.hpp>

#include <iostream>
using namespace std;
using namespace cv;

void mouse_callback(int event, int x, int y, int flags, void *userdata)
{
	if (event == EVENT_RBUTTONDOWN) {
		cout << "Right mouse button is pressed." << endl;
	}
}

int main()
{
	Mat img = imread("./cpp_img.png");

	imshow("example", img);
	setMouseCallback("example", mouse_callback);
	waitKey();

	return 0;
}
```

![RightMouseClick1.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/92978/c49f1315-8e28-526e-9dc9-d6739ebd7dd5.gif)

マウスの右ボタンクリック後判定(ボタンを離したとき)には、**EVENT_RBUTTONUP**を使用します。

```cpp:RightMouseClick2.cpp
#include <opencv2\opencv.hpp>
#include <opencv_lib.hpp>

#include <iostream>
using namespace std;
using namespace cv;

void mouse_callback(int event, int x, int y, int flags, void *userdata)
{
	if (event == EVENT_RBUTTONUP) {
		cout << "Right mouse button is released." << endl;
	}
}

int main()
{
	Mat img = imread("./cpp_img.png");

	imshow("example", img);
	setMouseCallback("example", mouse_callback);
	waitKey();

	return 0;
}
```

![RightMouseClick2.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/92978/7c754f6f-b31a-7e18-8e34-6074a9489f57.gif)

マウスの右ボタンクリック状態判定(ボタンが押されている状態)には、**EVENT_FLAG_RBUTTON**を使用します。
左ボタンと同様、違いがわかりにくいので特に紹介しません。。。

##### 左ボタンダブルクリック (EVENT_LBUTTONDBLCLK)
マウスの左ボタンダブルクリック判定(ボタンを2回押したとき)には、**EVENT_LBUTTONDBLCLK**を使用します。<br>
以下のコードでは、わかりやすいように1回クリックしたときの判定EVENT_LBUTTONDOWNを入れています。実行結果からわかるように、2回目のクリックでダブルクリックの判定が出ていることがわかります。

```cpp:LeftDoubleClick.cpp
#include <opencv2\opencv.hpp>
#include <opencv_lib.hpp>

#include <iostream>
using namespace std;
using namespace cv;

void mouse_callback(int event, int x, int y, int flags, void *userdata)
{
	if (event == EVENT_LBUTTONDOWN) {
		cout << "Left mouse button is pressed." << endl;
	}
	if (event == EVENT_LBUTTONDBLCLK) {
		cout << "Left mouse button is double clicked. " << endl;
	}
}

int main()
{
	Mat img = imread("./cpp_img.png");

	imshow("example", img);
	setMouseCallback("example", mouse_callback);
	waitKey();

	return 0;
}
```

![LeftDoubleClick.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/92978/253f6842-464d-7386-77bc-9b2f61083fa1.gif)


##### 右ボタンダブルクリック (EVENT_RBUTTONDBLCLK)
マウスの右ボタンダブルクリック判定(ボタンを押したとき)には、**EVENT_RBUTTONDBLCLK**を使用します。<br>
以下のコードでは、わかりやすいように1回クリックしたときの判定EVENT_RBUTTONDOWNを入れています。実行結果からわかるように、2回目のクリックでダブルクリックの判定が出ていることがわかります。

```cpp:RightDoubleClick.cpp
#include <opencv2\opencv.hpp>
#include <opencv_lib.hpp>

#include <iostream>
using namespace std;
using namespace cv;

void mouse_callback(int event, int x, int y, int flags, void *userdata)
{
	if (event == EVENT_RBUTTONDOWN) {
		cout << "Right mouse button is pressed." << endl;
	}
	if (event == EVENT_RBUTTONDBLCLK) {
		cout << "Right mouse button is double clicked. " << endl;
	}
}

int main()
{
	Mat img = imread("./cpp_img.png");

	imshow("example", img);
	setMouseCallback("example", mouse_callback);
	waitKey();

	return 0;
}
```

![RightDoubleClick.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/92978/395f3153-4ee8-2b10-1fa2-2728bbf442c7.gif)

##### マウスホイール (EVENT_MOUSEWHEEL, getMouseWheelDelta)
マウスのホイール判定には、**EVENT_MOUSEWHEEL**を使用します。
ただし、この**EVENT_MOUSEWHEEL**だけでは、ホイールを前にスクロールしたか後ろにスクロールしたかがわかりません。ここで、**getMouseWheelDelta**関数を使用して、スクロールの前後判定をします。具体的には、**getMouseWheelDelta**関数の引数にflagsを渡すことで、前にスクロールした場合は返り値が正に、後ろにスクロールした場合は返り値が負になります。**ただし、公式ドキュメントのNoteには、Windowsのみのサポートになるようです。**
※一応、[ここ](http://hardwareandsoonlab.blogspot.com/2017/02/opencv3x24x.html)によるとLinuxでも返り値は違うみたいですが、正負の判定はできるみたいです。私は試していません。

```cpp:MouseWheel.cpp
#include <opencv2\opencv.hpp>
#include <opencv_lib.hpp>

#include <iostream>
using namespace std;
using namespace cv;

void mouse_callback(int event, int x, int y, int flags, void *userdata)
{
	if (event == EVENT_MOUSEWHEEL) {
		if (getMouseWheelDelta(flags)>0) {
			cout << "Mouse wheel is forward." << endl;
		}
		if (getMouseWheelDelta(flags)<0) {
			cout << "Mouse wheel is backward." << endl;
		}
	}
}

int main()
{
	Mat img = imread("./cpp_img.png");

	imshow("example", img);
	setMouseCallback("example", mouse_callback);
	waitKey();

	return 0;
}
```

![MouseWheel.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/92978/58384b47-7f61-c0f0-b83e-a7d2558831b0.gif)

前後のスクロールだけでなく、左右のスクロール判定をする**EVENT_MOUSEHWHEEL**もありますが、試せる環境がないので試していないです。。。
※一応、ホイールを左右に動かしてみて試しましたが、それではダメっぽいです。なのでクリックではなく、スクロールじゃないとダメみたいです。

### 座標取得 (EVENT_MOUSEMOVE)
マウス移動時の座標取得には、**EVENT_MOUSEMOVE**を使用します。
ほぼ、下記のサイトと変わりませんが結果とともに載せておきます。。。
[Can I get the mouse position in OpenCV without a mouse event?](https://stackoverflow.com/questions/42005809/can-i-get-the-mouse-position-in-opencv-without-a-mouse-event)

```cpp:GetMousePos.cpp
#include <opencv2\opencv.hpp>
#include <opencv_lib.hpp>

#include <iostream>
using namespace std;
using namespace cv;

void mouse_callback(int event, int x, int y, int flags, void *userdata)
{
	if (event == EVENT_MOUSEMOVE) {
		cout << "(" << x << ", " << y << ")" << endl;
	}
}

int main()
{
	Mat img = imread("./cpp_img.png");

	imshow("example", img);
	setMouseCallback("example", mouse_callback);
	waitKey();

	return 0;
}
```
![GetMousePos.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/92978/7a9ce25d-2fa8-371a-cd5e-451ebbba7355.gif)

### 応用 (矩形描画時の左上座標と右下座標の取得)
**EVENT_LBUTTONDOWN**, **EVENT_LBUTTONUP**, **EVENT_MOUSEMOVE**の3つを使用して矩形描画時の左上座標と右下座標の取得をしています。
※ウィンドウ外のマイナス値などのエラー処理は特にしていません。

```cpp:DrawRectangle.cpp
#include <opencv2\opencv.hpp>
#include <opencv_lib.hpp>

#include <iostream>
using namespace std;
using namespace cv;

Rect2i rectangle_value;


void mouse_callback(int event, int x, int y, int flags, void *userdata)
{
	bool *isClick = static_cast<bool *>(userdata);
	if (event == EVENT_LBUTTONDOWN) {
		*isClick = true;
		cout << "Draw rectangle\n"
			<< " start position (x, y) : " << x << ", " << y << endl;
		rectangle_value = Rect2i(x, y, 0, 0);
	}
	if (event == EVENT_LBUTTONUP) {
		*isClick = false;
		cout << " end   position (x, y) : " << x << ", " << y << endl;
		rectangle_value.width = x - rectangle_value.x;
		rectangle_value.height = y - rectangle_value.y;
	}
	if (event == EVENT_MOUSEMOVE) {
		if (*isClick) {
			rectangle_value.width = x - rectangle_value.x;
			rectangle_value.height = y - rectangle_value.y;
		}
	}
}

int main()
{
	Mat img = imread("./cpp_img.png");
	Mat draw_img = img.clone();
	string window_name = "example";
	bool isClick = false;
	int key;

	imshow(window_name, img);
	setMouseCallback(window_name, mouse_callback, &isClick);
	for (;;) {
		key = 0;
		
		// 左ボタンが押されたら描画開始
		if (isClick == true) {
			rectangle(draw_img, rectangle_value, Scalar(255, 0, 0), 3, CV_AA);
		}

		imshow(window_name, draw_img);
		draw_img = img.clone();

		// qキーが押されたら終了
		key = waitKey(1);
		if (key == 'q')
			break;
	}

	return 0;
}
```

![DrawRectangle.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/92978/12e3d680-fa9a-8429-fd6f-786288fcd523.gif)

ちなみに矩形での切り抜き関数[**SelectROI**](https://github.com/opencv/opencv/blob/master/modules/highgui/src/roiSelector.cpp)などは、このマウスイベントがうまく使われているので、参考になります。

# 参考サイト
[1]:[Can I get the mouse position in OpenCV without a mouse event?](https://stackoverflow.com/questions/42005809/can-i-get-the-mouse-position-in-opencv-without-a-mouse-event)
[2]:[OpenCV: High-level GUI](https://docs.opencv.org/3.4.1/d7/dfc/group__highgui.html#ga73b55e9851b5691f6a6a4a4e6c57258e)
[3]:[opencv/roiSelector.cpp at master · opencv/opencv](https://github.com/opencv/opencv/blob/master/modules/highgui/src/roiSelector.cpp)
[4]:[ハードウェアとか研究所: OpenCV（3.x系）でマウスホイールを使う（2.4.x系は不可）](http://hardwareandsoonlab.blogspot.com/2017/02/opencv3x24x.html)