Python과 OpenCV – 6 : 이미지에 대한 기본 연산

이 글을 작성하기 위해 참조한 글은 https://opencv-python-tutroals.readthedocs.io/en/latest/py_tutorials/py_core/py_basic_ops/py_basic_ops.html 입니다.

이 글에서 이미지에 대한 기본 연산이라함은 다음과 같습니다.

  • 이미지의 픽셀값을 읽거나 변경하기
  • 이미지의 속성 읽기
  • ROI(Region Of Image, 이미지 영역) 지정하기
  • 이미지를 채널별로 나누거나 합치기
  • 이미지의 가장자리 만들기

위의 항목만 읽어서는 무엇을 의미하는지 완벽하게 이해하긴 어렵습니다. 하나씩 살펴보겠습니다.

먼저 다음 코드로 시작해 하나씩 덧붙여 보겠습니다.

import cv2
import numpy as np

img = cv2.imread('./data/people.png')

people.png라는 이미지를 하나 읽어 img 변수에 저장합니다. 그리고..

px = img[0,100] # [row,column]
print(px) # [B,G,R]

이미지의 픽셀 중 y=0, x=100 인 값을 읽어 표시하는 코드입니다. 이 예제의 경우 결과는 [ 68 129 117]가 출력되는데, 이미지가 3개의 채널로 구성되어 있으므로 3개의 값이 표시되며 순서대로 Blue, Green, Red 채널의 값입니다.

blue = img[0,100,0] # [row,column,channel]
print(blue) # Blue

위의 코드는 이미지의 y=0, x=100인 픽셀의 첫번째 채널의 값을 읽습니다. 이미지의 픽셀 색상은 BGR 순서이므로 첫번째는 Blue 채널의 값을 읽습니다. 이 예제의 경우 결과는 68로 표시 되었습니다.

img[0,100] = [255,255,255]
img[0,100,0] = 0

위의 코드에서 1번은 x=99, y=0인 픽셀의 색상을 BGR 순서로 255,255,255로 변경하는 것입니다. 그리고 2번은 x=100, y=0인 픽셀의 첫번째 채널을 0으로 변경하는 코드입니다. 첫번째 채널이므로 Blue 채널입니다.

px = img.item(0,100,0)
print(px)

위의 코드는 이미지의 y=0, x=100 인 픽셀의 첫번째 채널의 값을 출력합니다. 값은 255가 출력됩니다.

img.itemset((0,100,0),255)
px = img.item(0,100,0)
print(px)

위의 코드는 먼저 y=0, x=100인 픽셀의 첫번째(0) 채널의 값을 255로 변경하고, 다시 동일한 픽셀의 첫번째 채널의 값을 표시합니다. 255가 출력됩니다.

print(img.shape)
print(img.size)
print(img.dtype)

위의 코드 중 1번은 이미지의 차원 크기를 출력합니다. 이미지가 640×480 크기이고 1픽셀이 3Byte인 RGB 형식이므로 출력은 (480, 640, 3)입니다. 2번 코드는 이미지의 전체 Byte 크기로 480*640*3 값인 921600가 출력됩니다. 3번째 코드는 채널의 데이터 형식으로써 출력은 uint8입니다.

roi = img[280:340, 330:390]
img[273:333, 100:160] = roi

cv2.rectangle(img, (330,280), (390,340), (0,0,255), 1)
cv2.rectangle(img, (100,273), (160,333), (255,0,0), 1)

cv2.imshow('img', img)
cv2.waitKey()
cv2.destroyAllWindows()

위의 코드 ROI에 대한 코드로써 1번 코드는 y가 280번째에서 (340-1)번째이면서 x는 330번째에서 (390-1)번째까지의 영역의 픽셀값을 roi 객체에 복사(Copy)합니다. 그리고 2번 코드를 통해 roi 객체에 저장된 픽셀값을 이미지(img 객체)의 y가 273번째에서 (333-1)번째이면서 x는 100번째에서 (160-1)번째까지의 영역에 복사합니다. 4번은 1번 코드의 ROI 영역을 빨간색 사각형으로 그리고 5번 코드는 2번 코드의 ROI 영역을 파란색 사각형으로 그리라는 것입니다. 결과는 다음과 같습니다.

