이번 장에서는 openCV에서 나온 영상 또는 이미지에서 마우스를 이용하여 할 수 있는 작업에 대해서 알아보겠다

마우스를 이용하여 그림을 그리거나 특정 색상의 값을 얻을 수 있다 

 

1. 마우스 동작 

마우스의 동작의 종류는 3가지가 있다.  
마우스 오른쪽 버튼 동작, 왼쪽 버튼 동작, 가운데 버튼 동작 

이 3가지 동작마다 각각 눌렸을 때, 눌리고 올렸을 때, 더블클릭했을 때의 기능에 대해서 정의할 수 있다. 

  • EVENT_MOUSEMOVE : 마우스가 움직였을 때 
  • EVENT_LBUTTONDOWN : 마우스 왼쪽 버튼을 눌렀을 때 
  • EVENT_LBUTTONUP :  마우스 왼쪽 버튼을 올렸을 때 
  • EVENT_RBUTTONDOWN : 마우스 오른쪽 버튼을 눌렀을 때 
  • EVENT_RBUTTONUP : 마우스 오른쪽 버튼을 올렸을 때 
  • EVENT_MBUTTONDOWN : 마우스 가운데 버튼을 눌렀을 때 
  • EVENT_MBUTTONUP : 마우스 가운데 버튼을 올렸을 때 
  • EVENT_LBUTTONDBCLICK : 마우스 왼쪽 버튼을 두번 눌렀을 때
  • EVENT_RBUTTONDBCLICK : 마우스 오른쪽 버튼을 두번 눌렀을 때 
  • EVENT_MBUTTONDBCLICK : 마우스 가운데 버튼을 두분 눌렀을 때 

2. 마우스 콜백 함수의 파라미터 

def onMouse(event, x, y, flags, param):
    event = 위에서 정의한 마우스의 동작에 대한 감지 
    x, y = 해당 동작이 감지된 x, y 좌표값 ( 영상또는 이미지에 대한 )
    flags = 마우스 이벤트가 발생할 때의 특정 조건 (컨트롤, 쉬프트, 알트 등 키 조합 생성) 
    param = 파라미터로 전송될 값 (이미지 또는 특정 변수 전송)

 

import cv2 
import numpy as np 

class MouseGesture():
    def __init__(self) -> None:
        self.is_dragging = False 
        self.x0, self.y0, self.w0, self.h0 = -1,-1,-1,-1

    def on_mouse(self, event, x, y, flags, param):
        if event == cv2.EVENT_LBUTTONDOWN:
            print("왼쪽 버튼 눌림 \t좌표 : x : {} y : {}".format(x,y) )
        elif event == cv2.EVENT_LBUTTONUP:
            print("왼쪽 버튼 올림\t좌표 : x : {} y : {}".format(x,y) )
        elif event == cv2.EVENT_RBUTTONDOWN:
            print("오른쪽 버튼 눌림\t좌표 : x : {} y : {}".format(x,y) )
        elif event == cv2.EVENT_RBUTTONUP:
            print("오른쪽 버튼 올림\t좌표 : x : {} y : {}".format(x,y) )    
        elif event == cv2.EVENT_MBUTTONDOWN:
            print("가운데 버튼 내림\t좌표 : x : {} y : {}".format(x,y) )
        elif event == cv2.EVENT_MBUTTONUP:
            print("가운데 버튼 올림\t좌표 : x : {} y : {}".format(x,y) )
        # elif event == cv2.EVENT_MOUSEMOVE:
        #     # 마우스 움직임은 너무 많이 나와서 생략    
        #     print("마우스 움직임\t좌표 : x : {} y : {}".format(x,y) )
        elif event == cv2.EVENT_MOUSEHWHEEL:
            # 가로휠이 없는 마우스라 .... 
            print("마우스 가로 휠 \t좌표 : x : {} y : {}".format(x,y) )
        elif event == cv2.EVENT_MOUSEWHEEL:
            print("마우스 그냥 휠 \t좌표 : x : {} y : {}".format(x,y) )
            
        return 

