본문 바로가기
프로그래밍/OpenCV

[OpenCV] 이벤트 처리

by 별준 2022. 5. 4.

References

Contents

  • 키보드 이벤트
  • 마우스 이벤트
  • 트랙바

OpenCV는 영상 출력 창에서 발생하는 키보드 입력과 마우스 이벤트를 사용자에게 전달하는 인터페이스를 제공하며, 이를 이용하여 사용자 입력을 실시간으로 처리하는 영상 처리 프로그램을 만들 수 있습니다. 또한 트랙바 인터페이스를 사용하면 프로그램 실행 중 특정 범위의 값을 사용자가 선택하여 프로그램 동작을 제어할 수 있습니다.

 

키보드 이벤트

OpenCV에서 키 입력을 확인하기 위한 함수는 waitKey()입니다.

int cv::waitKey(int delay = 0);

이 함수는 delay에 해당하는 밀리초 시간 동안 키 입력을 기다리다가 키 입력이 있으면 해당 키의 아스키 코드 값을 반환합니다. 만약 지정한 시간 동안 키 입력이 없으면 -1을 반환합니다. waitKey() 함수의 인자를 지정하지 않거나 0 또는 음수로 설정하면 키 입력이 있을 때까지 무한히 기다리게 됩니다.

 

만약 특정 키를 눌렀을 때만 영상 출력 창을 닫도록 하려면 waitKey() 함수의 반환값을 조사해야 합니다. 예를 들어, 이미지를 출력한 상태에서 키보드의 Esc 키를 누를 때에만 창을 닫도록 하려면 아래처럼 코드를 작성해야 합니다.

cv::Mat img = cv::imread("lena.bmp", cv::IMREAD_GRAYSCALE);

cv::namedWindow("img");
cv::imshow("img", img);

while (true) {
    if (cv::waitKey() == 27)
        break;
}

cv::destroyWindow("img");

위 코드는 이미지를 화면에 출력한 후, while 문을 이용해 무한히 반복하도록 설정하였습니다. 그리고 반복문 내에서는 if 조건문을 사용하여 조건이 true인 경우에만 while 문을 빠져나올 수 있도록 설정했습니다. if 조건문에서는 waitKey() 함수를 호출하고, 함수의 반환값이 27인지 검사합니다. 여기서 숫자 27은 키보드의 Esc 키에 해당하는 아스키 코드 값입니다. 따라서, Esc 키를 누를 때에만 while 문을 빠져나오고, 그 이외의 키를 누를 때에는 계속 while 문 안에 머무르게 됩니다.

 

만약 키보드에서 여러 키 입력에 대해 서로 다른 처리를 하고 싶다면 waitKey() 함수의 반환값을 정수형 변수에 저장하였다가 비교하는 작업을 수행합니다.

다음은 여러 키 입력에 대해 서로 다른 동작을 수행하는 예제 코드입니다.

#include <iostream>
#include "opencv2/opencv.hpp"

int main(int argc, char* argv[])
{
    cv::Mat img = cv::imread("lena.bmp", cv::IMREAD_GRAYSCALE);

    if (img.empty()) {
        std::cout << "Image load failed!\n";
        return -1;
    }

    cv::namedWindow("img");
    cv::imshow("img", img);

    while (true) {
        int keycode = cv::waitKey();

        if (keycode == 'i' || keycode == 'I') {
            img = ~img;
            cv::imshow("img", img);
        }
        else if (keycode == 27 || keycode == 'q' || keycode == 'Q') {
            break;
        }
    }
}

위 코드는 "lena.bmp" 이미지를 불러와서 화면에 출력하고, 키보드의 i 또는 I를 누를 때마다 영상을 반전시켜 보여줍니다. 만약 키보드의 Esc나 q, Q 키를 누르면 프로그램이 종료됩니다. 이외의 키를 눌렀을 경우에는 아무런 동작을 수행하지 않으며, 다시 waitKey() 함수가 실행되어 새로운 키 입력을 기다립니다.

 

윈도우 운영체제에서 waitKey() 함수를 이용하여 일반적인 키보드 입력은 처리할 수 있지만, function 키 또는 화살표 키 등 특수한 키 입력은 처리하지 못합니다. 만약 키보드의 특수 키에 대한 처리를 하고 싶다면 waitKey() 대신 waitKeyEx() 함수를 사용해야 합니다. waitKeyEx() 함수의 사용법은 waitKey()와 유사하지만 특수 키에 대해 동작한다는 점이 다릅니다.

 


마우스 이벤트

OpenCV는 영상 출력 창에서 발생하는 마우스 이벤트를 사용자에게 전달하는 기능을 제공합니다. 이를 이용하면 OpenCV에 의해 만들어진 창에서 마우스 클릭에 반응하거나 마우스를 드래그하여 영상에 그림을 그리는 등의 동작을 수행할 수 있습니다. OpenCV 응용프로그램에서 마우스 이벤트를 처리하려면 먼저 마우스 콜백 함수를 등록하고, 이후 마우스 콜백 함수에 마우스 이벤트를 처리하는 코드를 추가해야 합니다.

 

