Color formats:

Last updated on 4th October, 2010 by Shervin Emami. Posted originally on 31st May, 2010.

The RGB color format can represent any standard color or brightness using a combination of Red, Green and Blue components. For efficiency, this is typically stored as a 24-bit number using 8-bits for each color component (0 to 255) so that for example, White is made of 255 Red + 255 Green + 255 Blue. This is pretty much the same technique that nearly all computer screens have used for decades, and so it is the standard color format used in computer software. Unfortunately when it comes to computer vision, RGB values will vary a lot depending on strong or dim lighting conditions and shadows, etc. In comparison, HSV is much better at handling lighting differences, and it gives you an easy to use color value.

HSV Color Cylinder, showing that white has a Sat of 0 and Value of 255 HSV means Hue-Saturation-Value, where the Hue is the color. And since color is not an easy thing to separate or compare, Hue is often represented as a circular angle (between 0.0 to 1.0 when stored as floats). Being a circular value means that 1.0 is the same as 0.0. For example, a Hue of 0.0 is red, a Hue of 0.25 would be green, a Hue of 0.5 is blue, a Hue of 0.75 is pink, and a Hue of 1.0 would be the same as a Hue of 0.0 which is red (again). Saturation is the greyness, so that a Saturation value near 0 means it is dull or grey looking whereas as a Saturation value of 0.8 might be a very strong color (eg: red if Hue is 0). And Value is the brightness of the pixel, so 0.1 is black and 0.9 is white. Unfortunately, there are different ways to represent HSV colors, such as whether a full brightness V of 1.0 should be bright white or a bright color. Most software chooses full brightness V to mean White, whereas OpenCV chooses full brightness V to mean a bright color!

(For a lot more info about HSV and other color spaces, go to HSL and HSV on Wikipedia).

 

HSV Color format in OpenCV:

OpenCV is a great library for writing Computer Vision software using state-of-the-art techniques, and it is available for many Operating Systems, including Windows, Linux, Mac and even the iPhone. It's main color format is RGB (actually it is BGRX, which is the bytes of RGB reversed and with a 4th byte of padding for efficiency, but generally referred to as RGB for simplicity). It also includes various functions to work with different color formats, such as to convert from RGB to HSV or YUV or LAB or XYZ, etc. However, OpenCV's HSV format is different than what you would expect!

OpenCV's RGB-to-HSV and HSV-to-RGB conversion only stores the Hue component as an 8-bit integer in the range 0 to 179, when it could have easily used 0 to 255. This means that if you use OpenCV's cvCvtColor() function to obtain a HSV image, you will loose some of the color resolution, since it is basically storing the Hue as a 7-bit number instead of an 8-bit number. Also, if you process this HSV image based on color values you found from other software or libraries, it is likely to cause you problems, since most systems use a range of 0 to 255. For example, if you are writing a skin detector and want to find some good HSV thresholds, if you use any image editor (such as MS Paint or Adobe Photoshop) to read the HSV values, they will be different than the values you need for OpenCV.

Also, RGB colors can be converted to the HSV color space in several different ways, resulting in quite different values for the same color. So in OpenCV a Saturation of 255 is always a bright color, whereas it is pure white in most software. This makes it even more difficult to use other software with OpenCV, since the HSV format used in OpenCV is different to the format used in most image editing software.

Therefore, I have written an application called "ColorWheelHSV" to help anyone use the HSV color format in OpenCV. It will let you see the HSV color space of OpenCV.

And further down in this page I have also provided custom conversion functions that store Hue in the full range 0 to 255 instead of just 0 to 179. I have also written some functions to convert between RGB and YIQ color space, which is used in Color TV such as NTSC TV.

 

Viewing OpenCV's HSV color space:

Here is an interactive program I created to see the HSV (Hue-Saturation-Value) color format that OpenCV uses in its "cvCvtColor()" function, since it is different than the color space you would see in commercial image editing software.

When running the program, you can click anywhere in the 2D color map, the horizontal Hue map, or use the 3 sliders, to select a color, and you will see OpenCV's RGB and HSV values written to the console window.



As you can see in the screenshot above, the range of Hue values in OpenCV's method is 0 to 179, instead of 0 to 255. To get the full range of 0 to 255, you can use my conversion functions, further down on this page.

The source-code to my program is available here (opensource freeware), to use as you wish for non-military projects only.

Click here to download "ColorWheelHSV": colorWheelHSV.7z

(12kB 7-Zip file, including C/C++ source code, VS2008 project files and the compiled Win32 program, created 27th Apr 2010).

If you dont have the freeware 7-Zip program (better than WinZip and WinRar in my opinion), you can download it HERE.

