귀퉁이 서재

OpenCV - 6. 이미지 내 관심영역(Region of Interest, ROI) 표시 본문

OpenCV

OpenCV - 6. 이미지 내 관심영역(Region of Interest, ROI) 표시

Baek Kyun Shin 2020. 9. 14. 22:50

이번 포스팅에서는 이미지 내에서 관심 영역(Region of Interest, ROI)을 표시하는 방법에 대해 알아보겠습니다. 이번 포스팅 역시 '파이썬으로 만드는 OpenCV 프로젝트(이세우 저)'를 정리한 것임을 밝힙니다.

코드: github.com/BaekKyunShin/OpenCV_Project_Python/tree/master/04.img_processing

관심 영역(ROI) 표시하기

관심 영역(ROI)이란 말 그대로 영상 내에서 관심이 있는 영역을 뜻합니다. 아래는 일몰 사진입니다. 일몰 사진 내에서 원하는 영역을 표시하는 방법에 대해 살펴보겠습니다.

아래 코드를 실행하면 태양 주위를 초록색 사각형으로 표시한 이미지가 생성됩니다.

# 관심영역 표시 (roi.py)

import cv2
import numpy as np

img = cv2.imread('./img/sunset.jpg')

x=320; y=150; w=50; h=50        # roi 좌표
roi = img[y:y+h, x:x+w]         # roi 지정        ---①

print(roi.shape)                # roi shape, (50,50,3)
cv2.rectangle(roi, (0,0), (h-1, w-1), (0,255,0)) # roi 전체에 사각형 그리기 ---②
cv2.imshow("img", img)

key = cv2.waitKey(0)
print(key)
cv2.destroyAllWindows()

cv2.imread() 함수를 실행하면 이미지를 numpy 배열로 반환합니다. numpy 배열은 슬라이싱(slicing)이 가능합니다. 원하는 영역을 지정하기 위해서는 이미지 numpy 배열을 슬라이싱 하면 됩니다. 위 코드에서 img[y:y+h, x:x+w]는 원하는 영역을 슬라이싱 합니다. 즉, roi 변수에는 관심 영역인 태양을 슬라이싱 한 numpy 배열이 담깁니다.

cv2.rectangle(roi, (0,0), (h-1, w-1), (0,255,0))은 태양 부분만 슬라이싱 한 roi 이미지의 (0, 0)부터 (h-1, w-1)까지 (0, 255, 0) 색으로 사각형을 표시하는 코드입니다. (0, 0)은 좌측 상단이며, (h-1, w-1)은 우측 하단입니다. roi 이미지의 좌측 상단부터 우측 하단까지를 꼭짓점으로 갖는 사각형을 그려준다는 것입니다. RGB 값인 (0, 255, 0)은 녹색을 나타냅니다.

관심 영역인 roi를 따로 지정하지 않았다면 이 코드는 다음과 같게 됩니다.

cv2.rectangle(roi, (x, y), (x+w, y+h), (0,255,0))

다음 코드는 지정한 관심 영역을 원본 이미지에 복제해서 두 개로 보이게 하거나, 지정한 관심 영역만 새 창에 뜨게 합니다.

# 관심영역 복제 및 새 창에 띄우기 (roi_copy.py)

import cv2
import numpy as np

img = cv2.imread('../img/sunset.jpg')

x=320; y=150; w=50; h=50
roi = img[y:y+h, x:x+w]     # roi 지정
img2 = roi.copy()           # roi 배열 복제 ---①

img[y:y+h, x+w:x+w+w] = roi # 새로운 좌표에 roi 추가, 태양 2개 만들기
cv2.rectangle(img, (x,y), (x+w+w, y+h), (0,255,0)) # 2개의 태양 영역에 사각형 표시

cv2.imshow("img", img)      # 원본 이미지 출력
cv2.imshow("roi", img2)     # roi 만 따로 출력

cv2.waitKey(0)
cv2.destroyAllWindows()

원본 이미지에 태양이 하나 더 복제가 되어 두 개로 표시가 되었고, 좌측 상단에 새로운 창으로도 표시가 되었습니다.

마우스 드래그로 관심 영역 표시하기

관심 영역을 관심 영역을 표시하기 위해서는 원하는 영역의 좌표와 크기(높이, 너비)가 필요합니다. 하지만 이를 매번 수치로 입력하기는 여간 번거로운 게 아닙니다. 마우스로 관심 영역을 드래그해서 표시한다면 더 편리하겠죠? 아래 코드는 관심 영역을 마우스로 드래그하여 지정하고, 관심 영역만 새 창에 띄우며 파일로 저장하는 예제입니다.

# 마우스로 관심영역 지정 및 표시, 저장 (roi_crop_mouse.py)

import cv2
import numpy as np

isDragging = False                      # 마우스 드래그 상태 저장 
x0, y0, w, h = -1,-1,-1,-1              # 영역 선택 좌표 저장
blue, red = (255,0,0),(0,0,255)         # 색상 값 