OpenCV에서 특정 창에 마우스 콜백 함수를 등록할 때는 setMouseCallback() 함수를 사용합니다.

이 함수는 winname 창에서 마우스 이벤트가 발생하면 onMouse로 등록된 콜백 함수가 자동으로 호출되도록 설정합니다. userdata 인자에는 사용자가 마우스 콜백 함수에 전달하고 싶은 데이터를 void* 타입으로 전달할 수 있습니다. 만약 전달한 데이터가 없다면 지정하지 않아도 됩니다.

 

두 번째 인사인 onMouse에 지정하는 마우스 콜백 함수에 대해 살펴보겠습니다. 마우스 콜백 함수는 마우스 이벤트가 발생할 때 자동으로 호출되는 함수이며, 이 콜백 함수의 타입 MouseCallback은 다음과 같이 정의되어 있습니다.

/** @brief Callback function for mouse events. see cv::setMouseCallback
@param event one of the cv::MouseEventTypes constants.
@param x The x-coordinate of the mouse event.
@param y The y-coordinate of the mouse event.
@param flags one of the cv::MouseEventFlags constants.
@param userdata The optional parameter.
 */
typedef void (*MouseCallback)(int event, int x, int y, int flags, void* userdata);

즉, 4 개의 정수형과 하나의 void* 타입을 인자로 가지며, void를 반환형으로 가집니다. 마우스 콜백 이름은 onMouse가 아닌 다른 이름을 사용해도 됩니다. 콜백 함수의 첫 번째 인자 event에는 MouseEventTypes로 정의된 열거형 상수 중 하나가 전달되며, 그 값과 의미는 다음과 같습니다.

콜백 함수의 두 번째와 세 번째 인자에는 마우스 이벤트가 발생한 위치의 x 좌표와 y 좌표가 전달됩니다.

네 번째 인자 flags는 마우스 이벤트가 발생할 때의 마우스 또는 키보드의 상태 정보를 담고 있습니다. flags 인자에는 MouseEventFlags 열거형 상수의 논리합 조합이 전달됩니다.

 

마지막으로 void* 타입의 다섯 번째 인자에는 setMouseCallback() 함수에서 설정한 사용자 데이터의 포인터가 전달됩니다. 만약 setMouseCallback() 함수에서 세 번째 인자 userdata를 설정하지 않았다면 마우스 콜백 함수의 userdata 인자에는 항상 0(NULL)이 전달됩니다.

 

다음 코드는 setMouseCallback() 함수를 사용하여 마우스 이벤트를 처리하는 예제 코드입니다. 이 예제 코드는 마우스 왼쪽 버튼이 눌리거나 떼어진 좌표를 콘솔 창에 출력하고, 마우스 왼쪽 버튼을 누른 상태로 마우스를 움직이면 마우스 움직임의 궤적을 영상 위의 노란색으로 표시합니다.

#include <iostream>
#include "opencv2/opencv.hpp"

cv::Mat img;
cv::Point ptOld;

void on_mouse(int event, int x, int y, int flags, void*)
{
    switch (event) {
    case cv::EVENT_LBUTTONDOWN:
        ptOld = cv::Point(x, y);
        std::cout << "EVENT_LBUTTONDOWN: " << x << ", " << y << std::endl;
        break;

    case cv::EVENT_LBUTTONUP:
        std::cout << "EVENT_LBUTTONUP: " << x << ", " << y << std::endl;
        break;

    case cv::EVENT_MOUSEMOVE:
        if (flags & cv::EVENT_FLAG_LBUTTON) {
            cv::line(img, ptOld, cv::Point(x, y), cv::Scalar(0, 255, 255));

            cv::imshow("img", img);
            ptOld = cv::Point(x, y);
        }
        break;

    default:
        break;
    }
}

int main(int argc, char* argv[])
{
    img = cv::imread("lena.bmp");

    if (img.empty()) {
        std::cout << "Image load failed!\n";
        return -1;
    }

    cv::namedWindow("img");
    cv::setMouseCallback("img", on_mouse);

    cv::imshow("img", img);
    cv::waitKey(0);

    return 0;
}

위 코드에서는 전역 변수 img를 선언하여 사용합니다. setMouseCallback() 함수의 세 번째 인자를 이용하여 영상 데이터를 마우스 콜백 함수로 전달할 수 있지만, 간단하게 전역 변수를 사용하는 형태로 작성하였습니다.

 

