귀퉁이 서재

OpenCV - 7. 이미지 색상 표현 방식(BGR, HSV, YUV) 본문

OpenCV

OpenCV - 7. 이미지 색상 표현 방식(BGR, HSV, YUV)

Baek Kyun Shin 2020. 9. 15. 22:24

이번 포스팅에서는 OpenCV로 색상을 표현하는 방식에 대해 알아보겠습니다. 이번 포스팅 역시 '파이썬으로 만드는 OpenCV 프로젝트(이세우 저)'를 정리한 것임을 밝힙니다.

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

BGR, BGRA

색상을 표현하는 방법으로는 RGB(Red, Green, Blue) 방식이 있습니다. 빨강, 초록, 파랑 세 가지 색의 빛을 섞어서 원하는 색을 만드는 방식입니다. 각 색상은 0~255 사이의 값으로 표시하고 값이 커질수록 해당 색상의 빛이 밝아지는 원리입니다. RGB = (255, 255, 255) 일 때는 흰색이고, RGB = (0, 0, 0) 일 때는 검은색입니다. 그러나 OpenCV는 그 반대의 순서인 BGR로 표현합니다. 예를 들어, 빨간색은 RGB 값으로 (255, 0, 0)이지만 BGR 값으로는 (0, 0, 255)입니다.

RGBA는 RGB에 A(알파, alpha)가 추가된 색상 표기법입니다. A는 배경의 투명도를 의미합니다. A 역시 0~255의 값을 가질 수 있지만, 배경의 투명도를 표현하기 위해 0과 255만 사용하는 경우가 많습니다. A값이 255면 흰색, 0이면 검은색입니다. 

이제 아래의 예제 코드를 살펴보겠습니다. cv2.imread() 함수에 두 번째 파라미터로 cv2.IMREAD_COLOR를 넣어주면 BGR 방식으로 이미지를 읽습니다. cv2.IMREAD_UNCHANGED인 경우 이미지가 알파 채널을 가지고 있는 경우 BGRA 방식으로 읽습니다.

# BGR, BGRA, Ahlpha 채널 (rgba.py)

import cv2
import numpy as np

# 기본 값 옵션
img = cv2.imread('../img/opencv_logo.png')   
# IMREAD_COLOR 옵션                   
bgr = cv2.imread('../img/opencv_logo.png', cv2.IMREAD_COLOR)    
# IMREAD_UNCHANGED 옵션
bgra = cv2.imread('../img/opencv_logo.png', cv2.IMREAD_UNCHANGED) 
# 각 옵션에 따른 이미지 shape
print("default", img.shape, "color", bgr.shape, "unchanged", bgra.shape) 

cv2.imshow('bgr', bgr)
cv2.imshow('bgra', bgra)
cv2.imshow('alpha', bgra[:,:,3])  # 알파 채널만 표시
cv2.waitKey(0)
cv2.destroyAllWindows()

파라미터를 cv2.IMREAD_COLOR로 전달한 것과 아무 파라미터를 전달하지 않은 이미지의 차이는 없습니다. 배경이 검은색이다 보니 OpenCV라는 검은색 글씨도 안 보입니다. 또한, 첫 번째와 두 번째 이미지 모두 shape가 (240, 195, 3)입니다. 반면 세 번째 이미지는 알파 채널이 하나 더 있어 shape가 (240, 195, 4)입니다. 세 번째 이미지에서 전경의 알파 값은 255, 배경의 알파 값은 0인 것을 알 수 있습니다. 알파 값이 255면 흰색, 0이면 검은색이기 때문입니다. 첫 번째, 두 번째 이미지와 달리 세 번째 이미지는 알파 채널만 표시했으므로 전경과 배경을 쉽게 분리해서 볼 수 있습니다. 이런 이유로 알파 채널은 마스크 채널(mask channel)이라고도 부릅니다.

BGR 색상 이미지를 회색조 이미지로 변환하기

컬러 이미지를 회색조 이미지로 변환하는 것은 이미지 연산의 양을 줄여서 속도를 높이는 데 꼭 필요합니다. 처음부터 회색조로 읽어 들이는 함수는 cv2.imread(img, cv2.IMREAD_GRAYSCALE)입니다. cv2.imread() 함수의 두 번째 파라미터로 cv2.IMREAD_GRAYSCALE을 전달하면 됩니다. 그러나 처음에는 BGR 컬러 이미지로 읽어 들이고 그 이후에 회색조로 변환해야 할 때도 있습니다. 이는 cv2.cvtcolor() 함수로 구현할 수 있습니다. convert color의 약자입니다.

아래 코드는 색상 이미지를 회색조 이미지로 변환하는 두 가지 방법을 보여줍니다. 첫 번째 방법은 평균값을 이용해 직접 구현하는 것이고, 두 번째 방법은 OpenCV에서 제공하는 cv2.cvtcolor() 함수를 이용하는 것입니다.