And if you dont have the OpenCV 2.0 SDK then you can just get the Win32 DLLs for running this program here: openCV200_Win32DLLs.7z (1.3MB 7-Zip file).

 

Better HSV to RGB color conversion for OpenCV:

Here is some code to do a better color conversion between HSV and RGB in OpenCV, since it uses the full 0 - 255 range of Hues that can be stored in a byte. These functions will create their own image in memory, so make sure you remember to free the returned image when you are finished, and that you don't create the image already before calling these conversion functions, since this would case a memory leak.

How to use my conversion functions:

For example, this is the correct way to use my conversion functions:
IplImage *imHSV;
imHSV = convertRGBtoHSV(imRGB);	// Allocates a new HSV image.
... do stuff with imHSV ...
cvReleaseImage(&imHSV);		// Frees the new HSV image.
cvReleaseImage(&imRGB);		// Frees the original RGB image.

And this example below is the WRONG way, since it will produce a memory leak:
IplImage *imHSV;
imHSV = cvCreateImage(...);	// Allocates a new blank image (WRONG!).
imHSV = convertRGBtoHSV(imRGB);	// Also allocates a new HSV image.
... do stuff with imHSV ...
cvReleaseImage(&imHSV);		// Only frees one of the new images, not both!
cvReleaseImage(&imRGB);		// Frees the original RGB image.

Color Conversion Code:

// Create a HSV image from the RGB image using the full 8-bits, since OpenCV only allows Hues up to 180 instead of 255.
// ref: "http://cs.haifa.ac.il/hagit/courses/ist/Lectures/Demos/ColorApplet2/t_convert.html"
// Remember to free the generated HSV image.
IplImage* convertImageRGBtoHSV(const IplImage *imageRGB)
{
	float fR, fG, fB;
	float fH, fS, fV;
	const float FLOAT_TO_BYTE = 255.0f;
	const float BYTE_TO_FLOAT = 1.0f / FLOAT_TO_BYTE;

	// Create a blank HSV image
	IplImage *imageHSV = cvCreateImage(cvGetSize(imageRGB), 8, 3);
	if (!imageHSV || imageRGB->depth != 8 || imageRGB->nChannels != 3) {
		printf("ERROR in convertImageRGBtoHSV()! Bad input image.\n");
		exit(1);
	}

	int h = imageRGB->height;		// Pixel height.
	int w = imageRGB->width;		// Pixel width.
	int rowSizeRGB = imageRGB->widthStep;	// Size of row in bytes, including extra padding.
	char *imRGB = imageRGB->imageData;	// Pointer to the start of the image pixels.
	int rowSizeHSV = imageHSV->widthStep;	// Size of row in bytes, including extra padding.
	char *imHSV = imageHSV->imageData;	// Pointer to the start of the image pixels.
	for (int y=0; y<h; y++) {
		for (int x=0; x<w; x++) {
			// Get the RGB pixel components. NOTE that OpenCV stores RGB pixels in B,G,R order.
			uchar *pRGB = (uchar*)(imRGB + y*rowSizeRGB + x*3);
			int bB = *(uchar*)(pRGB+0);	// Blue component
			int bG = *(uchar*)(pRGB+1);	// Green component
			int bR = *(uchar*)(pRGB+2);	// Red component

			// Convert from 8-bit integers to floats.
			fR = bR * BYTE_TO_FLOAT;
			fG = bG * BYTE_TO_FLOAT;
			fB = bB * BYTE_TO_FLOAT;

			// Convert from RGB to HSV, using float ranges 0.0 to 1.0.
			float fDelta;
			float fMin, fMax;
			int iMax;
			// Get the min and max, but use integer comparisons for slight speedup.
			if (bB < bG) {
				if (bB < bR) {
					fMin = fB;
					if (bR > bG) {
						iMax = bR;
						fMax = fR;
					}
					else {
						iMax = bG;
						fMax = fG;
					}
				}
				else {
					fMin = fR;
					fMax = fG;
					iMax = bG;
				}
			}
			else {
				if (bG < bR) {
					fMin = fG;
					if (bB > bR) {
						fMax = fB;
						iMax = bB;
					}
					else {
						fMax = fR;
						iMax = bR;
					}
				}
				else {
					fMin = fR;
					fMax = fB;
					iMax = bB;
				}
			}
			fDelta = fMax - fMin;
			fV = fMax;				// Value (Brightness).
			if (iMax != 0) {			// Make sure it's not pure black.
				fS = fDelta / fMax;		// Saturation.
				float ANGLE_TO_UNIT = 1.0f / (6.0f * fDelta);	// Make the Hues between 0.0 to 1.0 instead of 6.0
				if (iMax == bR) {		// between yellow and magenta.
					fH = (fG - fB) * ANGLE_TO_UNIT;
				}
				else if (iMax == bG) {		// between cyan and yellow.
					fH = (2.0f/6.0f) + ( fB - fR ) * ANGLE_TO_UNIT;
				}
				else {				// between magenta and cyan.
					fH = (4.0f/6.0f) + ( fR - fG ) * ANGLE_TO_UNIT;
				}
				// Wrap outlier Hues around the circle.
				if (fH < 0.0f)
					fH += 1.0f;
				if (fH >= 1.0f)
					fH -= 1.0f;
			}
			else {
				// color is pure Black.
				fS = 0;
				fH = 0;	// undefined hue
			}

			// Convert from floats to 8-bit integers.
			int bH = (int)(0.5f + fH * 255.0f);
			int bS = (int)(0.5f + fS * 255.0f);
			int bV = (int)(0.5f + fV * 255.0f);

			// Clip the values to make sure it fits within the 8bits.
			if (bH > 255)
				bH = 255;
			if (bH < 0)
				bH = 0;
			if (bS > 255)
				bS = 255;
			if (bS < 0)
				bS = 0;
			if (bV > 255)
				bV = 255;
			if (bV < 0)
				bV = 0;

			// Set the HSV pixel components.
			uchar *pHSV = (uchar*)(imHSV + y*rowSizeHSV + x*3);
			*(pHSV+0) = bH;		// H component
			*(pHSV+1) = bS;		// S component
			*(pHSV+2) = bV;		// V component
		}
	}
	return imageHSV;
}


