Search
Duplicate

OpenCV 4로 배우는 컴퓨터 비전과 머신 러닝/ 컬러 영상 처리

컬러 영상 다루기

컬러 영상의 픽셀 값 참조

OpenCV의 컬러 영상은 기본적으로 RGB 색상 순서가 아니라 BGR 색상 순서로 픽셀값을 표현한다.
컬러 영상에서 각각의 RGB 성분은 0-255 사이의 값을 가질 수 있다.
OpenCV에서 각 생상 성분 값은 uchar 자료형을 사용하여 표현한다.
컬러 영상에서 하나의 픽셀은 세 개의 색상 성분을 가지고 있으므로 컬러 영상의 한 픽셀을 정확히 표현하려면 Vec3b 자료형을 사용해야 한다.
Vec3b 클래스는 크기가 3인 uchar 자료형 배열을 멤버로 가진 클래스로 3바이트의 크기를 갖는다.
컬러 영상에서 픽셀 값을 참조할 때는 Mat::at() 함수를 사용한다.
Mat::at() 함수는 템플릿으로 정의된 함수이므로 3채널 컬러 영상에 대해 Mat::at() 함수를 사용하려면 Vec3b 자료형을 명시해야 한다.
Vec3b& pixel = img.at<Vec3b>(0, 0); uchar b1 = pixel[0]; uchar g1 = pixel[1]; uchar r1 = pixel[2];
C++
Mat::ptr() 함수를 이용하여 컬러 영상의 특정 행 시작 주소를 얻어 올 때에도 Vec3b 자료형을 명시하여 사용해야 한다.
Vec3b& ptr = img.ptr<Vec3b>(0); uchar b2 = ptr[0][0]; uchar g2 = ptr[0][1]; uchar r2 = ptr[0][2];
C++

색 공간 변환

OpenCV에서는 컬러 영상을 Mat 객체에 저장할 때 BGR 순서로 색 정보를 표현하는데, 이를 RGB 색 모델(color model) 또는 RGB 색 공간(color space) 라고 한다.
RGB 색 공간은 널리 쓰이지만 컬러 영상 처리 관점에서는 환영 받지 못하는 편이다. 컬러 영상 처리에서는 보통 색상 구분이 용이한 HSV, HSL 색 공간을 사용하거나 휘도 겅분이 구분되어 있는 YCrCb, YUV 등 다른 색 공간을 사용하는 것이 좋다.
때문에 OpenCV에서는 HSV나 YGrCb 등 다른 색 공간으로 변환하는 인터페이스를 제공한다.
OpenCV에서 색 공간을 다른 색 공간으로 변환할 때는 cvtColor() 함수를 사용한다.
아래 표는 주로 사용되는 색 공간 변환 코드이다.
Search
ColorConversionCodes
설명
COLOR_BGR2GRAY
Open
3채널 BGR 컬러 영상을 1채널 그레이스케일 영상으로 변환
COLOR_GRAY2BGR
Open
1채널 그레이스케일 영상을 3채널 BGR 컬러 영상으로 변환
COLOR_BGR2XYZ
Open
BGR 색 공간을 CIE CYZ 색 공간으로 변환
COLOR_XYZ2BGR
Open
CIE XYZ 색 공간을 BGR 색 공간으로 변환
COLOR_BGR2YCrCb
Open
BGR 색 공간을 YCrCb 색 공간으로 변환
COLOR_YCrCb2BGR
Open
YCrCb 색 공간을 BGR 색 공간으로 변환
COLOR_BGR2HSV
Open
BGR 색 공간을 HSV 색 공간으로 변환
COLOR_HSV2BGR
Open
HSV 색 공간을 BGR 색 공간으로 변환
COLOR_BGR2Lab
Open
BGR 색 공간을 CIE Lab 색 공간으로 변환
COLOR_Lab2BGR
Open
CIE Lab 색 공간을 BGR 색 공간으로 변환

BGR2GRAY와 GRAY2BGR

BGR2GRAY 색 공간 변환 코드는 GBR 컬러 영상을 그레이스케일 영상으로 변환할 때 사용한다.
컬러 영상을 그레이스케일 영상으로 변환하는 이유는 연산 속도와 메모리 사용량을 줄이기 위함
BGR 3채널 컬러 영상을 그레이스케일 영상으로 변환할 때는 다음 공식을 사용한다.
BGR2GRAY 색 공간 변환 코드에 의해 만들어지는 결과 영상은 CV_8UC1 타입으로 설정된다.
Y=0.299R+0.587G+0.114BY = 0.299R + 0.587G + 0.114B
반대로 GRAY2BGR 색 공간 변환 코드는 그레이스케일 영상을 BGR 컬러 영상으로 변환할 때 사용한다. 이 경우 결과 영상은 CV_8UC3 타입으로 결정되고, 각 피셀의 BGR 색상 성분 값은 다음과 같이 결정된다.
R=G=B=YR = G = B = Y

BGR2HSV와 HSV2BGR

