본문 바로가기
데이터 과학 관련 스터디/OpenCV

[Open CV] 파이썬 창 관리 - 마우스,키보드 이벤트/트랙바

by inhovation97 2022. 3. 6.
본 글은 파이썬으로 만드는 OpenCV프로젝트 서적의 내용을 포스팅하는 내용입니다. 
2장 기본 입출력
해당 실습은 cv2 라이브러리를 이용하여 진행합니다.

1. 창 관리
2. 키보드 이벤트 처리
3. 마우스 이벤트 처리
4. 마우스 이벤트 처리 2
5. 트랙바 활용

 

1. 창 관리

 

import cv2

file_path = '../CV2/img/img1.PNG'
img = cv2.imread(file_path)                            # 이미지 기본 값으로 읽기
img_gray = cv2.imread(file_path, cv2.IMREAD_GRAYSCALE) # 이미지를 그레이 스케일로 읽기

cv2.namedWindow('origin', cv2.WINDOW_AUTOSIZE)         # origin이라는 이름으로 창 생성
cv2.namedWindow('gray', cv2.WINDOW_NORMAL)             # gray라는 이름으로 창 생성

cv2.imshow('origin', img)                              # origin 창에 이미지 표시
cv2.imshow('gray', img_gray)                           # gray 창에 이미지 표시

cv2.moveWindow('origin', 0,0)                          # 창 위치 변경
cv2.moveWindow('origin', 100,100)                      # 창 위치 변경

cv2.waitKey(0)                                         # 아무키나 누르면 
cv2.resizeWindow('origin', 200, 200)                   # 창 크기 변경 ( 변경 안 됨)
cv2.resizeWindow('gray', 100, 100)                     # 창 크기 변경 (변경됨)

cv2.waitKey(0)                                         # 아무키나 누르면
cv2.destroyWindow('gray')                              # gray 창 닫기

cv2.waitKey(0)                                         # 아무키나 누르면
cv2.destroyAllWindows()                                # 모든 창 닫기

 

위 코드를 실행하면, origin이라는 이미지 창, gray라는 흑백 이미지 창이 2개 뜨는데 이때 아무키나 누르면 origin 창은 크기가 변경되지 않지만 gray 창은 크기가 변경됩니다.

다시 한 번 아무키나 누르면 gray창이 먼저 닫히고 키를 한 번 더 누르면 origin 창 마저 닫히게 됩니다.

 

cv2.namedWindow( title, [, option] ) - 이름을 갖는 창 열기

입력 : title - 제목

        option - 창 옵션, cv2.WINDOW_로 시작

                cv2.WINDOW_NORMAL - 임의의 크기, 사용자 창 크기 조정 가능

                cv2.WINDOW_AUTOSIZE- 이미지와 같은 크기, 창 크기 조정 불가능

 

cv2.moveWindow( title, x, y ) - 창 위치 이동

입력 : title - 위치를 변경할 창의 이름

        x,y - 이동할 창의 위치

 

cv2.resizeWindow( title, w, h ) - 창 크기 변경

입력 : title - 크기를 변경할 창의 이름

        w, h - 변경하고 싶은 창의 width, height

 

cv2.destroyWindow( title ) - 창 닫기

입력 : title - 닫고싶은 창의 이름

       

cv2.destroyAllWindow() - 열린 창 모두 닫기

 

결과창

 

2. 키보드 이벤트 처리

 

import cv2

file_path = '../CV2/img/img1.PNG'
img = cv2.imread(file_path)             
title = 'IMG'
x, y = 100, 100

while True:
    cv2.imshow(title, img)
    cv2.moveWindow(title, x, y)
    key = cv2.waitKey(0) & 0xFF # 키보드 입력을 무한 대기, 8비트 마스크 처리
    print(key, chr(key))        # 키보드 입력 값, 문자 값 출력
    if key == ord('h'):
        x -= 10                # h라면 좌로 이동
    
    elif key == ord('j'):
        y += 10                # j라면 아래로 이동
    
    elif key == ord('l'):
        y -= 10                # l아라면 오른쪽 이동
        
    elif key == ord('k'):
        x += 10                # k라면 위로 이동
    
    elif key == ord('q') or key == 27:
        break                  # q이거나 esc이면 종료.
        cv2.destroyAllwindows()
    cv2.moveWindow(title, x, y) # 새로운 좌표로 창 이동

 

이번 코드는 이미지 창을 띄운 뒤 지정 키를 누르면 이미지가 코드 좌표상으로 10만큼씩 상하좌우로 움직이는 이벤트를 처리하는 것입니다.