# BGR 색상 이미지를 회색조 이미지로 변환 (bgr2gray.py)

import cv2
import numpy as np

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

img2 = img.astype(np.uint16)                # dtype 변경 ---①
b,g,r = cv2.split(img2)                     # 채널 별로 분리 ---②
#b,g,r = img2[:,:,0], img2[:,:,1], img2[:,:,2]
gray1 = ((b + g + r)/3).astype(np.uint8)    # 평균 값 연산후 dtype 변경 ---③

gray2 = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # BGR을 그레이 스케일로 변경 ---④
cv2.imshow('original', img)
cv2.imshow('gray1', gray1)
cv2.imshow('gray2', gray2)

cv2.waitKey(0)
cv2.destroyAllWindows()

gray1 이미지는 평균값을 이용해 회색조 이미지로 표현하는 알고리즘을 직접 구현한 것입니다. 평균을 구하기 전에 dtype을 unit16으로 바꿔주었습니다. 3 채널의 값을 합하면 255보다 큰 값이 나올 수 있기 때문입니다. 평균을 구한 뒤에는 다시 unit8로 바꿔줍니다. 

cv2.split(img2) 함수는 BGR 채널별로 분리해서 튜플로 반환합니다. 이는 numpy 슬라이싱과 동일합니다. 따라서 아래의 두 코드는 동일한 코드입니다.

b, g, r = cv2.split(img2)
b, g, r = img2[:, :, 0], img2[:, :, 1], img2[:, :, 2]

그러나 BGR 색상 이미지를 회색조 이미지로 바꾸기 위해서 단순히 평균값을 취하는 것 말고 함수를 이용하는 방법도 있습니다. 이는 cv2.cvtcolor(img, flag) 함수를 사용하면 되며, flag 파라미터에 cv2.COLOR_BRG2GRAY를 넣어주면 됩니다.

flag 파라미터는 총 274개이지만, 자주 사용되는 것은 아래와 같습니다.

cv2.COLOR_BGR2GRAY: BGR 색상 이미지를 회색조 이미지로 변환
cv2.COLOR_GRAY2BGR: 회색조 이미지를 BGR 색상 이미지로 변환
cv2.COLOR_BGR2RGB: BGR 색상 이미지를 RGB 색상 이미지로 변환
cv2.COLOR_BGR2HSV: BGR 색상 이미지를 HSV 색상 이미지로 변환
cv2.COLOR_HSV2BGR: HSV 색상 이미지를 BGR 색상 이미지로 변환
cv2.COLOR_BGR2YUV: BGR 색상 이미지를 YUV 색상 이미지로 변환
cv2.COLOR_YUV2BGR: YUB 색상 이미지를 BGR 색상 이미지로 변환

HSV, YUV에 대해서는 뒤이어 살펴보겠습니다. 참고로 cv2.COLOR_GRAY2BGR가 회색조 이미지를 BGR 색상 이미지로 변환하는 파라미터인데, 이는 실제 회색조 이미지를 색깔이 있는 이미지로 바꿔준다는 뜻이 아닙니다. 2차원의 배열을 갖는 이미지를 3개 채널 모두 같은 값을 갖는 3차원 배열로 변환한다는 뜻입니다. 이미지 간 연산을 할 때 차원이 다르면 연산이 불가능하므로 차원을 맞추기 위해 필요한 작업입니다.

HSV 방식

HSV 방식은 RGB와 마찬가지로 3개의 채널을 갖는 색상 이미지 표현법입니다. 3개의 채널은 H(Hue, 색조), S(Saturation, 채도), V(Value, 명도)입니다. 

출처: 위키백과

위 그림에서 H, S, V를 보면 이해가 쉬울 것입니다. H 값은 이미지가 어떤 색상인지를 나타냅니다. S는 이미지의 색상이 얼마나 순수하게 포함되어 있는지를 나타냅니다. V는 색상이 얼마나 밝은지 어두운지를 표현합니다. cv2.cvtColor() 함수에서 두 번째 파라미터로 cv2.COLOR_BGR2HSV를 넣어주면 BGR 방식을 HSV 방식으로 변환합니다. cv2.COLOR_HSV2BGR을 넣어주면 HSV 방식을 BGR 방식으로 변환합니다. 아래는 BGR 방식을 HSV 방식으로 변환하는 예제 코드입니다.

# BGR을 HSV로 변환 (bgr2hsv.py)

import cv2
import numpy as np