def onMouse(event,x,y,flags,param):     # 마우스 이벤트 핸들 함수  ---①
    global isDragging, x0, y0, img      # 전역변수 참조
    if event == cv2.EVENT_LBUTTONDOWN:  # 왼쪽 마우스 버튼 다운, 드래그 시작 ---②
        isDragging = True
        x0 = x
        y0 = y
    elif event == cv2.EVENT_MOUSEMOVE:  # 마우스 움직임 ---③
        if isDragging:                  # 드래그 진행 중
            img_draw = img.copy()       # 사각형 그림 표현을 위한 이미지 복제
            cv2.rectangle(img_draw, (x0, y0), (x, y), blue, 2) # 드래그 진행 영역 표시
            cv2.imshow('img', img_draw) # 사각형 표시된 그림 화면 출력
    elif event == cv2.EVENT_LBUTTONUP:  # 왼쪽 마우스 버튼 업 ---④
        if isDragging:                  # 드래그 중지
            isDragging = False          
            w = x - x0                  # 드래그 영역 폭 계산
            h = y - y0                  # 드래그 영역 높이 계산
            print("x:%d, y:%d, w:%d, h:%d" % (x0, y0, w, h))
            if w > 0 and h > 0:         # 폭과 높이가 양수이면 드래그 방향이 옳음 ---⑤
                img_draw = img.copy()   # 선택 영역에 사각형 그림을 표시할 이미지 복제
                # 선택 영역에 빨간 사각형 표시
                cv2.rectangle(img_draw, (x0, y0), (x, y), red, 2) 
                cv2.imshow('img', img_draw) # 빨간 사각형 그려진 이미지 화면 출력
                roi = img[y0:y0+h, x0:x0+w] # 원본 이미지에서 선택 영영만 ROI로 지정 ---⑥
                cv2.imshow('cropped', roi)  # ROI 지정 영역을 새창으로 표시
                cv2.moveWindow('cropped', 0, 0) # 새창을 화면 좌측 상단에 이동
                cv2.imwrite('./cropped.jpg', roi)   # ROI 영역만 파일로 저장 ---⑦
                print("croped.")
            else:
                cv2.imshow('img', img)  # 드래그 방향이 잘못된 경우 사각형 그림ㅇㅣ 없는 원본 이미지 출력
                print("좌측 상단에서 우측 하단으로 영역을 드래그 하세요.")

img = cv2.imread('../img/sunset.jpg')
cv2.imshow('img', img)
cv2.setMouseCallback('img', onMouse) # 마우스 이벤트 등록 ---⑧
cv2.waitKey()
cv2.destroyAllWindows()

위 코드에서는 onMouse()라는 콜백 함수를 선언했습니다. 이 콜백 함수에서는 세 가지 경우에 따라 이벤트를 분기합니다. (1) 마우스 왼쪽 버튼을 눌렀을 때, (2) 누른 상태로 드래그했을 때, (3) 마우스 왼쪽 버튼을 뗄 때입니다. 마우스 왼쪽 버튼을 눌렀을 때는 드래그가 시작되었다는 것을 기억하기 위한 isDragging이라는 변수의 상태를 True로 변경하고, 드래그가 시작된 점을 x0, y0로 지정합니다. 마우스를 누른 상태로 드래그를 할 때는 img를 복제하고, 복제한 이미지의 (x0, y0)부터 (x, y)까지 파란색 사각형으로 표시합니다. (x0, y0)는 마우스 드래그가 시작된 위치이고, (x, y)는 마우스의 현재 위치입니다. 마지막으로 마우스 왼쪽 버튼을 떼면 isDragging은 False로 바꾸고, 드래그 영역의 폭은 w, 높이는 h로 계산합니다. w와 h가 모두 양수이면 지정 영역을 빨간색 사각형으로 표시하고, 새 창으로도 띄웁니다. 추가로 지정 영역의 이미지를 파일로도 저장해줍니다. 코드 실행 결과는 다음과 같습니다.

하지만 마우스로 관심 영역을 지정하고 싶을 때 매번 이렇게 코드를 짜는 것도 상당히 번거롭습니다. OpenCV에서는 이를 간단하게 도와주는 함수를 제공합니다. 이 함수를 사용하면 마우스 이벤트 처리를 위한 코드 없이도 관심 영역을 지정할 수 있습니다.

  • ret = cv2.selectROI(win_name, img, showCrossHair=True, fromCenter=False)
    win_name: 관심영역을 표시할 창의 이름
    img: 관심영역을 표시할 이미지
    showCrossHair: 선택 영역 중심에 십자 모양 표시 여부
    fromCenter: 마우스 시작 지점을 영역의 중심으로 지정
    ret: 선택한 영역의 좌표와 크기 (x, y, w, h); 선택을 취소하면 모두 0으로 지정됨

아래 코드는 cv2.selectROI() 함수를 이용하여 선택 영역을 표시합니다. 마우스로 관심 영역을 드래그 한 뒤 스페이스나 엔터를 누르면 새 창에 관심 영역이 뜹니다. 드래그 한 뒤 'c'를 누르면 취소가 됩니다.

# selectROI로 관심영역 지정 및 표시, 저장 (roi_select_img.py)

import cv2, numpy as np

img = cv2.imread('../img/sunset.jpg')

x,y,w,h	= cv2.selectROI('img', img, False)
if w and h:
    roi = img[y:y+h, x:x+w]
    cv2.imshow('cropped', roi)  # ROI 지정 영역을 새창으로 표시
    cv2.moveWindow('cropped', 0, 0) # 새창을 화면 좌측 상단에 이동
    cv2.imwrite('./cropped2.jpg', roi)   # ROI 영역만 파일로 저장

cv2.waitKey(0)
cv2.destroyAllWindows()

위 코드에서 관심영역을 새 창으로 띄워주는 코드 cv2.imshow('cropped', roi)는 있는데, 원본 이미지인 img를 띄워주는 코드는 없습니다. 그러나 코드를 실행하면 원본 이미지도 같이 뜹니다. 이는 cv2.selectROI('img', img, False)가 해주는 것입니다. cv2.selectROI() 함수를 호출하면 원본 이미지가 뜨고, 마우스 이벤트 처리도 도와줍니다.

Comments