• OpenCV Colour Temperature Calibration via rendition chart

    by  • February 26, 2017 • Programming • 5 Comments

    Different types of light output different colour temperatures which is expressed in kelvin; ranging from the yellow end of the spectrum e.g. 2800k up to blue e.g. 6500k. Natural sunlight is around 5500-6000k. I could spend a long time discussing colour temperature and how its the temperature of an ideal black body radiator which emits light comparable to the light source, but I would just suggest you read the Wikipedia article for more information.

    Most cameras have a built in mechanism for auto white balance, however in certain situations this isn’t always appropriate. This tutorial discusses how to perform a white balance and determine the cameras best settings via the use of a Macbeth Colour Chart. A Macbeth colour chart (or referred to as a Colour Rendition Chart) is a calibrated target with squares of predefined colours. These colours are selected as they have spectral reflections that mimic certain natural objects such as skin, foliage etc.

    For this tutorial I am using a SpyderCheckr 24 which is a reasonably priced and small 24 colour rendition chart. After a bit of searching I found the relevant colours in the L*a*b* colour space for each square as a base for normalisation.

    // LAB Colour values for the SpyderCheckr24 Macbeth Colour Chart
    vector<cv::Scalar> colourChartLAB = vector<cv::Scalar>({
    cv::Scalar(70.19, -31.9, 1.98),
    cv::Scalar(54.38, 8.84, -24.48),
    cv::Scalar(42.03, -15.8, 22.93),
    cv::Scalar(48.82, -5.11, -23.08),
    cv::Scalar(65.1, 18.14, 18.68),
    cv::Scalar(36.13, 14.15, 15.78),
    cv::Scalar(60.94, 38.21, 61.31),
    cv::Scalar(37.8, 7.3, -43.04),
    cv::Scalar(49.81, 48.5, 15.76),
    cv::Scalar(28.88, 19.36, -24.48),
    cv::Scalar(72.45, -23.6, 60.47),
    cv::Scalar(71.65, 23.74, 72.28),
    cv::Scalar(47.12, -32.5, -28.75),
    cv::Scalar(50.49, 53.45, -13.55),
    cv::Scalar(83.61, 3.36, 87.02),
    cv::Scalar(41.05, 60.75, 31.17),
    cv::Scalar(54.14, -40.8, 34.75),
    cv::Scalar(24.75, 13.78, -49.48),
    cv::Scalar(96.04, 2.16, 2.6),
    cv::Scalar(80.44, 1.17, 2.05),
    cv::Scalar(65.52, 0.69, 1.86),
    cv::Scalar(49.62, 0.58, 1.56),
    cv::Scalar(33.55, 0.35, 1.4),
    cv::Scalar(16.91, 1.43, -0.81)

    Most of the calibration steps will be done using the L*a*b* colour space as it enables me to calculate the colour delta between two values using the Euclidean distance as the colour space enables this due to how colours are mapped in a 3d space similar to the perceptual vision of humans.

    Put simply, the calibration mechanism iterates through the colour temperature range, extracts the colours from a camera frame, and compares them to the known optimal values. The colour temperature with the lowest overall delta is then picked.

    The following code is how we calculate the euclidean delta given the deskewed image of the colour checker chart. The code divides the chart into its 6×4 sections and extracts an average colour for each section by taking a sample.

    double WhiteBalanceCalibration::CalculateWhiteBalanceDelta(cv::Mat &deskewedChart)
    	double sampleDelta = 0;
    	const int CCRows = 4;
    	const int CCCols = 6;
    	// Calculations are performed in the LAB colourspace to not affect Luminance
    	cv::cvtColor(deskewedChart, deskewedChart, CV_RGB2Lab);
    	// Colour sample size is a 1/4 of each square.
    	double sampleSize = (deskewedChart.cols / CCCols) / 4;
    	int col = 0; // Track the colour sample being compared.
    	vector<cv::Scalar> diffs;
    	for (int y = 0; y < CCRows; y++)
    		for (int x = 0; x < CCCols; x++)
    			// Get the average colour from a sample section of the colour segment
    			double offX = (deskewedChart.cols - sampleSize - 1) / (CCCols - 1) * x;
    			double offY = (deskewedChart.rows - sampleSize - 1) / (CCRows - 1) * y;
    			offX = max(0.0, offX);
    			offY = max(0.0, offY);
    			cv::Scalar colourMean = cv::mean(deskewedChart.rowRange(offY, min(deskewedChart.rows - 1.0, offY + sampleSize)).colRange(offX, min(deskewedChart.cols - 1.0, offX + sampleSize)));
    			// LAB values in OpenCV are different to CIE D50 standards.
    			colourMean[0] = 100.0 / 255.0 * colourMean[0]; // L
    			colourMean[1] -= 128.0; // A
    			colourMean[2] -= 128.0; // B
    			// Calculate the Euclidean distance as the delta
    			double delta = cv::sqrt(
    					(colourChartLAB[col][0] - colourMean[0]) * (colourChartLAB[col][0] - colourMean[0]) +
    					(colourChartLAB[col][1] - colourMean[1]) * (colourChartLAB[col][1] - colourMean[1]) +
    					(colourChartLAB[col][2] - colourMean[2]) * (colourChartLAB[col][2] - colourMean[2]));
    			sampleDelta += delta;
    	return sampleDelta;

    An example run through of this code across all the colour temperature range generates the following results; with a red 3 step moving average. As you see the delta is lowest just below 4000k which is the correct colour temperature for the scene.

    OpenCV Colour Temperature


    Software engineer. Tea drinker


    5 Responses to OpenCV Colour Temperature Calibration via rendition chart

    1. Peter Britton
      April 19, 2017 at 12:06 pm

      Hi Kevin,

      Shouldn’t the lower colour temperatures be towards the yellow end and the higher towards the blue and not as in the first paragraph?

      • April 19, 2017 at 12:17 pm

        Good spot, now corrected. So used to adjusting the colour temperature in apps like Lightroom to perform the inverse of what is needed.

    2. Ed C.
      October 11, 2017 at 9:36 pm

      What is meant by the phrase “the calibration mechanism iterates through the colour temperature range”? I would like to try out the calibration described, but need some additional information about what occurs at the iteration steps. Is there a reference you can point to for a more detailed description?

    3. Varun
      October 13, 2017 at 6:44 pm

      Hi Kevin,

      Very informative article.
      I am trying to mimic this in python/Open CV.
      Where is the temperature range value accounted for in the above function?


      • Varun
        October 13, 2017 at 6:55 pm

        Are you changing the temperature of the original image and then calculating the delta for each value in the range?

    Leave a Reply

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