#---① BGR 컬러 스페이스로 원색 픽셀 생성
red_bgr = np.array([[[0,0,255]]], dtype=np.uint8)   # 빨강 값만 갖는 픽셀
green_bgr = np.array([[[0,255,0]]], dtype=np.uint8) # 초록 값만 갖는 픽셀
blue_bgr = np.array([[[255,0,0]]], dtype=np.uint8)  # 파랑 값만 갖는 픽셀
yellow_bgr = np.array([[[0,255,255]]], dtype=np.uint8) # 노랑 값만 갖는 픽셀

#---② BGR 컬러 스페이스를 HSV 컬러 스페이스로 변환
red_hsv = cv2.cvtColor(red_bgr, cv2.COLOR_BGR2HSV);
green_hsv = cv2.cvtColor(green_bgr, cv2.COLOR_BGR2HSV);
blue_hsv = cv2.cvtColor(blue_bgr, cv2.COLOR_BGR2HSV);
yellow_hsv = cv2.cvtColor(yellow_bgr, cv2.COLOR_BGR2HSV);

#---③ HSV로 변환한 픽셀 출력
print("red:",red_hsv)
print("green:", green_hsv)
print("blue", blue_hsv)
print("yellow", yellow_hsv)

BGR이 (0, 0, 255)인 색을 HSV로 표현하면 (0, 255, 255)입니다. 색상을 알아내기 위해서 RGB 방식은 세 가지 채널의 값을 모두 알아야 하지만, HSV 방식은 오직 H값 하나만 알면 되므로 좀 더 편리하고 효과적입니다.

YUV, YCbCr 방식

YUV 방식은 YCbCr 방식이라고도 하며, Y는 밝기(Luma), U는 밝기와 파란색과의 색상 차(Chroma Blue, Cb), V는 밝기와 빨간색과의 색상 차(Chroma Red, Cr)를 의미합니다. Y(밝기)에는 많은 비트수를 할당하고 U(Cb)와 V(Cr)에는 적은 비트수를 할당하여 데이터를 압축하는 효과를 갖습니다. 아래는 Y=0.5일 때, V와 U에 따른 YUV 방식의 색상 이미지 영역입니다.

출처: 위키백과, Y=0.5일 때

아래는 BGR 값을 YUV 값으로 변환하는 예제 코드입니다.

# BGR 값을 YUV로 변환 (bgr2yuv.py)

import cv2
import numpy as np

#---① BGR 컬러 스페이스로 3가지 밝기의 픽셀 생성
dark = np.array([[[0,0,0]]], dtype=np.uint8)        # 3 채널 모두 0인 가장 어두운 픽셀
middle = np.array([[[127,127,127]]], dtype=np.uint8) # 3 채널 모두 127인 중간 밝기 픽셀
bright = np.array([[[255,255,255]]], dtype=np.uint8) # 3 채널 모두 255인 가장 밝은 픽셀

#---② BGR 컬러 스페이스를 YUV 컬러 스페이스로 변환
dark_yuv = cv2.cvtColor(dark, cv2.COLOR_BGR2YUV)
middle_yuv = cv2.cvtColor(middle, cv2.COLOR_BGR2YUV)
bright_yuv = cv2.cvtColor(bright, cv2.COLOR_BGR2YUV)

#---③ YUV로 변환한 픽셀 출력
print("dark:",dark_yuv)
print("middle:", middle_yuv)
print("bright", bright_yuv)

BGR값은 (0, 0, 0), (127, 127, 127), (255, 255, 255)로 어두운 픽셀, 중간 밝기의 픽셀, 가장 밝은 픽셀입니다. 이를 YUV 방식으로 변환하면 각각 (0, 128, 128), (127, 128, 128), (255, 128, 128)입니다. 맨 처음 값인 Y가 밝기를 뜻한다고 했습니다. 두 번째, 세 번째 값은 동일한데 Y값만 0, 127, 255로 바뀝니다. 즉 어두운 값에서 밝은 값으로 변환하는 것을 볼 수 있습니다. 밝기에 좀 더 신경을 써야 한다면 BGR 방식보다 YUV 방식을 사용하는 것이 더 현명한 방법이겠죠?

 

요약하자면 OpenCV에서 색상을 표현하는 방식은 네 가지가 있습니다. BGR 방식, BGRA 방식, HSV 방식, YUV 방식입니다. BGR 방식은 전통적인 RGB 방식과 유사하며 그 순서만 반대입니다. BGRA 방식은 BGR 방식에서 투명도를 나타내는 A(알파) 값이 추가된 방식입니다. HSV 방식은 색조, 채도, 명도를 이용해서 색상을 표현하는 방식으로 H만 알면 색조는 어느 정도 파악이 가능합니다. 따라서 색조를 한눈에 알고자 한다면 HSV 방식을 사용하면 됩니다. YUV 방식은 밝기에 더 신경을 써야 하는 경우에 사용하면 좋습니다.

Comments