From 1e9eed38a9d92e7731a526d0da465a87841a0f7c Mon Sep 17 00:00:00 2001 From: Chenqi Shan Date: Thu, 10 Sep 2020 21:18:23 +0800 Subject: [PATCH 1/8] Add basic io, utils, operations helpers. Implement color distance. --- modules/mcc/include/opencv2/mcc/distance.hpp | 298 ++++++++++++++++++ modules/mcc/include/opencv2/mcc/io.hpp | 109 +++++++ .../mcc/include/opencv2/mcc/operations.hpp | 146 +++++++++ modules/mcc/include/opencv2/mcc/utils.hpp | 235 ++++++++++++++ 4 files changed, 788 insertions(+) create mode 100644 modules/mcc/include/opencv2/mcc/distance.hpp create mode 100644 modules/mcc/include/opencv2/mcc/io.hpp create mode 100644 modules/mcc/include/opencv2/mcc/operations.hpp create mode 100644 modules/mcc/include/opencv2/mcc/utils.hpp diff --git a/modules/mcc/include/opencv2/mcc/distance.hpp b/modules/mcc/include/opencv2/mcc/distance.hpp new file mode 100644 index 00000000000..d0c9ac16307 --- /dev/null +++ b/modules/mcc/include/opencv2/mcc/distance.hpp @@ -0,0 +1,298 @@ +// This file is part of OpenCV project. +// It is subject to the license terms in the LICENSE file found in the top-level directory +// of this distribution and at http://opencv.org/license.html. +// +// +// License Agreement +// For Open Source Computer Vision Library +// +// Copyright(C) 2020, Huawei Technologies Co.,Ltd. All rights reserved. +// Third party copyrights are property of their respective owners. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// Author: Longbu Wang +// Jinheng Zhang +// Chenqi Shan + +#ifndef __OPENCV_MCC_DISTANCE_HPP__ +#define __OPENCV_MCC_DISTANCE_HPP__ + +#include "opencv2/mcc/utils.hpp" + +namespace cv +{ +namespace ccm +{ + +/* *\brief Enum of possibale functions to calculate the distance between + * colors.see https://en.wikipedia.org/wiki/Color_difference for details;*/ +enum DISTANCE_TYPE +{ + CIE76, + CIE94_GRAPHIC_ARTS, + CIE94_TEXTILES, + CIE2000, + CMC_1TO1, + CMC_2TO1, + RGB, + RGBL +}; + +double deltaCIE76(cv::Vec3d lab1, cv::Vec3d lab2); +double deltaCIE94(cv::Vec3d lab1, cv::Vec3d lab2, double kH = 1.0, + double kC = 1.0, double kL = 1.0, double k1 = 0.045, + double k2 = 0.015); +double deltaCIE94GraphicArts(cv::Vec3d lab1, cv::Vec3d lab2); +double toRad(double degree); +double deltaCIE94Textiles(cv::Vec3d lab1, cv::Vec3d lab2); +double deltaCIEDE2000_(cv::Vec3d lab1, cv::Vec3d lab2, double kL = 1.0, + double kC = 1.0, double kH = 1.0); +double deltaCIEDE2000(cv::Vec3d lab1, cv::Vec3d lab2); +double deltaCMC(cv::Vec3d lab1, cv::Vec3d lab2, double kL = 1, double kC = 1); +double deltaCMC1To1(cv::Vec3d lab1, cv::Vec3d lab2); +double deltaCMC2To1(cv::Vec3d lab1, cv::Vec3d lab2); +cv::Mat distance(cv::Mat src, cv::Mat ref, DISTANCE_TYPE distance_type); + + +/* *\ brief distance between two points in formula CIE76 + *\ param lab1 a 3D vector + *\ param lab2 a 3D vector + *\ return distance between lab1 and lab2 +*/ +double deltaCIE76(cv::Vec3d lab1, cv::Vec3d lab2) { return norm(lab1 - lab2); }; + +/* *\ brief distance between two points in formula CIE94 + *\ param lab1 a 3D vector + *\ param lab2 a 3D vector + *\ param kH Hue scale + *\ param kC Chroma scale + *\ param kL Lightness scale + *\ param k1 first scale parameter + *\ param k2 second scale parameter + *\ return distance between lab1 and lab2 +*/ +double deltaCIE94(cv::Vec3d lab1, cv::Vec3d lab2, double kH, + double kC, double kL, double k1, double k2) +{ + double dl = lab1[0] - lab2[0]; + double c1 = sqrt(pow(lab1[1], 2) + pow(lab1[2], 2)); + double c2 = sqrt(pow(lab2[1], 2) + pow(lab2[2], 2)); + double dc = c1 - c2; + double da = lab1[1] - lab2[1]; + double db = lab1[2] - lab2[2]; + double dh = pow(da, 2) + pow(db, 2) - pow(dc, 2); + double sc = 1.0 + k1 * c1; + double sh = 1.0 + k2 * c1; + double sl = 1.0; + double res = + pow(dl / (kL * sl), 2) + pow(dc / (kC * sc), 2) + dh / pow(kH * sh, 2); + + return res > 0 ? sqrt(res) : 0; +} + +double deltaCIE94GraphicArts(cv::Vec3d lab1, cv::Vec3d lab2) +{ + return deltaCIE94(lab1, lab2); +} + +double toRad(double degree) { return degree / 180 * CV_PI; }; + +double deltaCIE94Textiles(cv::Vec3d lab1, cv::Vec3d lab2) +{ + return deltaCIE94(lab1, lab2, 1.0, 1.0, 2.0, 0.048, 0.014); +} + +/* *\ brief distance between two points in formula CIE2000 + *\ param lab1 a 3D vector + *\ param lab2 a 3D vector + *\ param kL Lightness scale + *\ param kC Chroma scale + *\ param kH Hue scale + *\ return distance between lab1 and lab2 +*/ +double deltaCIEDE2000_(cv::Vec3d lab1, cv::Vec3d lab2, double kL, + double kC, double kH) +{ + double delta_L_apo = lab2[0] - lab1[0]; + double l_bar_apo = (lab1[0] + lab2[0]) / 2.0; + double C1 = sqrt(pow(lab1[1], 2) + pow(lab1[2], 2)); + double C2 = sqrt(pow(lab2[1], 2) + pow(lab2[2], 2)); + double C_bar = (C1 + C2) / 2.0; + double G = sqrt(pow(C_bar, 7) / (pow(C_bar, 7) + pow(25, 7))); + double a1_apo = lab1[1] + lab1[1] / 2.0 * (1.0 - G); + double a2_apo = lab2[1] + lab2[1] / 2.0 * (1.0 - G); + double C1_apo = sqrt(pow(a1_apo, 2) + pow(lab1[2], 2)); + double C2_apo = sqrt(pow(a2_apo, 2) + pow(lab2[2], 2)); + double C_bar_apo = (C1_apo + C2_apo) / 2.0; + double delta_C_apo = C2_apo - C1_apo; + + double h1_apo; + if (C1_apo == 0) + { + h1_apo = 0.0; + } + else + { + h1_apo = atan2(lab1[2], a1_apo); + if (h1_apo < 0.0) h1_apo += 2. * CV_PI; + } + + double h2_apo; + if (C2_apo == 0) + { + h2_apo = 0.0; + } + else + { + h2_apo = atan2(lab2[2], a2_apo); + if (h2_apo < 0.0) h2_apo += 2. * CV_PI; + } + + double delta_h_apo; + if (abs(h2_apo - h1_apo) <= CV_PI) + { + delta_h_apo = h2_apo - h1_apo; + } + else if (h2_apo <= h1_apo) + { + delta_h_apo = h2_apo - h1_apo + 2. * CV_PI; + } + else + { + delta_h_apo = h2_apo - h1_apo - 2. * CV_PI; + } + + double H_bar_apo; + if (C1_apo == 0 || C2_apo == 0) + { + H_bar_apo = h1_apo + h2_apo; + } + else if (abs(h1_apo - h2_apo) <= CV_PI) + { + H_bar_apo = (h1_apo + h2_apo) / 2.0; + } + else if (h1_apo + h2_apo < 2. * CV_PI) + { + H_bar_apo = (h1_apo + h2_apo + 2. * CV_PI) / 2.0; + } + else + { + H_bar_apo = (h1_apo + h2_apo - 2. * CV_PI) / 2.0; + } + + double delta_H_apo = 2.0 * sqrt(C1_apo * C2_apo) * sin(delta_h_apo / 2.0); + double T = 1.0 - 0.17 * cos(H_bar_apo - toRad(30.)) + + 0.24 * cos(2.0 * H_bar_apo) + + 0.32 * cos(3.0 * H_bar_apo + toRad(6.0)) - + 0.2 * cos(4.0 * H_bar_apo - toRad(63.0)); + double sC = 1.0 + 0.045 * C_bar_apo; + double sH = 1.0 + 0.015 * C_bar_apo * T; + double sL = 1.0 + ((0.015 * pow(l_bar_apo - 50.0, 2.0)) / + sqrt(20.0 + pow(l_bar_apo - 50.0, 2.0))); + double RT = -2.0 * G * + sin(toRad(60.0) * + exp(-pow((H_bar_apo - toRad(275.0)) / toRad(25.0), 2.0))); + double res = + (pow(delta_L_apo / (kL * sL), 2.0) + pow(delta_C_apo / (kC * sC), 2.0) + + pow(delta_H_apo / (kH * sH), 2.0) + + RT * (delta_C_apo / (kC * sC)) * (delta_H_apo / (kH * sH))); + return res > 0 ? sqrt(res) : 0; +} + +double deltaCIEDE2000(cv::Vec3d lab1, cv::Vec3d lab2) +{ + return deltaCIEDE2000_(lab1, lab2); +} + +/* *\ brief distance between two points in formula CMC + *\ param lab1 a 3D vector + *\ param lab2 a 3D vector + *\ param kL Lightness scale + *\ param kC Chroma scale + *\ return distance between lab1 and lab2 +*/ +double deltaCMC(cv::Vec3d lab1, cv::Vec3d lab2, double kL, double kC) +{ + double dL = lab2[0] - lab1[0]; + double da = lab2[1] - lab1[1]; + double db = lab2[2] - lab1[2]; + double C1 = sqrt(pow(lab1[1], 2.0) + pow(lab1[2], 2.0)); + double C2 = sqrt(pow(lab2[1], 2.0) + pow(lab2[2], 2.0)); + double dC = C2 - C1; + double dH = sqrt(pow(da, 2) + pow(db, 2) - pow(dC, 2)); + + double H1; + if (C1 == 0.) + { + H1 = 0.0; + } + else + { + H1 = atan2(lab1[2], lab1[1]); + if (H1 < 0.0) H1 += 2. * CV_PI; + } + + double F = pow(C1, 2) / sqrt(pow(C1, 4) + 1900); + double T = (H1 > toRad(164) && H1 <= toRad(345)) + ? 0.56 + abs(0.2 * cos(H1 + toRad(168))) + : 0.36 + abs(0.4 * cos(H1 + toRad(35))); + double sL = + lab1[0] < 16. ? 0.511 : (0.040975 * lab1[0]) / (1.0 + 0.01765 * lab1[0]); + double sC = (0.0638 * C1) / (1.0 + 0.0131 * C1) + 0.638; + double sH = sC * (F * T + 1.0 - F); + + return sqrt(pow(dL / (kL * sL), 2.0) + pow(dC / (kC * sC), 2.0) + + pow(dH / sH, 2.0)); +} + +double deltaCMC1To1(cv::Vec3d lab1, cv::Vec3d lab2) +{ + return deltaCMC(lab1, lab2); +} + +double deltaCMC2To1(cv::Vec3d lab1, cv::Vec3d lab2) +{ + return deltaCMC(lab1, lab2, 2, 1); +} + +cv::Mat distance(cv::Mat src, cv::Mat ref, DISTANCE_TYPE distance_type) +{ + switch (distance_type) + { + case cv::ccm::CIE76: + return distanceWise(src, ref, deltaCIE76); + case cv::ccm::CIE94_GRAPHIC_ARTS: + return distanceWise(src, ref, deltaCIE94GraphicArts); + case cv::ccm::CIE94_TEXTILES: + return distanceWise(src, ref, deltaCIE94Textiles); + case cv::ccm::CIE2000: + return distanceWise(src, ref, deltaCIEDE2000); + case cv::ccm::CMC_1TO1: + return distanceWise(src, ref, deltaCMC1To1); + case cv::ccm::CMC_2TO1: + return distanceWise(src, ref, deltaCMC2To1); + case cv::ccm::RGB: + return distanceWise(src, ref, deltaCIE76); + case cv::ccm::RGBL: + return distanceWise(src, ref, deltaCIE76); + default: + throw std::invalid_argument{ "Wrong distance_type!" }; + break; + } +}; + +} // namespace ccm +} // namespace cv + +#endif diff --git a/modules/mcc/include/opencv2/mcc/io.hpp b/modules/mcc/include/opencv2/mcc/io.hpp new file mode 100644 index 00000000000..dd62d46e423 --- /dev/null +++ b/modules/mcc/include/opencv2/mcc/io.hpp @@ -0,0 +1,109 @@ +// This file is part of OpenCV project. +// It is subject to the license terms in the LICENSE file found in the top-level directory +// of this distribution and at http://opencv.org/license.html. +// +// +// License Agreement +// For Open Source Computer Vision Library +// +// Copyright(C) 2020, Huawei Technologies Co.,Ltd. All rights reserved. +// Third party copyrights are property of their respective owners. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// Author: Longbu Wang +// Jinheng Zhang +// Chenqi Shan + +#ifndef __OPENCV_MCC_IO_HPP__ +#define __OPENCV_MCC_IO_HPP__ + +#include +#include +#include +#include + +namespace cv +{ +namespace ccm +{ + +/* *\ brief Io is the meaning of illuminant and observer. See notes of ccm.hpp + * for supported list for illuminant and observer*/ +class IO +{ +public: + + std::string illuminant; + std::string observer; + + IO() {}; + + IO(std::string illuminant_, std::string observer_) :illuminant(illuminant_), observer(observer_) {}; + + virtual ~IO() {}; + + bool operator<(const IO& other) const + { + return (illuminant < other.illuminant || ((illuminant == other.illuminant) && (observer < other.observer))); + } + + bool operator==(const IO& other) const + { + return illuminant == other.illuminant && observer == other.observer; + }; +}; + +const IO A_2("A", "2"), A_10("A", "10"), + D50_2("D50", "2"), D50_10("D50", "10"), + D55_2("D55", "2"), D55_10("D55", "10"), + D65_2("D65", "2"), D65_10("D65", "10"), + D75_2("D75", "2"), D75_10("D75", "10"), + E_2("E", "2"), E_10("E", "10"); + +// data from https://en.wikipedia.org/wiki/Standard_illuminant. +const static std::map> illuminants_xy = +{ + {A_2, { 0.44757, 0.40745 }}, {A_10, { 0.45117, 0.40594 }}, + {D50_2, { 0.34567, 0.35850 }}, {D50_10, { 0.34773, 0.35952 }}, + {D55_2, { 0.33242, 0.34743 }}, {D55_10, { 0.33411, 0.34877 }}, + {D65_2, { 0.31271, 0.32902 }}, {D65_10, { 0.31382, 0.33100 }}, + {D75_2, { 0.29902, 0.31485 }}, {D75_10, { 0.45117, 0.40594 }}, + {E_2, { 1 / 3, 1 / 3 }}, {E_10, { 1 / 3, 1 / 3 }}, +}; + +std::vector xyY2XYZ(const std::vector& xyY); +std::vector xyY2XYZ(const std::vector& xyY) +{ + double Y = xyY.size() >= 3 ? xyY[2] : 1; + return { Y * xyY[0] / xyY[1], Y, Y / xyY[1] * (1 - xyY[0] - xyY[1]) }; +} + +/* *\ brief function to get illuminants*/ +static std::map > getIlluminant(); +static std::map > getIlluminant() +{ + std::map > illuminants; + for (auto it = illuminants_xy.begin(); it != illuminants_xy.end(); ++it) + { + illuminants[it->first] = xyY2XYZ(it->second); + } + return illuminants; +} + +const std::map > illuminants = getIlluminant(); +} // namespace ccm +} // namespace cv + + +#endif \ No newline at end of file diff --git a/modules/mcc/include/opencv2/mcc/operations.hpp b/modules/mcc/include/opencv2/mcc/operations.hpp new file mode 100644 index 00000000000..71947306071 --- /dev/null +++ b/modules/mcc/include/opencv2/mcc/operations.hpp @@ -0,0 +1,146 @@ +// This file is part of OpenCV project. +// It is subject to the license terms in the LICENSE file found in the top-level directory +// of this distribution and at http://opencv.org/license.html. +// +// +// License Agreement +// For Open Source Computer Vision Library +// +// Copyright(C) 2020, Huawei Technologies Co.,Ltd. All rights reserved. +// Third party copyrights are property of their respective owners. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// Author: Longbu Wang +// Jinheng Zhang +// Chenqi Shan + + +#ifndef __OPENCV_MCC_OPERATIONS_HPP__ +#define __OPENCV_MCC_OPERATIONS_HPP__ + +#include +#include +#include "opencv2/mcc/utils.hpp" + +namespace cv +{ +namespace ccm +{ + +typedef std::function MatFunc; + +/* *\ brief Operation class contains some operarions used for color space + * conversion containing linear transformation and non-linear transformation + */ +class Operation +{ +public: + bool linear; + cv::Mat M; + MatFunc f; + + Operation() : linear(true), M(cv::Mat()) {}; + + Operation(cv::Mat M_) :linear(true), M(M_) {}; + + Operation(MatFunc f_) : linear(false), f(f_) {}; + + virtual ~Operation() {}; + /* *\ brief operator function will run operation + */ + cv::Mat operator()(cv::Mat& abc) + { + if (!linear) + { + return f(abc); + } + if (M.empty()) + { + return abc; + } + return multiple(abc, M); + }; + + /* *\ brief add function will conbine this operation + * with other linear transformation operation + */ + void add(const Operation& other) + { + if (M.empty()) + { + M = other.M.clone(); + } + else + { + M = M * other.M; + } + }; + + void clear() + { + M = cv::Mat(); + }; +}; + +const Operation IDENTITY_OP([](cv::Mat x) {return x; }); + +class Operations +{ +public: + std::vector ops; + + Operations() :ops{ } {}; + + Operations(std::initializer_list op) :ops{ op } {}; + + virtual ~Operations() {}; + + /* *\ brief add function will conbine this operation with other transformation operations + */ + Operations& add(const Operations& other) + { + ops.insert(ops.end(), other.ops.begin(), other.ops.end()); + return *this; + }; + + /* *\ brief run operations to make color conversion + */ + cv::Mat run(cv::Mat abc) + { + Operation hd; + for (auto& op : ops) + { + if (op.linear) + { + hd.add(op); + } + else + { + abc = hd(abc); + hd.clear(); + abc = op(abc); + } + } + abc = hd(abc); + return abc; + }; +}; + +const Operations IDENTITY_OPS{ IDENTITY_OP }; + +} // namespace ccm +} // namespace cv + + +#endif \ No newline at end of file diff --git a/modules/mcc/include/opencv2/mcc/utils.hpp b/modules/mcc/include/opencv2/mcc/utils.hpp new file mode 100644 index 00000000000..d1f56854c85 --- /dev/null +++ b/modules/mcc/include/opencv2/mcc/utils.hpp @@ -0,0 +1,235 @@ +// This file is part of OpenCV project. +// It is subject to the license terms in the LICENSE file found in the top-level directory +// of this distribution and at http://opencv.org/license.html. +// +// +// License Agreement +// For Open Source Computer Vision Library +// +// Copyright(C) 2020, Huawei Technologies Co.,Ltd. All rights reserved. +// Third party copyrights are property of their respective owners. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// Author: Longbu Wang +// Jinheng Zhang +// Chenqi Shan + +#ifndef __OPENCV_MCC_UTILS_HPP__ +#define __OPENCV_MCC_UTILS_HPP__ + +#include +#include +#include +#include +#include + +namespace cv +{ +namespace ccm +{ + +double gammaCorrection_(const double& element, const double& gamma); +cv::Mat gammaCorrection(const cv::Mat& src, const double& gamma); +cv::Mat maskCopyTo(const cv::Mat& src, const cv::Mat& mask); +cv::Mat multiple(const cv::Mat& xyz, const cv::Mat& ccm); +cv::Mat saturate(cv::Mat& src, const double& low, const double& up); +cv::Mat rgb2gray(cv::Mat rgb); + +/* *\ brief function for elementWise operation + *\ param src the input array, type of cv::Mat + *\ lambda a for operation +*/ +template +cv::Mat elementWise(const cv::Mat& src, F&& lambda) +{ + cv::Mat dst = src.clone(); + const int channel = src.channels(); + switch (channel) + { + case 1: + { + cv::MatIterator_ it, end; + for (it = dst.begin(), end = dst.end(); it != end; ++it) + { + (*it) = lambda((*it)); + } + break; + } + case 3: + { + cv::MatIterator_ it, end; + for (it = dst.begin(), end = dst.end(); it != end; ++it) + { + for (int j = 0; j < 3; j++) + { + (*it)[j] = lambda((*it)[j]); + } + } + break; + } + default: + throw std::invalid_argument{ "Wrong channel!" }; + break; + } + return dst; +} + +/* *\ brief function for channel operation + *\ param src the input array, type of cv::Mat + *\ lambda the function for operation +*/ +template +cv::Mat channelWise(const cv::Mat& src, F&& lambda) +{ + cv::Mat dst = src.clone(); + cv::MatIterator_ it, end; + for (it = dst.begin(), end = dst.end(); it != end; ++it) + { + *it = lambda(*it); + } + return dst; +} + +/* *\ brief function for distance operation. + *\ param src the input array, type of cv::Mat. + *\ param ref another input array, type of cv::Mat. + *\ param lambda the computing method for distance . +*/ +template +cv::Mat distanceWise(cv::Mat& src, cv::Mat& ref, F&& lambda) +{ + cv::Mat dst = cv::Mat(src.size(), CV_64FC1); + cv::MatIterator_ it_src = src.begin(), end_src = src.end(), + it_ref = ref.begin(); + cv::MatIterator_ it_dst = dst.begin(); + for (; it_src != end_src; ++it_src, ++it_ref, ++it_dst) + { + *it_dst = lambda(*it_src, *it_ref); + } + return dst; +} + + +double gammaCorrection_(const double& element, const double& gamma) +{ + return (element >= 0 ? pow(element, gamma) : -pow((-element), gamma)); +} + +/* *\ brief gamma correction, see ColorSpace.pdf for details. + *\ param src the input array, type of cv::Mat. + *\ param gamma a constant for gamma correction. +*/ +cv::Mat gammaCorrection(const cv::Mat& src, const double& gamma) +{ + return elementWise(src, [gamma](double element)->double {return gammaCorrection_(element, gamma); }); +} + +/* *\ brief maskCopyTo a function to delete unsatisfied elementwise. + *\ param src the input array, type of cv::Mat. + *\ param mask operation mask that used to choose satisfided elementwise. +*/ +cv::Mat maskCopyTo(const cv::Mat& src, const cv::Mat& mask) +{ + cv::Mat dst(countNonZero(mask), 1, src.type()); + const int channel = src.channels(); + auto it_mask = mask.begin(); + switch (channel) + { + case 1: + { + auto it_src = src.begin(), end_src = src.end(); + auto it_dst = dst.begin(); + for (; it_src != end_src; ++it_src, ++it_mask) + { + if (*it_mask) + { + (*it_dst) = (*it_src); + ++it_dst; + } + } + break; + } + case 3: + { + auto it_src = src.begin(), end_src = src.end(); + auto it_dst = dst.begin(); + for (; it_src != end_src; ++it_src, ++it_mask) + { + if (*it_mask) + { + (*it_dst) = (*it_src); + ++it_dst; + } + } + break; + } + default: + throw std::invalid_argument{ "Wrong channel!" }; + break; + } + return dst; +} + +/* *\ brief multiple the function used to compute an array with n channels mulipied by ccm. + *\ param src the input array, type of cv::Mat. + *\ param ccm the ccm matrix to make color correction. +*/ +cv::Mat multiple(const cv::Mat& xyz, const cv::Mat& ccm) +{ + cv::Mat tmp = xyz.reshape(1, xyz.rows * xyz.cols); + cv::Mat res = tmp * ccm; + res = res.reshape(res.cols, xyz.rows); + return res; +} + +/* *\ brief multiple the function used to get the mask of saturated colors, + colors between low and up will be choosed. + *\ param src the input array, type of cv::Mat. + *\ param low the threshold to choose saturated colors + *\ param up the threshold to choose saturated colors +*/ +cv::Mat saturate(cv::Mat& src, const double& low, const double& up) +{ + cv::Mat dst = cv::Mat::ones(src.size(), CV_8UC1); + cv::MatIterator_ it_src = src.begin(), end_src = src.end(); + cv::MatIterator_ it_dst = dst.begin(); + for (; it_src != end_src; ++it_src, ++it_dst) + { + for (int i = 0; i < 3; ++i) + { + if ((*it_src)[i] > up || (*it_src)[i] < low) + { + *it_dst = 0; + break; + } + } + } + return dst; +} + +const static cv::Mat m_gray = (cv::Mat_(3, 1) << 0.2126, 0.7152, 0.0722); + +/* *\ brief rgb2gray it is an approximation grayscale function for relative RGB color space, + * see Miscellaneous.pdf for details; + *\ param rgb the input array, type of cv::Mat. +*/ +cv::Mat rgb2gray(cv::Mat rgb) +{ + return multiple(rgb, m_gray); +} +} // namespace ccm +} // namespace cv + + +#endif \ No newline at end of file From 91b0591d19f998bc7afc25908ca10dac2baeb6ba Mon Sep 17 00:00:00 2001 From: Jinheng Zhang Date: Thu, 10 Sep 2020 21:18:56 +0800 Subject: [PATCH 2/8] Implement color, colorspace, linearization and ccm features. --- modules/mcc/include/opencv2/mcc/ccm.hpp | 545 +++++++++++++++ modules/mcc/include/opencv2/mcc/color.hpp | 285 ++++++++ .../mcc/include/opencv2/mcc/colorspace.hpp | 635 ++++++++++++++++++ modules/mcc/include/opencv2/mcc/linearize.hpp | 304 +++++++++ 4 files changed, 1769 insertions(+) create mode 100644 modules/mcc/include/opencv2/mcc/ccm.hpp create mode 100644 modules/mcc/include/opencv2/mcc/color.hpp create mode 100644 modules/mcc/include/opencv2/mcc/colorspace.hpp create mode 100644 modules/mcc/include/opencv2/mcc/linearize.hpp diff --git a/modules/mcc/include/opencv2/mcc/ccm.hpp b/modules/mcc/include/opencv2/mcc/ccm.hpp new file mode 100644 index 00000000000..9a581cf9112 --- /dev/null +++ b/modules/mcc/include/opencv2/mcc/ccm.hpp @@ -0,0 +1,545 @@ +// This file is part of OpenCV project. +// It is subject to the license terms in the LICENSE file found in the top-level directory +// of this distribution and at http://opencv.org/license.html. +// +// +// License Agreement +// For Open Source Computer Vision Library +// +// Copyright(C) 2020, Huawei Technologies Co.,Ltd. All rights reserved. +// Third party copyrights are property of their respective owners. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// Author: Longbu Wang +// Jinheng Zhang +// Chenqi Shan + +#ifndef __OPENCV_MCC_CCM_HPP__ +#define __OPENCV_MCC_CCM_HPP__ + +#include +#include +#include +#include +#include +#include +#include +#include "opencv2/mcc/linearize.hpp" + +namespace cv +{ +namespace ccm +{ + +/** + src : + detected colors of ColorChecker patches; + NOTICE: the color type is RGB not BGR, and the color values are in [0, 1]; + type: cv::Mat; + dst : + the reference colors; + NOTICE: Built-in color card or custom color card are supported; + Built-in: + Macbeth_D50_2: Macbeth ColorChecker with 2deg D50; + Macbeth_D65_2: Macbeth ColorChecker with 2deg D65; + Custom: + You should use Color + For the list of color spaces supported, see the notes below; + If the color type is some RGB, the format is RGB not BGR, and the color values are in [0, 1]; + type: Color; + colorspace : + the absolute color space that detected colors convert to; + NOTICE: it should be some RGB color space; + For the list of RGB color spaces supported, see the notes below; + type: ColorSpace; + ccm_type : + the shape of color correction matrix(CCM); + Supported list: + "CCM_3x3": 3x3 matrix; + "CCM_4x3": 4x3 matrix; + type: enum CCM_TYPE; + default: CCM_3x3; + distance : + the type of color distance; + Supported list: + "CIE2000"; + "CIE94_GRAPHIC_ARTS"; + "CIE94_TEXTILES"; + "CIE76"; + "CMC_1TO1"; + "CMC_2TO1"; + "RGB" : Euclidean distance of rgb color space; + "RGBL" : Euclidean distance of rgbl color space; + type: enum DISTANCE_TYPE; + default: CIE2000; + linear_type : + the method of linearization; + NOTICE: see Linearization.pdf for details; + Supported list: + "IDENTITY_" : no change is made; + "GAMMA": gamma correction; + Need assign a value to gamma simultaneously; + "COLORPOLYFIT": polynomial fitting channels respectively; + Need assign a value to deg simultaneously; + "GRAYPOLYFIT": grayscale polynomial fitting; + Need assign a value to deg and dst_whites simultaneously; + "COLORLOGPOLYFIT": logarithmic polynomial fitting channels respectively; + Need assign a value to deg simultaneously; + "GRAYLOGPOLYFIT": grayscale Logarithmic polynomial fitting; + Need assign a value to deg and dst_whites simultaneously; + type: enum LINEAR_TYPE; + default: IDENTITY_; + gamma : + the gamma value of gamma correction; + NOTICE: only valid when linear is set to "gamma"; + type: double; + default: 2.2; + deg : + the degree of linearization polynomial; + NOTICE: only valid when linear is set to "COLORPOLYFIT", "GRAYPOLYFIT", + "COLORLOGPOLYFIT" and "GRAYLOGPOLYFIT"; + type: int; + default: 3; + saturated_threshold : + the threshold to determine saturation; + NOTICE: it is a tuple of [low, up]; + The colors in the closed interval [low, up] are reserved to participate + in the calculation of the loss function and initialization parameters. + type: std::vector; + default: { 0, 0.98 }; + --------------------------------------------------- + There are some ways to set weights: + 1. set weights_list only; + 2. set weights_coeff only; + see CCM.pdf for details; + weights_list : + the list of weight of each color; + type: cv::Mat; + default: empty array; + weights_coeff : + the exponent number of L* component of the reference color in CIE Lab color space; + type: double; + default: 0; + --------------------------------------------------- + initial_method_type : + the method of calculating CCM initial value; + see CCM.pdf for details; + Supported list: + 'LEAST_SQUARE': least-squre method; + 'WHITE_BALANCE': white balance method; + type: enum INITIAL_METHOD_TYPE; + max_count, epsilon : + used in MinProblemSolver-DownhillSolver; + Terminal criteria to the algorithm; + type: int, double; + default: 5000, 1e-4; + --------------------------------------------------- + Supported Color Space: + Supported list of RGB color spaces: + sRGB; + AdobeRGB; + WideGamutRGB; + ProPhotoRGB; + DCI_P3_RGB; + AppleRGB; + REC_709_RGB; + REC_2020_RGB; + Supported list of linear RGB color spaces: + sRGBL; + AdobeRGBL; + WideGamutRGBL; + ProPhotoRGBL; + DCI_P3_RGBL; + AppleRGBL; + REC_709_RGBL; + REC_2020_RGBL; + Supported list of non-RGB color spaces: + Lab_D50_2; + Lab_D65_2; + XYZ_D50_2; + XYZ_D65_2; + Supported IO (You can use Lab(io) or XYZ(io) to create color space): + A_2; + A_10; + D50_2; + D50_10; + D55_2; + D55_10; + D65_2; + D65_10; + D75_2; + D75_10; + E_2; + E_10; + --------------------------------------------------- + Abbr. + src, s: source; + dst, d: destination; + io: illuminant & observer; + sio, dio: source of io; destination of io; + rgbl: linear RGB + cs: color space; + cc: Colorchecker; + M, m: matrix + ccm: color correction matrix; + cam: chromatic adaption matrix; +*/ + + +/* *\ brief Enum of the possible types of ccm. +*/ +enum CCM_TYPE +{ + CCM_3x3, + CCM_4x3 +}; + +/* *\ brief Enum of the possible types of initial method. +*/ +enum INITIAL_METHOD_TYPE +{ + WHITE_BALANCE, + LEAST_SQUARE +}; + + +/* *\ brief Core class of ccm model. + * produce a ColorCorrectionModel instance for inference. +*/ +class ColorCorrectionModel +{ +public: + // detected colors, the referenceand the RGB colorspace for conversion + cv::Mat src; + Color dst; + + RGBBase_& cs; + cv::Mat mask; + + // ccm type and shape + CCM_TYPE ccm_type; + int shape; + + // linear method and distance + std::shared_ptr linear; + DISTANCE_TYPE distance; + + cv::Mat weights; + cv::Mat ccm; + cv::Mat ccm0; + double loss; + + int max_count; + double epsilon; + + ColorCorrectionModel(cv::Mat src_, cv::Mat colors_, const ColorSpace& ref_cs_, RGBBase_& cs_=sRGB, CCM_TYPE ccm_type_ = CCM_3x3, DISTANCE_TYPE distance_ = CIE2000, LINEAR_TYPE linear_type = GAMMA, + double gamma = 2.2, int deg = 3, std::vector saturated_threshold = { 0, 0.98 }, cv::Mat weights_list = Mat(), double weights_coeff = 0, + INITIAL_METHOD_TYPE initial_method_type = LEAST_SQUARE, int max_count_ = 5000, double epsilon_ = 1.e-4) : + ColorCorrectionModel(src_, Color(colors_, ref_cs_), cs_, ccm_type_, distance_, linear_type, + gamma, deg, saturated_threshold, weights_list, weights_coeff, initial_method_type, max_count_, epsilon_) {} + + ColorCorrectionModel(cv::Mat src_, Color dst_, RGBBase_& cs_= sRGB, CCM_TYPE ccm_type_ = CCM_3x3, DISTANCE_TYPE distance_ = CIE2000, LINEAR_TYPE linear_type = GAMMA, + double gamma = 2.2, int deg = 3, std::vector saturated_threshold = { 0, 0.98 }, cv::Mat weights_list = Mat(), double weights_coeff = 0, + INITIAL_METHOD_TYPE initial_method_type = LEAST_SQUARE, int max_count_ = 5000, double epsilon_ = 1.e-4) : + src(src_), dst(dst_), cs(cs_), ccm_type(ccm_type_), distance(distance_), max_count(max_count_), epsilon(epsilon_) + { + cv::Mat saturate_mask = saturate(src, saturated_threshold[0], saturated_threshold[1]); + this->linear = getLinear(gamma, deg, this->src, this->dst, saturate_mask, this->cs, linear_type); + calWeightsMasks(weights_list, weights_coeff, saturate_mask); + + src_rgbl = this->linear->linearize(maskCopyTo(this->src, mask)); + dst.colors = maskCopyTo(dst.colors, mask); + dst_rgbl =this->dst.to(*(this->cs.l)).colors; + + // make no change for CCM_3x3, make change for CCM_4x3. + src_rgbl = prepare(src_rgbl); + + + // distance function may affect the loss function and the fitting function + switch (this->distance) + { + case cv::ccm::RGBL: + initialLeastSquare(true); + break; + default: + switch (initial_method_type) + { + case cv::ccm::WHITE_BALANCE: + initialWhiteBalance(); + break; + case cv::ccm::LEAST_SQUARE: + initialLeastSquare(); + break; + default: + throw std::invalid_argument{ "Wrong initial_methoddistance_type!" }; + break; + } + break; + } + + fitting(); + } + + /* *\ brief Make no change for CCM_3x3. + * convert cv::Mat A to [A, 1] in CCM_4x3. + *\ param inp the input array, type of cv::Mat. + *\ return the output array, type of cv::Mat + */ + cv::Mat prepare(const cv::Mat& inp) + { + switch (ccm_type) + { + case cv::ccm::CCM_3x3: + shape = 9; + return inp; + case cv::ccm::CCM_4x3: + { + shape = 12; + cv::Mat arr1 = cv::Mat::ones(inp.size(), CV_64F); + cv::Mat arr_out(inp.size(), CV_64FC4); + cv::Mat arr_channels[3]; + split(inp, arr_channels); + merge(std::vector{arr_channels[0], arr_channels[1], arr_channels[2], arr1}, arr_out); + return arr_out; + } + default: + throw std::invalid_argument{ "Wrong ccm_type!" }; + break; + } + }; + + /* *\ brief Fitting nonlinear - optimization initial value by white balance. + * see CCM.pdf for details. + *\ return the output array, type of cv::Mat + */ + cv::Mat initialWhiteBalance(void) + { + cv::Mat schannels[3]; + split(src_rgbl, schannels); + cv::Mat dchannels[3]; + split(dst_rgbl, dchannels); + std::vector initial_vec = { sum(dchannels[0])[0] / sum(schannels[0])[0], 0, 0, 0, + sum(dchannels[1])[0] / sum(schannels[1])[0], 0, 0, 0, + sum(dchannels[2])[0] / sum(schannels[2])[0], 0, 0, 0 }; + std::vector initial_vec_(initial_vec.begin(), initial_vec.begin() + shape); + cv::Mat initial_white_balance = cv::Mat(initial_vec_, true).reshape(0, shape / 3); + + return initial_white_balance; + }; + + /* *\ brief Fitting nonlinear-optimization initial value by least square. + * see CCM.pdf for details + *\ param fit if fit is True, return optimalization for rgbl distance function. + */ + void initialLeastSquare(bool fit = false) + { + cv::Mat A, B, w; + if (weights.empty()) + { + A = src_rgbl; + B = dst_rgbl; + } + else + { + pow(weights, 0.5, w); + cv::Mat w_; + merge(std::vector{w, w, w}, w_); + A = w_.mul(src_rgbl); + B = w_.mul(dst_rgbl); + } + solve(A.reshape(1, A.rows), B.reshape(1, B.rows), ccm0, DECOMP_SVD); + + // if fit is True, return optimalization for rgbl distance function. + if (fit) + { + ccm = ccm0; + cv::Mat residual = A.reshape(1, A.rows) * ccm.reshape(0, shape / 3) - B.reshape(1, B.rows); + Scalar s = residual.dot(residual); + double sum = s[0]; + loss = sqrt(sum / masked_len); + } + }; + + /* *\ brief Loss function base on cv::MinProblemSolver::Function. + * see details in https://github.com/opencv/opencv/blob/master/modules/core/include/opencv2/core/optim.hpp + */ + class LossFunction : public cv::MinProblemSolver::Function + { + public: + ColorCorrectionModel* ccm_loss; + LossFunction(ColorCorrectionModel* ccm) : ccm_loss(ccm) {}; + + /* *\ brief Reset dims to ccm->shape. + */ + int getDims() const CV_OVERRIDE + { + return ccm_loss->shape; + } + + /* *\ brief Reset calculation. + */ + double calc(const double* x) const CV_OVERRIDE + { + cv::Mat ccm_(ccm_loss->shape, 1, CV_64F); + for (int i = 0; i < ccm_loss->shape; i++) + { + ccm_.at(i, 0) = x[i]; + } + ccm_ = ccm_.reshape(0, ccm_loss->shape / 3); + return ccm_loss->calc_loss(ccm_); + } + }; + + double calc_loss_(Color color) + { + cv::Mat distlist = color.diff(dst, distance); + Color lab = color.to(Lab_D50_2); + cv::Mat dist_; + pow(distlist, 2, dist_); + if (!weights.empty()) + { + dist_ = weights.mul(dist_); + } + Scalar ss = sum(dist_); + return ss[0]; + } + + double calc_loss(const Mat ccm_) + { + Mat converted = src_rgbl.reshape(1, 0) * ccm_; + Color color(converted.reshape(3, 0), *(cs.l)); + return calc_loss_(color); + } + + /* *\ brief Fitting ccm if distance function is associated with CIE Lab color space. + * see details in https://github.com/opencv/opencv/blob/master/modules/core/include/opencv2/core/optim.hpp + * Set terminal criteria for solver is possible. + */ + void fitting(void) + { + cv::Ptr solver = cv::DownhillSolver::create(); + cv::Ptr ptr_F(new LossFunction(this)); + solver->setFunction(ptr_F); + cv::Mat reshapeccm = ccm0.clone().reshape(0, 1); + cv::Mat step = cv::Mat::ones(reshapeccm.size(), CV_64F); + solver->setInitStep(step); + TermCriteria termcrit = TermCriteria(TermCriteria::MAX_ITER + TermCriteria::EPS, max_count, epsilon); + solver->setTermCriteria(termcrit); + double res = solver->minimize(reshapeccm); + ccm = reshapeccm.reshape(0, shape/3); + loss = pow((res / masked_len), 0.5); + std::cout << " ccm " << ccm << std::endl; + std::cout << " loss " << loss << std::endl; + }; + + /* *\ brief Infer using fitting ccm. + *\ param img the input image, type of cv::Mat. + *\ return the output array, type of cv::Mat. + */ + cv::Mat infer(const cv::Mat& img, bool islinear = false) + { + if (!ccm.data) + { + throw "No CCM values!"; + } + cv::Mat img_lin = linear->linearize(img); + cv::Mat img_ccm(img_lin.size(), img_lin.type()); + cv::Mat ccm_ = ccm.reshape(0, shape / 3); + img_ccm = multiple(prepare(img_lin), ccm_); + if (islinear == true) + { + return img_ccm; + } + return cs.fromL(img_ccm); + }; + + /* *\ brief Infer image and output as an BGR image with uint8 type. + * mainly for test or debug. + * input size and output size should be 255. + *\ param imgfile path name of image to infer. + *\ param islinear if linearize or not. + *\ return the output array, type of cv::Mat. + */ + cv::Mat inferImage(std::string imgfile, bool islinear = false) + { + const int inp_size = 255; + const int out_size = 255; + cv::Mat img = imread(imgfile); + cv::Mat img_; + cvtColor(img, img_, COLOR_BGR2RGB); + img_.convertTo(img_, CV_64F); + img_ = img_ / inp_size; + cv::Mat out = this->infer(img_, islinear); + cv::Mat out_ = out * out_size; + out_.convertTo(out_, CV_8UC3); + cv::Mat img_out = min(max(out_, 0), out_size); + cv::Mat out_img; + cvtColor(img_out, out_img, COLOR_RGB2BGR); + return out_img; + }; + + // void report() { + // std::cout << "CCM0: " << ccm0 << std::endl; + // std::cout << "CCM: " << ccm << std::endl; + // std::cout << "Loss: " << loss << std::endl; + // } + +private: + cv::Mat dist; + int masked_len; + + // RGBl of detected data and the reference + cv::Mat src_rgbl; + cv::Mat dst_rgbl; + + /* *\ brief Calculate weights and mask. + *\ param weights_list the input array, type of cv::Mat. + *\ param weights_coeff type of double. + *\ param saturate_list the input array, type of cv::Mat. + */ + void calWeightsMasks(cv::Mat weights_list, double weights_coeff, cv::Mat saturate_mask) + { + // weights + if (!weights_list.empty()) + { + weights = weights_list; + } + else if (weights_coeff != 0) + { + pow(dst.toLuminant(dst.cs.io), weights_coeff, weights); + } + + // masks + cv::Mat weight_mask = cv::Mat::ones(src.rows, 1, CV_8U); + if (!weights.empty()) + { + weight_mask = weights > 0; + } + this->mask = (weight_mask) & (saturate_mask); + + // weights' mask + if (!weights.empty()) + { + cv::Mat weights_masked = maskCopyTo(this->weights, this->mask); + weights = weights_masked / mean(weights_masked)[0]; + } + masked_len = (int)sum(mask)[0]; + }; +}; + +} // namespace ccm +} // namespace cv + + +#endif \ No newline at end of file diff --git a/modules/mcc/include/opencv2/mcc/color.hpp b/modules/mcc/include/opencv2/mcc/color.hpp new file mode 100644 index 00000000000..1b542e5d45f --- /dev/null +++ b/modules/mcc/include/opencv2/mcc/color.hpp @@ -0,0 +1,285 @@ +// This file is part of OpenCV project. +// It is subject to the license terms in the LICENSE file found in the top-level directory +// of this distribution and at http://opencv.org/license.html. +// +// +// License Agreement +// For Open Source Computer Vision Library +// +// Copyright(C) 2020, Huawei Technologies Co.,Ltd. All rights reserved. +// Third party copyrights are property of their respective owners. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// Author: Longbu Wang +// Jinheng Zhang +// Chenqi Shan + +#ifndef __OPENCV_MCC_COLOR_HPP__ +#define __OPENCV_MCC_COLOR_HPP__ + +#include +#include "opencv2/mcc/colorspace.hpp" +#include "opencv2/mcc/distance.hpp" + +namespace cv +{ +namespace ccm +{ + +/* *\ brief Color defined by color_values and color space +*/ + +class Color +{ +public: + + /* *\ param grays mask of grayscale color + *\ param colored mask of colored color + *\ param history storage of historical conversion + */ + cv::Mat colors; + const ColorSpace& cs; + cv::Mat grays; + cv::Mat colored; + std::map> history; + + Color(cv::Mat colors_, const ColorSpace& cs_, cv::Mat colored_) : colors(colors_), cs(cs_), colored(colored_) + { + grays= ~colored; + } + Color(cv::Mat colors_, const ColorSpace& cs_) :colors(colors_), cs(cs_) {}; + + virtual ~Color() {}; + + /* *\ brief Change to other color space. + * The conversion process incorporates linear transformations to speed up. + * method is chromatic adapation method. + * when save if True, get data from history first. + *\ param other type of ColorSpace. + *\ return Color. + */ + Color to(const ColorSpace& other, CAM method = BRADFORD, bool save = true) + { + if (history.count(other) == 1) + { + + return *history[other]; + } + if (cs.relate(other)) + { + return Color(cs.relation(other).run(colors), other); + } + Operations ops; + ops.add(cs.to).add(XYZ(cs.io).cam(other.io, method)).add(other.from); + std::shared_ptr color(new Color(ops.run(colors), other)); + if (save) + { + history[other] = color; + } + return *color; + } + + /* *\ brief Channels split. + *\ return each channel. + */ + cv::Mat channel(cv::Mat m, int i) + { + cv::Mat dchannels[3]; + split(m, dchannels); + return dchannels[i]; + } + + /* *\ brief To Gray. + */ + cv::Mat toGray(IO io, CAM method = BRADFORD, bool save = true) + { + XYZ xyz(io); + return channel(this->to(xyz, method, save).colors, 1); + } + + /* *\ brief To Luminant. + */ + cv::Mat toLuminant(IO io, CAM method = BRADFORD, bool save = true) + { + Lab lab(io); + return channel(this->to(lab, method, save).colors, 0); + } + + /* *\ brief Diff without IO. + *\ param other type of Color. + *\ param method type of distance. + *\ return distance between self and other + */ + cv::Mat diff(Color& other, DISTANCE_TYPE method = CIE2000) + { + return diff(other, cs.io, method); + } + + /* *\ brief Diff with IO. + *\ param other type of Color. + *\ param io type of IO. + *\ param method type of distance. + *\ return distance between self and other + */ + cv::Mat diff(Color& other, IO io, DISTANCE_TYPE method = CIE2000) + { + Lab lab(io); + switch (method) + { + case cv::ccm::CIE76: + case cv::ccm::CIE94_GRAPHIC_ARTS: + case cv::ccm::CIE94_TEXTILES: + case cv::ccm::CIE2000: + case cv::ccm::CMC_1TO1: + case cv::ccm::CMC_2TO1: + return distance(to(lab).colors, other.to(lab).colors, method); + case cv::ccm::RGB: + return distance(to(*cs.nl).colors, other.to(*cs.nl).colors, method); + case cv::ccm::RGBL: + return distance(to(*cs.l).colors, other.to(*cs.l).colors, method); + default: + throw std::invalid_argument{ "Wrong method!" }; + break; + } + } + + /* *\ brief Calculate gray mask. + */ + void getGray(double JDN = 2.0) + { + if (!grays.empty()) { + return; + } + cv::Mat lab = to(Lab_D65_2).colors; + cv::Mat gray(colors.size(), colors.type()); + int fromto[] = { 0,0, -1,1, -1,2 }; + mixChannels(&lab, 1, &gray, 1, fromto, 3); + cv::Mat d = distance(lab, gray, CIE2000); + this->grays = d < JDN; + this->colored = ~grays; + } + + /* *\ brief Operator for mask copy. + */ + Color operator[](cv::Mat mask) + { + return Color(maskCopyTo(colors, mask), cs); + } + + +}; + + +/* *\ brief Data is from https://www.imatest.com/wp-content/uploads/2011/11/Lab-data-Iluminate-D65-D50-spectro.xls + * see Miscellaneous.md for details. +*/ +const cv::Mat ColorChecker2005_LAB_D50_2 = (cv::Mat_(24, 1) << + cv::Vec3d(37.986, 13.555, 14.059), + cv::Vec3d(65.711, 18.13, 17.81), + cv::Vec3d(49.927, -4.88, -21.925), + cv::Vec3d(43.139, -13.095, 21.905), + cv::Vec3d(55.112, 8.844, -25.399), + cv::Vec3d(70.719, -33.397, -0.199), + cv::Vec3d(62.661, 36.067, 57.096), + cv::Vec3d(40.02, 10.41, -45.964), + cv::Vec3d(51.124, 48.239, 16.248), + cv::Vec3d(30.325, 22.976, -21.587), + cv::Vec3d(72.532, -23.709, 57.255), + cv::Vec3d(71.941, 19.363, 67.857), + cv::Vec3d(28.778, 14.179, -50.297), + cv::Vec3d(55.261, -38.342, 31.37), + cv::Vec3d(42.101, 53.378, 28.19), + cv::Vec3d(81.733, 4.039, 79.819), + cv::Vec3d(51.935, 49.986, -14.574), + cv::Vec3d(51.038, -28.631, -28.638), + cv::Vec3d(96.539, -0.425, 1.186), + cv::Vec3d(81.257, -0.638, -0.335), + cv::Vec3d(66.766, -0.734, -0.504), + cv::Vec3d(50.867, -0.153, -0.27), + cv::Vec3d(35.656, -0.421, -1.231), + cv::Vec3d(20.461, -0.079, -0.973)); + +const cv::Mat ColorChecker2005_LAB_D65_2 = (cv::Mat_(24, 1) << + cv::Vec3d(37.542, 12.018, 13.33), + cv::Vec3d(65.2, 14.821, 17.545), + cv::Vec3d(50.366, -1.573, -21.431), + cv::Vec3d(43.125, -14.63, 22.12), + cv::Vec3d(55.343, 11.449, -25.289), + cv::Vec3d(71.36, -32.718, 1.636), + cv::Vec3d(61.365, 32.885, 55.155), + cv::Vec3d(40.712, 16.908, -45.085), + cv::Vec3d(49.86, 45.934, 13.876), + cv::Vec3d(30.15, 24.915, -22.606), + cv::Vec3d(72.438, -27.464, 58.469), + cv::Vec3d(70.916, 15.583, 66.543), + cv::Vec3d(29.624, 21.425, -49.031), + cv::Vec3d(55.643, -40.76, 33.274), + cv::Vec3d(40.554, 49.972, 25.46), + cv::Vec3d(80.982, -1.037, 80.03), + cv::Vec3d(51.006, 49.876, -16.93), + cv::Vec3d(52.121, -24.61, -26.176), + cv::Vec3d(96.536, -0.694, 1.354), + cv::Vec3d(81.274, -0.61, -0.24), + cv::Vec3d(66.787, -0.647, -0.429), + cv::Vec3d(50.872, -0.059, -0.247), + cv::Vec3d(35.68, -0.22, -1.205), + cv::Vec3d(20.475, 0.049, -0.972)); + +const cv::Mat ColorChecker2005_COLORED_MASK = (cv::Mat_(24, 1) << + 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, + 0, 0, 0, 0, 0, 0); + +const cv::Mat Vinyl_LAB_D50_2 = (Mat_(18, 1) << + Vec3d(1.00000000e+02, 5.20000001e-03, -1.04000000e-02), + Vec3d(7.30833969e+01, -8.19999993e-01, -2.02099991e+00), + Vec3d(6.24930000e+01, 4.25999999e-01, -2.23099995e+00), + Vec3d(5.04640007e+01, 4.46999997e-01, -2.32399988e+00), + Vec3d(3.77970009e+01, 3.59999985e-02, -1.29700005e+00), + Vec3d(0.00000000e+00, 0.00000000e+00, 0.00000000e+00), + Vec3d(5.15880013e+01, 7.35179977e+01, 5.15690002e+01), + Vec3d(9.36989975e+01, -1.57340002e+01, 9.19420013e+01), + Vec3d(6.94079971e+01, -4.65940018e+01, 5.04869995e+01), + Vec3d(6.66100006e+01, -1.36789999e+01, -4.31720009e+01), + Vec3d(1.17110004e+01, 1.69799995e+01, -3.71759987e+01), + Vec3d(5.19739990e+01, 8.19440002e+01, -8.40699959e+00), + Vec3d(4.05489998e+01, 5.04399986e+01, 2.48490009e+01), + Vec3d(6.08160019e+01, 2.60690002e+01, 4.94420013e+01), + Vec3d(5.22529984e+01, -1.99500008e+01, -2.39960003e+01), + Vec3d(5.12859993e+01, 4.84700012e+01, -1.50579996e+01), + Vec3d(6.87070007e+01, 1.22959995e+01, 1.62129993e+01), + Vec3d(6.36839981e+01, 1.02930002e+01, 1.67639999e+01)); + +const cv::Mat Vinyl_COLORED_MASK = (cv::Mat_(18, 1) << + 0, 0, 0, 0, 0, 0, + 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1); + +/* *\ brief Macbeth ColorChecker with 2deg D50. +*/ +Color Macbeth_D50_2(ColorChecker2005_LAB_D50_2, Lab_D50_2, ColorChecker2005_COLORED_MASK); + +/* *\ brief Macbeth ColorChecker with 2deg D65. +*/ +Color Macbeth_D65_2(ColorChecker2005_LAB_D65_2, Lab_D65_2, ColorChecker2005_COLORED_MASK); + +Color Vinyl_D50_2(Vinyl_LAB_D50_2, Lab_D50_2, Vinyl_COLORED_MASK); + + +} // namespace ccm +} // namespace cv + + +#endif \ No newline at end of file diff --git a/modules/mcc/include/opencv2/mcc/colorspace.hpp b/modules/mcc/include/opencv2/mcc/colorspace.hpp new file mode 100644 index 00000000000..9742344c365 --- /dev/null +++ b/modules/mcc/include/opencv2/mcc/colorspace.hpp @@ -0,0 +1,635 @@ +// This file is part of OpenCV project. +// It is subject to the license terms in the LICENSE file found in the top-level directory +// of this distribution and at http://opencv.org/license.html. +// +// +// License Agreement +// For Open Source Computer Vision Library +// +// Copyright(C) 2020, Huawei Technologies Co.,Ltd. All rights reserved. +// Third party copyrights are property of their respective owners. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// Author: Longbu Wang +// Jinheng Zhang +// Chenqi Shan + +#ifndef __OPENCV_MCC_COLORSPACE_HPP__ +#define __OPENCV_MCC_COLORSPACE_HPP__ + +#include +#include +#include +#include "opencv2/mcc/io.hpp" +#include "opencv2/mcc/operations.hpp" +#include "opencv2/mcc/utils.hpp" + +namespace cv +{ +namespace ccm +{ +/* *\ brief Basic class for ColorSpace. +*/ +class ColorSpace +{ +public: + IO io; + std::string type; + bool linear; + Operations to; + Operations from; + ColorSpace* l; + ColorSpace* nl; + + ColorSpace() {}; + + ColorSpace(IO io_, std::string type_, bool linear_) :io(io_), type(type_), linear(linear_) {}; + + virtual ~ColorSpace() + { + l = 0; + nl = 0; + }; + + virtual bool relate(const ColorSpace& other) const + { + return (type == other.type) && (io == other.io); + }; + + virtual Operations relation(const ColorSpace& /*other*/) const + { + return IDENTITY_OPS; + }; + + bool operator<(const ColorSpace& other)const + { + return (io < other.io || (io == other.io && type < other.type) || (io == other.io && type == other.type && linear < other.linear)); + } +}; + +/* *\ brief Base of RGB color space; + * the argument values are from AdobeRGB; + * Data from https://en.wikipedia.org/wiki/Adobe_RGB_color_space +*/ +class RGBBase_ : public ColorSpace +{ +public: + // primaries + double xr; + double yr; + double xg; + double yg; + double xb; + double yb; + MatFunc toL; + MatFunc fromL; + cv::Mat M_to; + cv::Mat M_from; + + using ColorSpace::ColorSpace; + + /* *\ brief There are 3 kinds of relationships for RGB: + * 1. Different types; - no operation + * 1. Same type, same linear; - copy + * 2. Same type, different linear, self is nonlinear; - 2 toL + * 3. Same type, different linear, self is linear - 3 fromL + *\ param other type of ColorSpace. + *\ return Operations. + */ + Operations relation(const ColorSpace& other) const CV_OVERRIDE + { + if (linear == other.linear) + { + return IDENTITY_OPS; + } + if (linear) + { + return Operations({ Operation(fromL) }); + } + return Operations({ Operation(toL) }); + }; + + /* *\ brief Initial operations. + */ + void init() + { + setParameter(); + calLinear(); + calM(); + calOperations(); + } + + /* *\ brief Produce color space instance with linear and non-linear versions. + *\ param rgbl type of RGBBase_. + */ + void bind(RGBBase_& rgbl) + { + init(); + rgbl.init(); + l = &rgbl; + rgbl.l = &rgbl; + nl = this; + rgbl.nl = this; + } + +private: + virtual void setParameter() {}; + + /* *\ brief Calculation of M_RGBL2XYZ_base. + * see ColorSpace.pdf for details. + */ + virtual void calM() + { + cv::Mat XYZr, XYZg, XYZb, XYZ_rgbl, Srgb; + XYZr = cv::Mat(xyY2XYZ({ xr, yr }), true); + XYZg = cv::Mat(xyY2XYZ({ xg, yg }), true); + XYZb = cv::Mat(xyY2XYZ({ xb, yb }), true); + merge(std::vector{ XYZr, XYZg, XYZb }, XYZ_rgbl); + XYZ_rgbl = XYZ_rgbl.reshape(1, XYZ_rgbl.rows); + cv::Mat XYZw = cv::Mat(illuminants.find(io)->second, true); + solve(XYZ_rgbl, XYZw, Srgb); + merge(std::vector{ Srgb.at(0)* XYZr, + Srgb.at(1)* XYZg, + Srgb.at(2)* XYZb }, M_to); + M_to = M_to.reshape(1, M_to.rows); + M_from = M_to.inv(); + }; + + /* *\ brief operations to or from XYZ. + */ + virtual void calOperations() + { + // rgb -> rgbl + toL = [this](cv::Mat rgb)->cv::Mat {return toLFunc(rgb); }; + + // rgbl -> rgb + fromL = [this](cv::Mat rgbl)->cv::Mat {return fromLFunc(rgbl); }; + + if (linear) + { + to = Operations({ Operation(M_to.t()) }); + from = Operations({ Operation(M_from.t()) }); + } + else + { + to = Operations({ Operation(toL), Operation(M_to.t()) }); + from = Operations({ Operation(M_from.t()), Operation(fromL) }); + } + } + + virtual void calLinear() {} + + virtual cv::Mat toLFunc(cv::Mat& /*rgb*/) + { + return cv::Mat(); + }; + + virtual cv::Mat fromLFunc(cv::Mat& /*rgbl*/) + { + return cv::Mat(); + }; + +}; + +/* *\ brief Base of Adobe RGB color space; +*/ +class AdobeRGBBase_ : public RGBBase_ +{ +public: + using RGBBase_::RGBBase_; + double gamma; + +private: + cv::Mat toLFunc(cv::Mat& rgb) CV_OVERRIDE + { + return gammaCorrection(rgb, gamma); + } + + cv::Mat fromLFunc(cv::Mat& rgbl) CV_OVERRIDE + { + return gammaCorrection(rgbl, 1. / gamma); + } +}; + +/* *\ brief Base of sRGB color space; +*/ +class sRGBBase_ : public RGBBase_ +{ +public: + using RGBBase_::RGBBase_; + double a; + double gamma; + double alpha; + double beta; + double phi; + double K0; + +private: + /* *\ brief linearization parameters + * see ColorSpace.pdf for details; + */ + virtual void calLinear() CV_OVERRIDE + { + alpha = a + 1; + K0 = a / (gamma - 1); + phi = (pow(alpha, gamma) * pow(gamma - 1, gamma - 1)) / (pow(a, gamma - 1) * pow(gamma, gamma)); + beta = K0 / phi; + } + + /* *\ brief Used by toLFunc. + */ + double toLFuncEW(double& x) + { + if (x > K0) + { + return pow(((x + alpha - 1) / alpha), gamma); + } + else if (x >= -K0) + { + return x / phi; + } + else + { + return -(pow(((-x + alpha - 1) / alpha), gamma)); + } + } + + /* *\ brief Linearization. + * see ColorSpace.pdf for details. + *\ param rgb the input array, type of cv::Mat. + *\ return the output array, type of cv::Mat. + */ + cv::Mat toLFunc(cv::Mat& rgb) CV_OVERRIDE + { + return elementWise(rgb, [this](double a_)->double {return toLFuncEW(a_); }); + } + + /* *\ brief Used by fromLFunc. + */ + double fromLFuncEW(double& x) + { + if (x > beta) + { + return alpha * pow(x, 1 / gamma) - (alpha - 1); + } + else if (x >= -beta) + { + return x * phi; + } + else + { + return -(alpha * pow(-x, 1 / gamma) - (alpha - 1)); + } + } + + /* *\ brief Delinearization. + * see ColorSpace.pdf for details. + *\ param rgbl the input array, type of cv::Mat. + *\ return the output array, type of cv::Mat. + */ + cv::Mat fromLFunc(cv::Mat& rgbl) CV_OVERRIDE + { + return elementWise(rgbl, [this](double a_)->double {return fromLFuncEW(a_); }); + } +}; + +/* *\ brief sRGB color space. + * data from https://en.wikipedia.org/wiki/SRGB. +*/ +class sRGB_ :public sRGBBase_ +{ +public: + sRGB_(bool linear_) :sRGBBase_(D65_2, "sRGB", linear_) {}; + +private: + void setParameter() CV_OVERRIDE + { + xr = 0.64; + yr = 0.33; + xg = 0.3; + yg = 0.6; + xb = 0.15; + yb = 0.06; + a = 0.055; + gamma = 2.4; + } +}; + +/* *\ brief Adobe RGB color space. +*/ +class AdobeRGB_ : public AdobeRGBBase_ +{ +public: + AdobeRGB_(bool linear_ = false) :AdobeRGBBase_(D65_2, "AdobeRGB", linear_) {}; + +private: + void setParameter() CV_OVERRIDE + { + xr = 0.64; + yr = 0.33; + xg = 0.21; + yg = 0.71; + xb = 0.15; + yb = 0.06; + gamma = 2.2; + } +}; + +/* *\ brief Wide-gamut RGB color space. + * data from https://en.wikipedia.org/wiki/Wide-gamut_RGB_color_space. +*/ +class WideGamutRGB_ : public AdobeRGBBase_ +{ +public: + WideGamutRGB_(bool linear_ = false) :AdobeRGBBase_(D50_2, "WideGamutRGB", linear_) {}; + +private: + void setParameter() CV_OVERRIDE + { + xr = 0.7347; + yr = 0.2653; + xg = 0.1152; + yg = 0.8264; + xb = 0.1566; + yb = 0.0177; + gamma = 2.2; + } +}; + +/* *\ brief ProPhoto RGB color space. + * data from https://en.wikipedia.org/wiki/ProPhoto_RGB_color_space. +*/ +class ProPhotoRGB_ : public AdobeRGBBase_ +{ +public: + ProPhotoRGB_(bool linear_ = false) :AdobeRGBBase_(D50_2, "ProPhotoRGB", linear_) {}; + +private: + void setParameter() CV_OVERRIDE + { + xr = 0.734699; + yr = 0.265301; + xg = 0.159597; + yg = 0.840403; + xb = 0.036598; + yb = 0.000105; + gamma = 1.8; + } +}; + +/* *\ brief DCI-P3 RGB color space. + * data from https://en.wikipedia.org/wiki/DCI-P3. +*/ +class DCI_P3_RGB_ : public AdobeRGBBase_ +{ +public: + DCI_P3_RGB_(bool linear_ = false) :AdobeRGBBase_(D65_2, "DCI_P3_RGB", linear_) {}; + +private: + void setParameter() CV_OVERRIDE + { + xr = 0.68; + yr = 0.32; + xg = 0.265; + yg = 0.69; + xb = 0.15; + yb = 0.06; + gamma = 2.2; + } +}; + +/* *\ brief Apple RGB color space. + * data from http://www.brucelindbloom.com/index.html?WorkingSpaceInfo.html. +*/ +class AppleRGB_ : public AdobeRGBBase_ +{ +public: + AppleRGB_(bool linear_ = false) :AdobeRGBBase_(D65_2, "AppleRGB", linear_) {}; + +private: + void setParameter() CV_OVERRIDE + { + xr = 0.625; + yr = 0.34; + xg = 0.28; + yg = 0.595; + xb = 0.155; + yb = 0.07; + gamma = 1.8; + } +}; + +/* *\ brief REC_709 RGB color space. + * data from https://en.wikipedia.org/wiki/Rec._709. +*/ +class REC_709_RGB_ : public sRGBBase_ +{ +public: + REC_709_RGB_(bool linear_) :sRGBBase_(D65_2, "REC_709_RGB", linear_) {}; + +private: + void setParameter() CV_OVERRIDE + { + xr = 0.64; + yr = 0.33; + xg = 0.3; + yg = 0.6; + xb = 0.15; + yb = 0.06; + a = 0.099; + gamma = 1 / 0.45; + } +}; + +/* *\ brief REC_2020 RGB color space. + * data from https://en.wikipedia.org/wiki/Rec._2020. +*/ +class REC_2020_RGB_ : public sRGBBase_ +{ +public: + REC_2020_RGB_(bool linear_) :sRGBBase_(D65_2, "REC_2020_RGB", linear_) {}; + +private: + void setParameter() CV_OVERRIDE + { + xr = 0.708; + yr = 0.292; + xg = 0.17; + yg = 0.797; + xb = 0.131; + yb = 0.046; + a = 0.09929682680944; + gamma = 1 / 0.45; + } +}; + + +sRGB_ sRGB(false), sRGBL(true); +AdobeRGB_ AdobeRGB(false), AdobeRGBL(true); +WideGamutRGB_ WideGamutRGB(false), WideGamutRGBL(true); +ProPhotoRGB_ ProPhotoRGB(false), ProPhotoRGBL(true); +DCI_P3_RGB_ DCI_P3_RGB(false), DCI_P3_RGBL(true); +AppleRGB_ AppleRGB(false), AppleRGBL(true); +REC_709_RGB_ REC_709_RGB(false), REC_709_RGBL(true); +REC_2020_RGB_ REC_2020_RGB(false), REC_2020_RGBL(true); + +/* *\ brief Bind RGB with RGBL. +*/ +class ColorSpaceInitial +{ +public: + ColorSpaceInitial() + { + sRGB.bind(sRGBL); + AdobeRGB.bind(AdobeRGBL); + WideGamutRGB.bind(WideGamutRGBL); + ProPhotoRGB.bind(ProPhotoRGBL); + DCI_P3_RGB.bind(DCI_P3_RGBL); + AppleRGB.bind(AppleRGBL); + REC_709_RGB.bind(REC_709_RGBL); + REC_2020_RGB.bind(REC_2020_RGBL); + + } +}; + +ColorSpaceInitial color_space_initial; + + +/* *\ brief Enum of the possible types of CAMs. +*/ +enum CAM +{ + IDENTITY, + VON_KRIES, + BRADFORD +}; + +static std::map , cv::Mat > cams; +const static cv::Mat Von_Kries = (cv::Mat_(3, 3) << 0.40024, 0.7076, -0.08081, -0.2263, 1.16532, 0.0457, 0., 0., 0.91822); +const static cv::Mat Bradford = (cv::Mat_(3, 3) << 0.8951, 0.2664, -0.1614, -0.7502, 1.7135, 0.0367, 0.0389, -0.0685, 1.0296); +const static std::map > MAs = { + {IDENTITY , { cv::Mat::eye(cv::Size(3,3),CV_64FC1) , cv::Mat::eye(cv::Size(3,3),CV_64FC1)} }, + {VON_KRIES, { Von_Kries ,Von_Kries.inv() }}, + {BRADFORD, { Bradford ,Bradford.inv() }} +}; + +/* *\ brief XYZ color space. + * Chromatic adaption matrices. +*/ +class XYZ :public ColorSpace +{ +public: + XYZ(IO io_) : ColorSpace(io_, "XYZ", true) {}; + Operations cam(IO dio, CAM method = BRADFORD) + { + return (io == dio) ? Operations() : Operations({ Operation(cam_(io, dio, method).t()) }); + } + +private: + /* *\ brief Get cam. + *\ param sio the input IO of src. + *\ param dio the input IO of dst. + *\ param method type of CAM. + *\ return the output array, type of cv::Mat. + */ + cv::Mat cam_(IO sio, IO dio, CAM method = BRADFORD) const + { + if (sio == dio) + { + return cv::Mat::eye(cv::Size(3, 3), CV_64FC1); + } + if (cams.count(std::make_tuple(dio, sio, method)) == 1) + { + return cams[std::make_tuple(dio, sio, method)]; + } + + // Function from http ://www.brucelindbloom.com/index.html?ColorCheckerRGB.html. + cv::Mat XYZws = cv::Mat(illuminants.find(dio)->second); + cv::Mat XYZWd = cv::Mat(illuminants.find(sio)->second); + cv::Mat MA = MAs.at(method)[0]; + cv::Mat MA_inv = MAs.at(method)[1]; + cv::Mat M = MA_inv * cv::Mat::diag((MA * XYZws) / (MA * XYZWd)) * MA; + cams[std::make_tuple(dio, sio, method)] = M; + cams[std::make_tuple(sio, dio, method)] = M.inv(); + return M; + } +}; + +/* *\ brief Define XYZ_D65_2 and XYZ_D50_2. +*/ +const XYZ XYZ_D65_2(D65_2); +const XYZ XYZ_D50_2(D50_2); + + +/* *\ brief Lab color space. +*/ +class Lab :public ColorSpace +{ +public: + Lab(IO io_) : ColorSpace(io_, "Lab", true) + { + to = { Operation([this](cv::Mat src)->cv::Mat {return tosrc(src); }) }; + from = { Operation([this](cv::Mat src)->cv::Mat {return fromsrc(src); }) }; + } + +private: + static constexpr double delta = (6. / 29.); + static constexpr double m = 1. / (3. * delta * delta); + static constexpr double t0 = delta * delta * delta; + static constexpr double c = 4. / 29.; + + cv::Vec3d fromxyz(cv::Vec3d& xyz) + { + double x = xyz[0] / illuminants.find(io)->second[0], y = xyz[1] / illuminants.find(io)->second[1], z = xyz[2] / illuminants.find(io)->second[2]; + auto f = [](double t)->double { return t > t0 ? std::cbrt(t) : (m * t + c); }; + double fx = f(x), fy = f(y), fz = f(z); + return { 116. * fy - 16. ,500 * (fx - fy),200 * (fy - fz) }; + } + + /* *\ brief Calculate From. + *\ param src the input array, type of cv::Mat. + *\ return the output array, type of cv::Mat + */ + cv::Mat fromsrc(cv::Mat& src) + { + return channelWise(src, [this](cv::Vec3d a)->cv::Vec3d {return fromxyz(a); }); + } + + cv::Vec3d tolab(cv::Vec3d& lab) + { + auto f_inv = [](double t)->double {return t > delta ? pow(t, 3.0) : (t - c) / m; }; + double L = (lab[0] + 16.) / 116., a = lab[1] / 500., b = lab[2] / 200.; + return { illuminants.find(io)->second[0] * f_inv(L + a),illuminants.find(io)->second[1] * f_inv(L),illuminants.find(io)->second[2] * f_inv(L - b) }; + } + + /* *\ brief Calculate To. + *\ param src the input array, type of cv::Mat. + *\ return the output array, type of cv::Mat + */ + cv::Mat tosrc(cv::Mat& src) + { + return channelWise(src, [this](cv::Vec3d a)->cv::Vec3d {return tolab(a); }); + } +}; + +/* *\ brief Define Lab_D65_2 and Lab_D50_2. +*/ +const Lab Lab_D65_2(D65_2); +const Lab Lab_D50_2(D50_2); + +} // namespace ccm +} // namespace cv + + +#endif \ No newline at end of file diff --git a/modules/mcc/include/opencv2/mcc/linearize.hpp b/modules/mcc/include/opencv2/mcc/linearize.hpp new file mode 100644 index 00000000000..3dff3b87572 --- /dev/null +++ b/modules/mcc/include/opencv2/mcc/linearize.hpp @@ -0,0 +1,304 @@ +// This file is part of OpenCV project. +// It is subject to the license terms in the LICENSE file found in the top-level directory +// of this distribution and at http://opencv.org/license.html. +// +// +// License Agreement +// For Open Source Computer Vision Library +// +// Copyright(C) 2020, Huawei Technologies Co.,Ltd. All rights reserved. +// Third party copyrights are property of their respective owners. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// Author: Longbu Wang +// Jinheng Zhang +// Chenqi Shan + +#ifndef __OPENCV_MCC_LINEARIZE_HPP__ +#define __OPENCV_MCC_LINEARIZE_HPP__ + +#include "opencv2/mcc/color.hpp" + +namespace cv +{ +namespace ccm +{ + +/* *\ brief Enum of the possible types of linearization. +*/ +enum LINEAR_TYPE +{ + IDENTITY_, + GAMMA, + COLORPOLYFIT, + COLORLOGPOLYFIT, + GRAYPOLYFIT, + GRAYLOGPOLYFIT +}; + +/* *\ brief Polyfit model. +*/ +class Polyfit +{ +public: + int deg; + cv::Mat p; + + Polyfit() {}; + + /* *\ brief Polyfit method. + https://en.wikipedia.org/wiki/Polynomial_regression + polynomial: yi = a0 + a1*xi + a2*xi^2 + ... + an*xi^deg (i = 1,2,...,n) + and deduct: Ax = y + See linear.pdf for details + */ + Polyfit(cv::Mat x, cv::Mat y, int deg_) :deg(deg_) + { + int n = x.cols * x.rows * x.channels(); + x = x.reshape(1, n); + y = y.reshape(1, n); + cv::Mat_ A = cv::Mat_::ones(n, deg + 1); + for (int i = 0; i < n; ++i) + { + for (int j = 1; j < A.cols; ++j) + { + A.at(i, j) = x.at(i) * A.at(i, j - 1); + } + } + cv::Mat y_(y); + cv::solve(A, y_, p, DECOMP_SVD); + } + + virtual ~Polyfit() {}; + cv::Mat operator()(const cv::Mat& inp) + { + return elementWise(inp, [this](double x)->double {return fromEW(x); }); + }; + +private: + double fromEW(double x) + { + double res = 0; + for (int d = 0; d <= deg; ++d) + { + res += pow(x, d) * p.at(d, 0); + } + return res; + }; +}; + +/* *\ brief Logpolyfit model. +*/ +class LogPolyfit +{ +public: + int deg; + Polyfit p; + + LogPolyfit() {}; + + /* *\ brief Logpolyfit method. + */ + LogPolyfit(cv::Mat x, cv::Mat y, int deg_) :deg(deg_) + { + cv::Mat mask_ = (x > 0) & (y > 0); + cv::Mat src_, dst_, s_, d_; + src_ = maskCopyTo(x, mask_); + dst_ = maskCopyTo(y, mask_); + log(src_, s_); + log(dst_, d_); + p = Polyfit(s_, d_, deg); + } + + virtual ~LogPolyfit() {}; + + cv::Mat operator()(const cv::Mat& inp) + { + cv::Mat mask_ = inp >= 0; + cv::Mat y, y_, res; + log(inp, y); + y = p(y); + exp(y, y_); + y_.copyTo(res, mask_); + return res; + }; +}; + +/* *\ brief Linearization base. +*/ +class Linear +{ +public: + Linear() {}; + + virtual ~Linear() {}; + + /* *\ brief Inference. + *\ param inp the input array, type of cv::Mat. + */ + virtual cv::Mat linearize(cv::Mat inp) + { + return inp; + }; + + /* *\brief Evaluate linearization model. + */ + virtual void value(void) {}; +}; + + +/* *\ brief Linearization identity. + * make no change. +*/ +class LinearIdentity : public Linear {}; + +/* *\ brief Linearization gamma correction. +*/ +class LinearGamma : public Linear +{ +public: + double gamma; + + LinearGamma(double gamma_) :gamma(gamma_) {}; + + cv::Mat linearize(cv::Mat inp) CV_OVERRIDE + { + return gammaCorrection(inp, gamma); + }; +}; + +/* *\ brief Linearization. + * Grayscale polynomial fitting. +*/ +template +class LinearGray :public Linear +{ +public: + int deg; + T p; + + LinearGray(int deg_, cv::Mat src, Color dst, cv::Mat mask, RGBBase_ cs) :deg(deg_) + { + dst.getGray(); + Mat lear_gray_mask = mask & dst.grays; + + // the grayscale function is approximate for src is in relative color space. + src = rgb2gray(maskCopyTo(src, lear_gray_mask)); + cv::Mat dst_ = maskCopyTo(dst.toGray(cs.io), lear_gray_mask); + calc(src, dst_); + } + + /* *\ brief monotonically increase is not guaranteed. + *\ param src the input array, type of cv::Mat. + *\ param dst the input array, type of cv::Mat. + */ + void calc(const cv::Mat& src, const cv::Mat& dst) + { + p = T(src, dst, deg); + }; + + cv::Mat linearize(cv::Mat inp) CV_OVERRIDE + { + return p(inp); + }; +}; + +/* *\ brief Linearization. + * Fitting channels respectively. +*/ +template +class LinearColor :public Linear +{ +public: + int deg; + T pr; + T pg; + T pb; + + LinearColor(int deg_, cv::Mat src_, Color dst, cv::Mat mask, RGBBase_ cs) :deg(deg_) + { + Mat src = maskCopyTo(src_, mask); + cv::Mat dst_ = maskCopyTo(dst.to(*cs.l).colors, mask); + calc(src, dst_); + } + + void calc(const cv::Mat& src, const cv::Mat& dst) + { + cv::Mat schannels[3]; + cv::Mat dchannels[3]; + split(src, schannels); + split(dst, dchannels); + pr = T(schannels[0], dchannels[0], deg); + pg = T(schannels[1], dchannels[1], deg); + pb = T(schannels[2], dchannels[2], deg); + }; + + cv::Mat linearize(cv::Mat inp) CV_OVERRIDE + { + cv::Mat channels[3]; + split(inp, channels); + std::vector channel; + cv::Mat res; + merge(std::vector{ pr(channels[0]), pg(channels[1]), pb(channels[2]) }, res); + return res; + }; +}; + + +/* *\ brief Get linearization method. + * used in ccm model. + *\ param gamma used in LinearGamma. + *\ param deg degrees. + *\ param src the input array, type of cv::Mat. + *\ param dst the input array, type of cv::Mat. + *\ param mask the input array, type of cv::Mat. + *\ param cs type of RGBBase_. + *\ param linear_type type of linear. +*/ +std::shared_ptr getLinear(double gamma, int deg, cv::Mat src, Color dst, cv::Mat mask, RGBBase_ cs, LINEAR_TYPE linear_type); +std::shared_ptr getLinear(double gamma, int deg, cv::Mat src, Color dst, cv::Mat mask, RGBBase_ cs, LINEAR_TYPE linear_type) +{ + std::shared_ptr p = std::make_shared(); + switch (linear_type) + { + case cv::ccm::IDENTITY_: + p.reset(new LinearIdentity()); + break; + case cv::ccm::GAMMA: + p.reset(new LinearGamma(gamma)); + break; + case cv::ccm::COLORPOLYFIT: + p.reset(new LinearColor(deg, src, dst, mask, cs)); + break; + case cv::ccm::COLORLOGPOLYFIT: + p.reset(new LinearColor(deg, src, dst, mask, cs)); + break; + case cv::ccm::GRAYPOLYFIT: + p.reset(new LinearGray(deg, src, dst, mask, cs)); + break; + case cv::ccm::GRAYLOGPOLYFIT: + p.reset(new LinearGray(deg, src, dst, mask, cs)); + break; + default: + throw std::invalid_argument{ "Wrong linear_type!" }; + break; + } + return p; +}; + +} // namespace ccm +} // namespace cv + + +#endif From 4c8f94a0bf868fbe394c76e86d0153fcbd9d9b62 Mon Sep 17 00:00:00 2001 From: Jinheng Zhang Date: Thu, 10 Sep 2020 21:19:44 +0800 Subject: [PATCH 3/8] Add the dependencies to opencv_imgcodecs in CMakeLists.txt --- modules/mcc/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/mcc/CMakeLists.txt b/modules/mcc/CMakeLists.txt index 806303505f3..c45daaffab3 100644 --- a/modules/mcc/CMakeLists.txt +++ b/modules/mcc/CMakeLists.txt @@ -1,2 +1,2 @@ set(the_description "Macbeth Chart Detection") -ocv_define_module(mcc opencv_core opencv_imgproc opencv_calib3d opencv_photo opencv_dnn WRAP python) +ocv_define_module(mcc opencv_core opencv_imgproc opencv_imgcodecs opencv_calib3d opencv_photo opencv_dnn WRAP python) From 9fba07c373e35912f6e46d86c0d678cc86b9a67b Mon Sep 17 00:00:00 2001 From: Jinheng Zhang Date: Thu, 10 Sep 2020 21:20:57 +0800 Subject: [PATCH 4/8] Add color correction model sample code. Co-authored-by: Chenqi Shan --- .../mcc/samples/color_correction_model.cpp | 124 ++++++++++++++++++ 1 file changed, 124 insertions(+) create mode 100644 modules/mcc/samples/color_correction_model.cpp diff --git a/modules/mcc/samples/color_correction_model.cpp b/modules/mcc/samples/color_correction_model.cpp new file mode 100644 index 00000000000..fd5ffef8f2b --- /dev/null +++ b/modules/mcc/samples/color_correction_model.cpp @@ -0,0 +1,124 @@ +#include + +#include +#include +#include +#include +#include + +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 usage ? | | 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); + + int t = parser.get("t"); + int nc = parser.get("nc"); + string filepath = parser.get("f"); + + CV_Assert(0 <= t && t <= 2); + TYPECHART chartType = TYPECHART(t); + + cout << "t: " << t << " , nc: " << nc << ", \nf: " << filepath << endl; + + if (!parser.check()) + { + parser.printErrors(); + return 0; + } + + Mat image = cv::imread(filepath, IMREAD_COLOR); + if (!image.data) + { + cout << "Invalid Image!" << endl; + return 1; + } + + Mat imageCopy = image.clone(); + Ptr detector = CCheckerDetector::create(); + // Marker type to detect + if (!detector->process(image, chartType, nc)) + { + printf("ChartColor not detected \n"); + return 2; + } + // get checker + vector> checkers = detector->getListColorChecker(); + + for (Ptr checker : checkers) + { + Ptr cdraw = CCheckerDraw::create(checker); + cdraw->draw(image); + Mat chartsRGB = checker->getChartsRGB(); + Mat src = chartsRGB.col(1).clone().reshape(3, 18); + src /= 255.0; + + //compte color correction matrix + ColorCorrectionModel model1(src, Vinyl_D50_2); + + /* brief More models with different parameters, try it & check the document for details. + */ + // ColorCorrectionModel model2(src, Vinyl_D50_2, AdobeRGB, CCM_4x3, CIE2000, GAMMA, 2.2, 3); + // ColorCorrectionModel model3(src, Vinyl_D50_2, WideGamutRGB, CCM_4x3, CIE2000, GRAYPOLYFIT, 2.2, 3); + // ColorCorrectionModel model4(src, Vinyl_D50_2, ProPhotoRGB, CCM_4x3, RGBL, GRAYLOGPOLYFIT, 2.2, 3); + // ColorCorrectionModel model5(src, Vinyl_D50_2, DCI_P3_RGB, CCM_3x3, RGB, IDENTITY_, 2.2, 3); + // ColorCorrectionModel model6(src, Vinyl_D50_2, AppleRGB, CCM_3x3, CIE2000, COLORPOLYFIT, 2.2, 2,{ 0, 0.98 },Mat(),2); + // ColorCorrectionModel model7(src, Vinyl_D50_2, REC_2020_RGB, CCM_3x3, CIE94_GRAPHIC_ARTS, COLORLOGPOLYFIT, 2.2, 3); + + /* 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_(18, 1) << + // Vec3d(1.00000000e+02, 5.20000001e-03, -1.04000000e-02), + // Vec3d(7.30833969e+01, -8.19999993e-01, -2.02099991e+00), + // Vec3d(6.24930000e+01, 4.25999999e-01, -2.23099995e+00), + // Vec3d(5.04640007e+01, 4.46999997e-01, -2.32399988e+00), + // Vec3d(3.77970009e+01, 3.59999985e-02, -1.29700005e+00), + // Vec3d(0.00000000e+00, 0.00000000e+00, 0.00000000e+00), + // Vec3d(5.15880013e+01, 7.35179977e+01, 5.15690002e+01), + // Vec3d(9.36989975e+01, -1.57340002e+01, 9.19420013e+01), + // Vec3d(6.94079971e+01, -4.65940018e+01, 5.04869995e+01), + // Vec3d(6.66100006e+01, -1.36789999e+01, -4.31720009e+01), + // Vec3d(1.17110004e+01, 1.69799995e+01, -3.71759987e+01), + // Vec3d(5.19739990e+01, 8.19440002e+01, -8.40699959e+00), + // Vec3d(4.05489998e+01, 5.04399986e+01, 2.48490009e+01), + // Vec3d(6.08160019e+01, 2.60690002e+01, 4.94420013e+01), + // Vec3d(5.22529984e+01, -1.99500008e+01, -2.39960003e+01), + // Vec3d(5.12859993e+01, 4.84700012e+01, -1.50579996e+01), + // Vec3d(6.87070007e+01, 1.22959995e+01, 1.62129993e+01), + // Vec3d(6.36839981e+01, 1.02930002e+01, 1.67639999e+01)); + + // ColorCorrectionModel model8(src,ref,Lab_D50_2); + + //make color correction + Mat calibratedImage = model1.inferImage(filepath); + + // Save the calibrated image to {FILE_NAME}.calibrated.{FILE_EXT} + string filename = filepath.substr(filepath.find_last_of('/')+1); + int 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; + cv::imwrite(calibratedFilePath, calibratedImage); + } + + return 0; +} From e8e6d9618d4a0201f6e71f1276335d181546e714 Mon Sep 17 00:00:00 2001 From: Jinheng Zhang Date: Thu, 10 Sep 2020 21:21:46 +0800 Subject: [PATCH 5/8] Add the index markdown of color correction tutorial. Co-authored-by: Chenqi Shan --- modules/mcc/tutorials/table_of_content_ccm.markdown | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 modules/mcc/tutorials/table_of_content_ccm.markdown diff --git a/modules/mcc/tutorials/table_of_content_ccm.markdown b/modules/mcc/tutorials/table_of_content_ccm.markdown new file mode 100644 index 00000000000..178f309b993 --- /dev/null +++ b/modules/mcc/tutorials/table_of_content_ccm.markdown @@ -0,0 +1,8 @@ +Color Correction Model {#tutorial_table_of_content_ccm} +=========================== + +- @subpage tutorial_ccm_color_correction_model + + *Author:* riskiest, shanchenqi, JinhengZhang + + How to do color correction, using Color Correction Model. \ No newline at end of file From 3dc3d87e6d2310bf9f76e2f3af3b0cbb2d01fa32 Mon Sep 17 00:00:00 2001 From: Chenqi Shan Date: Thu, 10 Sep 2020 21:23:04 +0800 Subject: [PATCH 6/8] Add the introduction for color correction sample. --- .../basic_ccm/color_correction_model.markdown | 238 ++++++++++++++++++ 1 file changed, 238 insertions(+) create mode 100644 modules/mcc/tutorials/basic_ccm/color_correction_model.markdown diff --git a/modules/mcc/tutorials/basic_ccm/color_correction_model.markdown b/modules/mcc/tutorials/basic_ccm/color_correction_model.markdown new file mode 100644 index 00000000000..0999c92cd72 --- /dev/null +++ b/modules/mcc/tutorials/basic_ccm/color_correction_model.markdown @@ -0,0 +1,238 @@ +Color Correction Model{#tutorial_ccm_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 module: + +```make +cmake -D OPENCV_EXTRA_MODULES_PATH=/modules/ +``` + +Or only build the mcc module: + +```make +cmake -D OPENCV_EXTRA_MODULES_PATH=/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[basic_chart_detection](https://github.com/opencv/opencv_contrib/tree/master/modules/mcc/tutorials/basic_chart_detection), 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]; + type: cv::Mat; +dst : + the reference colors; + NOTICE: Built-in color card or custom color card are supported; + Built-in: + Macbeth_D50_2: Macbeth ColorChecker with 2deg D50; + Macbeth_D65_2: Macbeth ColorChecker with 2deg D65; + Custom: + You should use Color + For the list of color spaces supported, see the notes below; + If the color type is some RGB, the format is RGB not BGR, and the color values are in [0, 1]; + type: Color; +colorspace : + the absolute color space that detected colors convert to; + NOTICE: it should be some RGB color space; + For the list of RGB color spaces supported, see the notes below; + type: ColorSpace; +ccm_type : + the shape of color correction matrix(CCM); + Supported list: + "CCM_3x3": 3x3 matrix; + "CCM_4x3": 4x3 matrix; + type: enum CCM_TYPE; +distance : + the type of color distance; + Supported list: + "CIE2000"; + "CIE94_GRAPHIC_ARTS"; + "CIE94_TEXTILES"; + "CIE76"; + "CMC_1TO1"; + "CMC_2TO1"; + "RGB" : Euclidean distance of rgb color space; + "RGBL" : Euclidean distance of rgbl color space; + type: enum DISTANCE_TYPE; +linear_type : + the method of linearization; + NOTICE: see Linearization.pdf for details; + Supported list: + "IDENTITY_" : no change is made; + "GAMMA": gamma correction; + Need assign a value to gamma simultaneously; + "COLORPOLYFIT": polynomial fitting channels respectively; + Need assign a value to deg simultaneously; + "GRAYPOLYFIT": grayscale polynomial fitting; + Need assign a value to deg and dst_whites simultaneously; + "COLORLOGPOLYFIT": logarithmic polynomial fitting channels respectively; + Need assign a value to deg simultaneously; + "GRAYLOGPOLYFIT": grayscale Logarithmic polynomial fitting; + Need assign a value to deg and dst_whites simultaneously; + type: enum LINEAR_TYPE; +gamma : + the gamma value of gamma correction; + NOTICE: only valid when linear is set to "gamma"; + type: double; + +deg : + the degree of linearization polynomial; + NOTICE: only valid when linear is set to "COLORPOLYFIT", "GRAYPOLYFIT", + "COLORLOGPOLYFIT" and "GRAYLOGPOLYFIT"; + type: int; +saturated_threshold : + the threshold to determine saturation; + NOTICE: it is a tuple of [low, up]; + The colors in the closed interval [low, up] are reserved to participate + in the calculation of the loss function and initialization parameters. + type: std::vector; +--------------------------------------------------- +There are some ways to set weights: + 1. set weights_list only; + 2. set weights_coeff only; +see CCM.pdf for details; + +weights_list : + the list of weight of each color; + type: cv::Mat; + +weights_coeff : + the exponent number of L* component of the reference color in CIE Lab color space; + type: double; +--------------------------------------------------- +initial_method_type : + the method of calculating CCM initial value; + see CCM.pdf for details; + Supported list: + 'LEAST_SQUARE': least-squre method; + 'WHITE_BALANCE': white balance method; + +max_count, epsilon : + used in MinProblemSolver-DownhillSolver; + Terminal criteria to the algorithm; + type: int, double; + + +--------------------------------------------------- +Supported Color Space: + Supported list of RGB color spaces: + sRGB; + AdobeRGB; + WideGamutRGB; + ProPhotoRGB; + DCI_P3_RGB; + AppleRGB; + REC_709_RGB; + REC_2020_RGB; + + Supported list of linear RGB color spaces: + sRGBL; + AdobeRGBL; + WideGamutRGBL; + ProPhotoRGBL; + DCI_P3_RGBL; + AppleRGBL; + REC_709_RGBL; + REC_2020_RGBL; + + Supported list of non-RGB color spaces: + Lab_D50_2; + Lab_D65_2; + XYZ_D50_2; + XYZ_D65_2; + + Supported IO (You can use Lab(io) or XYZ(io) to create color space): + A_2; + A_10; + D50_2; + D50_10; + D55_2; + D55_10; + D65_2; + D65_10; + D75_2; + D75_10; + E_2; + E_10; +``` + + +## Code + +@include mcc/samples/color_correction_model.cpp + +## Explanation + + The first part is to detect the ColorChecker position. + +@code{.cpp}#include #include #include #include #include #include using namespace std;using namespace cv;using namespace mcc;using namespace ccm;using namespace std;@endcode + +``` +Here is sets of header and namespaces. You can set other namespace like the code above. +``` + +@code{.cpp} + +const char *about = "Basic chart detection";const char *keys = { "{ help h usage ? | | 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 }"};@ endcode + +``` +Some arguments for ColorChecker detection. +``` + +@code{.cpp} CommandLineParser parser(argc, argv, keys); parser.about(about); int t = parser.get("t"); int nc = parser.get("nc"); string filepath = parser.get("f"); CV_Assert(0 <= t && t <= 2); TYPECHART chartType = TYPECHART(t); cout << "t: " << t << " , nc: " << nc << ", \nf: " << filepath << endl;if (!parser.check()){ parser.printErrors();return 0;} Mat image = cv::imread(filepath, IMREAD_COLOR);if (!image.data) { cout << "Invalid Image!" << endl; return 1; } @endcode + +``` +Preparation for ColorChecker detection to get messages for the image. +``` + +@code{.cpp}Mat imageCopy = image.clone(); + + Ptr detector = CCheckerDetector::create(); if (!detector->process(image, chartType, nc)) { printf("ChartColor not detected \n"); return 2; } vector> checkers = detector->getListColorChecker();@endcode + +``` +The CCheckerDetectorobject is created and uses getListColorChecker function to get ColorChecker message. +``` + +@code{.cpp} for (Ptr checker : checkers) { Ptr cdraw = CCheckerDraw::create(checker); cdraw->draw(image); Mat chartsRGB = checker->getChartsRGB();Mat src = chartsRGB.col(1).clone().reshape(3, 18); src /= 255.0; //compte color correction matrix ColorCorrectionModel model1(src, Vinyl_D50_2);}@endcode + +``` +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. +``` + +@code{.cpp}cv::Mat ref = = (Mat_(18, 1) < Date: Fri, 11 Sep 2020 17:38:19 +0800 Subject: [PATCH 7/8] fix building warning, namespace problem --- modules/mcc/include/opencv2/mcc/ccm.hpp | 166 ++++++++---------- modules/mcc/include/opencv2/mcc/color.hpp | 43 +++-- .../mcc/include/opencv2/mcc/colorspace.hpp | 72 ++++---- modules/mcc/include/opencv2/mcc/distance.hpp | 12 +- modules/mcc/include/opencv2/mcc/io.hpp | 1 + modules/mcc/include/opencv2/mcc/linearize.hpp | 62 +++---- .../mcc/include/opencv2/mcc/operations.hpp | 17 +- modules/mcc/include/opencv2/mcc/utils.hpp | 57 +++--- .../mcc/samples/color_correction_model.cpp | 2 +- 9 files changed, 209 insertions(+), 223 deletions(-) diff --git a/modules/mcc/include/opencv2/mcc/ccm.hpp b/modules/mcc/include/opencv2/mcc/ccm.hpp index 9a581cf9112..c7e94ba1d6f 100644 --- a/modules/mcc/include/opencv2/mcc/ccm.hpp +++ b/modules/mcc/include/opencv2/mcc/ccm.hpp @@ -221,11 +221,15 @@ class ColorCorrectionModel { public: // detected colors, the referenceand the RGB colorspace for conversion - cv::Mat src; + Mat src; Color dst; - + Mat dist; RGBBase_& cs; - cv::Mat mask; + Mat mask; + + // RGBl of detected data and the reference + Mat src_rgbl; + Mat dst_rgbl; // ccm type and shape CCM_TYPE ccm_type; @@ -235,26 +239,26 @@ class ColorCorrectionModel std::shared_ptr linear; DISTANCE_TYPE distance; - cv::Mat weights; - cv::Mat ccm; - cv::Mat ccm0; + Mat weights; + Mat ccm; + Mat ccm0; + int masked_len; double loss; - int max_count; double epsilon; - ColorCorrectionModel(cv::Mat src_, cv::Mat colors_, const ColorSpace& ref_cs_, RGBBase_& cs_=sRGB, CCM_TYPE ccm_type_ = CCM_3x3, DISTANCE_TYPE distance_ = CIE2000, LINEAR_TYPE linear_type = GAMMA, - double gamma = 2.2, int deg = 3, std::vector saturated_threshold = { 0, 0.98 }, cv::Mat weights_list = Mat(), double weights_coeff = 0, + ColorCorrectionModel(Mat src_, Mat colors_, const ColorSpace& ref_cs_, RGBBase_& cs_=sRGB, CCM_TYPE ccm_type_ = CCM_3x3, DISTANCE_TYPE distance_ = CIE2000, LINEAR_TYPE linear_type = GAMMA, + double gamma = 2.2, int deg = 3, std::vector saturated_threshold = { 0, 0.98 }, Mat weights_list = Mat(), double weights_coeff = 0, INITIAL_METHOD_TYPE initial_method_type = LEAST_SQUARE, int max_count_ = 5000, double epsilon_ = 1.e-4) : ColorCorrectionModel(src_, Color(colors_, ref_cs_), cs_, ccm_type_, distance_, linear_type, gamma, deg, saturated_threshold, weights_list, weights_coeff, initial_method_type, max_count_, epsilon_) {} - ColorCorrectionModel(cv::Mat src_, Color dst_, RGBBase_& cs_= sRGB, CCM_TYPE ccm_type_ = CCM_3x3, DISTANCE_TYPE distance_ = CIE2000, LINEAR_TYPE linear_type = GAMMA, - double gamma = 2.2, int deg = 3, std::vector saturated_threshold = { 0, 0.98 }, cv::Mat weights_list = Mat(), double weights_coeff = 0, + ColorCorrectionModel(Mat src_, Color dst_, RGBBase_& cs_= sRGB, CCM_TYPE ccm_type_ = CCM_3x3, DISTANCE_TYPE distance_ = CIE2000, LINEAR_TYPE linear_type = GAMMA, + double gamma = 2.2, int deg = 3, std::vector saturated_threshold = { 0, 0.98 }, Mat weights_list = Mat(), double weights_coeff = 0, INITIAL_METHOD_TYPE initial_method_type = LEAST_SQUARE, int max_count_ = 5000, double epsilon_ = 1.e-4) : src(src_), dst(dst_), cs(cs_), ccm_type(ccm_type_), distance(distance_), max_count(max_count_), epsilon(epsilon_) { - cv::Mat saturate_mask = saturate(src, saturated_threshold[0], saturated_threshold[1]); + Mat saturate_mask = saturate(src, saturated_threshold[0], saturated_threshold[1]); this->linear = getLinear(gamma, deg, this->src, this->dst, saturate_mask, this->cs, linear_type); calWeightsMasks(weights_list, weights_coeff, saturate_mask); @@ -265,7 +269,6 @@ class ColorCorrectionModel // make no change for CCM_3x3, make change for CCM_4x3. src_rgbl = prepare(src_rgbl); - // distance function may affect the loss function and the fitting function switch (this->distance) { @@ -296,7 +299,7 @@ class ColorCorrectionModel *\ param inp the input array, type of cv::Mat. *\ return the output array, type of cv::Mat */ - cv::Mat prepare(const cv::Mat& inp) + Mat prepare(const Mat& inp) { switch (ccm_type) { @@ -306,9 +309,9 @@ class ColorCorrectionModel case cv::ccm::CCM_4x3: { shape = 12; - cv::Mat arr1 = cv::Mat::ones(inp.size(), CV_64F); - cv::Mat arr_out(inp.size(), CV_64FC4); - cv::Mat arr_channels[3]; + Mat arr1 = Mat::ones(inp.size(), CV_64F); + Mat arr_out(inp.size(), CV_64FC4); + Mat arr_channels[3]; split(inp, arr_channels); merge(std::vector{arr_channels[0], arr_channels[1], arr_channels[2], arr1}, arr_out); return arr_out; @@ -319,21 +322,55 @@ class ColorCorrectionModel } }; + /* *\ brief Calculate weights and mask. + *\ param weights_list the input array, type of cv::Mat. + *\ param weights_coeff type of double. + *\ param saturate_list the input array, type of cv::Mat. + */ + void calWeightsMasks(Mat weights_list, double weights_coeff, Mat saturate_mask) + { + // weights + if (!weights_list.empty()) + { + weights = weights_list; + } + else if (weights_coeff != 0) + { + pow(dst.toLuminant(dst.cs.io), weights_coeff, weights); + } + + // masks + Mat weight_mask = Mat::ones(src.rows, 1, CV_8U); + if (!weights.empty()) + { + weight_mask = weights > 0; + } + this->mask = (weight_mask) & (saturate_mask); + + // weights' mask + if (!weights.empty()) + { + Mat weights_masked = maskCopyTo(this->weights, this->mask); + weights = weights_masked / mean(weights_masked)[0]; + } + masked_len = (int)sum(mask)[0]; + }; + /* *\ brief Fitting nonlinear - optimization initial value by white balance. * see CCM.pdf for details. - *\ return the output array, type of cv::Mat + *\ return the output array, type of Mat */ - cv::Mat initialWhiteBalance(void) + Mat initialWhiteBalance(void) { - cv::Mat schannels[3]; + Mat schannels[3]; split(src_rgbl, schannels); - cv::Mat dchannels[3]; + Mat dchannels[3]; split(dst_rgbl, dchannels); std::vector initial_vec = { sum(dchannels[0])[0] / sum(schannels[0])[0], 0, 0, 0, sum(dchannels[1])[0] / sum(schannels[1])[0], 0, 0, 0, sum(dchannels[2])[0] / sum(schannels[2])[0], 0, 0, 0 }; std::vector initial_vec_(initial_vec.begin(), initial_vec.begin() + shape); - cv::Mat initial_white_balance = cv::Mat(initial_vec_, true).reshape(0, shape / 3); + Mat initial_white_balance = Mat(initial_vec_, true).reshape(0, shape / 3); return initial_white_balance; }; @@ -344,7 +381,7 @@ class ColorCorrectionModel */ void initialLeastSquare(bool fit = false) { - cv::Mat A, B, w; + Mat A, B, w; if (weights.empty()) { A = src_rgbl; @@ -353,7 +390,7 @@ class ColorCorrectionModel else { pow(weights, 0.5, w); - cv::Mat w_; + Mat w_; merge(std::vector{w, w, w}, w_); A = w_.mul(src_rgbl); B = w_.mul(dst_rgbl); @@ -364,7 +401,7 @@ class ColorCorrectionModel if (fit) { ccm = ccm0; - cv::Mat residual = A.reshape(1, A.rows) * ccm.reshape(0, shape / 3) - B.reshape(1, B.rows); + Mat residual = A.reshape(1, A.rows) * ccm.reshape(0, shape / 3) - B.reshape(1, B.rows); Scalar s = residual.dot(residual); double sum = s[0]; loss = sqrt(sum / masked_len); @@ -391,7 +428,7 @@ class ColorCorrectionModel */ double calc(const double* x) const CV_OVERRIDE { - cv::Mat ccm_(ccm_loss->shape, 1, CV_64F); + Mat ccm_(ccm_loss->shape, 1, CV_64F); for (int i = 0; i < ccm_loss->shape; i++) { ccm_.at(i, 0) = x[i]; @@ -403,9 +440,9 @@ class ColorCorrectionModel double calc_loss_(Color color) { - cv::Mat distlist = color.diff(dst, distance); + Mat distlist = color.diff(dst, distance); Color lab = color.to(Lab_D50_2); - cv::Mat dist_; + Mat dist_; pow(distlist, 2, dist_); if (!weights.empty()) { @@ -431,8 +468,8 @@ class ColorCorrectionModel cv::Ptr solver = cv::DownhillSolver::create(); cv::Ptr ptr_F(new LossFunction(this)); solver->setFunction(ptr_F); - cv::Mat reshapeccm = ccm0.clone().reshape(0, 1); - cv::Mat step = cv::Mat::ones(reshapeccm.size(), CV_64F); + Mat reshapeccm = ccm0.clone().reshape(0, 1); + Mat step = Mat::ones(reshapeccm.size(), CV_64F); solver->setInitStep(step); TermCriteria termcrit = TermCriteria(TermCriteria::MAX_ITER + TermCriteria::EPS, max_count, epsilon); solver->setTermCriteria(termcrit); @@ -447,15 +484,15 @@ class ColorCorrectionModel *\ param img the input image, type of cv::Mat. *\ return the output array, type of cv::Mat. */ - cv::Mat infer(const cv::Mat& img, bool islinear = false) + Mat infer(const Mat& img, bool islinear = false) { if (!ccm.data) { throw "No CCM values!"; } - cv::Mat img_lin = linear->linearize(img); - cv::Mat img_ccm(img_lin.size(), img_lin.type()); - cv::Mat ccm_ = ccm.reshape(0, shape / 3); + Mat img_lin = linear->linearize(img); + Mat img_ccm(img_lin.size(), img_lin.type()); + Mat ccm_ = ccm.reshape(0, shape / 3); img_ccm = multiple(prepare(img_lin), ccm_); if (islinear == true) { @@ -471,71 +508,24 @@ class ColorCorrectionModel *\ param islinear if linearize or not. *\ return the output array, type of cv::Mat. */ - cv::Mat inferImage(std::string imgfile, bool islinear = false) + Mat inferImage(std::string imgfile, bool islinear = false) { const int inp_size = 255; const int out_size = 255; - cv::Mat img = imread(imgfile); - cv::Mat img_; + Mat img = imread(imgfile); + Mat img_; cvtColor(img, img_, COLOR_BGR2RGB); img_.convertTo(img_, CV_64F); img_ = img_ / inp_size; - cv::Mat out = this->infer(img_, islinear); - cv::Mat out_ = out * out_size; + Mat out = this->infer(img_, islinear); + Mat out_ = out * out_size; out_.convertTo(out_, CV_8UC3); - cv::Mat img_out = min(max(out_, 0), out_size); - cv::Mat out_img; + Mat img_out = min(max(out_, 0), out_size); + Mat out_img; cvtColor(img_out, out_img, COLOR_RGB2BGR); return out_img; }; - // void report() { - // std::cout << "CCM0: " << ccm0 << std::endl; - // std::cout << "CCM: " << ccm << std::endl; - // std::cout << "Loss: " << loss << std::endl; - // } - -private: - cv::Mat dist; - int masked_len; - - // RGBl of detected data and the reference - cv::Mat src_rgbl; - cv::Mat dst_rgbl; - - /* *\ brief Calculate weights and mask. - *\ param weights_list the input array, type of cv::Mat. - *\ param weights_coeff type of double. - *\ param saturate_list the input array, type of cv::Mat. - */ - void calWeightsMasks(cv::Mat weights_list, double weights_coeff, cv::Mat saturate_mask) - { - // weights - if (!weights_list.empty()) - { - weights = weights_list; - } - else if (weights_coeff != 0) - { - pow(dst.toLuminant(dst.cs.io), weights_coeff, weights); - } - - // masks - cv::Mat weight_mask = cv::Mat::ones(src.rows, 1, CV_8U); - if (!weights.empty()) - { - weight_mask = weights > 0; - } - this->mask = (weight_mask) & (saturate_mask); - - // weights' mask - if (!weights.empty()) - { - cv::Mat weights_masked = maskCopyTo(this->weights, this->mask); - weights = weights_masked / mean(weights_masked)[0]; - } - masked_len = (int)sum(mask)[0]; - }; }; } // namespace ccm diff --git a/modules/mcc/include/opencv2/mcc/color.hpp b/modules/mcc/include/opencv2/mcc/color.hpp index 1b542e5d45f..9cab5e1ea98 100644 --- a/modules/mcc/include/opencv2/mcc/color.hpp +++ b/modules/mcc/include/opencv2/mcc/color.hpp @@ -48,17 +48,17 @@ class Color *\ param colored mask of colored color *\ param history storage of historical conversion */ - cv::Mat colors; + Mat colors; const ColorSpace& cs; - cv::Mat grays; - cv::Mat colored; + Mat grays; + Mat colored; std::map> history; - Color(cv::Mat colors_, const ColorSpace& cs_, cv::Mat colored_) : colors(colors_), cs(cs_), colored(colored_) + Color(Mat colors_, const ColorSpace& cs_, Mat colored_) : colors(colors_), cs(cs_), colored(colored_) { grays= ~colored; } - Color(cv::Mat colors_, const ColorSpace& cs_) :colors(colors_), cs(cs_) {}; + Color(Mat colors_, const ColorSpace& cs_) :colors(colors_), cs(cs_) {}; virtual ~Color() {}; @@ -73,7 +73,6 @@ class Color { if (history.count(other) == 1) { - return *history[other]; } if (cs.relate(other)) @@ -93,16 +92,16 @@ class Color /* *\ brief Channels split. *\ return each channel. */ - cv::Mat channel(cv::Mat m, int i) + Mat channel(Mat m, int i) { - cv::Mat dchannels[3]; + Mat dchannels[3]; split(m, dchannels); return dchannels[i]; } /* *\ brief To Gray. */ - cv::Mat toGray(IO io, CAM method = BRADFORD, bool save = true) + Mat toGray(IO io, CAM method = BRADFORD, bool save = true) { XYZ xyz(io); return channel(this->to(xyz, method, save).colors, 1); @@ -110,7 +109,7 @@ class Color /* *\ brief To Luminant. */ - cv::Mat toLuminant(IO io, CAM method = BRADFORD, bool save = true) + Mat toLuminant(IO io, CAM method = BRADFORD, bool save = true) { Lab lab(io); return channel(this->to(lab, method, save).colors, 0); @@ -121,7 +120,7 @@ class Color *\ param method type of distance. *\ return distance between self and other */ - cv::Mat diff(Color& other, DISTANCE_TYPE method = CIE2000) + Mat diff(Color& other, DISTANCE_TYPE method = CIE2000) { return diff(other, cs.io, method); } @@ -132,7 +131,7 @@ class Color *\ param method type of distance. *\ return distance between self and other */ - cv::Mat diff(Color& other, IO io, DISTANCE_TYPE method = CIE2000) + Mat diff(Color& other, IO io, DISTANCE_TYPE method = CIE2000) { Lab lab(io); switch (method) @@ -161,30 +160,29 @@ class Color if (!grays.empty()) { return; } - cv::Mat lab = to(Lab_D65_2).colors; - cv::Mat gray(colors.size(), colors.type()); + Mat lab = to(Lab_D65_2).colors; + Mat gray(colors.size(), colors.type()); int fromto[] = { 0,0, -1,1, -1,2 }; mixChannels(&lab, 1, &gray, 1, fromto, 3); - cv::Mat d = distance(lab, gray, CIE2000); + Mat d = distance(lab, gray, CIE2000); this->grays = d < JDN; this->colored = ~grays; } /* *\ brief Operator for mask copy. */ - Color operator[](cv::Mat mask) + Color operator[](Mat mask) { return Color(maskCopyTo(colors, mask), cs); } - }; /* *\ brief Data is from https://www.imatest.com/wp-content/uploads/2011/11/Lab-data-Iluminate-D65-D50-spectro.xls * see Miscellaneous.md for details. */ -const cv::Mat ColorChecker2005_LAB_D50_2 = (cv::Mat_(24, 1) << +const Mat ColorChecker2005_LAB_D50_2 = (Mat_(24, 1) << cv::Vec3d(37.986, 13.555, 14.059), cv::Vec3d(65.711, 18.13, 17.81), cv::Vec3d(49.927, -4.88, -21.925), @@ -210,7 +208,7 @@ const cv::Mat ColorChecker2005_LAB_D50_2 = (cv::Mat_(24, 1) << cv::Vec3d(35.656, -0.421, -1.231), cv::Vec3d(20.461, -0.079, -0.973)); -const cv::Mat ColorChecker2005_LAB_D65_2 = (cv::Mat_(24, 1) << +const Mat ColorChecker2005_LAB_D65_2 = (Mat_(24, 1) << cv::Vec3d(37.542, 12.018, 13.33), cv::Vec3d(65.2, 14.821, 17.545), cv::Vec3d(50.366, -1.573, -21.431), @@ -236,13 +234,13 @@ const cv::Mat ColorChecker2005_LAB_D65_2 = (cv::Mat_(24, 1) << cv::Vec3d(35.68, -0.22, -1.205), cv::Vec3d(20.475, 0.049, -0.972)); -const cv::Mat ColorChecker2005_COLORED_MASK = (cv::Mat_(24, 1) << +const Mat ColorChecker2005_COLORED_MASK = (Mat_(24, 1) << 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0); -const cv::Mat Vinyl_LAB_D50_2 = (Mat_(18, 1) << +const Mat Vinyl_LAB_D50_2 = (Mat_(18, 1) << Vec3d(1.00000000e+02, 5.20000001e-03, -1.04000000e-02), Vec3d(7.30833969e+01, -8.19999993e-01, -2.02099991e+00), Vec3d(6.24930000e+01, 4.25999999e-01, -2.23099995e+00), @@ -262,7 +260,7 @@ const cv::Mat Vinyl_LAB_D50_2 = (Mat_(18, 1) << Vec3d(6.87070007e+01, 1.22959995e+01, 1.62129993e+01), Vec3d(6.36839981e+01, 1.02930002e+01, 1.67639999e+01)); -const cv::Mat Vinyl_COLORED_MASK = (cv::Mat_(18, 1) << +const Mat Vinyl_COLORED_MASK = (Mat_(18, 1) << 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1); @@ -277,7 +275,6 @@ Color Macbeth_D65_2(ColorChecker2005_LAB_D65_2, Lab_D65_2, ColorChecker2005_COLO Color Vinyl_D50_2(Vinyl_LAB_D50_2, Lab_D50_2, Vinyl_COLORED_MASK); - } // namespace ccm } // namespace cv diff --git a/modules/mcc/include/opencv2/mcc/colorspace.hpp b/modules/mcc/include/opencv2/mcc/colorspace.hpp index 9742344c365..f8c5d92d250 100644 --- a/modules/mcc/include/opencv2/mcc/colorspace.hpp +++ b/modules/mcc/include/opencv2/mcc/colorspace.hpp @@ -94,8 +94,8 @@ class RGBBase_ : public ColorSpace double yb; MatFunc toL; MatFunc fromL; - cv::Mat M_to; - cv::Mat M_from; + Mat M_to; + Mat M_from; using ColorSpace::ColorSpace; @@ -151,15 +151,15 @@ class RGBBase_ : public ColorSpace */ virtual void calM() { - cv::Mat XYZr, XYZg, XYZb, XYZ_rgbl, Srgb; - XYZr = cv::Mat(xyY2XYZ({ xr, yr }), true); - XYZg = cv::Mat(xyY2XYZ({ xg, yg }), true); - XYZb = cv::Mat(xyY2XYZ({ xb, yb }), true); - merge(std::vector{ XYZr, XYZg, XYZb }, XYZ_rgbl); + Mat XYZr, XYZg, XYZb, XYZ_rgbl, Srgb; + XYZr = Mat(xyY2XYZ({ xr, yr }), true); + XYZg = Mat(xyY2XYZ({ xg, yg }), true); + XYZb = Mat(xyY2XYZ({ xb, yb }), true); + merge(std::vector{ XYZr, XYZg, XYZb }, XYZ_rgbl); XYZ_rgbl = XYZ_rgbl.reshape(1, XYZ_rgbl.rows); - cv::Mat XYZw = cv::Mat(illuminants.find(io)->second, true); + Mat XYZw = Mat(illuminants.find(io)->second, true); solve(XYZ_rgbl, XYZw, Srgb); - merge(std::vector{ Srgb.at(0)* XYZr, + merge(std::vector{ Srgb.at(0)* XYZr, Srgb.at(1)* XYZg, Srgb.at(2)* XYZb }, M_to); M_to = M_to.reshape(1, M_to.rows); @@ -171,10 +171,10 @@ class RGBBase_ : public ColorSpace virtual void calOperations() { // rgb -> rgbl - toL = [this](cv::Mat rgb)->cv::Mat {return toLFunc(rgb); }; + toL = [this](Mat rgb)->Mat {return toLFunc(rgb); }; // rgbl -> rgb - fromL = [this](cv::Mat rgbl)->cv::Mat {return fromLFunc(rgbl); }; + fromL = [this](Mat rgbl)->Mat {return fromLFunc(rgbl); }; if (linear) { @@ -190,14 +190,14 @@ class RGBBase_ : public ColorSpace virtual void calLinear() {} - virtual cv::Mat toLFunc(cv::Mat& /*rgb*/) + virtual Mat toLFunc(Mat& /*rgb*/) { - return cv::Mat(); + return Mat(); }; - virtual cv::Mat fromLFunc(cv::Mat& /*rgbl*/) + virtual Mat fromLFunc(Mat& /*rgbl*/) { - return cv::Mat(); + return Mat(); }; }; @@ -211,12 +211,12 @@ class AdobeRGBBase_ : public RGBBase_ double gamma; private: - cv::Mat toLFunc(cv::Mat& rgb) CV_OVERRIDE + Mat toLFunc(Mat& rgb) CV_OVERRIDE { return gammaCorrection(rgb, gamma); } - cv::Mat fromLFunc(cv::Mat& rgbl) CV_OVERRIDE + Mat fromLFunc(Mat& rgbl) CV_OVERRIDE { return gammaCorrection(rgbl, 1. / gamma); } @@ -270,7 +270,7 @@ class sRGBBase_ : public RGBBase_ *\ param rgb the input array, type of cv::Mat. *\ return the output array, type of cv::Mat. */ - cv::Mat toLFunc(cv::Mat& rgb) CV_OVERRIDE + Mat toLFunc(Mat& rgb) CV_OVERRIDE { return elementWise(rgb, [this](double a_)->double {return toLFuncEW(a_); }); } @@ -298,7 +298,7 @@ class sRGBBase_ : public RGBBase_ *\ param rgbl the input array, type of cv::Mat. *\ return the output array, type of cv::Mat. */ - cv::Mat fromLFunc(cv::Mat& rgbl) CV_OVERRIDE + Mat fromLFunc(Mat& rgbl) CV_OVERRIDE { return elementWise(rgbl, [this](double a_)->double {return fromLFuncEW(a_); }); } @@ -505,7 +505,6 @@ class ColorSpaceInitial ColorSpaceInitial color_space_initial; - /* *\ brief Enum of the possible types of CAMs. */ enum CAM @@ -515,11 +514,11 @@ enum CAM BRADFORD }; -static std::map , cv::Mat > cams; -const static cv::Mat Von_Kries = (cv::Mat_(3, 3) << 0.40024, 0.7076, -0.08081, -0.2263, 1.16532, 0.0457, 0., 0., 0.91822); -const static cv::Mat Bradford = (cv::Mat_(3, 3) << 0.8951, 0.2664, -0.1614, -0.7502, 1.7135, 0.0367, 0.0389, -0.0685, 1.0296); -const static std::map > MAs = { - {IDENTITY , { cv::Mat::eye(cv::Size(3,3),CV_64FC1) , cv::Mat::eye(cv::Size(3,3),CV_64FC1)} }, +static std::map , Mat > cams; +const static Mat Von_Kries = (Mat_(3, 3) << 0.40024, 0.7076, -0.08081, -0.2263, 1.16532, 0.0457, 0., 0., 0.91822); +const static Mat Bradford = (Mat_(3, 3) << 0.8951, 0.2664, -0.1614, -0.7502, 1.7135, 0.0367, 0.0389, -0.0685, 1.0296); +const static std::map > MAs = { + {IDENTITY , { Mat::eye(cv::Size(3,3),CV_64FC1) , Mat::eye(cv::Size(3,3),CV_64FC1)} }, {VON_KRIES, { Von_Kries ,Von_Kries.inv() }}, {BRADFORD, { Bradford ,Bradford.inv() }} }; @@ -543,11 +542,11 @@ class XYZ :public ColorSpace *\ param method type of CAM. *\ return the output array, type of cv::Mat. */ - cv::Mat cam_(IO sio, IO dio, CAM method = BRADFORD) const + Mat cam_(IO sio, IO dio, CAM method = BRADFORD) const { if (sio == dio) { - return cv::Mat::eye(cv::Size(3, 3), CV_64FC1); + return Mat::eye(cv::Size(3, 3), CV_64FC1); } if (cams.count(std::make_tuple(dio, sio, method)) == 1) { @@ -555,11 +554,11 @@ class XYZ :public ColorSpace } // Function from http ://www.brucelindbloom.com/index.html?ColorCheckerRGB.html. - cv::Mat XYZws = cv::Mat(illuminants.find(dio)->second); - cv::Mat XYZWd = cv::Mat(illuminants.find(sio)->second); - cv::Mat MA = MAs.at(method)[0]; - cv::Mat MA_inv = MAs.at(method)[1]; - cv::Mat M = MA_inv * cv::Mat::diag((MA * XYZws) / (MA * XYZWd)) * MA; + Mat XYZws = Mat(illuminants.find(dio)->second); + Mat XYZWd = Mat(illuminants.find(sio)->second); + Mat MA = MAs.at(method)[0]; + Mat MA_inv = MAs.at(method)[1]; + Mat M = MA_inv * Mat::diag((MA * XYZws) / (MA * XYZWd)) * MA; cams[std::make_tuple(dio, sio, method)] = M; cams[std::make_tuple(sio, dio, method)] = M.inv(); return M; @@ -571,7 +570,6 @@ class XYZ :public ColorSpace const XYZ XYZ_D65_2(D65_2); const XYZ XYZ_D50_2(D50_2); - /* *\ brief Lab color space. */ class Lab :public ColorSpace @@ -579,8 +577,8 @@ class Lab :public ColorSpace public: Lab(IO io_) : ColorSpace(io_, "Lab", true) { - to = { Operation([this](cv::Mat src)->cv::Mat {return tosrc(src); }) }; - from = { Operation([this](cv::Mat src)->cv::Mat {return fromsrc(src); }) }; + to = { Operation([this](Mat src)->Mat {return tosrc(src); }) }; + from = { Operation([this](Mat src)->Mat {return fromsrc(src); }) }; } private: @@ -601,7 +599,7 @@ class Lab :public ColorSpace *\ param src the input array, type of cv::Mat. *\ return the output array, type of cv::Mat */ - cv::Mat fromsrc(cv::Mat& src) + Mat fromsrc(Mat& src) { return channelWise(src, [this](cv::Vec3d a)->cv::Vec3d {return fromxyz(a); }); } @@ -617,7 +615,7 @@ class Lab :public ColorSpace *\ param src the input array, type of cv::Mat. *\ return the output array, type of cv::Mat */ - cv::Mat tosrc(cv::Mat& src) + Mat tosrc(Mat& src) { return channelWise(src, [this](cv::Vec3d a)->cv::Vec3d {return tolab(a); }); } diff --git a/modules/mcc/include/opencv2/mcc/distance.hpp b/modules/mcc/include/opencv2/mcc/distance.hpp index d0c9ac16307..2d591d30118 100644 --- a/modules/mcc/include/opencv2/mcc/distance.hpp +++ b/modules/mcc/include/opencv2/mcc/distance.hpp @@ -62,8 +62,7 @@ double deltaCIEDE2000(cv::Vec3d lab1, cv::Vec3d lab2); double deltaCMC(cv::Vec3d lab1, cv::Vec3d lab2, double kL = 1, double kC = 1); double deltaCMC1To1(cv::Vec3d lab1, cv::Vec3d lab2); double deltaCMC2To1(cv::Vec3d lab1, cv::Vec3d lab2); -cv::Mat distance(cv::Mat src, cv::Mat ref, DISTANCE_TYPE distance_type); - +Mat distance(Mat src, Mat ref, DISTANCE_TYPE distance_type); /* *\ brief distance between two points in formula CIE76 *\ param lab1 a 3D vector @@ -266,7 +265,7 @@ double deltaCMC2To1(cv::Vec3d lab1, cv::Vec3d lab2) return deltaCMC(lab1, lab2, 2, 1); } -cv::Mat distance(cv::Mat src, cv::Mat ref, DISTANCE_TYPE distance_type) +Mat distance(Mat src, Mat ref, DISTANCE_TYPE distance_type) { switch (distance_type) { @@ -292,7 +291,8 @@ cv::Mat distance(cv::Mat src, cv::Mat ref, DISTANCE_TYPE distance_type) } }; -} // namespace ccm -} // namespace cv +} // namespace ccm +} // namespace cv + -#endif +#endif \ No newline at end of file diff --git a/modules/mcc/include/opencv2/mcc/io.hpp b/modules/mcc/include/opencv2/mcc/io.hpp index dd62d46e423..2fb6d658627 100644 --- a/modules/mcc/include/opencv2/mcc/io.hpp +++ b/modules/mcc/include/opencv2/mcc/io.hpp @@ -102,6 +102,7 @@ static std::map > getIlluminant() } const std::map > illuminants = getIlluminant(); + } // namespace ccm } // namespace cv diff --git a/modules/mcc/include/opencv2/mcc/linearize.hpp b/modules/mcc/include/opencv2/mcc/linearize.hpp index 3dff3b87572..dcec99b7469 100644 --- a/modules/mcc/include/opencv2/mcc/linearize.hpp +++ b/modules/mcc/include/opencv2/mcc/linearize.hpp @@ -53,7 +53,7 @@ class Polyfit { public: int deg; - cv::Mat p; + Mat p; Polyfit() {}; @@ -63,12 +63,12 @@ class Polyfit and deduct: Ax = y See linear.pdf for details */ - Polyfit(cv::Mat x, cv::Mat y, int deg_) :deg(deg_) + Polyfit(Mat x, Mat y, int deg_) :deg(deg_) { int n = x.cols * x.rows * x.channels(); x = x.reshape(1, n); y = y.reshape(1, n); - cv::Mat_ A = cv::Mat_::ones(n, deg + 1); + Mat_ A = Mat_::ones(n, deg + 1); for (int i = 0; i < n; ++i) { for (int j = 1; j < A.cols; ++j) @@ -76,12 +76,13 @@ class Polyfit A.at(i, j) = x.at(i) * A.at(i, j - 1); } } - cv::Mat y_(y); + Mat y_(y); cv::solve(A, y_, p, DECOMP_SVD); } virtual ~Polyfit() {}; - cv::Mat operator()(const cv::Mat& inp) + + Mat operator()(const Mat& inp) { return elementWise(inp, [this](double x)->double {return fromEW(x); }); }; @@ -110,10 +111,10 @@ class LogPolyfit /* *\ brief Logpolyfit method. */ - LogPolyfit(cv::Mat x, cv::Mat y, int deg_) :deg(deg_) + LogPolyfit(Mat x, Mat y, int deg_) :deg(deg_) { - cv::Mat mask_ = (x > 0) & (y > 0); - cv::Mat src_, dst_, s_, d_; + Mat mask_ = (x > 0) & (y > 0); + Mat src_, dst_, s_, d_; src_ = maskCopyTo(x, mask_); dst_ = maskCopyTo(y, mask_); log(src_, s_); @@ -123,10 +124,10 @@ class LogPolyfit virtual ~LogPolyfit() {}; - cv::Mat operator()(const cv::Mat& inp) + Mat operator()(const Mat& inp) { - cv::Mat mask_ = inp >= 0; - cv::Mat y, y_, res; + Mat mask_ = inp >= 0; + Mat y, y_, res; log(inp, y); y = p(y); exp(y, y_); @@ -147,7 +148,7 @@ class Linear /* *\ brief Inference. *\ param inp the input array, type of cv::Mat. */ - virtual cv::Mat linearize(cv::Mat inp) + virtual Mat linearize(Mat inp) { return inp; }; @@ -172,7 +173,7 @@ class LinearGamma : public Linear LinearGamma(double gamma_) :gamma(gamma_) {}; - cv::Mat linearize(cv::Mat inp) CV_OVERRIDE + Mat linearize(Mat inp) CV_OVERRIDE { return gammaCorrection(inp, gamma); }; @@ -188,14 +189,14 @@ class LinearGray :public Linear int deg; T p; - LinearGray(int deg_, cv::Mat src, Color dst, cv::Mat mask, RGBBase_ cs) :deg(deg_) + LinearGray(int deg_, Mat src, Color dst, Mat mask, RGBBase_ cs) :deg(deg_) { dst.getGray(); Mat lear_gray_mask = mask & dst.grays; // the grayscale function is approximate for src is in relative color space. src = rgb2gray(maskCopyTo(src, lear_gray_mask)); - cv::Mat dst_ = maskCopyTo(dst.toGray(cs.io), lear_gray_mask); + Mat dst_ = maskCopyTo(dst.toGray(cs.io), lear_gray_mask); calc(src, dst_); } @@ -203,12 +204,12 @@ class LinearGray :public Linear *\ param src the input array, type of cv::Mat. *\ param dst the input array, type of cv::Mat. */ - void calc(const cv::Mat& src, const cv::Mat& dst) + void calc(const Mat& src, const Mat& dst) { p = T(src, dst, deg); }; - cv::Mat linearize(cv::Mat inp) CV_OVERRIDE + Mat linearize(Mat inp) CV_OVERRIDE { return p(inp); }; @@ -226,17 +227,17 @@ class LinearColor :public Linear T pg; T pb; - LinearColor(int deg_, cv::Mat src_, Color dst, cv::Mat mask, RGBBase_ cs) :deg(deg_) + LinearColor(int deg_, Mat src_, Color dst, Mat mask, RGBBase_ cs) :deg(deg_) { Mat src = maskCopyTo(src_, mask); - cv::Mat dst_ = maskCopyTo(dst.to(*cs.l).colors, mask); + Mat dst_ = maskCopyTo(dst.to(*cs.l).colors, mask); calc(src, dst_); } - void calc(const cv::Mat& src, const cv::Mat& dst) + void calc(const Mat& src, const Mat& dst) { - cv::Mat schannels[3]; - cv::Mat dchannels[3]; + Mat schannels[3]; + Mat dchannels[3]; split(src, schannels); split(dst, dchannels); pr = T(schannels[0], dchannels[0], deg); @@ -244,18 +245,17 @@ class LinearColor :public Linear pb = T(schannels[2], dchannels[2], deg); }; - cv::Mat linearize(cv::Mat inp) CV_OVERRIDE + Mat linearize(Mat inp) CV_OVERRIDE { - cv::Mat channels[3]; + Mat channels[3]; split(inp, channels); - std::vector channel; - cv::Mat res; - merge(std::vector{ pr(channels[0]), pg(channels[1]), pb(channels[2]) }, res); + std::vector channel; + Mat res; + merge(std::vector{ pr(channels[0]), pg(channels[1]), pb(channels[2]) }, res); return res; }; }; - /* *\ brief Get linearization method. * used in ccm model. *\ param gamma used in LinearGamma. @@ -266,8 +266,8 @@ class LinearColor :public Linear *\ param cs type of RGBBase_. *\ param linear_type type of linear. */ -std::shared_ptr getLinear(double gamma, int deg, cv::Mat src, Color dst, cv::Mat mask, RGBBase_ cs, LINEAR_TYPE linear_type); -std::shared_ptr getLinear(double gamma, int deg, cv::Mat src, Color dst, cv::Mat mask, RGBBase_ cs, LINEAR_TYPE linear_type) +std::shared_ptr getLinear(double gamma, int deg, Mat src, Color dst, Mat mask, RGBBase_ cs, LINEAR_TYPE linear_type); +std::shared_ptr getLinear(double gamma, int deg, Mat src, Color dst, Mat mask, RGBBase_ cs, LINEAR_TYPE linear_type) { std::shared_ptr p = std::make_shared(); switch (linear_type) @@ -301,4 +301,4 @@ std::shared_ptr getLinear(double gamma, int deg, cv::Mat src, Color dst } // namespace cv -#endif +#endif \ No newline at end of file diff --git a/modules/mcc/include/opencv2/mcc/operations.hpp b/modules/mcc/include/opencv2/mcc/operations.hpp index 71947306071..c03a357237a 100644 --- a/modules/mcc/include/opencv2/mcc/operations.hpp +++ b/modules/mcc/include/opencv2/mcc/operations.hpp @@ -38,7 +38,7 @@ namespace cv namespace ccm { -typedef std::function MatFunc; +typedef std::function MatFunc; /* *\ brief Operation class contains some operarions used for color space * conversion containing linear transformation and non-linear transformation @@ -47,19 +47,20 @@ class Operation { public: bool linear; - cv::Mat M; + Mat M; MatFunc f; - Operation() : linear(true), M(cv::Mat()) {}; + Operation() : linear(true), M(Mat()) {}; - Operation(cv::Mat M_) :linear(true), M(M_) {}; + Operation(Mat M_) :linear(true), M(M_) {}; Operation(MatFunc f_) : linear(false), f(f_) {}; virtual ~Operation() {}; + /* *\ brief operator function will run operation */ - cv::Mat operator()(cv::Mat& abc) + Mat operator()(Mat& abc) { if (!linear) { @@ -89,11 +90,11 @@ class Operation void clear() { - M = cv::Mat(); + M = Mat(); }; }; -const Operation IDENTITY_OP([](cv::Mat x) {return x; }); +const Operation IDENTITY_OP([](Mat x) {return x; }); class Operations { @@ -116,7 +117,7 @@ class Operations /* *\ brief run operations to make color conversion */ - cv::Mat run(cv::Mat abc) + Mat run(Mat abc) { Operation hd; for (auto& op : ops) diff --git a/modules/mcc/include/opencv2/mcc/utils.hpp b/modules/mcc/include/opencv2/mcc/utils.hpp index d1f56854c85..38b90a6b394 100644 --- a/modules/mcc/include/opencv2/mcc/utils.hpp +++ b/modules/mcc/include/opencv2/mcc/utils.hpp @@ -40,26 +40,26 @@ namespace ccm { double gammaCorrection_(const double& element, const double& gamma); -cv::Mat gammaCorrection(const cv::Mat& src, const double& gamma); -cv::Mat maskCopyTo(const cv::Mat& src, const cv::Mat& mask); -cv::Mat multiple(const cv::Mat& xyz, const cv::Mat& ccm); -cv::Mat saturate(cv::Mat& src, const double& low, const double& up); -cv::Mat rgb2gray(cv::Mat rgb); +Mat gammaCorrection(const Mat& src, const double& gamma); +Mat maskCopyTo(const Mat& src, const Mat& mask); +Mat multiple(const Mat& xyz, const Mat& ccm); +Mat saturate(Mat& src, const double& low, const double& up); +Mat rgb2gray(Mat rgb); /* *\ brief function for elementWise operation *\ param src the input array, type of cv::Mat *\ lambda a for operation */ template -cv::Mat elementWise(const cv::Mat& src, F&& lambda) +Mat elementWise(const Mat& src, F&& lambda) { - cv::Mat dst = src.clone(); + Mat dst = src.clone(); const int channel = src.channels(); switch (channel) { case 1: { - cv::MatIterator_ it, end; + MatIterator_ it, end; for (it = dst.begin(), end = dst.end(); it != end; ++it) { (*it) = lambda((*it)); @@ -68,7 +68,7 @@ cv::Mat elementWise(const cv::Mat& src, F&& lambda) } case 3: { - cv::MatIterator_ it, end; + MatIterator_ it, end; for (it = dst.begin(), end = dst.end(); it != end; ++it) { for (int j = 0; j < 3; j++) @@ -90,10 +90,10 @@ cv::Mat elementWise(const cv::Mat& src, F&& lambda) *\ lambda the function for operation */ template -cv::Mat channelWise(const cv::Mat& src, F&& lambda) +Mat channelWise(const Mat& src, F&& lambda) { - cv::Mat dst = src.clone(); - cv::MatIterator_ it, end; + Mat dst = src.clone(); + MatIterator_ it, end; for (it = dst.begin(), end = dst.end(); it != end; ++it) { *it = lambda(*it); @@ -107,12 +107,12 @@ cv::Mat channelWise(const cv::Mat& src, F&& lambda) *\ param lambda the computing method for distance . */ template -cv::Mat distanceWise(cv::Mat& src, cv::Mat& ref, F&& lambda) +Mat distanceWise(Mat& src, Mat& ref, F&& lambda) { - cv::Mat dst = cv::Mat(src.size(), CV_64FC1); - cv::MatIterator_ it_src = src.begin(), end_src = src.end(), + Mat dst = Mat(src.size(), CV_64FC1); + MatIterator_ it_src = src.begin(), end_src = src.end(), it_ref = ref.begin(); - cv::MatIterator_ it_dst = dst.begin(); + MatIterator_ it_dst = dst.begin(); for (; it_src != end_src; ++it_src, ++it_ref, ++it_dst) { *it_dst = lambda(*it_src, *it_ref); @@ -120,7 +120,6 @@ cv::Mat distanceWise(cv::Mat& src, cv::Mat& ref, F&& lambda) return dst; } - double gammaCorrection_(const double& element, const double& gamma) { return (element >= 0 ? pow(element, gamma) : -pow((-element), gamma)); @@ -130,7 +129,7 @@ double gammaCorrection_(const double& element, const double& gamma) *\ param src the input array, type of cv::Mat. *\ param gamma a constant for gamma correction. */ -cv::Mat gammaCorrection(const cv::Mat& src, const double& gamma) +Mat gammaCorrection(const Mat& src, const double& gamma) { return elementWise(src, [gamma](double element)->double {return gammaCorrection_(element, gamma); }); } @@ -139,9 +138,9 @@ cv::Mat gammaCorrection(const cv::Mat& src, const double& gamma) *\ param src the input array, type of cv::Mat. *\ param mask operation mask that used to choose satisfided elementwise. */ -cv::Mat maskCopyTo(const cv::Mat& src, const cv::Mat& mask) +Mat maskCopyTo(const Mat& src, const Mat& mask) { - cv::Mat dst(countNonZero(mask), 1, src.type()); + Mat dst(countNonZero(mask), 1, src.type()); const int channel = src.channels(); auto it_mask = mask.begin(); switch (channel) @@ -185,10 +184,10 @@ cv::Mat maskCopyTo(const cv::Mat& src, const cv::Mat& mask) *\ param src the input array, type of cv::Mat. *\ param ccm the ccm matrix to make color correction. */ -cv::Mat multiple(const cv::Mat& xyz, const cv::Mat& ccm) +Mat multiple(const Mat& xyz, const Mat& ccm) { - cv::Mat tmp = xyz.reshape(1, xyz.rows * xyz.cols); - cv::Mat res = tmp * ccm; + Mat tmp = xyz.reshape(1, xyz.rows * xyz.cols); + Mat res = tmp * ccm; res = res.reshape(res.cols, xyz.rows); return res; } @@ -199,11 +198,11 @@ cv::Mat multiple(const cv::Mat& xyz, const cv::Mat& ccm) *\ param low the threshold to choose saturated colors *\ param up the threshold to choose saturated colors */ -cv::Mat saturate(cv::Mat& src, const double& low, const double& up) +Mat saturate(Mat& src, const double& low, const double& up) { - cv::Mat dst = cv::Mat::ones(src.size(), CV_8UC1); - cv::MatIterator_ it_src = src.begin(), end_src = src.end(); - cv::MatIterator_ it_dst = dst.begin(); + Mat dst = Mat::ones(src.size(), CV_8UC1); + MatIterator_ it_src = src.begin(), end_src = src.end(); + MatIterator_ it_dst = dst.begin(); for (; it_src != end_src; ++it_src, ++it_dst) { for (int i = 0; i < 3; ++i) @@ -218,13 +217,13 @@ cv::Mat saturate(cv::Mat& src, const double& low, const double& up) return dst; } -const static cv::Mat m_gray = (cv::Mat_(3, 1) << 0.2126, 0.7152, 0.0722); +const static Mat m_gray = (Mat_(3, 1) << 0.2126, 0.7152, 0.0722); /* *\ brief rgb2gray it is an approximation grayscale function for relative RGB color space, * see Miscellaneous.pdf for details; *\ param rgb the input array, type of cv::Mat. */ -cv::Mat rgb2gray(cv::Mat rgb) +Mat rgb2gray(Mat rgb) { return multiple(rgb, m_gray); } diff --git a/modules/mcc/samples/color_correction_model.cpp b/modules/mcc/samples/color_correction_model.cpp index fd5ffef8f2b..ef9e5308c04 100644 --- a/modules/mcc/samples/color_correction_model.cpp +++ b/modules/mcc/samples/color_correction_model.cpp @@ -113,7 +113,7 @@ int main(int argc, char *argv[]) // Save the calibrated image to {FILE_NAME}.calibrated.{FILE_EXT} string filename = filepath.substr(filepath.find_last_of('/')+1); - int dotIndex = filename.find_last_of('.'); + 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; From 47c716db455c1b9aaff0fe4cafba2c283132fb39 Mon Sep 17 00:00:00 2001 From: JinhengZhang Date: Mon, 14 Sep 2020 01:58:54 +0800 Subject: [PATCH 8/8] add unittest --- modules/mcc/test/test_ccm.cpp | 146 +++++++++++++++++ modules/mcc/test/test_color.cpp | 123 +++++++++++++++ modules/mcc/test/test_io.cpp | 42 +++++ modules/mcc/test/test_linearize.cpp | 232 ++++++++++++++++++++++++++++ modules/mcc/test/test_precomp.hpp | 1 + modules/mcc/test/test_utils.cpp | 47 ++++++ 6 files changed, 591 insertions(+) create mode 100644 modules/mcc/test/test_ccm.cpp create mode 100644 modules/mcc/test/test_color.cpp create mode 100644 modules/mcc/test/test_io.cpp create mode 100644 modules/mcc/test/test_linearize.cpp create mode 100644 modules/mcc/test/test_utils.cpp diff --git a/modules/mcc/test/test_ccm.cpp b/modules/mcc/test/test_ccm.cpp new file mode 100644 index 00000000000..15b2ac531ee --- /dev/null +++ b/modules/mcc/test/test_ccm.cpp @@ -0,0 +1,146 @@ +// This file is part of OpenCV project. +// It is subject to the license terms in the LICENSE file found in the top-level directory +// of this distribution and at http://opencv.org/license.html. + +#include "test_precomp.hpp" + +#include + +namespace opencv_test +{ +namespace +{ + +Mat s = (Mat_(24, 1) << + Vec3d(214.11, 98.67, 37.97), + Vec3d(231.94, 153.1, 85.27), + Vec3d(204.08, 143.71, 78.46), + Vec3d(190.58, 122.99, 30.84), + Vec3d(230.93, 148.46, 100.84), + Vec3d(228.64, 206.97, 97.5), + Vec3d(229.09, 137.07, 55.29), + Vec3d(189.21, 111.22, 92.66), + Vec3d(223.5, 96.42, 75.45), + Vec3d(201.82, 69.71, 50.9), + Vec3d(240.52, 196.47, 59.3), + Vec3d(235.73, 172.13, 54.), + Vec3d(131.6, 75.04, 68.86), + Vec3d(189.04, 170.43, 42.05), + Vec3d(222.23, 74., 71.95), + Vec3d(241.01, 199.1, 61.15), + Vec3d(224.99, 101.4, 100.24), + Vec3d(174.58, 152.63, 91.52), + Vec3d(248.06, 227.69, 140.5), + Vec3d(241.15, 201.38, 115.58), + Vec3d(236.49, 175.87, 88.86), + Vec3d(212.19, 133.49, 54.79), + Vec3d(181.17, 102.94, 36.18), + Vec3d(115.1, 53.77, 15.23)); + +TEST(CV_ccmRunColorCorrection, test_model) +{ + ColorCorrectionModel model(s / 255, Color(ColorChecker2005_Lab_D50_2, Lab_D50_2)); + Mat src_rgbl = (Mat_(24, 1) << + Vec3d(0.68078957, 0.12382801, 0.01514889), + Vec3d(0.81177942, 0.32550452, 0.089818), + Vec3d(0.61259378, 0.2831933, 0.07478902), + Vec3d(0.52696493, 0.20105976, 0.00958657), + Vec3d(0.80402284, 0.30419523, 0.12989841), + Vec3d(0.78658646, 0.63184111, 0.12062068), + Vec3d(0.78999637, 0.25520249, 0.03462853), + Vec3d(0.51866697, 0.16114393, 0.1078387), + Vec3d(0.74820768, 0.11770076, 0.06862177), + Vec3d(0.59776825, 0.05765816, 0.02886627), + Vec3d(0.8793145, 0.56346033, 0.0403954), + Vec3d(0.84124847, 0.42120746, 0.03287592), + Vec3d(0.23333214, 0.06780408, 0.05612276), + Vec3d(0.5176423, 0.41210976, 0.01896255), + Vec3d(0.73888613, 0.06575388, 0.06181293), + Vec3d(0.88326036, 0.58018751, 0.04321991), + Vec3d(0.75922531, 0.13149072, 0.1282041), + Vec3d(0.4345097, 0.32331019, 0.10494139), + Vec3d(0.94110142, 0.77941419, 0.26946323), + Vec3d(0.88438952, 0.5949049 , 0.17536928), + Vec3d(0.84722687, 0.44160449, 0.09834799), + Vec3d(0.66743106, 0.24076803, 0.03394333), + Vec3d(0.47141286, 0.13592419, 0.01362205), + Vec3d(0.17377101, 0.03256864, 0.00203026)); + ASSERT_EQ(src_rgbl, model.src_rgbl); + + Mat dst_rgbl = (Mat_(24, 1) << + Vec3d(0.17303173, 0.08211037, 0.05672686), + Vec3d(0.56832031, 0.29269488, 0.21835529), + Vec3d(0.10365019, 0.19588357, 0.33140475), + Vec3d(0.10159676, 0.14892193, 0.05188294), + Vec3d(0.22159627, 0.21584476, 0.43461196), + Vec3d(0.10806379, 0.51437196, 0.41264213), + Vec3d(0.74736423, 0.20062878, 0.02807988), + Vec3d(0.05757947, 0.10516793, 0.40296109), + Vec3d(0.56676218, 0.08424805, 0.11969461), + Vec3d(0.11099515, 0.04230796, 0.14292554), + Vec3d(0.34546869, 0.50872001, 0.04944204), + Vec3d(0.79461323, 0.35942459, 0.02051968), + Vec3d(0.01710416, 0.05022043, 0.29220674), + Vec3d(0.05598012, 0.30021149, 0.06871162), + Vec3d(0.45585457, 0.03033727, 0.04085654), + Vec3d(0.85737614, 0.56757335, 0.0068503), + Vec3d(0.53348585, 0.08861148, 0.30750446), + Vec3d(-0.0374061, 0.24699498, 0.40041217), + Vec3d(0.91262695, 0.91493909, 0.89367049), + Vec3d(0.57981916, 0.59200418, 0.59328881), + Vec3d(0.35490581, 0.36544831, 0.36755375), + Vec3d(0.19007357, 0.19186587, 0.19308397), + Vec3d(0.08529188, 0.08887994, 0.09257601), + Vec3d(0.0303193, 0.03113818, 0.03274845)); + ASSERT_EQ(dst_rgbl, model.dst_rgbl); + + Mat wb = (Mat_(3, 3) <<0.49129477, 0., 0., 0., 0.85751383, 0., 0., 0., 3.15992365); + ASSERT_EQ(wb, model.initial_white_balance()); + + Mat ccm = (Mat_(3, 3) << + 0.7409443, 0.35435699, 0.33689953, + 0.20459753, 0.84167375, 0.07261064, + -0.20511954, 0.1767599, 3.10780907); + ASSERT_EQ(ccm, model.ccm); + + ASSERT_FALSE(model.weights); + ASSERT_EQ(model.mask, Mat::ones(24, 1, CV_8U)); +} + +TEST(CV_ccmRunColorCorrection, test_masks_weights_1) +{ + Mat weights_list_ = (Mat_(24, 1) << + 1.1, 0, 0, 1.2, 0, 0, + 1.3, 0, 0, 1.4, 0, 0, + 0.5, 0, 0, 0.6, 0, 0, + 0.7, 0, 0, 0.8, 0, 0); + ColorCorrectionModel model1(s / 255, Macbeth_D50_2, weights_list=weights_list_, weights_coeff=1.5); + + ASSERT_EQ(model1.weights, (Mat_(1, 8) << + 1.15789474, 1.26315789, 1.36842105, 1.47368421, + 0.52631579, 0.63157895, 0.73684211, 0.84210526)); + ASSERT_EQ(model1.mask, (Mat_(24, 1) << + True, False, False, True, False, False, + True, False, False, True, False, False, + True, False, False, True, False, False, + True, False, False, True, False, False)); +} + +TEST(CV_ccmRunColorCorrection, test_masks_weights_1) +{ + ColorCorrectionModel model2(s / 255, Macbeth_D50_2, weights_coeff=1.5, saturated_threshold={0.05, 0.93}); + + ASSERT_EQ(model2.weights, (Mat_(24, 1) << + 0.65554256, 1.49454705, 1.00499244, 0.79735434, 1.16327759, + 1.68623868, 1.37973155, 0.73213388, 1.0169629, 0.47430246, + 1.70312161, 0.45414218, 1.15910007, 0.7540434, 1.05049802, + 1.04551645, 1.54082353, 1.02453421, 0.6015915, 0.26154558)); + ASSERT_EQ(model2.mask, (Mat_(24, 1) << + True, True, True, True, True, True, + True, True, True, True, False, True, + True, True, True, False, True, True, + False, False, True, True, True, True])); +} + +} // namespace +} // namespace opencv_test \ No newline at end of file diff --git a/modules/mcc/test/test_color.cpp b/modules/mcc/test/test_color.cpp new file mode 100644 index 00000000000..34c80a42362 --- /dev/null +++ b/modules/mcc/test/test_color.cpp @@ -0,0 +1,123 @@ +// This file is part of OpenCV project. +// It is subject to the license terms in the LICENSE file found in the top-level directory +// of this distribution and at http://opencv.org/license.html. + +#include "test_precomp.hpp" + +namespace opencv_test +{ +namespace +{ + +TEST(CV_ccmColor, test_srgb) +{ + Color color = Color((Mat_(1, 3) <<0.3, 0.2, 0.5), sRGB); + Color color_rgb = color.to(sRGB); + Color color_rgbl = color.to(sRGBL); + Color color_xyz = color.to(XYZ_D65_2); + Color color_lab = color.to(Lab_D65_2); + Color color_xyz_d50 = color.to(XYZ_D50_2); + Color color_lab_d50 = color.to(Lab_D50_2); + + ASSERT_EQ(color_rgb.colors, (Mat_(1, 3) <<0.3, 0.2, 0.5)); + ASSERT_EQ(color_rgbl.colors, (Mat_(1, 3) <<0.07323896, 0.03310477, 0.21404114)); + ASSERT_EQ(color_xyz.colors, (Mat_(1, 3) <<0.080666, 0.054699, 0.208766)); + ASSERT_EQ(color_lab.colors, (Mat_(1, 3) <<28.0337, 29.9289, -39.4065)); + ASSERT_EQ(color_xyz_d50.colors, (Mat_(1, 3) <<0.075310, 0.053003, 0.157097)); + ASSERT_EQ(color_lab_d50.colors, (Mat_(1, 3) <<27.5736, 25.9112, -39.9261)); +} + +TEST(CV_ccmColor, test_adobergbl) +{ + Color color = Color((Mat_(2, 3) <<0.3, 0.2, 0.5, 0.7, 0.1, 0.4), AdobeRGBL); + Color color_rgb = color.to(AdobeRGB); + Color color_rgbl = color.to(AdobeRGBL); + Color color_xyz = color.to(XYZ_D65_2); + Color color_lab = color.to(Lab_D65_2); + Color color_xyz_d50 = color.to(XYZ_D50_2); + Color color_lab_d50 = color.to(Lab_D50_2); + + ASSERT_EQ(color_rgb.colors, (Mat_(2, 3) <<0.578533, 0.481157, 0.729740, 0.850335, 0.351119, 0.659353)); + ASSERT_EQ(color_rgbl.colors, (Mat_(2, 3) <<0.3, 0.2, 0.5, 0.7, 0.1, 0.4)); + ASSERT_EQ(color_xyz.colors, (Mat_(2, 3) <<0.304223, 0.252320, 0.517802, 0.497541, 0.301008, 0.422436)); + ASSERT_EQ(color_lab.colors, (Mat_(2, 3) <<57.3008, 26.0707, -29.7295, 61.7411, 67.8735, -11.8328)); + ASSERT_EQ(color_xyz_d50.colors, (Mat_(2, 3) <<0.298587, 0.250078, 0.390442, 0.507043, 0.305640, 0.317661)); + ASSERT_EQ(color_lab_d50.colors, (Mat_(2, 3) <<57.0831, 23.2605, -29.8401, 62.1379, 66.7756, -10.7684)); +} + +TEST(CV_ccmColor, test_xyz) +{ + Color color = Color((Mat_(1, 3) <<0.3, 0.2, 0.5), XYZ_D65_2); + Color color_rgb = color.to(ProPhotoRGB, VON_KRIES); + Color color_rgbl = color.to(ProPhotoRGB, VON_KRIES); + Color color_xyz = color.to(XYZ_D65_2, VON_KRIES); + Color color_lab = color.to(Lab_D65_2, VON_KRIES); + Color color_xyz_d50 = color.to(XYZ_D50_2, VON_KRIES); + Color color_lab_d50 = color.to(Lab_D50_2, VON_KRIES); + + ASSERT_EQ(color_rgb.colors, (Mat_(1, 3) <<0.530513, 0.351224, 0.648975)); + ASSERT_EQ(color_rgbl.colors, (Mat_(1, 3) <<0.319487, 0.152073, 0.459209)); + ASSERT_EQ(color_xyz.colors, (Mat_(1, 3) <<0.3, 0.2, 0.5)); + ASSERT_EQ(color_lab.colors, (Mat_(1, 3) <<51.8372, 48.0307, -37.3395)); + ASSERT_EQ(color_xyz_d50.colors, (Mat_(1, 3) <<0.289804, 0.200321, 0.378944)); + ASSERT_EQ(color_lab_d50.colors, (Mat_(1, 3) <<51.8735, 42.3654, -37.2770)); +} + +TEST(CV_ccmColor, test_lab) +{ + Color color = Color((Mat_(1, 3) <<30., 20., 10.), Lab_D50_2); + Color color_rgb = color.to(AppleRGB, IDENTITY); + Color color_rgbl = color.to(AppleRGBL, IDENTITY); + Color color_xyz = color.to(XYZ_D65_2, IDENTITY); + Color color_lab = color.to(Lab_D65_2, IDENTITY); + Color color_xyz_d50 = color.to(XYZ_D50_2, IDENTITY); + Color color_lab_d50 = color.to(Lab_D50_2, IDENTITY); + + ASSERT_EQ(color_rgb.colors, (Mat_(1, 3) <<0.323999, 0.167314, 0.165874)); + ASSERT_EQ(color_rgbl.colors, (Mat_(1, 3) <<0.131516, 0.040028, 0.039410)); + ASSERT_EQ(color_xyz.colors, (Mat_(1, 3) <<0.079076, 0.062359, 0.045318)); + ASSERT_EQ(color_lab.colors, (Mat_(1, 3) <<30.0001, 19.9998, 9.9999)); + ASSERT_EQ(color_xyz_d50.colors, (Mat_(1, 3) <<0.080220, 0.062359, 0.034345)); + ASSERT_EQ(color_lab_d50.colors, (Mat_(1, 3) <<30., 20., 10.)); +} + +TEST(CV_ccmColor, test_grays) +{ + Mat grays = (Mat_(24, 1) << + False, False, False, False, False, False, + False, False, False, False, False, False, + False, False, False, False, False, False, + True, True, True, True, True, True); + Macbeth_D50_2.getGray(); + Macbeth_D65_2.getGray(); + + ASSERT_EQ(Macbeth_D50_2.grays, grays); + ASSERT_EQ(Macbeth_D65_2.grays, grays); +} + +TEST(CV_ccmColor, test_gray_luminant) +{ + Color color = Color((Mat_(1, 3) <<0.3, 0.2, 0.5), sRGB); + ASSERT_EQ(color.toGray(color.cs.io), (Mat_(1, 1) <<0.054699)); + ASSERT_EQ(color.toLuminant(color.cs.io), (Mat_(1, 1) <<28.0337)); + + Color color = Color((Mat_(2, 3) <<0.3, 0.2, 0.5, 0.7, 0.1, 0.4), sRGB); + ASSERT_EQ(color.toGray(color.cs.io), (Mat_(1, 2) <<0.054699, 0.112033)); + ASSERT_EQ(color.toLuminant(color.cs.io), (Mat_(1, 2) <<28.0337, 39.9207)); +} + +TEST(CV_ccmColor, test_diff) +{ + Color color1 = Color((Mat_(1, 3) <<0.3, 0.2, 0.5), sRGB); + Color color2 = Color((Mat_(1, 3) <<0.3, 0.2, 0.5), XYZ_D50_2); + + ASSERT_EQ(color1.diff(color2, method=CIE2000, io=D65_2), (Mat_(1, 1) <<22.58031)); + ASSERT_EQ(color1.diff(color2, method=CIE94_GRAPHIC_ARTS, io=D65_2), (Mat_(1, 1) <<25.701214)); + ASSERT_EQ(color1.diff(color2, method=CIE76, io=D65_2), (Mat_(1, 1) <<34.586351)); + ASSERT_EQ(color1.diff(color2, method=CMC_1TO1, io=D65_2), (Mat_(1, 1) <<33.199419)); + ASSERT_EQ(color1.diff(color2, method=RGB, io=D65_2), (Mat_(1, 1) <<0.51057)); + ASSERT_EQ(color1.diff(color2, method=RGBL, io=D65_2), (Mat_(1, 1) <<0.556741)); +} + +} // namespace +} // namespace opencv_test \ No newline at end of file diff --git a/modules/mcc/test/test_io.cpp b/modules/mcc/test/test_io.cpp new file mode 100644 index 00000000000..38879be7c7a --- /dev/null +++ b/modules/mcc/test/test_io.cpp @@ -0,0 +1,42 @@ +// This file is part of OpenCV project. +// It is subject to the license terms in the LICENSE file found in the top-level directory +// of this distribution and at http://opencv.org/license.html. + +#include "test_precomp.hpp" + +#include +#include + +namespace opencv_test +{ +namespace +{ + +using namespace std; + +std::map> illuminants_test = +{ + {A_2, { 1.098466069456375, 1, 0.3558228003436005 }}, + {A_10, { 1.111420406956693, 1, 0.3519978321919493 }}, + {D50_2, { 0.9642119944211994, 1, 0.8251882845188288 }}, + {D50_10, { 0.9672062750333777, 1, 0.8142801513128616 }}, + {D55_2, { 0.956797052643698, 1, 0.9214805860173273 }}, + {D55_10, { 0.9579665682254781, 1, 0.9092525159847462 }}, + {D65_2, { 0.95047, 1., 1.08883 }}, + {D65_10, { 0.94811, 1., 1.07304 }}, + {D75_2, { 0.9497220898840717, 1, 1.226393520724154 }}, + {D75_10, { 0.9441713925645873, 1, 1.2064272211720228 }}, + {E_2, { 1., 1., 1. }}, + {E_10, { 1., 1., 1. }}, +}; + +TEST(CV_ccmIO, test_illuminants) +{ + for (auto i = illuminants.begin(); i != illuminants.end(); ++i) + { + ASSERT_EQ(illuminants[i->second], illuminants_test[i->second]); + } +} + +} // namespace +} // namespace opencv_test \ No newline at end of file diff --git a/modules/mcc/test/test_linearize.cpp b/modules/mcc/test/test_linearize.cpp new file mode 100644 index 00000000000..1f57f472f80 --- /dev/null +++ b/modules/mcc/test/test_linearize.cpp @@ -0,0 +1,232 @@ +// This file is part of OpenCV project. +// It is subject to the license terms in the LICENSE file found in the top-level directory +// of this distribution and at http://opencv.org/license.html. + +#include "test_precomp.hpp" + +namespace opencv_test +{ +namespace +{ + +Mat s = (Mat_(24, 1) << + Vec3d(214.11, 98.67, 37.97), + Vec3d(231.94, 153.1, 85.27), + Vec3d(204.08, 143.71, 78.46), + Vec3d(190.58, 122.99, 30.84), + Vec3d(230.93, 148.46, 100.84), + Vec3d(228.64, 206.97, 97.5), + Vec3d(229.09, 137.07, 55.29), + Vec3d(189.21, 111.22, 92.66), + Vec3d(223.5, 96.42, 75.45), + Vec3d(201.82, 69.71, 50.9), + Vec3d(240.52, 196.47, 59.3), + Vec3d(235.73, 172.13, 54.), + Vec3d(131.6, 75.04, 68.86), + Vec3d(189.04, 170.43, 42.05), + Vec3d(222.23, 74., 71.95), + Vec3d(241.01, 199.1, 61.15), + Vec3d(224.99, 101.4, 100.24), + Vec3d(174.58, 152.63, 91.52), + Vec3d(248.06, 227.69, 140.5), + Vec3d(241.15, 201.38, 115.58), + Vec3d(236.49, 175.87, 88.86), + Vec3d(212.19, 133.49, 54.79), + Vec3d(181.17, 102.94, 36.18), + Vec3d(115.1, 53.77, 15.23)); +s = s / 255; +double gamma = 2.2; +int deg = 3; +Color color = Macbeth_D50_2; +color.getGray(); +RGBBase_ cs = sRGB; +Mat mask = saturate(s, 0.05, 0.93); + +TEST(CV_ccmLinearize, test_identity) +{ + Mat y = (Mat_(24, 1) << + Vec3d(0.83964706, 0.38694118, 0.14890196), + Vec3d(0.90956863, 0.60039216, 0.33439216), + Vec3d(0.80031373, 0.56356863, 0.30768627), + Vec3d(0.74737255, 0.48231373, 0.12094118), + Vec3d(0.90560784, 0.58219608, 0.39545098), + Vec3d(0.89662745, 0.81164706, 0.38235294), + Vec3d(0.89839216, 0.53752941, 0.21682353), + Vec3d(0.742, 0.43615686, 0.36337255), + Vec3d(0.87647059, 0.37811765, 0.29588235), + Vec3d(0.79145098, 0.27337255, 0.19960784), + Vec3d(0.94321569, 0.77047059, 0.23254902), + Vec3d(0.92443137, 0.67501961, 0.21176471), + Vec3d(0.51607843, 0.29427451, 0.27003922), + Vec3d(0.74133333, 0.66835294, 0.16490196), + Vec3d(0.8714902, 0.29019608, 0.28215686), + Vec3d(0.94513725, 0.78078431, 0.23980392), + Vec3d(0.88231373, 0.39764706, 0.39309804), + Vec3d(0.68462745, 0.59854902, 0.35890196), + Vec3d(0.97278431, 0.89290196, 0.55098039), + Vec3d(0.94568627, 0.78972549, 0.4532549), + Vec3d(0.92741176, 0.68968627, 0.34847059), + Vec3d(0.83211765, 0.5234902 , 0.21486275), + Vec3d(0.71047059, 0.40368627, 0.14188235), + Vec3d(0.45137255, 0.21086275, 0.05972549)); + + ASSERT_EQ(LinearIdentity().linearize(s), y); +} + +TEST(CV_ccmLinearize, test_gamma) +{ + Mat y = (Mat_(24, 1) << + Vec3d(0.68078957, 0.12382801, 0.01514889), + Vec3d(0.81177942, 0.32550452, 0.089818), + Vec3d(0.61259378, 0.2831933 , 0.07478902), + Vec3d(0.52696493, 0.20105976, 0.00958657), + Vec3d(0.80402284, 0.30419523, 0.12989841), + Vec3d(0.78658646, 0.63184111, 0.12062068), + Vec3d(0.78999637, 0.25520249, 0.03462853), + Vec3d(0.51866697, 0.16114393, 0.1078387), + Vec3d(0.74820768, 0.11770076, 0.06862177), + Vec3d(0.59776825, 0.05765816, 0.02886627), + Vec3d(0.8793145, 0.56346033, 0.0403954), + Vec3d(0.84124847, 0.42120746, 0.03287592), + Vec3d(0.23333214, 0.06780408, 0.05612276), + Vec3d(0.5176423, 0.41210976, 0.01896255), + Vec3d(0.73888613, 0.06575388, 0.06181293), + Vec3d(0.88326036, 0.58018751, 0.04321991), + Vec3d(0.75922531, 0.13149072, 0.1282041), + Vec3d(0.4345097, 0.32331019, 0.10494139), + Vec3d(0.94110142, 0.77941419, 0.26946323), + Vec3d(0.88438952, 0.5949049 , 0.17536928), + Vec3d(0.84722687, 0.44160449, 0.09834799), + Vec3d(0.66743106, 0.24076803, 0.03394333), + Vec3d(0.47141286, 0.13592419, 0.01362205), + Vec3d(0.17377101, 0.03256864, 0.00203026)); + + ASSERT_EQ(LinearGamma(gamma).linearize(s), y); +} + +TEST(CV_ccmLinearize, test_color_polyfit) +{ + Mat y = (Mat_(24, 1) << + Vec3d(2.63480590e-01, 8.19784230e-02, 5.88380570e-02), + Vec3d(5.00308824e-01, 2.55670153e-01, 2.84774400e-01), + Vec3d(1.65547676e-01, 2.18326729e-01, 2.34834241e-01), + Vec3d(6.91579431e-02, 1.46243551e-01, 4.85506219e-02), + Vec3d(4.84584944e-01, 2.36873580e-01, 4.21828895e-01), + Vec3d(4.49999903e-01, 5.15593650e-01, 3.89716261e-01), + Vec3d(4.56680121e-01, 1.93624454e-01, 1.09260805e-01), + Vec3d(6.14667752e-02, 1.12219287e-01, 3.45822331e-01), + Vec3d(3.77645006e-01, 7.72485552e-02, 2.14671313e-01), + Vec3d(1.46690033e-01, 3.83676678e-02, 9.30261311e-02), + Vec3d(6.45841448e-01, 4.59722537e-01, 1.26168624e-01), + Vec3d(5.61924874e-01, 3.39304873e-01, 1.04244341e-01), + Vec3d(-1.24921202e-03, 4.34819857e-02, 1.74574965e-01), + Vec3d(6.05378564e-02, 3.31429676e-01, 6.74475908e-02), + Vec3d(3.60866062e-01, 4.23768038e-02, 1.92686670e-01), + Vec3d(6.54814616e-01, 4.73515714e-01, 1.34642338e-01), + Vec3d(3.97880165e-01, 8.80047529e-02, 4.15950016e-01), + Vec3d(8.88761384e-04, 2.53736288e-01, 3.35935826e-01), + Vec3d(7.92140863e-01, 6.31444183e-01, 9.20127919e-01), + Vec3d(6.57391804e-01, 4.85584457e-01, 5.81564694e-01), + Vec3d(5.74784077e-01, 3.56891943e-01, 3.13534251e-01), + Vec3d(2.42877023e-01, 1.80918764e-01, 1.07292088e-01), + Vec3d(2.34750448e-02, 9.15416417e-02, 5.56885760e-02), + Vec3d(4.16360011e-02, 3.14799517e-02, 4.67810688e-02)); + + ASSERT_EQ(LinearColor(deg, s, color, mask, cs).linearize(s), y); +} + +TEST(CV_ccmLinearize, test_color_logpolyfit) +{ + Mat y = (Mat_(24, 1) << + Vec3d(0.21210199, 0.08042872, 0.06177358), + Vec3d(0.43902276, 0.25803696, 0.22625212), + Vec3d(0.1392843, 0.21910892, 0.16649895), + Vec3d(0.07929871, 0.14429388, 0.06124959), + Vec3d(0.42175787, 0.23847603, 0.50200816), + Vec3d(0.38486859, 0.49908647, 0.41903521), + Vec3d(0.39187715, 0.19330825, 0.07678465), + Vec3d(0.07496683, 0.10997862, 0.3253915), + Vec3d(0.31256906, 0.07589457, 0.14682645), + Vec3d(0.12666458, 0.03865245, 0.07035133), + Vec3d(0.61317011, 0.453896, 0.08471713), + Vec3d(0.509618, 0.34295792, 0.07466823), + Vec3d(0.01792609, 0.04375426, 0.11416985), + Vec3d(0.07444757, 0.33517131, 0.06308572), + Vec3d(0.29675398, 0.04267084, 0.1279182), + Vec3d(0.62473933, 0.46546083, 0.08914635), + Vec3d(0.33211973, 0.08623901, 0.48580852), + Vec3d(0.04223237, 0.25602914, 0.30707565), + Vec3d(0.81340438, 0.57786249, 5.63433098), + Vec3d(0.62807965, 0.47536376, 1.17181577), + Vec3d(0.52493661, 0.36014609, 0.26894465), + Vec3d(0.19575899, 0.18007616, 0.0759408), + Vec3d(0.05430091, 0.08966718, 0.06150038), + Vec3d(0.02983422, 0.03045434, 0.03305337)); + + ASSERT_EQ(LinearColor(deg, s, color, mask, cs).linearize(s), y); +} + +TEST(CV_ccmLinearize, test_gray_polyfit) +{ + Mat y = (Mat_(24, 1) << + Vec3d(0.51666899, 0.05119734, 0.08355961), + Vec3d(0.59846831, 0.22746696, 0.03275719), + Vec3d(0.46851612, 0.18821734, 0.02831637), + Vec3d(0.40274707, 0.11294869, 0.10983447), + Vec3d(0.59401581, 0.20773439, 0.05530918), + Vec3d(0.58382767, 0.48249077, 0.04910519), + Vec3d(0.58583962, 0.16222916, 0.04147151), + Vec3d(0.39606823, 0.07893876, 0.04140637), + Vec3d(0.56052673, 0.0472528, 0.02748943), + Vec3d(0.45754742, 0.02793019, 0.04935323), + Vec3d(0.63517156, 0.43148826, 0.0358448), + Vec3d(0.61493813, 0.31386138, 0.04359845), + Vec3d(0.14207934, 0.02743228, 0.02822652), + Vec3d(0.39523985, 0.30585866, 0.07094004), + Vec3d(0.55468508, 0.02734776, 0.0274375), + Vec3d(0.6372021, 0.44431108, 0.03374266), + Vec3d(0.56733935, 0.05641847, 0.05414246), + Vec3d(0.32546855, 0.22544012, 0.0398225), + Vec3d(0.66553671, 0.57956479, 0.17545548), + Vec3d(0.63778086, 0.45540855, 0.09069633), + Vec3d(0.61819338, 0.33161218, 0.03647687), + Vec3d(0.50753594, 0.14890516, 0.0422774), + Vec3d(0.35705032, 0.05956936, 0.08964481), + Vec3d(0.0893518, 0.04399409, 0.18717526)); + + ASSERT_EQ(LinearGray(deg, s, color, mask, cs).linearize(s), y); +} + +TEST(CV_ccmLinearize, test_gray_logpolyfit) +{ + Mat y = (Mat_(24, 1) << + Vec3d(4.60331981e-01, 5.51120816e-02, 3.97365482e-01), + Vec3d(4.73679407e-01, 2.29393836e-01, 3.84195886e-02), + Vec3d(4.38764462e-01, 1.88051166e-01, 3.33576233e-02), + Vec3d(3.95560026e-01, 1.11621603e-01, 5.06142257e+00), + Vec3d(4.73785486e-01, 2.08586471e-01, 5.87127685e-02), + Vec3d(4.73648490e-01, 4.45971601e-01, 5.32792915e-02), + Vec3d(4.73716999e-01, 1.61001724e-01, 4.02782927e-02), + Vec3d(3.90399630e-01, 7.97334727e-02, 4.64809651e-02), + Vec3d(4.71407192e-01, 5.16538838e-02, 3.18491932e-02), + Vec3d(4.32594201e-01, 3.03426453e-02, 5.36226594e-02), + Vec3d(4.68788432e-01, 4.16218138e-01, 3.42954435e-02), + Vec3d(4.72387015e-01, 3.17700868e-01, 4.32262905e-02), + Vec3d(1.40435644e-01, 3.16799217e-02, 3.02925240e-02), + Vec3d(3.89750609e-01, 3.09885279e-01, 1.63338781e-01), + Vec3d(4.70437333e-01, 3.12909236e-02, 3.07003735e-02), + Vec3d(4.68300410e-01, 4.24570259e-01, 3.26637048e-02), + Vec3d(4.72334300e-01, 5.96850848e-02, 5.76907392e-02), + Vec3d(3.28844114e-01, 2.27258414e-01, 4.50583791e-02), + Vec3d(4.58942833e-01, 4.73436696e-01, 1.74710299e-01), + Vec3d(4.68156974e-01, 4.31339830e-01, 9.05259853e-02), + Vec3d(4.71960375e-01, 3.34642678e-01, 4.19957551e-02), + Vec3d(4.56964629e-01, 1.47352730e-01, 4.13462335e-02), + Vec3d(3.57831381e-01, 6.24515609e-02, 6.54872609e-01), + Vec3d(8.92793201e-02, 4.38221622e-02, 3.22837394e+08)); + + ASSERT_EQ(LinearGray(deg, s, color, mask, cs).linearize(s)(cv::Rect(0, 0, 1, 23)), y(cv::Rect(0, 0, 1, 23))); +} + +} // namespace +} // namespace opencv_test \ No newline at end of file diff --git a/modules/mcc/test/test_precomp.hpp b/modules/mcc/test/test_precomp.hpp index 1f399441353..962a8d13d4e 100644 --- a/modules/mcc/test/test_precomp.hpp +++ b/modules/mcc/test/test_precomp.hpp @@ -7,6 +7,7 @@ #include "opencv2/ts.hpp" #include "opencv2/mcc.hpp" +#include "opencv2/mcc/ccm.hpp" namespace opencv_test { diff --git a/modules/mcc/test/test_utils.cpp b/modules/mcc/test/test_utils.cpp new file mode 100644 index 00000000000..09a3d295a4b --- /dev/null +++ b/modules/mcc/test/test_utils.cpp @@ -0,0 +1,47 @@ +// This file is part of OpenCV project. +// It is subject to the license terms in the LICENSE file found in the top-level directory +// of this distribution and at http://opencv.org/license.html. + +#include "test_precomp.hpp" + +namespace opencv_test +{ +namespace +{ + +TEST(CV_ccmUtils, test_gamma_correction) +{ + Mat x = (Mat_(4, 3) << + 0.8, -0.5, 0.6, + 0.2, 0.9, -0.9, + 1. , -0.2 , 0.4, + -0.4, 0.1, 0.3); + Mat y = (Mat_(4, 3) << + 0.6120656, -0.21763764, 0.32503696, + 0.02899119, 0.79311017, -0.79311017, + 1., -0.02899119, 0.13320851, + -0.13320851, 0.00630957, 0.07074028); + ASSERT_EQ(gammaCorrection(x, 2.2), y); +} + +TEST(CV_ccmUtils, test_saturate) +{ + Mat x = (Mat_(5, 3) << + 0., 0.5, 0., + 0., 0.3, 0.4, + 0.3, 0.8, 0.4, + 0.7, 0.6, 0.2, + 1., 0.8, 0.5); + Mat y = (Mat_(1, 5) <(1, 3) <<0.2, 0.3, 0.4); + double y = 0.28596; + ASSERT_EQ(rgb2gray(x), y); +} + +} // namespace +} // namespace opencv_test \ No newline at end of file