보자마자 이해하기 힘든 코드는 key = cv2.waitKey(0) & 0xFF 입니다.

cv2.waitKey(0)는 상시 대기하면서 어떤키가 입력될때를 기다리는 코드입니다.

이때 이 함수가 출력하는 값이 ASCII 코드입니다. 8비트인 아스키 코드를 반환하면, 그 반환 key 값을 ord() 함수를 씌어 문자열을 비교하게 되는 것이죠. 아래 if문들을 보면 전부 ord() 함수를 씌운 문자열과 key 값을 비교합니다.

 

하지만 이렇게 비교할때 가끔씩 64비트 환경에서 cv2.waitKey(0) 함수가 32비트 정수를 반환하는 경우도 있다고합니다.

따라서 무조건 8비트를 만들어주기 위해 뒤에 & 0xFF 연산자를 붙여 key 값을 반환합니다.

그럼 이제 ord()문자열과 비교에 에러가 생기지 않는 것이죠.

( 한글 모드에서 키를 입력하면 오류가 발생할 수 있으니 한글은 지양하는 것이 좋다고 합니다. )

 

 

3. 마우스 이벤트 처리

 

import cv2

title = 'mouse event'
img = cv2.imread('../CV2/img/blank_500.jpg')
cv2.imshow(title, img)

def onMouse(event, x, y, flags, param):
    print(event, x, y, )
    if event == cv2.EVENT_LBUTTONDOWN:
        cv2.circle(img, (x,y), 30, (0,0,0), -1)
        cv2.imshow(title, img)
        
cv2.setMouseCallback(title, onMouse) # 마우스 콜백 함수를 GUI 윈도우에 등록

while True:
    if cv2.waitKey(0) & 0xFF == 27:
        break
cv2.destroyAllWindows()

마우스에서 입력을 받으려면 이벤트를 처리할 함수( onMouse 함수)를 미리 선언해 놓고 cv2.setMouseCallback()함수에 그 함수를 전달합니다. 

해당 코드는 마우스 왼쪽 버튼을 클릭하면 까만 원이 그려집니다.

 

cv2.setMouseCallback( win_name, onMouse [, param]) - onMouse 함수를 등록

입력 : win_name - 이벤트를 등록할 윈도 이름

        onMouse - 이벤트 처리를 위해 미리 선언해 놓은 콜백 함수

        param - 필요에 따라 onMouse 함수에 전달할 인자

 

MouseCallback( evnet, x, y, flags, param ) - 콜백 함수 선언부

입력 : event - 마우스 이벤트 종류, cv2.EVENT_로 시작하는 상수 (12가지)

                cv2.EVENT_MOUSEMOVE - 마우스 움직임

                cv2.EVENT_LBUTTONDOWN- 왼쪽 버튼 누름

                cv2.EVENT_RBUTTONDOWN- 오른쪽 버튼 누름

                cv2.EVENT_MBOTTONDOWN- 가운데 버튼 누름

                cv2.EVENT_LBOTTONUP- 왼쪽 버튼 뗌

                cv2.EVENT_RBOTTONUP- 오른쪽 버튼 뗌

                cv2.EVENT_MBOTTONUP- 가운데 버튼 뗌

                cv2.EVENT_LBOTTONBLUCK- 왼쪽 버튼 더블 클릭

                cv2.EVENT_RBOTTONBLUCK- 오른쪽 버튼 더블 클릭

                cv2.EVENT_MBOTTONBLUCK- 가운데 버튼 더블 클릭

                cv2.EVENT_MOUSEWHEEL - 휠 스크롤

                cv2.EVENT_MOUSEHWHEEL- 가로 휠 스크롤

     x, y - 마우스 좌표

     flags - 마우스 동작과 함께 일어난 상태, cv2.EVENT_FLAG_로 시작하는 상수 (6가지)

                cv2.EVENT_FLAGS_LBOTTON(1) - 왼쪽 버튼 누름

                cv2.EVENT_FLAGS_RBOTTON(2) - 오른쪽 버튼 누름

                cv2.EVENT_FLAGS_MBOTTON(4) - 가운데 버튼 누름

                cv2.EVENT_FLAGS_LCTRLKEY(8) - 컨트롤 버튼 누름

                cv2.EVENT_FLAGS_SHIFTKEY(16) - 쉬프트 버튼 누름

                cv2.EVENT_FLAGS_ALTKEY(32) - 알트 버튼 누름

     param - cv2.setMouseCallback() 함수에서 전달한 인자

 

