OpenCV  4.5.2
Open Source Computer Vision
Color Correction Model

In this tutorial you will learn how to use the 'Color Correction Model' to do a color correction in a image.

Reference

See details of ColorCorrection Algorithm at https://github.com/riskiest/color_calibration/tree/v4/doc/pdf/English/Algorithm

Building

When building OpenCV, run the following command to build all the contrib modules:

cmake -D OPENCV_EXTRA_MODULES_PATH=<opencv_contrib>/modules/

Or only build the mcc module:

cmake -D OPENCV_EXTRA_MODULES_PATH=<opencv_contrib>/modules/mcc

Or make sure you check the mcc module in the GUI version of CMake: cmake-gui.

Source Code of the sample

The sample has two parts of code, the first is the color checker detector model, see details at Detecting colorcheckers using basic algorithms, the second part is to make collor calibration.

Here are the parameters for ColorCorrectionModel
src :
detected colors of ColorChecker patches;
NOTICE: the color type is RGB not BGR, and the color values are in [0, 1];
constcolor :
the Built-in color card;
Supported list:
Macbeth: Macbeth ColorChecker ;
Vinyl: DKK ColorChecker ;
DigitalSG: DigitalSG ColorChecker with 140 squares;
Mat colors :
the reference color values
and corresponding color space
NOTICE: the color values are in [0, 1]
ref_cs :
the corresponding color space
If the color type is some RGB, the format is RGB not BGR;
Supported Color Space:
Supported list of RGB color spaces:
COLOR_SPACE_sRGB;
COLOR_SPACE_AdobeRGB;
COLOR_SPACE_WideGamutRGB;
COLOR_SPACE_ProPhotoRGB;
COLOR_SPACE_DCI_P3_RGB;
COLOR_SPACE_AppleRGB;
COLOR_SPACE_REC_709_RGB;
COLOR_SPACE_REC_2020_RGB;
Supported list of linear RGB color spaces:
COLOR_SPACE_sRGBL;
COLOR_SPACE_AdobeRGBL;
COLOR_SPACE_WideGamutRGBL;
COLOR_SPACE_ProPhotoRGBL;
COLOR_SPACE_DCI_P3_RGBL;
COLOR_SPACE_AppleRGBL;
COLOR_SPACE_REC_709_RGBL;
COLOR_SPACE_REC_2020_RGBL;
Supported list of non-RGB color spaces:
COLOR_SPACE_Lab_D50_2;
COLOR_SPACE_Lab_D65_2;
COLOR_SPACE_XYZ_D50_2;
COLOR_SPACE_XYZ_D65_2;
COLOR_SPACE_XYZ_D65_10;
COLOR_SPACE_XYZ_D50_10;
COLOR_SPACE_XYZ_A_2;
COLOR_SPACE_XYZ_A_10;
COLOR_SPACE_XYZ_D55_2;
COLOR_SPACE_XYZ_D55_10;
COLOR_SPACE_XYZ_D75_2;
COLOR_SPACE_XYZ_D75_10;
COLOR_SPACE_XYZ_E_2;
COLOR_SPACE_XYZ_E_10;
COLOR_SPACE_Lab_D65_10;
COLOR_SPACE_Lab_D50_10;
COLOR_SPACE_Lab_A_2;
COLOR_SPACE_Lab_A_10;
COLOR_SPACE_Lab_D55_2;
COLOR_SPACE_Lab_D55_10;
COLOR_SPACE_Lab_D75_2;
COLOR_SPACE_Lab_D75_10;
COLOR_SPACE_Lab_E_2;
COLOR_SPACE_Lab_E_10;

Code