HSV 색 모델은 색상(hue), 채도(saturation), 명도(value)로 색을 표현하는 방식이다.
색상은 빨간색, 노란색, 녹색과 같은 색의 종류를 의미한다.
채도는 색의 순도를 나타내는데, 빨간색에 대해 채도가 높으면 맑은 선홍색이되고, 채도가 낮으면 탁ㅎ나 빨간색으로 보이게 된다.
명도는 빛의 세기를 나타내며, 명도가 높으면 밝고 낮으면 어둡게 보인다.
HSV 색 공간은 아래 그림과 같이 원뿔 모양으로 표현할 수 있다.
HSV 색 공간 모형에서 색상은 원뿔을 가로로 잘랐을 때 나타나는 원형에서 각도로 정의된다.
각도가 0도에 해당할 때 빨간색을 나타내고, 각도가 증가할 수록 노란색, 녹색, 하늘색, 파란색, 보라색을 거쳐 각도가 360도에 가까워지면 다시 빨간색으로 표현된다.
채도는 원뿔을 가로로 잘랐을 때 나타나는 원 모양의 중심에서 최솟값을 갖고, 원의 중심에서 방사형으로 멀어지는 방향으로 값이 증가한다.
명도는 원뿔 아래쪽 꼭지점에서 최솟값을 갖고 원뿔의 축을 따라 올라가면서 증가한다.

OpenCV에서 BGR2HSV 색 공간 변환 코드를 이용하여 8비트 BGR 영상을 HSV 영상으로 변환할 경우 H 값은 0부터 179사이의 정수로 표현되고, S와 V는 0-255 사이의 정수로 표현된다.
색상 값은 0-360도 사이의 각도로 표현하지만 uchar 자료형으로는 256 이상의 정수를 표현할 수 없기 때문에 OpenCV에서는 각도를 2로 나눈 값을 H 성분으로 저정한다.
만약 cvtColor() 함수의 입력 BGR 영상이 0에서 1사이의 값으로 정규화된 CV_32FC3 타입의 행렬이라면 H 값은 0에서 360 사이의 실수로 표현되고 S와 V는 0-1 사이의 실수 값으로 표현된다.

BGR2YCrCb와 YCrCb2BGR