image = cv2.imread('test.JPG')
window_name = 'mouse_callback'
mouse_class = MouseGesture()

cv2.imshow(window_name, image)
cv2.setMouseCallback(window_name, mouse_class.on_mouse, param=image)
cv2.waitKey(0)

3. 적용 예시 

  • 마우스를 클릭한 위치의 픽셀의 값 확인 
    import cv2 
    import numpy as np 
    
    class MouseGesture():
        def __init__(self) -> None:
            self.is_dragging = False 
            # 마우스 위치 값 임시 저장을 위한 변수 
            self.x0, self.y0, self.w0, self.h0 = -1,-1,-1,-1
    
        def on_mouse(self, event, x, y, flags, param):
            if event == cv2.EVENT_LBUTTONDOWN:
                value = param[y,x,:]
                print("왼쪽 버튼 눌림 \t x : {} y : {} 좌표의 픽셀값은 : {}".format(x,y, value) )
            return 
    
    image = cv2.imread('test.JPG')
    window_name = 'mouse_callback'
    mouse_class = MouseGesture()
    
    cv2.imshow(window_name, image)
    cv2.setMouseCallback(window_name, mouse_class.on_mouse, param=image)
    cv2.waitKey(0)​
  • 마우스로 그림 그리기 
    import cv2 
    import numpy as np 
    
    class MouseGesture():
        def __init__(self) -> None:
            self.is_dragging = False 
            # 마우스 위치 값 임시 저장을 위한 변수 
            self.x0, self.y0, self.w0, self.h0 = -1,-1,-1,-1
    
        def on_mouse(self, event, x, y, flags, param):
            if event == cv2.EVENT_LBUTTONDOWN:
                self.x0 = x
                self.y0 = y
                self.is_dragging = True
                print("사각형의 시작 좌표는 x : {} y : {}".format(x,y) )
            elif event == cv2.EVENT_LBUTTONUP:
                self.is_dragging = False
                cv2.rectangle(param['image'], (self.x0, self.y0), (x,y),(0,0,255),2)            
                cv2.imshow(param['window_name'], param['image'])
                print("사각형의 좌표는 ({}, {}), ({}, {})".format(self.x0,self.y0,x,y) )
            elif event == cv2.EVENT_MOUSEMOVE:
                if self.is_dragging:
                    temp_img = param['image'].copy()
                    cv2.rectangle(temp_img, (self.x0, self.y0), (x,y),(0,0,255),2)            
                    cv2.imshow(param['window_name'], temp_img)
            return 
    
    
    image = cv2.imread('test.JPG')
    window_name = 'mouse_callback'
    mouse_class = MouseGesture()
    param = {
        "image" : image,
        "window_name" : window_name
    }
    cv2.imshow(window_name, image)
    cv2.setMouseCallback(window_name, mouse_class.on_mouse, param=param)
    cv2.waitKey(0)​
  •  

 

1. image 결합 

file_name = "checker_board.png"

image = cv2.imread(file_name, cv2.IMREAD_COLOR)
image2 = cv2.imread(file_name, cv2.IMREAD_GRAYSCALE)

image2 = np.stack((image2,)*3, axis=-1)

image = cv2.resize(image, (400,400))
image2 = cv2.resize(image2, (400,400))

image = np.concatenate((image, image2),axis=1)

  • opencv imread 는 numpy 객체로 이미지를 불러오게 된다 그렇기에 numpy 모듈을 사용하여 이미지를 수직, 수평으로 이어붙이기를 하여 원본이미지와 변경 이미지를 한눈에 보기 쉽게 펼칠 수 있다
  • grayscale 이미지와 color 이미지의 차원이 다르기 때문에 grayscale의 차원을 numpy의 stack을 이용하여 같은 이미지를 3번 중복하여 차원을 (x, x, 3) 으로 color 차원과 같게 맞추었다 
  • numpy.concatenate = 넘파이 객체를 잇는다
  • 첫 파라미터는 tuple 형태의 image 객체 2개이고 두 번째 파라미터는 수직 또는 수평을 결정하는 axis 이다
  • axis=0 은 수직(세로)으로 잇는다 axis=1은 수평(가로)으로 잇는다

 