b,g,r = cv2.split(img)
img = cv2.merge((b,g,r))

위의 1번 코드는 img 객체의 이미지를 각 채널로 분리하여 각각 b, g, r 객체에 복사(Copy)합니다. 그리고 2번 코드는 채널을 조합하는 코드입니다.

b = img[:,:,0]
img[:,:,2] = 0

위의 1번 코드는 이미지의 0번째 채널을 b 객체에 복사(Copy)하고, 2번은 이미지의 3번째 채널의 모든 값을 0으로 만듭니다. 결과적으로 이미지에서 Red 색감이 모두 제거됩니다.

끝으로 이미지에 가장자리를 만드는 예제입니다.

import cv2
import numpy as np
from matplotlib import pyplot as plt

RED = [255,0,0]

img1 = cv2.imread('./data/opencv_logo.png')

replicate = cv2.copyMakeBorder(img1,10,10,10,10,cv2.BORDER_REPLICATE)
reflect = cv2.copyMakeBorder(img1,10,10,10,10,cv2.BORDER_REFLECT)
reflect101 = cv2.copyMakeBorder(img1,10,10,10,10,cv2.BORDER_REFLECT_101)
wrap = cv2.copyMakeBorder(img1,10,10,10,10,cv2.BORDER_WRAP)
constant= cv2.copyMakeBorder(img1,10,10,10,10,cv2.BORDER_CONSTANT,value=RED)

plt.subplot(231),plt.imshow(img1,'gray'),plt.title('ORIGINAL')
plt.subplot(232),plt.imshow(replicate,'gray'),plt.title('REPLICATE')
plt.subplot(233),plt.imshow(reflect,'gray'),plt.title('REFLECT')
plt.subplot(234),plt.imshow(reflect101,'gray'),plt.title('REFLECT_101')
plt.subplot(235),plt.imshow(wrap,'gray'),plt.title('WRAP')
plt.subplot(236),plt.imshow(constant,'gray'),plt.title('CONSTANT')

plt.show()

만들어진 가장자리만큼 이미지의 크기는 커집니다. 결과는 아래와 같습니다.

결과에서 보이는 것처럼 cv2.copyMakeBorder 함수를 통해 가장자리가 추가되는데, 이 함수의 6번째 인자에 따라 추가되는 가장자리의 형태가 결정됩니다.

Python과 OpenCV – 5 : Trackbar UI

이 글의 원문은 https://opencv-python-tutroals.readthedocs.io/en/latest/py_tutorials/py_gui/py_trackbar/py_trackbar.html 입니다.

OpenCV는 Trackbar UI를 자체적으로 제공하는데, OpenCV의 Window에 이 Trackbar를 붙일 수 있습니다. 이미지의 특성값의 설정에 이 Trackbar UI가 효과적으로 사용될 수 있습니다. 간단한 예를 통해 필요한 함수를 살펴보겠습니다.

import cv2
import numpy as np

def nothing(x):
    pass

img = np.zeros((300,512,3), np.uint8)
cv2.namedWindow('image')

cv2.createTrackbar('R','image', 0, 255, nothing)
cv2.createTrackbar('G','image', 0, 255, nothing)
cv2.createTrackbar('B','image', 0, 255, nothing)

switch = '0 : OFF \n1 : ON'
cv2.createTrackbar(switch, 'image', 0, 1, nothing)

while(1):
    cv2.imshow('image', img)
    k = cv2.waitKey(1) & 0xFF
    if k == 27:
        break

    r = cv2.getTrackbarPos('R','image')
    g = cv2.getTrackbarPos('G','image')
    b = cv2.getTrackbarPos('B','image')
    s = cv2.getTrackbarPos(switch,'image')

    if s == 0:
        img[:] = 0
    else:
        img[:] = [b,g,r]