// Create an RGB image from the HSV image using the full 8-bits, since OpenCV only allows Hues up to 180 instead of 255.
// ref: "http://cs.haifa.ac.il/hagit/courses/ist/Lectures/Demos/ColorApplet2/t_convert.html"
// Remember to free the generated RGB image.
IplImage* convertImageHSVtoRGB(const IplImage *imageHSV)
{
	float fH, fS, fV;
	float fR, fG, fB;
	const float FLOAT_TO_BYTE = 255.0f;
	const float BYTE_TO_FLOAT = 1.0f / FLOAT_TO_BYTE;

	// Create a blank RGB image
	IplImage *imageRGB = cvCreateImage(cvGetSize(imageHSV), 8, 3);
	if (!imageRGB || imageHSV->depth != 8 || imageHSV->nChannels != 3) {
		printf("ERROR in convertImageHSVtoRGB()! Bad input image.\n");
		exit(1);
	}

	int h = imageHSV->height;			// Pixel height.
	int w = imageHSV->width;			// Pixel width.
	int rowSizeHSV = imageHSV->widthStep;		// Size of row in bytes, including extra padding.
	char *imHSV = imageHSV->imageData;		// Pointer to the start of the image pixels.
	int rowSizeRGB = imageRGB->widthStep;		// Size of row in bytes, including extra padding.
	char *imRGB = imageRGB->imageData;		// Pointer to the start of the image pixels.
	for (int y=0; y<h; y++) {
		for (int x=0; x<w; x++) {
			// Get the HSV pixel components
			uchar *pHSV = (uchar*)(imHSV + y*rowSizeHSV + x*3);
			int bH = *(uchar*)(pHSV+0);	// H component
			int bS = *(uchar*)(pHSV+1);	// S component
			int bV = *(uchar*)(pHSV+2);	// V component

			// Convert from 8-bit integers to floats
			fH = (float)bH * BYTE_TO_FLOAT;
			fS = (float)bS * BYTE_TO_FLOAT;
			fV = (float)bV * BYTE_TO_FLOAT;

			// Convert from HSV to RGB, using float ranges 0.0 to 1.0
			int iI;
			float fI, fF, p, q, t;

			if( bS == 0 ) {
				// achromatic (grey)
				fR = fG = fB = fV;
			}
			else {
				// If Hue == 1.0, then wrap it around the circle to 0.0
				if (fH >= 1.0f)
					fH = 0.0f;

				fH *= 6.0;			// sector 0 to 5
				fI = floor( fH );		// integer part of h (0,1,2,3,4,5 or 6)
				iI = (int) fH;			//		"		"		"		"
				fF = fH - fI;			// factorial part of h (0 to 1)

				p = fV * ( 1.0f - fS );
				q = fV * ( 1.0f - fS * fF );
				t = fV * ( 1.0f - fS * ( 1.0f - fF ) );

				switch( iI ) {
					case 0:
						fR = fV;
						fG = t;
						fB = p;
						break;
					case 1:
						fR = q;
						fG = fV;
						fB = p;
						break;
					case 2:
						fR = p;
						fG = fV;
						fB = t;
						break;
					case 3:
						fR = p;
						fG = q;
						fB = fV;
						break;
					case 4:
						fR = t;
						fG = p;
						fB = fV;
						break;
					default:		// case 5 (or 6):
						fR = fV;
						fG = p;
						fB = q;
						break;
				}
			}

			// Convert from floats to 8-bit integers
			int bR = (int)(fR * FLOAT_TO_BYTE);
			int bG = (int)(fG * FLOAT_TO_BYTE);
			int bB = (int)(fB * FLOAT_TO_BYTE);

			// Clip the values to make sure it fits within the 8bits.
			if (bR > 255)
				bR = 255;
			if (bR < 0)
				bR = 0;
			if (bG > 255)
				bG = 255;
			if (bG < 0)
				bG = 0;
			if (bB > 255)
				bB = 255;
			if (bB < 0)
				bB = 0;

			// Set the RGB pixel components. NOTE that OpenCV stores RGB pixels in B,G,R order.
			uchar *pRGB = (uchar*)(imRGB + y*rowSizeRGB + x*3);
			*(pRGB+0) = bB;		// B component
			*(pRGB+1) = bG;		// G component
			*(pRGB+2) = bR;		// R component
		}
	}
	return imageRGB;
}

 

