• OpenCV Colour Temperature Calibration via rendition chart

    by  • February 26, 2017 • Programming • 0 Comments

    Different types of light output different colour temperatures which is expressed in kelvin; ranging from the blue end of the spectrum e.g. 2800k up to very yellow e.g. 6500k. Natural sunlight is 5500k. 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;
    			col++;
    		}
    	}
    
    	return sampleDelta;
    }
    

    An example run through of this code across all the colour temperature range generates the following results. As you see the delta is lowest just below 4000k which is the correct colour temperature for the scene.

    OpenCV Colour Temperature

    About

    Software engineer. Tea drinker

    http://MrPfister.com

    Leave a Reply

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