#include <opencv2/core.hpp>
#include <opencv2/mcc.hpp>
#include <iostream>
using namespace std;
using namespace cv;
using namespace mcc;
using namespace ccm;
using namespace std;
const char *about = "Basic chart detection";
const char *keys =
"{ help h | | show this message }"
"{t | | chartType: 0-Standard, 1-DigitalSG, 2-Vinyl }"
"{v | | Input from video file, if ommited, input comes from camera }"
"{ci | 0 | Camera id if input doesnt come from video (-v) }"
"{f | 1 | Path of the file to process (-v) }"
"{nc | 1 | Maximum number of charts in the image }";
int main(int argc, char *argv[])
{
// ----------------------------------------------------------
// Scroll down a bit (~40 lines) to find actual relevant code
// ----------------------------------------------------------
CommandLineParser parser(argc, argv, keys);
parser.about(about);
if (argc==1 || parser.has("help"))
{
parser.printMessage();
return 0;
}
int t = parser.get<int>("t");
int nc = parser.get<int>("nc");
string filepath = parser.get<string>("f");
CV_Assert(0 <= t && t <= 2);
TYPECHART chartType = TYPECHART(t);
if (!parser.check())
{
parser.printErrors();
return 0;
}
Mat image = imread(filepath, IMREAD_COLOR);
if (!image.data)
{
cout << "Invalid Image!" << endl;
return 1;
}
Mat imageCopy = image.clone();
Ptr<CCheckerDetector> detector = CCheckerDetector::create();
// Marker type to detect
if (!detector->process(image, chartType, nc))
{
printf("ChartColor not detected \n");
return 2;
}
vector<Ptr<mcc::CChecker>> checkers = detector->getListColorChecker();
for (Ptr<mcc::CChecker> checker : checkers)
{
Ptr<CCheckerDraw> cdraw = CCheckerDraw::create(checker);
cdraw->draw(image);
Mat chartsRGB = checker->getChartsRGB();
Mat src = chartsRGB.col(1).clone().reshape(3, chartsRGB.rows/3);
src /= 255.0;
//compte color correction matrix
ColorCorrectionModel model1(src, COLORCHECKER_Vinyl);
model1.run();
Mat ccm = model1.getCCM();
std::cout<<"ccm "<<ccm<<std::endl;
double loss = model1.getLoss();
std::cout<<"loss "<<loss<<std::endl;
/* brief More models with different parameters, try it & check the document for details.
*/
// model1.setColorSpace(COLOR_SPACE_sRGB);
// model1.setCCM_TYPE(CCM_3x3);
// model1.setDistance(DISTANCE_CIE2000);
// model1.setLinear(LINEARIZATION_GAMMA);
// model1.setLinearGamma(2.2);
// model1.setLinearDegree(3);
// model1.setSaturatedThreshold(0, 0.98);
/* If you use a customized ColorChecker, you can use your own reference color values and corresponding color space in a way like:
*/
// cv::Mat ref = (Mat_<Vec3d>(18, 1) <<
// Vec3d(100, 0.00520000001, -0.0104),
// Vec3d(73.0833969, -0.819999993, -2.02099991),
// Vec3d(62.493, 0.425999999, -2.23099995),
// Vec3d(50.4640007, 0.446999997, -2.32399988),
// Vec3d(37.7970009, 0.0359999985, -1.29700005),
// Vec3d(0, 0, 0),
// Vec3d(51.5880013, 73.5179977, 51.5690002),
// Vec3d(93.6989975, -15.7340002, 91.9420013),
// Vec3d(69.4079971, -46.5940018, 50.4869995),
// Vec3d(66.61000060000001, -13.6789999, -43.1720009),
// Vec3d(11.7110004, 16.9799995, -37.1759987),
// Vec3d(51.973999, 81.9440002, -8.40699959),
// Vec3d(40.5489998, 50.4399986, 24.8490009),
// Vec3d(60.8160019, 26.0690002, 49.4420013),
// Vec3d(52.2529984, -19.9500008, -23.9960003),
// Vec3d(51.2859993, 48.4700012, -15.0579996),
// Vec3d(68.70700069999999, 12.2959995, 16.2129993),
// Vec3d(63.6839981, 10.2930002, 16.7639999));
// ColorCorrectionModel model8(src,ref,COLOR_SPACE_Lab_D50_2);
// model8.run();
Mat img_;
cvtColor(image, img_, COLOR_BGR2RGB);
img_.convertTo(img_, CV_64F);
const int inp_size = 255;
const int out_size = 255;
img_ = img_ / inp_size;
Mat calibratedImage= model1.infer(img_);
Mat out_ = calibratedImage * out_size;
// Save the calibrated image to {FILE_NAME}.calibrated.{FILE_EXT}
out_.convertTo(out_, CV_8UC3);
Mat img_out = min(max(out_, 0), out_size);
Mat out_img;
cvtColor(img_out, out_img, COLOR_RGB2BGR);
string filename = filepath.substr(filepath.find_last_of('/')+1);
size_t dotIndex = filename.find_last_of('.');
string baseName = filename.substr(0, dotIndex);
string ext = filename.substr(dotIndex+1, filename.length()-dotIndex);
string calibratedFilePath = baseName + ".calibrated." + ext;
imwrite(calibratedFilePath, out_img);
}
return 0;
}