cv2.destroyAllWindows()

위의 실행 화면은 다음과 같습니다.

화면에서와 같이 총 4개의 Trackbar가 표시되고 있습니다. 이 트랙바는 10, 11, 12, 15번 코드의 cv2.createTrackbar 함수를 통해 생성됩니다. 이 함수의 첫번째 인자는 트랙바의 ID이자 표시되는 라벨이고, 두번째는 붙을 Window의 이름, 세번째는 트랙바가 조정할 수 있는 값의 최소치, 네번째는 조정할 수 있는 값의 최대치입니다. 마지막 인자는 트랙바의 값이 변경될때마다 호출되는 이벤트 함수입니다. 이 프로그램은 트랙바로 조정한 값을 RGB 색상으로 조합하여 표시합니다.

Python과 OpenCV – 4 : 마우스 이벤트

원문은 https://opencv-python-tutroals.readthedocs.io/en/latest/py_tutorials/py_gui/py_mouse_handling/py_mouse_handling.html 입니다.

이미지 처리를 위해 OpenCV에서는 자체적인 마우스 이벤트 체계를 가지고 있습니다. 마우스 이벤트는 cv2.setMouseCallback 함수를 통해 마우스 이벤트를 처리할 Window에 등록됩니다. 먼저 간단한 예를 통해 살펴보겠습니다.

import cv2
import numpy as np

def draw_circle(event, x, y, flags, param):
    if event == cv2.EVENT_LBUTTONDBLCLK:
        cv2.circle(img,(x,y),100,(255,0,0),-1)

img = np.zeros((512,512,3), np.uint8)
cv2.namedWindow('image')
cv2.setMouseCallback('image',draw_circle)

while(1):
    cv2.imshow('image',img)
    if cv2.waitKey(20) & 0xFF == 27:
        break

cv2.destroyAllWindows()

위의 프로그램은 Window 위에서 마우스 버튼을 더블클릭하면 해당 지점에서 파란색 큰 원을 그립니다.

좀더 복잡한 예제를 살펴보겠습니다.

import cv2
import numpy as np

drawing = False
mode = True
ix, iy = -1, -1

def draw_circle(event,x,y,flags,param):
    global ix, iy, drawing, mode

    if event == cv2.EVENT_LBUTTONDOWN:
        drawing = True
        ix, iy = x, y

    elif event == cv2.EVENT_MOUSEMOVE:
        if drawing == True:
            if mode == True:
                cv2.rectangle(img, (ix,iy), (x,y), (0,255,0), -1)
            else:
                cv2.circle(img, (x,y), 5, (0,0,255), -1)

    elif event == cv2.EVENT_LBUTTONUP:
        drawing = False
        if mode == True:
            cv2.rectangle(img, (ix,iy), (x,y), (0,255,0), -1)
        else:
            cv2.circle(img, (x,y), 5, (0,0,255), -1)

img = np.zeros((512,512,3), np.uint8)
cv2.namedWindow('image')
cv2.setMouseCallback('image', draw_circle)

while(1):
    cv2.imshow('image', img)
    k = cv2.waitKey(1) & 0xFF
    if k == ord('m'):
        mode = not mode
    elif k == 27:
        break

cv2.destroyAllWindows()

위의 프로그램은 마우스 드래그를 이용해 사각형과 원을 그릴 수 있는데, m 키를 눌러 그리고자 하는 원과 사각형을 결정할 수 있습니다.

Python과 OpenCV – 3 : 코드로 이미지에 도형 그리기

참조한 글은 https://opencv-python-tutroals.readthedocs.io/en/latest/py_tutorials/py_gui/py_drawing_functions/py_drawing_functions.html 입니다.

이미지는 사진이나 동영상을 통해 얻을 수도 있지만 코드를 통해서도 생성할 수 있습니다. 아래의 코드는 512×512 크기의 3개의 채널로 되어 있으며 채널의 구성 데이터 타입은 unit8입니다.