함수 내부에서 사용하지 않더라도 onMouse 함수는5개의 인자를 모두 기재해야하며, 그렇지 않으면 오류가 발생합니다.

 

결과창

 

4. 마우스 이벤트 처리 2

 

import cv2

title = 'mouse event'
img = cv2.imread('../CV2/img/blank_500.jpg')
cv2.imshow(title, img)

colors = {'black' : (0,0,0),
          'red' : (0,0,255),
          'blue' : (255,0,0),
          'green' : (0,255,0) } # 색상 미리 정의

def onMouse(event, x, y, flags, param):
    print(event, x, y, flags)
    color = colors['black']
    if event == cv2.EVENT_LBUTTONDOWN: # 왼족버튼을 누른 경우
        # 컨트롤 & 쉬프트 키를 같이 누른 경우
        if flags & cv2.EVENT_FLAG_CTRLKEY and flags & cv2.EVENT_FLAG_SHIFTKEY:
            color = colors['green']
        
        # 쉬프트 키를 누른 경우
        elif flags & cv2.EVENT_FLAG_SHIFTKEY:
            color = colors['blue']
       
        # 컨트롤 키를 누른 경우
        elif flags & cv2.EVENT_FLAG_CTRLKEY:
            color = colors['red']
        
        # 지름 30크기의 검은색 원을 해당 좌표에 그림
        cv2.circle(img, (x,y), 30, color, -1)
        cv2.imshow(title, img)
        
cv2.setMouseCallback(title, onMouse)

while True:
    if cv2.waitKey(0) & 0xFF == 27:
        break
cv2.destroyAllWindows()

 

flags 값은 함께 누른 경우와 같이 여러 가지 상태를 하나의 값으로 한꺼번에 나타낼 수 있어야 합니다. 

그래서 따라서 실제 갖는 값은 2진수 비트 자릿수에 맞는 값을 가집니다.

( 위에서도 써놨지만, cv2.EVENT_FLAGS_SHIFTKEY(16) - 쉬프트 버튼 누름 -> 쉬프트 버튼 입력은 16임)

만약 flag 값이 홀수인 경우 (25 = 1+8+16) 어떤 플래그 상수인지 명확히 알 수 없어 위 코드처럼 if문을 짜야합니다.  

위 코드는 마우스 오른쪽 버튼을 클릭하면 검은색, 컨트롤 키를 누른 채로 클릭하면 빨간색, 시프트 키는 파란색, 시프트 + 컨트롤 키는 초록색 원을 그립니다.

 

결과창

 

5. 트랙바 활용

 

import cv2
import numpy as np

win_name = 'Trackbar'

img=cv2.imread('../CV2/img/blank_500.jpg')
cv2.imshow(win_name, img)

# 트랙바 이벤트 처리 함수 선언
def onChange(x):
    print(x)
    # R G B 각 트랙바 위치 값
    r = cv2.getTrackbarPos('R', win_name)
    g = cv2.getTrackbarPos('G', win_name)
    b = cv2.getTrackbarPos('B', win_name)
    print(r,g,b)
    img[:] = [b,g,r]
    cv2.imshow(win_name, img)
    
# 트랙바 생성
cv2.createTrackbar('R', win_name, 255, 255, onChange)
cv2.createTrackbar('G', win_name, 255, 255, onChange)
cv2.createTrackbar('B', win_name, 255, 255, onChange)    

while True:
    if cv2.waitKey(1) & 0xFF==27:
        break
cv2.destroyAllWindows()

 

트랙바는 슬라이드 모양의 인터페이스를 마우스로 움직여서 값을 입력받는 GUI 요소입니다.

 

cv2.createTrackbar( trackbar_name, win_name, value, count, onChange ) - 트랙바 생성

입력 : trackbar_name - 트랙바 이름

        win_name - 트랙바를 표시할 창 이름

        value - 트랙바 초기 값, 0~count 사이의 값

        count - 트랙바 눈금의 개수, 트랙바 이벤트 핸들러 함수

        onChange - TrackbarCallback, 트랙바 이벤트 핸들러 함수

               

TrackbarCallback( value ) - 트랙바 이벤트 콜백 함수

입력 : trackbar_name - 트랙바 이름

 

CV2.getTrackbarPos( trackbar_name, win_name ) - 트랙바의 현재 위치를 출력

입력 : trackbar_name - 찾고자 하는 트랙바 이름

        win_name - 트랙바가 있는 창의 이름

        pos - 트랙바 위치 값

 

결과 에러가 왜 나는지는 잘 모르겠네요... 트랙바 창은 잘 뜹니다.

 

결과창

 

댓글