본문 바로가기

프로그래밍 강좌/C++ - OpenCV

[OpenCV][C++] 마우스 이벤트 처리 총정리 - onMouse setMouseCallback

네이버 블로그에서 윈도우 생성 방법과 키보드 입력을 받아 이벤트를 처리하는 방법에 대해 알아보았습니다.

 

이번에는 마우스 이벤트 처리하는 방법에 대해 알아보겠습니다.

마우스 이벤트 OpenCV에서 만들어진 창에서 마우스 클릭에 반응하거나 마우스를 드래그하여 그림을 그릴 수 있습니다.

 

1. 마우스 이벤트 콜백 함수 생성

마우스 콜백 함수는 아래와 같은 형태로 작성해야 합니다.

함수 명은 아무렇게나 쓰셔도 되며, 인자는 정해진대로 작성해야 합니다.

flags는 마우스 이벤트가 발생했을 때 ctrl, shift, alt 등의 키가 눌렸는지를 확인하기 위해 사용됩니다.

 

[event 상수]

EVENT_MOUSEMOVE
마우스를 움직이는 경우
EVENT_LBUTTONDOWN
마우스 왼쪽 버튼 누른 경우
EVENT_RBUTTONDOWN
마우스 오른쪽 버튼 누른 경우
EVENT_MBUTTONDOWN
마우스 가운데 버튼 누른 경우
EVENT_LBUTTONUP
마우스 왼쪽 버튼 떼는 경우
EVENT_RBUTTONUP
마우스 오른쪽 버튼 떼는 경우
EVENT_MBUTTONUP
마우스 가운데 버튼 떼는 경우
EVENT_LBUTTONDBLCLK
마우스 왼쪽 버튼 더블클릭 하는 경우
EVENT_RBUTTONDBLCLK
마우스 오른쪽 버튼 더블클릭 하는 경우
EVENT_MBUTTONDBLCLK
마우스 가운데 버튼 더블클릭 하는 경우
EVENT_MOUSEWHEEL
마우스 휠을 위아래로 돌리는 경우
EVENT_MOUSEHWHEEL
마우스 휠을 좌우로 움직이는 경우

 

[flags 상수]

 
EVENT_FLAG_LBUTTON
마우스 왼쪽 버튼이 눌려 있음
EVENT_FLAG_RBUTTON
마우스 오른쪽 버튼이 눌려 있음
EVENT_FLAG_MBUTTON
마우스 가운데 버튼이 눌려 있음
EVENT_FLAG_CTRLKEY
ctrl 키가 눌려 있음
EVENT_FLAG_SHIFTKEY
shift 키가 눌려 있음
EVENT_FLAG_ALTKEY
alt 키가 눌려 있음

 

- 콜백 함수 이름을 mouseEvent라고 지어줬습니다. 그리고 인자는 약속대로 넣어 줬습니다.

void mouseEvent(int event, int x, int y, int flags, void* userData)
{

}
 

- userData에는 영상을 받은 후 (void*)를 cv::Mat으로 변경해 주었습니다.

cv::Mat *pSrc = reinterpret_cast<cv::Mat*>(userData);
cv::Mat src = cv::Mat(*pSrc);
 

- event 처리를 해줄 switch~case 문을 만듭니다.

switch (event) {
}
 

- 마우스의 왼쪽 버튼이 눌렸을 때 처리하는 루틴입니다.

라인을 그리기 위해 전역변수로 prevPt에 현재 위치를 저장합니다.

만약 ctrl 키가 눌려 있다면 색상을 red 로, 그 외에는 blue로 사각형을 그립니다.

case cv::EVENT_LBUTTONDOWN:
	prevPt = cv::Point(x, y);		
	if (flags & cv::EVENT_FLAG_CTRLKEY)
		color = cv::Scalar(0, 0, 255);
	else
		color = cv::Scalar(255, 0, 0);
	cv::rectangle(src, cv::Rect(x - 5, y - 5, 10, 10), color, 2);
	break;
 

- 마우스의 왼쪽 버튼이 떼질 경우 cyan 색으로 칠해진 사각형을 그립니다.

case cv::EVENT_LBUTTONUP:		
	cv::rectangle(src, cv::Rect(x - 5, y - 5, 10, 10), cv::Scalar(255, 255, 0), cv::FILLED); 
    break;
 

- 마우스 오른쪽 버튼이 눌릴 경우 Green 색의 동그라미를 그립니다.

case cv::EVENT_RBUTTONDOWN:
	cv::circle(src, cv::Point(x, y), 5, cv::Scalar(0, 255, 0), 3);
	break;
 

- 마우스가 움직이는데 마우스의 왼쪽 버튼이 눌린 상태로 움직인다면 line을 그리고(직전 위치에서 현재 위치까지) 아닌 경우에는 아무런 반응을 하지 않는다.