YIQ to RGB color conversion for OpenCV:

You can also use these functions to convert from RGB to YIQ, or YIQ to RGB. YIQ is a common format used in discrete electronic components such as in television broadcasting. Since the conversion is done by a linear matrix multiplication, it can appear smoother than HSV (which has a non-linear conversion). More info is on the "YIQ" Wikipedia page.

// Create a YIQ image from the RGB image using an approximation of NTSC conversion(ref: "YIQ" Wikipedia page).
// Remember to free the generated YIQ image.
IplImage* convertImageRGBtoYIQ(const IplImage *imageRGB)
{
	float fR, fG, fB;
	float fY, fI, fQ;
	const float FLOAT_TO_BYTE = 255.0f;
	const float BYTE_TO_FLOAT = 1.0f / FLOAT_TO_BYTE;
	const float MIN_I = -0.5957f;
	const float MIN_Q = -0.5226f;
	const float Y_TO_BYTE = 255.0f;
	const float I_TO_BYTE = 255.0f / (MIN_I * -2.0f);
	const float Q_TO_BYTE = 255.0f / (MIN_Q * -2.0f);

	// Create a blank YIQ image
	IplImage *imageYIQ = cvCreateImage(cvGetSize(imageRGB), 8, 3);
	if (!imageYIQ || imageRGB->depth != 8 || imageRGB->nChannels != 3) {
		printf("ERROR in convertImageRGBtoYIQ()! Bad input image.\n");
		exit(1);
	}

	int h = imageRGB->height;			// Pixel height
	int w = imageRGB->width;			// Pixel width
	int rowSizeRGB = imageRGB->widthStep;		// Size of row in bytes, including extra padding.
	char *imRGB = imageRGB->imageData;		// Pointer to the start of the image pixels.
	int rowSizeYIQ = imageYIQ->widthStep;		// Size of row in bytes, including extra padding.
	char *imYIQ = imageYIQ->imageData;		// Pointer to the start of the image pixels.
	for (int y=0; y<h; y++) {
		for (int x=0; x<w; x++) {
			// Get the RGB pixel components. NOTE that OpenCV stores RGB pixels in B,G,R order.
			uchar *pRGB = (uchar*)(imRGB + y*rowSizeRGB + x*3);
			int bB = *(uchar*)(pRGB+0);	// Blue component
			int bG = *(uchar*)(pRGB+1);	// Green component
			int bR = *(uchar*)(pRGB+2);	// Red component

			// Convert from 8-bit integers to floats
			fR = bR * BYTE_TO_FLOAT;
			fG = bG * BYTE_TO_FLOAT;
			fB = bB * BYTE_TO_FLOAT;
			// Convert from RGB to YIQ,
			// where R,G,B are 0-1, Y is 0-1, I is -0.5957 to +0.5957, Q is -0.5226 to +0.5226.
			fY =    0.299 * fR +    0.587 * fG +    0.114 * fB;
			fI = 0.595716 * fR - 0.274453 * fG - 0.321263 * fB;
			fQ = 0.211456 * fR - 0.522591 * fG + 0.311135 * fB;
			// Convert from floats to 8-bit integers
			int bY = (int)(0.5f + fY * Y_TO_BYTE);
			int bI = (int)(0.5f + (fI - MIN_I) * I_TO_BYTE);
			int bQ = (int)(0.5f + (fQ - MIN_Q) * Q_TO_BYTE);

			// Clip the values to make sure it fits within the 8bits.
			if (bY > 255)
				bY = 255;
			if (bY < 0)
				bY = 0;
			if (bI > 255)
				bI = 255;
			if (bI < 0)
				bI = 0;
			if (bQ > 255)
				bQ = 255;
			if (bQ < 0)
				bQ = 0;

			// Set the YIQ pixel components
			uchar *pYIQ = (uchar*)(imYIQ + y*rowSizeYIQ + x*3);
			*(pYIQ+0) = bY;		// Y component
			*(pYIQ+1) = bI;		// I component
			*(pYIQ+2) = bQ;		// Q component
		}
	}
	return imageYIQ;
}