Explanation

The first part is to detect the ColorChecker position.

vector<Ptr<mcc::CChecker>> checkers = detector->getListColorChecker();
CommandLineParser parser(argc, argv, keys);
parser.about(about);
if (argc==1 || parser.has("help"))
{
parser.printMessage();
return 0;
}
int t = parser.get<int>("t");
int nc = parser.get<int>("nc");
string filepath = parser.get<string>("f");
CV_Assert(0 <= t && t <= 2);
TYPECHART chartType = TYPECHART(t);
if (!parser.check())
{
parser.printErrors();
return 0;
}
Mat image = imread(filepath, IMREAD_COLOR);
if (!image.data)
{
cout << "Invalid Image!" << endl;
return 1;
}

Preparation for ColorChecker detection to get messages for the image.

Ptr<CCheckerDraw> cdraw = CCheckerDraw::create(checker);
cdraw->draw(image);
Mat chartsRGB = checker->getChartsRGB();
Mat src = chartsRGB.col(1).clone().reshape(3, chartsRGB.rows/3);
src /= 255.0;

The CCheckerDetectorobject is created and uses getListColorChecker function to get ColorChecker message.

ColorCorrectionModel model1(src, COLORCHECKER_Vinyl);
model1.run();
Mat ccm = model1.getCCM();
std::cout<<"ccm "<<ccm<<std::endl;
double loss = model1.getLoss();
std::cout<<"loss "<<loss<<std::endl;

For every ColorChecker, we can compute a ccm matrix for color correction. Model1 is an object of ColorCorrectionModel class. The parameters should be changed to get the best effect of color correction. See other parameters' detail at the Parameters.

// cv::Mat ref = (Mat_<Vec3d>(18, 1) <<
// Vec3d(100, 0.00520000001, -0.0104),
// Vec3d(73.0833969, -0.819999993, -2.02099991),
// Vec3d(62.493, 0.425999999, -2.23099995),
// Vec3d(50.4640007, 0.446999997, -2.32399988),
// Vec3d(37.7970009, 0.0359999985, -1.29700005),
// Vec3d(0, 0, 0),
// Vec3d(51.5880013, 73.5179977, 51.5690002),
// Vec3d(93.6989975, -15.7340002, 91.9420013),
// Vec3d(69.4079971, -46.5940018, 50.4869995),
// Vec3d(66.61000060000001, -13.6789999, -43.1720009),
// Vec3d(11.7110004, 16.9799995, -37.1759987),
// Vec3d(51.973999, 81.9440002, -8.40699959),
// Vec3d(40.5489998, 50.4399986, 24.8490009),
// Vec3d(60.8160019, 26.0690002, 49.4420013),
// Vec3d(52.2529984, -19.9500008, -23.9960003),
// Vec3d(51.2859993, 48.4700012, -15.0579996),
// Vec3d(68.70700069999999, 12.2959995, 16.2129993),
// Vec3d(63.6839981, 10.2930002, 16.7639999));
// ColorCorrectionModel model8(src,ref,COLOR_SPACE_Lab_D50_2);
// model8.run();

If you use a customized ColorChecker, you can use your own reference color values and corresponding color space as shown above.