2. 외곽선 검출

image = cv2.imread('milk.jpg', cv2.IMREAD_COLOR)
image2 = image.copy()
gray_image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)

return_value, threshold_image = cv2.threshold(gray_image,0,255,cv2.THRESH_BINARY + cv2.THRESH_OTSU)

contours, hierarchy = cv2.findContours(threshold_image, 
                        cv2.RETR_LIST, 
                        cv2.CHAIN_APPROX_NONE)

for contour in contours:
    cv2.drawContours(image,contour,-1,(0,0,255),2)
    
threshold_image = np.stack((threshold_image,)*3, axis=-1)
image = np.concatenate((image,threshold_image), axis=1)
image = np.concatenate((image,image2), axis=1)

    • cv2.threshold( image, min_value, max_value, TYPE )
    • 2차원 이미지만 입력으로 넣을 수 있기 떄문에 grayscale로 변환헀다
    • threshold를 해주는 이유는 외곽선을 찾을 때 색상정보를 단순하게 변경하여 (0과 255로) 외곽선을 더 찾기 쉽게 할 수 있다. 
    • threshold의 파라미터는 (image_source, min_value, max_value, TYPE)이다 
      TYPE의 종류
      cv2.THRESH_BINARY 
      cv2.THRESH_BINARY_INV 
      cv2.THRESH_TRUNC 
      cv2.THRESH_TOZERO 
      cv2.THRESH_TOZERO_INV 
      cv2. THRESH_OTSU : 이진화를 할 때 히스토그램 분석을 통해 자동으로 임계값을 찾아준다

https://opencv-python.readthedocs.io/en/latest/doc/09.imageThresholding/imageThresholding.html

 

    • cv2.findContours( image, mode, method)
    • 이진 이미지에서 외곽선을 자동으로 검출해 준다 
    • mode 의 종류 
      cv2.RETR_EXTERNAL : 외곽선중 가장 바깥쪽 선만 리턴한다 
      cv2.RETR_LIST : 모든 외곽선을 찾지만 계층관계는 구성하지 않는다 
      cv2.RETR_CCOMP : 모든 외곽선을 찾으며 2단계의 계층관계를 구성한다 
      cv2.RETR_TREE : 모든 외곽선을 찾으며 모든 계층관계를 구성한다 
      여기서 계층이란 검출된 외곽선 안에 있는 또다른 외곽선을 뜻한다 
    • method 의 종류
      cv2.CHAIN_APPROX_NONE : 모든 point를 저장
      cv2.CHAIN_APPROX_SIMPLE : 4개의 point만을 저장하여 메모리를 절약 
      cv2.CHAIN_APPROX_TC89_L1 : Teh_Chin 연결 근사 알고리즘 L1버전을 적용하여 point 개수를 줄임 
      cv2.CHAIN_APPROX_TC89_KCOS : Teh_Chin 연결 근사 알고리즘 KCOS 버전을 적용하여 point 개수를 줄임 
      return 값으로 오는 contours의 shape를 찍어보면 각각 method마다 shape이 다른것을 볼 수 있다. 
      각각 method마다의 contours[-1]의 draw 와 shape 

CNN 모델에 학습을 시키기 전 하고자 하는 목표에 맞게 이미지를 전처리하여 학습을 시키면 더 좋은 효과를 볼 수 있다. 

이미지에서 필요없는 부분이 있는지, 특정 영역만을 추출해도 되는지 개발자의 주관적 생각으로 판단하여야 한다. 

 

여기서 주관적이라는 말이 매우 거슬리지만 이미지란 참 복잡하다. 

 