import numpy as np
import cv2

img = np.zeros((512,512,3), np.uint8)

이 이미지 위에 다양한 도형을 그리는 코드를 정리해 볼텐데요. 먼저 두점으로 구성된 선분을 그리는 코드입니다.

cv2.line(img, (0,0), (512,512), (255,0,0), 5)

위의 코드는 좌표(0,0)에서 좌표(512,512)로 구성된 선분을 색상은 (B,G,R) 순서인 (255,0,0)인 파란색으로 굵기는 5pixel로 그립니다. 다음 코드는 사각형을 그리는 코드입니다.

cv2.rectangle(img, (384,0), (510,128), (0,255,0), 3)

위의 코드는 좌상단 (384,0)과 우하단 (510,128)인 지사각형을 초록색(0,255,0)으로 3Pixel 굵기로 그립니다. 다음은 원을 그리는 코드입니다.(

cv2.circle(img, (447,63), 63, (0,0,255), -1)

위의 코드는 중심점 (447,63)이고 반지름은 63인 원을 빨간색(0,0,255)으로 하여 굵기속성은 -1로 함으로써 도형을 채움형태로 그리는 코드입니다. 다음은 타원을 그리는 코드입니다.

cv2.ellipse(img, (256,256), (100,50), 45,0,180,(0,0,255),-1)

위의 코드는 중심점이 (256,256)이고 가로 반지름과 세로 반지름을 각각 (100,50)이며, 45도 시계방향으로 회전하고 시작각 0도 종료각 180도인 타원을 빨간색(0,0,255)로 채워진(-1) 타원을 그리는 코드입니다. 다음은 폴리라인을 그리는 코드입니다.

pts = np.array([[10,5],[20,30],[70,20],[50,10]], np.int32)
pts = pts.reshape((-1,1,2))
cv2.polylines(img, [pts], True, (0,255,255))

위의 코드는 총 4개의 좌표로 구성된 pts를 이용해 색상은 BRG값이 각각 (0,255,255)로 하여 폴리곤을 그리는 코드인데.. cv2.polylines의 3번째를 True로 지정하여 폐합(시작점과 끝점을 연결)하도록 그립니다. 마지막으로 문자를 그리는 코드입니다.

font = cv2.FONT_HERSHEY_SIMPLEX
cv2.putText(img, 'OpenCV', (10,500), font, 4, (255,255,255), 2)

위치 (10,500)에 OpenCV라는 문자열을 폰트 크기 4로 하여 백색(255,255,255)로 하여 굵기는 2로 그리는 코드입니다.

지금까지 그린 이미지를 화면에 표시하기 위해 아래의 코드를 덧붙입니다.

cv2.imshow('img', img)
cv2.waitKey()
cv2.destroyAllWindows()

개발성 향상을 위한 JavaScript의 주석

JavaScript는 type이 불명확하므로 문서화를 통해 그 상세정보를 남겨야 합니다. 그러나 소스코드와 분리된 문서는 사실상 그 활용가치가 매우 떨어집니다. 소스코드와 일원화된 방식으로 문서화가 되어야 하는데, 바로 주석입니다. 아래는 어떤 클래스의 특정함수에 대한 주석을 통한 문서화에 대한 간단한 예시입니다.

/**
 * 메뉴 항목을 추가한다.
 * @param {string} id 항목에 대한 고유 식별자 
 * @param {string} url 항목 아이콘 
 * @param {string} title 항목 타이틀
 * @param {function} callback 실행에 대한 호출 함수
 * @returns {boolean} 성공 여부
 */
addMenu(id, url, title, callback) {
   ...

IDE는 이 주석을 실시간으로 해석해 개발자에게 매우 직관적인 힌트를 제공합니다. 예를들어서 VSCode에서는 아래와 같은 힌트를 제공합니다.

아울러 이러한 주석은 다양한 문서 형식으로써, 예를들어 pdf, docx, html 등과 같은 별도의 문서를 자동으로 생성해 낼 수 있습니다.