• OpenCV Colour space and LAB WhitePoint

    by  • June 20, 2016 • Programming • 0 Comments

    Recently I have had the fun task of trying to normalise colours between two sets of images in OpenCV. Being a photographer I thought the easiest way of doing this would be to use a Macbeth Colour Chart such as the datacolor spyderCheckr24 which I have for each of the sets and convert to the known colours.

    It was easy enough in OpenCV to extract the colour chart from an image, rotate and deskew it, then read in an average for each of the 24 colour segments. However this is where things started going downhill.

    Due to the limitations of the RGB colorspace, and how luminance affects the colour, a different colourspace is used. Most colour checkers provide the known colours in the CIE L*A*B colourspace so this is what I used.

    First I found that OpenCV using the cvtColor(…) function maps LAB Mats into a slightly different range than the standard LAB range so a conversion had to be made.

    To convert OpenCV to the standard CIE LAB:

    L = 100 / 255 * L
    A = A – 128
    B = B – 128

    Perform the opposite conversion to push CIE LAB colours back to the range OpenCV uses.

    The next issue I found was more tricky. The reference White Point illumanti value OpenCV uses is different to what is used in Photography – D65 vs D50 and so the reference colour chart LAB values did not match correctly with OpenCVs LAB values. OpenCV only supports D65, so to modify it to D50 meant having to write a new conversion function.

    The XYZ colourspace has to be used as an intermediate format to perform the D50 whitespace normalisation.

    cv::Mat BGR2LAB_D50(cv::Mat &input)
    	float f[3];
    	float ls, as, bs;
    	// Use the Intent of the CIE Standard rather than the Actual
    	float eps = 216.0 / 24389.0;
    	float k = 24389.0 / 27.0;
    	cv::Mat labD50 = cv::Mat::zeros(input.rows, input.cols, input.type());
    	// CIE D50 Illumanti reference White Point
    	float Xr = 96.4221;
    	float Yr = 100.0;
    	float Zr = 82.5211;
    	// Convert BGR to XYZ CIE D65 Illumanti reference White Point
    	cv::Mat xyzD65;
    	cv::cvtColor(input, xyzD65, CV_BGR2XYZ);
    	printf(" - %d - ", xyzD65.type());
    	// Convert from XYZ D65 to LAB D50
     	for (int y = 0; y < xyzD65.rows; y++)
    		for (int x = 0; x < xyzD65.cols; x++)
    			cv::Vec3f xyzColour = xyzD65.at<cv::Vec3b>(y, x);
    			// Convert from XYZ D65 to D50
    			xyzColour = cv::Vec3f(xyzColour[0] / Xr, xyzColour[1] / Yr, xyzColour[2] / Zr);
    			// Convert XYZ colourspace to LAB
    			for (int i = 0; i < 3; i++) { if (xyzColour[i] > eps)
    					f[i] = (float)pow(xyzColour[i], 1.0 / 3.0);
    					f[i] = (float)((k * xyzColour[i] + 16.0) / 116.0);
    			ls = (116.0 * f[1]) - 16.0;
    			as = 500.0 * (f[0] - f[1]);
    			bs = 200.0 * (f[1] - f[2]);
    			// Final CIE D50 to OpenCV LAB colourspace
    			labD50.at<cv::Vec3b>(y, x) = cv::Vec3f(2.55 * ls, as + 128.0, bs + 128.0);
    	return labD50;


    Software engineer. Tea drinker


    Leave a Reply

    Your email address will not be published. Required fields are marked *