YCrCb 색 공간에서 Y 성분은 밝기 또는 휘도(luminance) 정보를 나타내고, Cr과 Cb 성분은 색상 또는 색차(chrominance) 정보를 나타낸다.
RGB 색상 성분으로부터 Y 성분을 계산하는 공식은 그레이스케일 계산공식과 완전히 같다.
Cr과 Cb 성분은 밝기에 대한 정보는 포함하지 않으며 오직 색상에 대한 정보만 갖고 있다. 그러므로 YCrCb 색공간은 영상을 그레이스케일 정보와 색상 정보로 분리하여 처리할 때 유용하다.
OpenCV에서 BGR2YCrCb 색 공간 변환 코드를 이용하여 8비트 BGR 영상을 YCrCb 영상으로 변환할 경우 Y, Cr, Cb 각각의 성분은 0-255 사이의 값으로 표현된다.
만약 cvtColor(0 함수의 입력 영상이 0-1 사이의 값으로 정규화된 CV_32FC3 타입의 행렬이라면 Y, Cr, Cb 각각의 성분 값도 0-1 사이의 실수 값으로 표현된다.
Y 성분을 128로 고정한 상태에서 Cr과 Cb 값에 따른 색상 표현은 아래 그림과 같다.
HSV 색 공간에서는 H 값만 이용하여 색 종류를 구분할 수 있지만 YCrCb 색 공간에서는 Cr과 Cb를 조합하여 색을 구분할 수 있다.

색상 채널 나누기

OpenCV에서는 컬러 영상을 uchar 자료형을 사용하고 세 개의 채널을 갖는 Mat 객체로 표현한다.
그런데 컬러 영상을 다루다 보면 빨간색 성분만 이용하거나 HSV 색 공간으로 변홚나 후 H 성분만을 이용하는 경우가 종종 발생한다. 이러한 경우에는 3채널 Mat 객체를 1채널 Mat 객체 3개로 분리하여 다루는 것이 효율적이다.
OpenCV에서 다채널 행렬을 1채널 행렬 여러 개로 변환할 때는 split() 함수를 사용한다.
split() 함수와 반대로 1채널 행렬 여러 개를 합쳐서 다채널 행렬 하나를 생성하려면 merge() 함수를 사용한다.

컬러 영상 처리 기법

컬러 히스토그램 평활화

OpenCV에서 equalizeHist() 함수를 통해 히스토그램 평활화를 수행할 수 있지만 equalizeHist() 함수는 그레이스케일 영상만 입력 받을 수 있다.
3채널 컬러 영상에 대해 히스토그램 평활화를 수행하려면 OpenCV 함수를 조합하여 직접 구현해야 한다.
일반적으로 컬러 영상의 히스토그램 평활화를 생각해 보면 아래 이미지와 같이 입력 영상을 R, G, B 3개의 채널로 나누고 채널별로 히스토그램 평활화를 수행한 후 이를 다시 합치는 방식을 생각하게 되는데, 이러한 방식은 R, G, B 색상 채널마다 서로 다은 형태의 명암비 변환 함수를 사용하게 됨으로써 원본 영상과 다른 색상의 결과 영상이 만들어지는 문제가 발생한다.
컬러 영사으이 색감은 변경하지 않고 명암비를 높이려면 영상의 밝기 정보만 이용해야 한다.
그러므로 컬러 영상에 대해 히스토그램 평활화를 수행하려면 아래 이미지와 같이 입력 영상을 밝기 정보와 색상 정보로 분리한 후, 밝기 정보에 대해서만 히스토그램 평활화를 수행하면 된다.
영상을 YCrCb 색공간으로 변환하여 Y 성분에 대해서만 히스토그램 평활화를 수행한 후 다시 합치면 된다.

색상 범위 지정에 의한 영역 분할

컬러 영상을 다루는 응용에서 자주 요구되는 기법이 특정 색상 영역을 추출하는 작업이다. 예컨대 영상에서 빨간색 픽셀을 모두 찾아내서 빨간색 객체의 위치와 크기를 알아내는 작업이 있다.
컬러 영상에서 빨간색, 파란색 등의 대표적인 색상 영역을 구분할 때는 HSV와 같은 색상 정보가 따로 설정되어 있는 색 공간을 사용하는 것이 유리하다.
예컨대 HSV 색 공간에서 녹색은 H 값이 60 근방으로 표현되기 때문에 H값이 60에 가까운지를 조사하여 녹색 픽셀을 찾아낼 수 있다.
OpenCV에서 행렬의 원소 값이 특정 범위 안에 있는지 확인하려면 inRange() 함수를 사용하면 된다.
inRange() 함수는 입력 영상 src의 픽셀 값이 지정한 밝기 또는 색상 범위에 포함되어 있으면 흰색, 그렇지 않으면 검은색으로 채워진 마스크 영상 dst를 반환한다.
입력 영상 src에는 1채널 행렬과 다채널 행렬을 모두 지정할 수 있다. 만약 그레이스케일 영상을 입력 영상으로 사용할 경우 특정 밝기 값 범위에 있는 픽셀 영역을 추출할 수 있다.
1채널 영상에 대해 inRange() 함수의 동작을 수식으로 표현하면 다음과 같다.
dst(x,y)={255lowerb(x,y)src(x,y)upperb(x,y)0elsedst(x, y) = \begin{cases} 255 & lowerb(x, y) \leq src(x, y) \leq upperb(x, y) \\ 0 & else \end{cases}

히스토그램 역투영

inRange() 함수를 이용하여 색상 영역을 검출하는 방법을 HSV 색 공간에서 H 값을 이용하여 수행하면 간단하게 특정 색상을 골라낼 수 있어서 편리하다.
그러나 이러한 방식은 빨간색, 노란색, 녹색, 파란색처럼 원색에 가까운 색상을 찾기에는 효과적이지만 사람의 피부색처럼 미세한 변화가 있거나 색상 값을 수치적으로 지정하기 어려운 경우에는 적합하지 않다.
만약 입력 영상에서 찾고자 하는 객체의 기준 영상을 미리 가지고 있다면 컬러 히스토그램 정보를 이용하여 비슷한 색상 영역을 찾을 수 있다.
즉 기준 영상으로부터 찾고자 하는 객체의 컬러 히스토그램을 미리 구하고, 주어진 입력 영상에서 해당 히스토그램에 부합하는 영역을 찾아내는 방식이다.
이처럼 주어진 히스토그램 모델과 일치하는 픽셀을 찾아내는 기법을 히스토그램 역투영(histogram backprojection)이라고 한다. 예컨대 피부색에 대한 색상 히스토그램을 갖고 있다면 역투영 방법을 사용하여 영상에서 피부색 영역을 검출할 수 있다.
OpenCV에서 히스토그램 역투영은 calcBackProject() 함수를 이용하여 수행할 수 있다.
calcBackProject() 함수는 입력 영상에서 히스토그램 hist를 따르는 픽셀을 찾고, 그 정보를 backProject 영상으로 반환한다.
Mat ref, ref_ycrcb, mask; ref = imread("ref.png", IMREAD_COLOR); mask = imread("mask.bmp", IMREAD_GRAYSCALE); cvtColor(ref, ref_ycrcb, COLOR_BGR2YCrCb); Mat hist; int channels[] = { 1, 2 }; int cr_bins = 128; int cb_bins = 128; int histSize[] = { cr_bins, cb_bins }; float cr_range[] = { 0, 256 }; float cb_range[] = { 0, 256 }; const float* ranges[] = { cr_range, cb_range }; calcHist(&ref_ycrcb, 1, channels, mask, hist, 2, histSize, ranges); Mat src, src_ycrcb; src = imread("kids.png", IMREAD_COLOR); cvtColor(src, src_ycrcb, COLOR_BGR2YCrCb); Mat backproj; calcBackProject(&src_ycrcb, 1, channels, hist, backproj, ranges, 1, true); imshow("src", src); imshow("backproj", backproj); waitkey(0);
C++