만약 사람을 검출하고 싶은데 사람의 머리카락이 검은색이라 검은색만을 추출했더니 옆에 있던 맨홀이 나온다던지 할 수 있다. 

 

나도 computer vision에 대하여 잘 알지 못하지만 이제 시작함으로써 내용정리를 시작한다. 

 

 

1. imread

image = cv2.imread("FILE_PATH", cv2.IMREAD_COLOR)

  • imread = image read 이미지를 읽는 함수이다 
  • 첫 파라미터로 파일의 경로(image.jpg)를 입력하고 두 번째 파라미터로 어떤 색상으로 읽어올 건지에 대해 입력한다. 
    • cv2.IMREAD_COLOR : 이미지 파일을 Color로 읽어들입니다. 투명한 부분은 무시되며, Default값입니다.
    • cv2.IMREAD_GRAYSCALE : 이미지를 Grayscale로 읽어 들입니다. 실제 이미지 처리 시 중간단계로 많이 사용합니다.
    • cv2.IMREAD_UNCHANGED : 이미지파일을 alpha channel까지 포함하여 읽어 들인다. 하지만 이미지에 alpha channel에 대한 정보가 있어야 다르다 위에 사진과 같이 COLOR와 UNCHANGED가 똑같다

 

2. imshow, waitKey, destroyAllWindows

cv2.imshow("window_name", image)
key = cv2.waitKey(0)
cv2.destroyAllWindows()
  • imshow = image show 이미지를 윈도우 창에 보여주는 함수이다
  • 첫 파라미터로 띄어질 윈도우의 이름을 설정하고 두 번째 파라미터로 어떤 이미지를 띄울것인지 입력한다
  • waitKey와 destroyAllWindows 는 함께 쌍으로 자주 쓰이는 함수이다. waitKey는 사용자의 키보드 입력이 올때까지 대기하고 destroyAllWindows는 Opencv에서 띄운 모든 윈도우를 종료한다
  • 위의 코드를 보면 imshow 이후 cv2.waitKey(0)에 의해서 그 다음 문단으로 가지않고 sleep()처럼 대기된다. 여기서 waitKey의 함수안에 파라미터로 0이 있는데 이것은 무한대로 기다린다는 의미이다. 만약 사용자의 키보드 입력이 들어오면 waitKey 함수는 끝나게되고 뒤에있는 destroyAllWindows 함수에 의해 띄어졌던 이미지가 종료된다

 

3. imwrite

cv2.imwrite("test.jpg", image)

  • imwrite = image write 이미지를 저장하는 함수이다.
  • 첫 파라미터로 파일이 저장될 경로(test.jpg)를 설정한다 두 번째 파라미터로 저장할 이미지변수를 입력한다.

 

4. cvtColor

image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
image = cv2.cvtColor(image, cv2.COLOR_BGR2HSV)
image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)

  • cvtColor = convert color 이미지의 색상을 변환시키는 함수이다. 
  • 첫 파라미터로 변경할 이미지변수를 입력하고 두 번째 파라미터로 변경할 색상을 입력한다 
  • 변환 가능한 색상 종류에는 BGR2RGB, HSV, GRAY, LAB, LUV, XYZ 등 여러가지가 존재한다 

 

5. resize

image = cv2.resize(image,(608,608))

  • resize = 말그대로 사이즈를 변경한다 
  • 첫 파라미터로 변경할 이미지변수를 입력하고 두 번쨰 파라미터로 변경할 사이즈를 tuple형태로 입력한다
  • 사이즈를 더 작게, 더 크게 변경이 가능하다 하지만 원본의 비율과 다르게 변환하면 형태가 변경될 수 있고 더 작게 변경한다면 비율이 안맞는 부분에 자동으로 padding이 들어가게 된다

 

 

이번에는 기초적으로 이미지를 읽고 이미지 색을 변경하는 법을 정리하였다 

다음에는 numpy를 이용해서 이미지를 합쳐서 보는 법과 간단한 이미지 처리 기법에 대하여 정리하겠다

+ Recent posts