해당 포스트는 "열혈강의 영상처리 프로그래밍" 책의 내용을 요약한 것이다.



색의 3속성으로 색상, 명도, 채도가 있다. YIQ, YUV, YCbCr은 명도만 따로 분리한 것이다. 색 공간의 축을 색의 3속성으로 구성하는 게 사람이 색을 인지하는 체계에 더 가깝고 '밝은 노랑'이나 '진한 노랑'을 표현하고자 할 때 색의 3속성을 이용하면 더욱 직관적으로 색을 선택할 수 있다. 기본적으로 색의 3속성을 축으로 하여 색 공간을 표현하는 것으로 HSV, HSL, HSI 가 있다.


※ HSV

다음 그림은 HSV 색 공간을 육각뿔 형태로 표현한 것이다. 

육각뿔의 높이 V(Value)는 밝기값, 육각뿔 축으로부터의 거리 S(Saturation)는 색의 진한 정도인 채도, 육각뿔에서 각도 H는 빨주노초파남보와 같은 색상을 표현한다. S 값이 0이면 색채가 없는 회색이 되고 V 값이 0이면 검은색이 된다. S값이 증가할수록 색이 진해져 원색에 가까워지며, V값이 증가할수록 밝은 색상이 된다. RGB 각 채널이 0에서 1 사이의 실수 값을 가지는 색을 HSV 색 공간으로 변환하면 S와 V값의 범위는 0에서 1사이가 된다. 그래서 HSV에서 RGB, RGB에서 HSV로 변환하는 함수를 구현할 때 사용하는 수식이 RGB 채널값이 0에서 1사이를 기준으로 한 것이기 때문에 입력 영상의 각 채널 값을 255로 나누는 작업을 해야 한다. H는 각도가 0이면 빨간색, 60이면 노란색, 120이면 초록색, 180이면 하늘색, 240이면 파란색, 300이면 자홍색에 해당한다. 육각뿔의 각 꼭짓점마다 RGB와 CMY 색 공간의 기본색이 등장한다.



※ HSL

HSL 색 공간은 다음의 그림과 같이 이중의 뿔 구조로 나타낼 수 있다.

H 값과 S 값은 HSV 색 공간과 거의 같지만 밝기를 나타내는 축, L 값이 조금 다르다. L이 0이면 검은색, 1이면 흰색을 나타낸다. 0.5일 경우 RGB와 CMY 색 공간의 기본색이 원색으로 나타난다. 


 

- RGB 색 공간에서 HSV 새 공간으로 변환하는 방법


M = max(R, G, B)

m = min(R, G, B)

D = M - m


H의 결과로 음수가 나올 시 360도를 더해준다.





- RGB 색 공간에서 HSL색 공간으로 변환하는 방법

H 값은 HSV 색 공간으로 변환하는 방법에서 나온 식과 같다.




- HSV 색 공간에서 RGB 색 공간으로 변환하는 식




- RGB를 HSV 색 공간으로 변환하는 함수

template <typename T>

CDoubleImage RGB2HSV(const CMyImage<T>& src) { ASSERT(src.GetChannel() == 3); int nWidth = src.GetWidth(); int nHeight = src.GetHeight(); CDoubleImage dst(nWidth, nHeight, 3); double vR, vG, vB; double vMin, vMax, delta, H; for (int r=0 ; r<nHeight ; r++) { T* pSrc = src.GetPtr(r); double* pDst = dst.GetPtr(r); int pos = 0; for (int c=0 ; c<nWidth ; c++) { vB = pSrc[pos ] / 255.0; vG = pSrc[pos+1] / 255.0; vR = pSrc[pos+2] / 255.0; vMax = MAX(MAX(vR, vG), vB); vMin = MIN(MIN(vR, vG), vB); delta = vMax - vMin; pDst[pos] = vMax; // V if (delta==0) // Gray { pDst[pos+1] = 0; pDst[pos+2] = 0; } else { pDst[pos+1] = delta / vMax; // S if (vR==vMax) H = (vG-vB)/delta; // 노란색과 자홍색 사이 else if (vG==vMax) H = 2 + (vB-vR)/delta; // 하늘색과 노란색 사이 else H = 4 + (vR-vG)/delta; // 자홍색과 하늘색 사이 H *= 60.0; if (H<0) H += 360.0; pDst[pos+2] = H; // H } pos += 3; } } return dst; } #define MIN(a,b) ((a) > (b) ? (b) : (a)) #define MAX(a,b) ((a) < (b) ? (b) : (a))


- HSV를 RGB 색 공간으로 변환하는 함수

template <typename T>
CDoubleImage HSV2RGB(const CMyImage<T>& src)
{
	ASSERT(src.GetChannel() == 3);

	int nWidth  = src.GetWidth();
	int nHeight = src.GetHeight();

	CDoubleImage dst(nWidth, nHeight, 3);

	double vS, vH, vV;
	double f, p, t, n;

	for (int r=0 ; r<nHeight ; r++)
	{
		T*		pSrc = src.GetPtr(r);
		double*	pDst = dst.GetPtr(r);

		int pos = 0;
		for (int c=0 ; c<nWidth ; c++)
		{
			vV = pSrc[pos  ];
			vS = pSrc[pos+1];
			vH = pSrc[pos+2];
			if (vS==0)
			{
				pDst[pos  ] = vV*255;
				pDst[pos+1] = vV*255;
				pDst[pos+2] = vV*255;
			}
			else
			{
				while (vH >= 360)
					vH -= 360;
				while (vH < 0)
					vH += 360;
				vH /= 60.0;
				int k = (int)vH;
				f = vH - k;
				t = vV * (1 - vS);
				n = vV * (1 - vS*f);
				p = vV * (1 - vS*(1-f));

				switch (k) // 6개의 구간에 따라
				{
				case 1:
					pDst[pos+2] = n*255;
					pDst[pos+1] = vV*255;
					pDst[pos  ] = t*255;
					break;
				case 2:
					pDst[pos+2] = t*255;
					pDst[pos+1] = vV*255;
					pDst[pos  ] = p*255;
					break;
				case 3:
					pDst[pos+2] = t*255;
					pDst[pos+1] = n*255;
					pDst[pos  ] = vV*255;
					break;
				case 4:
					pDst[pos+2] = p*255;
					pDst[pos+1] = t*255;
					pDst[pos  ] = vV*255;
					break;
				case 5:
					pDst[pos+2] = vV*255;
					pDst[pos+1] = t*255;
					pDst[pos  ] = n*255;
					break;
				default: // case 0
					pDst[pos+2] = vV*255;
					pDst[pos+1] = p*255;
					pDst[pos  ] = t*255;
					break;
				}
			}
			pos += 3;
		}
	}

	return dst;
}


+ Recent posts