on_mouse() 콜백 함수를 간단하게 살펴보면, switch문을 사용하여 다양한 마우스 이벤트를 처리합니다. 사용자가 마우스 왼쪽 버튼을 누르면 EVENT_LBUTTONDOWN, 눌렀던 왼쪽 버튼을 떼면 EVENT_LBUTTONUP 이벤트가 발생합니다. 두 이벤트에서는 해당 마우스 좌표를 처리하는 작업을 수행합니다. 그리고 EVENT_MOUSEMOVE는 마우스가 움직이는 이벤트를 처리합니다. 이때 flags 인자에 EVENT_FLAG_LBUTTON 상태가 설정되어 있으면 line() 함수를 이용하여 img에 노란색 마우스 궤적을 그립니다.

 


트랙바 (trackbar)

OpenCV에서는 윈도우, 리눅스, MacOS에서 공통으로 사용할 수 있는 트랙바 인터페이스를 제공합니다. 트랙바는 슬아이더 컨트롤(slider control)이라고도 부르며, 영상 출력 창에 부착되어 프로그램 동작 중에 사용자가 지정된 범위 안의 값을 선택할 수 있습니다.

 

트랙바는 사용자가 지정한 영상 출력 창의 상단에 부착되며, 필요한 경우 창 하나에 여러 개의 트랙바를 생성할 수 있습니다. 각각의 트랙바에는 고유한 이름을 지정해야 하며, 이 이름은 트랙바 왼쪽에 나타납니다. 트랙바 위치는 사용자가 마우스를 이용하여 이동시킬 수 있고, 트랙바의 현재 위치는 트랙바 이름 옆에 함께 표시됩니다. 트랙바가 가리킬 수 있는 최대 위치는 트랙바 생성 시 지정할 수 있으며 최소 위치는 항상 0으로 고정되어 있습니다.

 

OpenCV에서 트랙바를 생성하려면 createTrackbar() 함수를 사용하며, 이 함수의 원형은 다음과 같습니다.

사용되는 파라미터는 다음과 같습니다.

이 함수는 winname 이름의 창에 trackbarname 이름의 트랙바를 부착하고, 트랙바가 움직일 때마다 onChange에 해당하는 콜백 함수가 호출되도록 설정합니다. 생성된 트랙바의 최소 위치는 항상 0으로 설정되고, 최대 위치는 네 번째 인자인 count 값으로 지정합니다. 정수형 변수를 하나 만들고 그 변수의 주소를 value 인자로 설정하면, 트랙바 위치가 해당 변수에 자동으로 저장됩니다. 사용자가 트랙바 콜백 함수에 전달하고 싶은 데이터가 있다면 userdata 인자를 통해 void* 타입으로 전달할 수 있습니다. 사용할 데이터가 없다면 지정하지 않아도 됩니다.

 

createTrackbar() 함수에 전달되는 onChange 인자에 지정하는 트랙바 콜백 함수에 대해 살펴보겠습니다. 트랙바 콜백 함수는 트랙바 위치가 변경될 때 자동으로 호출되는 함수이며, 이 콜백 함수의 타입 TrackbarCallback은 다음과 같이 정의되어 있습니다.

/** @brief Callback function for Trackbar see cv::createTrackbar
@param pos current position of the specified trackbar.
@param userdata The optional parameter.
 */
typedef void (*TrackbarCallback)(int pos, void* userdata);

트랙바 콜백 함수의 첫 번째 인자에는 현재 트랙바의 위치 정보가 전달되고 두 번째 인자에는 createTrackbar() 함수에서 지정한 사용자 데이터 포인터 값이 전달됩니다.

 

다음 코드는 실제로 createTrackbar() 함수를 이용하여 트랙바를 만들고 이용하는 코드입니다. 이 코드에서는 image 이름의 새 창에 0부터 16 사이의 값을 선택할 수 있는 트랙바를 생성합니다. 그리고 콜백 함수로 등록된 on_level_change()  함수는 사용자가 선택한 트랙바 위치에 16을 곱하여 영상의 전체 픽셀 값으로 설정합니다. 즉, grayscale 레벨을 16단계로 보여줍니다.

#include <iostream>
#include "opencv2/opencv.hpp"

void on_level_change(int pos, void* userdata)
{
    cv::Mat img = *(cv::Mat*)userdata;

    img.setTo(pos * 16);
    cv::imshow("image", img);
}

int main(int argc, char* argv[])
{
    cv::Mat img = cv::Mat::zeros(400, 400, CV_8UC1);

    cv::namedWindow("image");
    cv::createTrackbar("level", "image", 0, 16, on_level_change, (void*)&img);

    cv::imshow("image", img);
    cv::waitKey(0);

    return 0;
}

 

트랙바를 생성한 후, 트랙바의 현재 위치를 알고 싶다면 getTrackbarPos() 함수를 사용할 수 있습니다. 또한 프로그램 동작 중 트랙바 위치를 강제로 특정 위치로 옮기고 싶다면 setTrackbarPos() 함수를 사용할 수 있습니다.

댓글