Mat img_;
cvtColor(image, img_, COLOR_BGR2RGB);
img_.convertTo(img_, CV_64F);
const int inp_size = 255;
const int out_size = 255;
img_ = img_ / inp_size;
Mat calibratedImage= model1.infer(img_);
Mat out_ = calibratedImage * out_size;

The member function infer_image is to make correction correction using ccm matrix.

// Save the calibrated image to {FILE_NAME}.calibrated.{FILE_EXT}
out_.convertTo(out_, CV_8UC3);
Mat img_out = min(max(out_, 0), out_size);
Mat out_img;
cvtColor(img_out, out_img, COLOR_RGB2BGR);
string filename = filepath.substr(filepath.find_last_of('/')+1);
size_t dotIndex = filename.find_last_of('.');
string baseName = filename.substr(0, dotIndex);
string ext = filename.substr(dotIndex+1, filename.length()-dotIndex);
string calibratedFilePath = baseName + ".calibrated." + ext;
imwrite(calibratedFilePath, out_img);

Save the calibrated image.

cv::Mat::reshape
Mat reshape(int cn, int rows=0) const
Changes the shape and/or the number of channels of a 2D matrix without copying the data.
cv::Mat::rows
int rows
the number of rows and columns or (-1, -1) when the matrix has more than 2 dimensions
Definition: mat.hpp:2096
cv::Mat::clone
Mat clone() const CV_NODISCARD
Creates a full copy of the array and the underlying data.
cv::cvtColor
void cvtColor(InputArray src, OutputArray dst, int code, int dstCn=0)
Converts an image from one color space to another.
cv::MatExpr::max
MatExpr max(const Mat &a, const Mat &b)
cv::COLOR_BGR2RGB
@ COLOR_BGR2RGB
Definition: imgproc.hpp:545
cv::MatExpr::min
MatExpr min(const Mat &a, const Mat &b)
highgui.hpp
cv::min
softfloat min(const softfloat &a, const softfloat &b)
Min and Max functions.
Definition: softfloat.hpp:437
cv::max
softfloat max(const softfloat &a, const softfloat &b)
Definition: softfloat.hpp:440
core.hpp
cv::mcc::TYPECHART
TYPECHART
enum to hold the type of the checker
Definition: checker_model.hpp:46
mcc.hpp
cv::Mat::convertTo
void convertTo(OutputArray m, int rtype, double alpha=1, double beta=0) const
Converts an array to another data type with optional scaling.
cv::imread
Mat imread(const String &filename, int flags=IMREAD_COLOR)
Loads an image from a file.
CV_8UC3
#define CV_8UC3
Definition: interface.h:90
imgcodecs.hpp
cv::Mat::col
Mat col(int x) const
Creates a matrix header for the specified matrix column.
cv::Ptr
std::shared_ptr< _Tp > Ptr
Definition: cvstd_wrapper.hpp:23
CV_64F
#define CV_64F
Definition: interface.h:79
CV_Assert
#define CV_Assert(expr)
Checks a condition at runtime and throws exception if it fails.
Definition: base.hpp:342
cv::Mat
n-dimensional dense array class
Definition: mat.hpp:801
cv::CommandLineParser
Designed for command line parsing.
Definition: utility.hpp:799
cv::IMREAD_COLOR
@ IMREAD_COLOR
If set, always convert image to the 3 channel BGR color image.
Definition: imgcodecs.hpp:72
cv::imwrite
bool imwrite(const String &filename, InputArray img, const std::vector< int > &params=std::vector< int >())
Saves an image to a specified file.
cv
"black box" representation of the file storage associated with a file on disk.
Definition: affine.hpp:51
cv::COLOR_RGB2BGR
@ COLOR_RGB2BGR
Definition: imgproc.hpp:546
cv::ccm::COLORCHECKER_Vinyl
@ COLORCHECKER_Vinyl
DKK ColorChecker.
Definition: ccm.hpp:90
cv::Mat::data
uchar * data
pointer to the data
Definition: mat.hpp:2098