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];
  • Mat::ptr() 함수를 이용하여 컬러 영상의 특정 행 시작 주소를 얻어 올 때에도 Vec3b 자료형을 명시하여 사용해야 한다.
Vec3b& ptr = img.ptr<Vec3b>(0);
uchar b2 = ptr[0][0];
uchar g2 = ptr[0][1];
uchar r2 = ptr[0][2];

색 공간 변환

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

COLOR_BGR2RGB
COLOR_RGB2RGR 

BRG 채널 순서와 RGB 채널 순서를 상호 변환
COLOR_BGR2GRAY 3채널 BGR 컬러 영상을 1채널 그레이스케일 영상으로 변환
COLOR_GRAY2BGR 1채널 그레이스케일 영상을 3채널 BGR 컬러 영상으로 변환
COLOR_BGR2XYZ BGR 색 공간을 CIE CYZ 색 공간으로 변환
COLOR_XYZ2BGR CIE XYZ 색 공간을 BGR 색 공간으로 변환
COLOR_BGR2YCrCb BGR 색 공간을 YCrCb 색 공간으로 변환
COLOR_YCrCb2BGR YCrCb 색 공간을 BGR 색 공간으로 변환
COLOR_BGR2HSV BGR 색 공간을 HSV 색 공간으로 변환
COLOR_HSV2BGR HSV 색 공간을 BGR 색 공간으로 변환
COLOR_BGR2Lab BGR 색 공간을 CIE Lab 색 공간으로 변환
COLOR_Lab2BGR CIE Lab 색 공간을 BGR 색 공간으로 변환

BGR2GRAY와 GRAY2BGR

  • BGR2GRAY 색 공간 변환 코드는 GBR 컬러 영상을 그레이스케일 영상으로 변환할 때 사용한다.
    • 컬러 영상을 그레이스케일 영상으로 변환하는 이유는 연산 속도와 메모리 사용량을 줄이기 위함
  • BGR 3채널 컬러 영상을 그레이스케일 영상으로 변환할 때는 다음 공식을 사용한다.
    • BGR2GRAY 색 공간 변환 코드에 의해 만들어지는 결과 영상은 CV_8UC1 타입으로 설정된다.

Y = 0.299R + 0.587G + 0.114B

  • 반대로 GRAY2BGR 색 공간 변환 코드는 그레이스케일 영상을 BGR 컬러 영상으로 변환할 때 사용한다. 이 경우 결과 영상은 CV_8UC3 타입으로 결정되고, 각 피셀의 BGR 색상 성분 값은 다음과 같이 결정된다.

R = 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) = \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);
[ssba]

The author

지성을 추구하는 사람/ suyeongpark@abyne.com

댓글 남기기

This site uses Akismet to reduce spam. Learn how your comment data is processed.