// Create an RGB image from the YIQ image using an approximation of NTSC conversion(ref: "YIQ" Wikipedia page).
// Remember to free the generated RGB image.
IplImage* convertImageYIQtoRGB(const IplImage *imageYIQ)
{
	float fY, fI, fQ;
	float fR, fG, fB;
	const float FLOAT_TO_BYTE = 255.0f;
	const float BYTE_TO_FLOAT = 1.0f / FLOAT_TO_BYTE;
	const float MIN_I = -0.5957f;
	const float MIN_Q = -0.5226f;
	const float Y_TO_FLOAT = 1.0f / 255.0f;
	const float I_TO_FLOAT = -2.0f * MIN_I / 255.0f;
	const float Q_TO_FLOAT = -2.0f * MIN_Q / 255.0f;

	// Create a blank RGB image
	IplImage *imageRGB = cvCreateImage(cvGetSize(imageYIQ), 8, 3);
	if (!imageRGB || imageYIQ->depth != 8 || imageYIQ->nChannels != 3) {
		printf("ERROR in convertImageYIQtoRGB()! Bad input image.\n");
		exit(1);
	}

	int h = imageYIQ->height;			// Pixel height
	int w = imageYIQ->width;			// Pixel width
	int rowSizeYIQ = imageYIQ->widthStep;		// Size of row in bytes, including extra padding.
	char *imYIQ = imageYIQ->imageData;		// Pointer to the start of the image pixels.
	int rowSizeRGB = imageRGB->widthStep;		// Size of row in bytes, including extra padding.
	char *imRGB = imageRGB->imageData;		// Pointer to the start of the image pixels.
	for (int y=0; y<h; y++) {
		for (int x=0; x<w; x++) {
			// Get the YIQ pixel components
			uchar *pYIQ = (uchar*)(imYIQ + y*rowSizeYIQ + x*3);
			int bY = *(uchar*)(pYIQ+0);	// Y component
			int bI = *(uchar*)(pYIQ+1);	// I component
			int bQ = *(uchar*)(pYIQ+2);	// Q component

			// Convert from 8-bit integers to floats
			fY = (float)bY * Y_TO_FLOAT;
			fI = (float)bI * I_TO_FLOAT + MIN_I;
			fQ = (float)bQ * Q_TO_FLOAT + MIN_Q;
			// Convert from YIQ to RGB
			// where R,G,B are 0-1, Y is 0-1, I is -0.5957 to +0.5957, Q is -0.5226 to +0.5226.
			fR =  fY  + 0.9563 * fI + 0.6210 * fQ;
			fG =  fY  - 0.2721 * fI - 0.6474 * fQ;
			fB =  fY  - 1.1070 * fI + 1.7046 * fQ;
			// Convert from floats to 8-bit integers
			int bR = (int)(fR * FLOAT_TO_BYTE);
			int bG = (int)(fG * FLOAT_TO_BYTE);
			int bB = (int)(fB * FLOAT_TO_BYTE);

			// Clip the values to make sure it fits within the 8bits.
			if (bR > 255)
				bR = 255;
			if (bR < 0)
				bR = 0;
			if (bG > 255)
				bG = 255;
			if (bG < 0)
				bG = 0;
			if (bB > 255)
				bB = 255;
			if (bB < 0)
				bB = 0;

			// Set the RGB pixel components. NOTE that OpenCV stores RGB pixels in B,G,R order.
			uchar *pRGB = (uchar*)(imRGB + y*rowSizeRGB + x*3);
			*(pRGB+0) = bB;		// B component
			*(pRGB+1) = bG;		// G component
			*(pRGB+2) = bR;		// R component
		}
	}
	return imageRGB;
}