case cv::EVENT_MOUSEMOVE:
	if (flags & cv::EVENT_FLAG_LBUTTON) {
		cv::line(src, prevPt, cv::Point(x, y), cv::Scalar(0, 255, 255), 2);
		prevPt = cv::Point(x, y);
	}
	break;
 

마우스 콜백 함수 전체 소스는 아래와 같습니다.

void mouseEvent(int event, int x, int y, int flags, void* userData)
{
	cv::Mat *pSrc = reinterpret_cast<cv::Mat*>(userData);
	cv::Mat src = cv::Mat(*pSrc);
	cv::Scalar color;
	switch (event) {
	case cv::EVENT_LBUTTONDOWN:
		prevPt = cv::Point(x, y);		
		if (flags & cv::EVENT_FLAG_CTRLKEY)
			color = cv::Scalar(0, 0, 255);
		else
			color = cv::Scalar(255, 0, 0);
		cv::rectangle(src, cv::Rect(x - 5, y - 5, 10, 10), color, 2);
		break;
	case cv::EVENT_LBUTTONUP:
		if(flags & cv::EVENT_LBUTTONDOWN){ }
		else
			cv::rectangle(src, cv::Rect(x - 5, y - 5, 10, 10), cv::Scalar(255, 255, 0), cv::FILLED);
		break;
	case cv::EVENT_RBUTTONDOWN:
		cv::circle(src, cv::Point(x, y), 5, cv::Scalar(0, 255, 0), 3);
		break;
	case cv::EVENT_MOUSEMOVE:
		if (flags & cv::EVENT_FLAG_LBUTTON) {
			cv::line(src, prevPt, cv::Point(x, y), cv::Scalar(0, 255, 255), 2);
			prevPt = cv::Point(x, y);
		}
		break;
	}
	cv::imshow("Test", src);
}
 

2. 마우스 콜백 함수 등록

마우스 이벤트를 처리하기 위해서는 마우스 이벤트 발생 시 부를 콜백 함수를 등록해야 합니다.

마우스 콜백 함수 원형은 아래와 같습니다.

 
winName
창 이름
onMouse
마우스 이벤트 처리를 위한 콜백함수 이름(이름은 onMouse가 아니어도 됨)
void onMouse(int event, int x, int y, int flags, void* userData) 식으로 콜백함수 생성(인자는 정해진 대로 작성해야 함)
userData
콜백함수에 전달할 사용자 데이터 포인터

 

현재 영상의 포인터를 콜백 함수에 넣어주려고 합니다. void*로 변환 후 영상의 주소를userData로 넘깁니다.

cv::setMouseCallback("Test", mouseEvent, (void*)&src);
 

위의 두 단계를 통합한 전체 소스는 아래와 같습니다.

#include <iostream>
#include "opencv2/opencv.hpp"
cv::Point prevPt;
void mouseEvent(int event, int x, int y, int flags, void* userData)
{
	cv::Mat *pSrc = reinterpret_cast<cv::Mat*>(userData);
	cv::Mat src = cv::Mat(*pSrc);
	cv::Scalar color;
	switch (event) {
	case cv::EVENT_LBUTTONDOWN:
		prevPt = cv::Point(x, y);		
		if (flags & cv::EVENT_FLAG_CTRLKEY)
			color = cv::Scalar(0, 0, 255);
		else
			color = cv::Scalar(255, 0, 0);
		cv::rectangle(src, cv::Rect(x - 5, y - 5, 10, 10), color, 2);
		break;
	case cv::EVENT_LBUTTONUP:		
		cv::rectangle(src, cv::Rect(x - 5, y - 5, 10, 10), cv::Scalar(255, 255, 0), cv::FILLED); 
		break;
	case cv::EVENT_RBUTTONDOWN:
		cv::circle(src, cv::Point(x, y), 5, cv::Scalar(0, 255, 0), 3);
		break;
	case cv::EVENT_MOUSEMOVE:
		if (flags & cv::EVENT_FLAG_LBUTTON) {
			cv::line(src, prevPt, cv::Point(x, y), cv::Scalar(0, 255, 255), 2);
			prevPt = cv::Point(x, y);
		}
		break;
	}
	cv::imshow("Test", src);
}
int main()
{
	cv::Mat src = cv::imread("lena_gray.bmp", cv::IMREAD_COLOR);
	if (src.empty())  return 1;
	
	cv::namedWindow("Test");
	cv::setMouseCallback("Test", mouseEvent, (void*)&src);
	cv::imshow("Test", src);
	cv::waitKey();	
	
	return 0;
}
 

결과는 아래와 같습니다.