mirror of
https://github.com/opencv/opencv.git
synced 2026-07-21 19:33:03 +04:00
Merge pull request #27051 from gursimarsingh:move_ccm_to_photo_module
Adding color correction module to photo module from opencv_contrib #27051 This PR moved color correction module from opencv_contrib to main repo inside photo module. ### Pull Request Readiness Checklist See details at https://github.com/opencv/opencv/wiki/How_to_contribute#making-a-good-pull-request - [x] I agree to contribute to the project under Apache 2 License. - [x] To the best of my knowledge, the proposed patch is not based on a code under GPL or another license that is incompatible with OpenCV - [x] The PR is proposed to the proper branch - [x] There is a reference to the original bug report and related work - [x] There is accuracy test, performance test and test data in opencv_extra repository, if applicable Patch to opencv_extra has the same branch name. - [x] The feature is well documented and sample code can be built with the project CMake
This commit is contained in:
@@ -1,4 +0,0 @@
|
||||
Computational photography (photo module) {#tutorial_table_of_content_photo}
|
||||
========================================
|
||||
|
||||
Content has been moved to this page: @ref tutorial_table_of_content_other
|
||||
@@ -1,7 +1,6 @@
|
||||
Other tutorials (photo, stitching, video) {#tutorial_table_of_content_other}
|
||||
Other tutorials (stitching, video) {#tutorial_table_of_content_other}
|
||||
========================================================
|
||||
|
||||
- photo. @subpage tutorial_hdr_imaging
|
||||
- stitching. @subpage tutorial_stitcher
|
||||
- video. @subpage tutorial_background_subtraction
|
||||
- video. @subpage tutorial_meanshift
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
Color Correction Model{#tutorial_ccm_color_correction_model}
|
||||
===========================
|
||||
|
||||
Introduction
|
||||
----
|
||||
|
||||
The purpose of color correction is to adjust the color response of input and output devices to a known state. The device being calibrated is sometimes called the calibration source; the color space used as the standard is sometimes called the calibration target. Color calibration has been used in many industries, such as television production, games, photography, engineering, chemistry, medicine, etc. Due to the manufacturing process of the input and output equipment, the channel response has nonlinear distortion. In order to correct the picture output of the equipment, it is nessary to calibrate the captured color and the actual color.
|
||||
|
||||
In this tutorial you will learn how to use the 'Color Correction Model' to do a color correction in a image.
|
||||
|
||||
The color correction functionalities are included in:
|
||||
```cpp
|
||||
#include <opencv2/photo/ccm.hpp>
|
||||
```
|
||||
|
||||
Reference
|
||||
----
|
||||
|
||||
See details of ColorCorrection Algorithm at https://github.com/riskiest/color_calibration/tree/v4/doc/pdf/English/Algorithm
|
||||
|
||||
Source Code of the sample
|
||||
-----------
|
||||
|
||||
The sample has two parts of code, the first is the color checker detector model, see details at tutorial_macbeth_chart_detection, the second part is to make color calibration.
|
||||
|
||||
```
|
||||
Here are the parameters for ColorCorrectionModel
|
||||
src :
|
||||
detected colors of ColorChecker patches;
|
||||
NOTICE: the color type is RGB not BGR, and the color values are in [0, 1];
|
||||
constcolor :
|
||||
the Built-in color card;
|
||||
Supported list:
|
||||
Macbeth: Macbeth ColorChecker ;
|
||||
Vinyl: DKK ColorChecker ;
|
||||
DigitalSG: DigitalSG ColorChecker with 140 squares;
|
||||
Mat colors :
|
||||
the reference color values
|
||||
and corresponding color space
|
||||
NOTICE: the color values are in [0, 1]
|
||||
refColorSpace :
|
||||
the corresponding color space
|
||||
If the color type is some RGB, the format is RGB not BGR;
|
||||
Supported Color Space:
|
||||
Must be one of the members of the ColorSpace enum.
|
||||
@snippet modules/photo/include/opencv2/photo/ccm.hpp ColorSpace
|
||||
For the full, up-to-date list see cv::ccm::ColorSpace in ccm.hpp.
|
||||
```
|
||||
|
||||
|
||||
## Code
|
||||
|
||||
@snippet samples/cpp/color_correction_model.cpp tutorial
|
||||
@@ -0,0 +1,231 @@
|
||||
Linearization Transformation For Color Correction {#tutorial_ccm_linearization_transformation}
|
||||
============================
|
||||
|
||||
Overview
|
||||
------------
|
||||
|
||||
The first step in color correction is to linearize the detected colors. Since the input color space may not be calibrated, empirical methods are used for linearization. The most common methods include:
|
||||
|
||||
1. Identical Transformation
|
||||
2. Gamma Correction
|
||||
3. Polynomial Fitting
|
||||
|
||||
Linearization is typically an element-wise function. The following symbols are used:
|
||||
|
||||
\f$C\f$: Any color channel (\f$R, G\f$, or \f$B\f$)
|
||||
\f$R, G, B\f$: Respective color channels
|
||||
\f$G\f$: Grayscale
|
||||
\f$s, sl\f$: Represents the detected data and its linearized value, the former is the input and the latter is the output
|
||||
\f$d, dl\f$: Reference data and its linearized value
|
||||
|
||||
---
|
||||
|
||||
Identical Transformation
|
||||
------------
|
||||
|
||||
No change is made during the Identical transformation linearization, usually because the tristimulus values of the input RGB image is already proportional to the luminance.\
|
||||
For example, if the input measurement data is in RAW format, the measurement data is already linear, so no linearization is required.
|
||||
|
||||
**Formula:**
|
||||
\f[
|
||||
C_{sl}=C_s
|
||||
\f]
|
||||
|
||||
---
|
||||
|
||||
Gamma Correction
|
||||
------------
|
||||
|
||||
Gamma correction is a means of performing nonlinearity in RGB space, see the Color Space documentation for details.\
|
||||
In the linearization part, the value of \f$gamma\f$ is usually set to 2.2.
|
||||
You can also customize the value.
|
||||
|
||||
**Formulas:**
|
||||
\f[
|
||||
\begin{aligned}
|
||||
C_{sl}=C_s^{\gamma},\qquad C_s\ge0\\
|
||||
C_{sl}=-(-C_s)^{\gamma},\qquad C_s<0\\\\
|
||||
\end{aligned}
|
||||
\f]
|
||||
|
||||
---
|
||||
|
||||
Polynomial Fitting
|
||||
------------
|
||||
|
||||
Linearization using polynomial fitting.
|
||||
|
||||
**Polynomial form:**
|
||||
\f[
|
||||
f(x)=a_nx^n+a_{n-1}x^{n-1}+... +a_0
|
||||
\f]
|
||||
Then:
|
||||
\f[
|
||||
C_{sl}=f(C_s)
|
||||
\f]
|
||||
|
||||
*Usually n ≤ 3 to avoid overfitting.*\
|
||||
It is usually necessary to use linearized reference colors and corresponding detected colors to calculate the polynomial parameters.\
|
||||
However, not all colors can participate in the calculation. The saturation detected colors needs to be removed. See the algorithm introduction document for details.
|
||||
|
||||
### Fitting Channels Respectively
|
||||
Use three polynomials, \f$r(x), g(x), b(x)\f$, to linearize each channel of the RGB color space[1-3]:
|
||||
\f[
|
||||
\begin{aligned}
|
||||
R_{sl}=r(R_s)\\
|
||||
G_{sl}=g(G_s)\\
|
||||
B_{sl}=b(B_s)\\
|
||||
\end{aligned}
|
||||
\f]
|
||||
The polynomial is generated by minimizing the residual sum of squares between the detected data and the linearized reference data.\
|
||||
Take the R-channel as an example:
|
||||
|
||||
\f[
|
||||
R=\arg min_{f}(\Sigma(R_{dl}-f(R_S)^2))
|
||||
\f]
|
||||
|
||||
It's equivalent to finding the least square regression for below equations:
|
||||
\f[
|
||||
\begin{aligned}
|
||||
f(R_{s1})=R_{dl1}\\
|
||||
f(R_{s2})=R_{dl2}\\
|
||||
...
|
||||
\end{aligned}
|
||||
\f]
|
||||
|
||||
With a polynomial, the equations become:
|
||||
\f[
|
||||
\begin{bmatrix}
|
||||
R_{s1}^{n} & R_{s1}^{n-1} & ... & 1\\
|
||||
R_{s2}^{n} & R_{s2}^{n-1} & ... & 1\\
|
||||
... & ... & ... & ...
|
||||
\end{bmatrix}
|
||||
\begin{bmatrix}
|
||||
a_{n}\\
|
||||
a_{n-1}\\
|
||||
... \\
|
||||
a_0
|
||||
\end{bmatrix}
|
||||
=
|
||||
\begin{bmatrix}
|
||||
R_{dl1}\\
|
||||
R_{dl2}\\
|
||||
...
|
||||
\end{bmatrix}
|
||||
\f]
|
||||
This can be expressed in matrix form as:
|
||||
\f[
|
||||
AX=B
|
||||
\f]
|
||||
**Coefficient calculation:**
|
||||
\f[
|
||||
X=(A^TA)^{-1}A^TB
|
||||
\f]
|
||||
Once we get the polynomial coefficients, we can get the polynomial r.\
|
||||
This method of finding polynomial coefficients can be implemented by numpy.polyfit in numpy, expressed here as:
|
||||
\f[
|
||||
R=polyfit(R_S, R_{dl})
|
||||
\f]
|
||||
Note that, in general, the polynomial that we want to obtain is guaranteed to monotonically increase in the interval [0,1] ,\
|
||||
but this means that nonlinear method is needed to generate the polynomials(see [4] for detail).\
|
||||
This would greatly increases the complexity of the program.\
|
||||
Considering that the monotonicity does not affect the correct operation of the color correction program, polyfit is still used to implement the program.
|
||||
|
||||
Parameters for other channels can also be derived in a similar way.
|
||||
|
||||
### Grayscale Polynomial Fitting
|
||||
In this method[2], single polynomial is used for all channels.
|
||||
The polynomial is still a polyfit result from the detected colors to the linear reference colors.
|
||||
However, only the gray of the reference colors can participate in the calculation.
|
||||
|
||||
Since the detected colors corresponding to the gray of reference colors is not necessarily gray, it needs to be grayed.
|
||||
Grayscale refers to the Y channel of the XYZ color space.
|
||||
The color space of the detected data is not determined and cannot be converted into the XYZ space.
|
||||
Therefore, the sRGB formula is used to approximate[5].
|
||||
\f[
|
||||
G_{s}=0.2126R_{s}+0.7152G_{s}+0.0722B_{s}
|
||||
\f]
|
||||
Then the polynomial parameters can be obtained by using the polyfit:
|
||||
\f[
|
||||
f=polyfit(G_{s}, G_{dl})
|
||||
\f]
|
||||
After \f$f\f$ is obtained, linearization can be performed.
|
||||
|
||||
### Logarithmic Polynomial Fitting
|
||||
Takes the logarithm of gamma correction:
|
||||
\f[
|
||||
ln(C_{sl})={\gamma}ln(C_s),\qquad C_s\ge0\
|
||||
\f]
|
||||
It can be seen that there is a linear relationship between \f$ln(C_s)\f$ and \f$ln(C_{sl})\f$. It can be considered that the formula is an approximation of a polynomial relationship, that is, there exists a polynomial \f$f\f$, which makes[2]:
|
||||
\f[
|
||||
\begin{aligned}
|
||||
ln(C_{sl})=f(ln(C_s)), \qquad C_s>0\\
|
||||
C_{sl}=0, \qquad C_s=0
|
||||
\end{aligned}
|
||||
\f]
|
||||
|
||||
Because \f$exp(ln(0))\to\infty \f$, the channel component that is zero is directly mapped to zero in this formula.
|
||||
|
||||
**Fitted using polyfit on logarithmic values:**
|
||||
\f[
|
||||
\begin{aligned}
|
||||
r=polyfit(ln(R_s),ln(R_{dl}))\\
|
||||
g=polyfit(ln(G_s),ln(G_{dl}))\\
|
||||
b=polyfit(ln(B_s),ln(B_{dl}))\\
|
||||
\end{aligned}
|
||||
\f]
|
||||
|
||||
Note: The parameter of \f$ln(*) \f$ cannot be zero. Therefore, we need to delete all channel values that are 0 from \f$R_s \f$ and \f$R_{dl} \f$, \f$G_s\f$ and \f$G_{dl}\f$, \f$B_s\f$ and \f$B_{dl}\f$.
|
||||
|
||||
The final fitting equations become:
|
||||
\f[
|
||||
\begin{aligned}
|
||||
\ln(R_{sl}) &= r(\ln(R_s)), \qquad R_s > 0 \\
|
||||
R_{sl} &= 0, \qquad R_s = 0 \\
|
||||
\ln(G_{sl}) &= g(\ln(G_s)), \qquad G_s > 0 \\
|
||||
G_{sl} &= 0, \qquad G_s = 0 \\
|
||||
\ln(B_{sl}) &= b(\ln(B_s)), \qquad B_s > 0 \\
|
||||
B_{sl} &= 0, \qquad B_s = 0
|
||||
\end{aligned}
|
||||
\f]
|
||||
|
||||
For grayscale polynomials, there are also:
|
||||
\f[
|
||||
f=polyfit(ln(G_{sl}),ln(G_{dl}))
|
||||
\f]
|
||||
and:
|
||||
\f[
|
||||
\begin{aligned}
|
||||
ln(C_{sl})=f(ln(C_s)), \qquad C_s>0\\
|
||||
C_sl=0, \qquad C_s=0
|
||||
\end{aligned}
|
||||
\f]
|
||||
---
|
||||
The functionalities are included in:
|
||||
@code{.cpp}
|
||||
#include <opencv2/photo/ccm.hpp>
|
||||
@endcode
|
||||
|
||||
Enum Definition
|
||||
------------
|
||||
|
||||
```cpp
|
||||
enum LINEAR_TYPE
|
||||
{
|
||||
LINEARIZATION_IDENTITY, // No change
|
||||
LINEARIZATION_GAMMA, // Gamma correction; requires gamma value
|
||||
LINEARIZATION_COLORPOLYFIT, // Polynomial fitting for each channel; requires degree
|
||||
LINEARIZATION_COLORLOGPOLYFIT, // Logarithmic polynomial fitting; requires degree
|
||||
LINEARIZATION_GRAYPOLYFIT, // Grayscale polynomial fitting; requires degree and dst_whites
|
||||
LINEARIZATION_GRAYLOGPOLYFIT // Grayscale logarithmic polynomial fitting; requires degree and dst_whites
|
||||
};
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## References
|
||||
- [1-3] Refer to polynomial fitting methods and empirical studies.
|
||||
- [4] Describes nonlinear polynomial generation methods.
|
||||
- [5] sRGB approximation for grayscale calculation.
|
||||
|
||||
This documentation is part of the OpenCV photo module.
|
||||
@@ -0,0 +1,6 @@
|
||||
Photo (photo module) {#tutorial_table_of_content_photo}
|
||||
==========================================================
|
||||
|
||||
- @subpage tutorial_hdr_imaging
|
||||
- @subpage tutorial_ccm_color_correction_model
|
||||
- @subpage tutorial_ccm_linearization_transformation
|
||||
@@ -9,9 +9,10 @@ OpenCV Tutorials {#tutorial_root}
|
||||
- @subpage tutorial_table_of_content_objdetect - INSERT OBJDETECT MODULE INFO
|
||||
- @subpage tutorial_table_of_content_features - feature detectors, descriptors and matching framework
|
||||
- @subpage tutorial_table_of_content_dnn - infer neural networks using built-in _dnn_ module
|
||||
- @subpage tutorial_table_of_content_other - other modules (stitching, video, photo)
|
||||
- @subpage tutorial_table_of_content_other - other modules (stitching, video)
|
||||
- @subpage tutorial_table_of_content_ios - running OpenCV on an iDevice
|
||||
- @subpage tutorial_table_of_content_3d - 3d objects processing and visualisation
|
||||
- @subpage tutorial_table_of_content_photo - photo module functions (hdr_image, ccm)
|
||||
@cond CUDA_MODULES
|
||||
- @subpage tutorial_table_of_content_gpu - utilizing power of video card to run CV algorithms
|
||||
@endcond
|
||||
|
||||
@@ -212,8 +212,10 @@ bool CCheckerDetectorImpl::
|
||||
// checker color analysis
|
||||
//-------------------------------------------------------------------
|
||||
std::vector<Ptr<CChecker>> checkers;
|
||||
Point2f total_offset = static_cast<Point2f>(region.tl());
|
||||
checkerAnalysis(img_rgb_f, nc, colorCharts, checkers, asp,
|
||||
img_rgb_org, img_ycbcr_org, rgb_planes, ycbcr_planes);
|
||||
img_rgb_org, img_ycbcr_org, rgb_planes, ycbcr_planes,
|
||||
total_offset);
|
||||
|
||||
#ifdef MCC_DEBUG
|
||||
Mat image_checker;
|
||||
@@ -223,16 +225,8 @@ bool CCheckerDetectorImpl::
|
||||
#endif
|
||||
for (Ptr<CChecker> checker : checkers)
|
||||
{
|
||||
const std::vector<Point2f>& checkerBox = checker->getBox();
|
||||
std::vector<Point2f> restore_box(checkerBox.size());
|
||||
for (size_t a = 0; a < checkerBox.size(); ++a) {
|
||||
restore_box[a] = checkerBox[a] + static_cast<Point2f>(region.tl());
|
||||
}
|
||||
checker->setBox(restore_box);
|
||||
{
|
||||
AutoLock lock(mtx);
|
||||
m_checkers.push_back(checker);
|
||||
}
|
||||
AutoLock lock(mtx);
|
||||
m_checkers.push_back(checker);
|
||||
}
|
||||
}
|
||||
#ifdef MCC_DEBUG
|
||||
@@ -433,8 +427,10 @@ bool CCheckerDetectorImpl::
|
||||
// checker color analysis
|
||||
//-------------------------------------------------------------------
|
||||
std::vector<Ptr<CChecker>> checkers;
|
||||
Point2f total_offset = static_cast<Point2f>(region.tl() + innerRegion.tl());
|
||||
checkerAnalysis(img_rgb_f, nc, colorCharts, checkers, asp,
|
||||
img_rgb_org, img_ycbcr_org, rgb_planes, ycbcr_planes);
|
||||
img_rgb_org, img_ycbcr_org, rgb_planes, ycbcr_planes,
|
||||
total_offset);
|
||||
#ifdef MCC_DEBUG
|
||||
Mat image_checker;
|
||||
innerCroppedImage.copyTo(image_checker);
|
||||
@@ -443,16 +439,8 @@ bool CCheckerDetectorImpl::
|
||||
#endif
|
||||
for (Ptr<CChecker> checker : checkers)
|
||||
{
|
||||
const std::vector<Point2f>& checkerBox = checker->getBox();
|
||||
std::vector<Point2f> restore_box(checkerBox.size());
|
||||
for (size_t a = 0; a < checkerBox.size(); ++a) {
|
||||
restore_box[a] = checkerBox[a] + static_cast<Point2f>(region.tl() + innerRegion.tl());
|
||||
}
|
||||
checker->setBox(restore_box);
|
||||
{
|
||||
AutoLock lock(mtx);
|
||||
m_checkers.push_back(checker);
|
||||
}
|
||||
AutoLock lock(mtx);
|
||||
m_checkers.push_back(checker);
|
||||
}
|
||||
}
|
||||
#ifdef MCC_DEBUG
|
||||
@@ -1216,7 +1204,8 @@ void CCheckerDetectorImpl::
|
||||
const Mat &img_rgb_org,
|
||||
const Mat &img_ycbcr_org,
|
||||
std::vector<Mat> &rgb_planes,
|
||||
std::vector<Mat> &ycbcr_planes)
|
||||
std::vector<Mat> &ycbcr_planes,
|
||||
const Point2f& offset)
|
||||
{
|
||||
size_t N;
|
||||
std::vector<Point2f> ibox;
|
||||
@@ -1252,9 +1241,9 @@ void CCheckerDetectorImpl::
|
||||
if (J[i] > m_params.maxError)
|
||||
continue;
|
||||
|
||||
// redimention box
|
||||
// redimension box
|
||||
for (size_t j = 0; j < 4; j++)
|
||||
ibox[j] = invAsp * ibox[j];
|
||||
ibox[j] = invAsp * ibox[j] + offset;
|
||||
|
||||
Mat charts_rgb, charts_ycbcr;
|
||||
get_profile(ibox, charts_rgb, charts_ycbcr, img_rgb_org,
|
||||
|
||||
@@ -161,7 +161,8 @@ protected: // methods pipeline
|
||||
const Mat &img_rgb_org,
|
||||
const Mat &img_ycbcr_org,
|
||||
std::vector<Mat> &rgb_planes,
|
||||
std::vector<Mat> &ycbcr_planes);
|
||||
std::vector<Mat> &ycbcr_planes,
|
||||
const Point2f& offset);
|
||||
|
||||
virtual void
|
||||
removeTooCloseDetections();
|
||||
|
||||
@@ -45,6 +45,7 @@
|
||||
|
||||
#include "opencv2/core.hpp"
|
||||
#include "opencv2/imgproc.hpp"
|
||||
#include "opencv2/photo/ccm.hpp"
|
||||
|
||||
/**
|
||||
@defgroup photo Computational Photography
|
||||
|
||||
@@ -0,0 +1,322 @@
|
||||
// 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.
|
||||
//
|
||||
// Author: Longbu Wang <wanglongbu@huawei.com.com>
|
||||
// Jinheng Zhang <zhangjinheng1@huawei.com>
|
||||
// Chenqi Shan <shanchenqi@huawei.com>
|
||||
|
||||
#ifndef OPENCV_PHOTO_CCM_HPP
|
||||
#define OPENCV_PHOTO_CCM_HPP
|
||||
|
||||
#include <opencv2/core.hpp>
|
||||
#include <opencv2/imgproc.hpp>
|
||||
|
||||
namespace cv
|
||||
{
|
||||
namespace ccm
|
||||
{
|
||||
|
||||
/** @defgroup ccm Color Correction module
|
||||
@{
|
||||
*/
|
||||
|
||||
/** @brief Enum of the possible types of ccm.
|
||||
*/
|
||||
enum CcmType
|
||||
{
|
||||
CCM_LINEAR, ///< Uses a \f$3\times3\f$ matrix to linearly transform RGB values without offsets.
|
||||
CCM_AFFINE, ///< Uses a \f$4\times3\f$ matrix to affine transform RGB values with both scaling and offset terms.
|
||||
};
|
||||
|
||||
/** @brief Enum of the possible types of initial method.
|
||||
*/
|
||||
enum InitialMethodType
|
||||
{
|
||||
INITIAL_METHOD_WHITE_BALANCE, ///< The white balance method. The initial value is:\n
|
||||
/// \f$
|
||||
/// M_{CCM}=
|
||||
/// \begin{bmatrix}
|
||||
/// k_R & 0 & 0\\
|
||||
/// 0 & k_G & 0\\
|
||||
/// 0 & 0 & k_B\\
|
||||
/// \end{bmatrix}
|
||||
/// \f$\n
|
||||
/// where\n
|
||||
/// \f$
|
||||
/// k_R=mean(R_{li}')/mean(R_{li})\\
|
||||
/// k_G=mean(G_{li}')/mean(G_{li})\\
|
||||
/// k_B=mean(B_{li}')/mean(B_{li})
|
||||
/// \f$
|
||||
INITIAL_METHOD_LEAST_SQUARE, ///< The least square method is an optimal solution under the linear RGB distance function
|
||||
};
|
||||
/** @brief Macbeth and Vinyl ColorChecker with 2deg D50
|
||||
*/
|
||||
enum ColorCheckerType {
|
||||
COLORCHECKER_MACBETH, ///< Macbeth ColorChecker
|
||||
COLORCHECKER_VINYL, ///< DKK ColorChecker
|
||||
COLORCHECKER_DIGITAL_SG, ///< DigitalSG ColorChecker with 140 squares
|
||||
};
|
||||
enum ColorSpace {
|
||||
COLOR_SPACE_SRGB, ///< https://en.wikipedia.org/wiki/SRGB , RGB color space
|
||||
COLOR_SPACE_SRGBL, ///< https://en.wikipedia.org/wiki/SRGB , linear RGB color space
|
||||
COLOR_SPACE_ADOBE_RGB, ///< https://en.wikipedia.org/wiki/Adobe_RGB_color_space , RGB color space
|
||||
COLOR_SPACE_ADOBE_RGBL, ///< https://en.wikipedia.org/wiki/Adobe_RGB_color_space , linear RGB color space
|
||||
COLOR_SPACE_WIDE_GAMUT_RGB, ///< https://en.wikipedia.org/wiki/Wide-gamut_RGB_color_space , RGB color space
|
||||
COLOR_SPACE_WIDE_GAMUT_RGBL, ///< https://en.wikipedia.org/wiki/Wide-gamut_RGB_color_space , linear RGB color space
|
||||
COLOR_SPACE_PRO_PHOTO_RGB, ///< https://en.wikipedia.org/wiki/ProPhoto_RGB_color_space , RGB color space
|
||||
COLOR_SPACE_PRO_PHOTO_RGBL, ///< https://en.wikipedia.org/wiki/ProPhoto_RGB_color_space , linear RGB color space
|
||||
COLOR_SPACE_DCI_P3_RGB, ///< https://en.wikipedia.org/wiki/DCI-P3 , RGB color space
|
||||
COLOR_SPACE_DCI_P3_RGBL, ///< https://en.wikipedia.org/wiki/DCI-P3 , linear RGB color space
|
||||
COLOR_SPACE_APPLE_RGB, ///< http://www.brucelindbloom.com/index.html?WorkingSpaceInfo.html , RGB color space
|
||||
COLOR_SPACE_APPLE_RGBL, ///< http://www.brucelindbloom.com/index.html?WorkingSpaceInfo.html , linear RGB color space
|
||||
COLOR_SPACE_REC_709_RGB, ///< https://en.wikipedia.org/wiki/Rec._709 , RGB color space
|
||||
COLOR_SPACE_REC_709_RGBL, ///< https://en.wikipedia.org/wiki/Rec._709 , linear RGB color space
|
||||
COLOR_SPACE_REC_2020_RGB, ///< https://en.wikipedia.org/wiki/Rec._2020 , RGB color space
|
||||
COLOR_SPACE_REC_2020_RGBL, ///< https://en.wikipedia.org/wiki/Rec._2020 , linear RGB color space
|
||||
COLOR_SPACE_XYZ_D65_2, ///< https://en.wikipedia.org/wiki/CIE_1931_color_space , XYZ color space, D65 illuminant, 2 degree
|
||||
COLOR_SPACE_XYZ_D50_2, ///< https://en.wikipedia.org/wiki/CIE_1931_color_space , XYZ color space, D50 illuminant, 2 degree
|
||||
COLOR_SPACE_XYZ_D65_10, ///< https://en.wikipedia.org/wiki/CIE_1931_color_space , XYZ color space, D65 illuminant, 10 degree
|
||||
COLOR_SPACE_XYZ_D50_10, ///< https://en.wikipedia.org/wiki/CIE_1931_color_space , XYZ color space, D50 illuminant, 10 degree
|
||||
COLOR_SPACE_XYZ_A_2, ///< https://en.wikipedia.org/wiki/CIE_1931_color_space , XYZ color space, A illuminant, 2 degree
|
||||
COLOR_SPACE_XYZ_A_10, ///< https://en.wikipedia.org/wiki/CIE_1931_color_space , XYZ color space, A illuminant, 10 degree
|
||||
COLOR_SPACE_XYZ_D55_2, ///< https://en.wikipedia.org/wiki/CIE_1931_color_space , XYZ color space, D55 illuminant, 2 degree
|
||||
COLOR_SPACE_XYZ_D55_10, ///< https://en.wikipedia.org/wiki/CIE_1931_color_space , XYZ color space, D55 illuminant, 10 degree
|
||||
COLOR_SPACE_XYZ_D75_2, ///< https://en.wikipedia.org/wiki/CIE_1931_color_space , XYZ color space, D75 illuminant, 2 degree
|
||||
COLOR_SPACE_XYZ_D75_10, ///< https://en.wikipedia.org/wiki/CIE_1931_color_space , XYZ color space, D75 illuminant, 10 degree
|
||||
COLOR_SPACE_XYZ_E_2, ///< https://en.wikipedia.org/wiki/CIE_1931_color_space , XYZ color space, E illuminant, 2 degree
|
||||
COLOR_SPACE_XYZ_E_10, ///< https://en.wikipedia.org/wiki/CIE_1931_color_space , XYZ color space, E illuminant, 10 degree
|
||||
COLOR_SPACE_LAB_D65_2, ///< https://en.wikipedia.org/wiki/CIELAB_color_space , Lab color space, D65 illuminant, 2 degree
|
||||
COLOR_SPACE_LAB_D50_2, ///< https://en.wikipedia.org/wiki/CIELAB_color_space , Lab color space, D50 illuminant, 2 degree
|
||||
COLOR_SPACE_LAB_D65_10, ///< https://en.wikipedia.org/wiki/CIELAB_color_space , Lab color space, D65 illuminant, 10 degree
|
||||
COLOR_SPACE_LAB_D50_10, ///< https://en.wikipedia.org/wiki/CIELAB_color_space , Lab color space, D50 illuminant, 10 degree
|
||||
COLOR_SPACE_LAB_A_2, ///< https://en.wikipedia.org/wiki/CIELAB_color_space , Lab color space, A illuminant, 2 degree
|
||||
COLOR_SPACE_LAB_A_10, ///< https://en.wikipedia.org/wiki/CIELAB_color_space , Lab color space, A illuminant, 10 degree
|
||||
COLOR_SPACE_LAB_D55_2, ///< https://en.wikipedia.org/wiki/CIELAB_color_space , Lab color space, D55 illuminant, 2 degree
|
||||
COLOR_SPACE_LAB_D55_10, ///< https://en.wikipedia.org/wiki/CIELAB_color_space , Lab color space, D55 illuminant, 10 degree
|
||||
COLOR_SPACE_LAB_D75_2, ///< https://en.wikipedia.org/wiki/CIELAB_color_space , Lab color space, D75 illuminant, 2 degree
|
||||
COLOR_SPACE_LAB_D75_10, ///< https://en.wikipedia.org/wiki/CIELAB_color_space , Lab color space, D75 illuminant, 10 degree
|
||||
COLOR_SPACE_LAB_E_2, ///< https://en.wikipedia.org/wiki/CIELAB_color_space , Lab color space, E illuminant, 2 degree
|
||||
COLOR_SPACE_LAB_E_10 ///< https://en.wikipedia.org/wiki/CIELAB_color_space , Lab color space, E illuminant, 10 degree
|
||||
};
|
||||
|
||||
/** @brief Linearization transformation type
|
||||
*/
|
||||
enum LinearizationType
|
||||
{
|
||||
|
||||
LINEARIZATION_IDENTITY, ///<no change is made
|
||||
LINEARIZATION_GAMMA, ///<gamma correction; Need assign a value to gamma simultaneously
|
||||
LINEARIZATION_COLORPOLYFIT, ///<polynomial fitting channels respectively; Need assign a value to deg simultaneously
|
||||
LINEARIZATION_COLORLOGPOLYFIT, ///<logarithmic polynomial fitting channels respectively; Need assign a value to deg simultaneously
|
||||
LINEARIZATION_GRAYPOLYFIT, ///<grayscale polynomial fitting; Need assign a value to deg and dst_whites simultaneously
|
||||
LINEARIZATION_GRAYLOGPOLYFIT ///<grayscale Logarithmic polynomial fitting; Need assign a value to deg and dst_whites simultaneously
|
||||
};
|
||||
|
||||
/** @brief Enum of possible functions to calculate the distance between colors.
|
||||
|
||||
See https://en.wikipedia.org/wiki/Color_difference for details
|
||||
*/
|
||||
enum DistanceType
|
||||
{
|
||||
DISTANCE_CIE76, ///<The 1976 formula is the first formula that related a measured color difference to a known set of CIELAB coordinates.
|
||||
DISTANCE_CIE94_GRAPHIC_ARTS, ///<The 1976 definition was extended to address perceptual non-uniformities.
|
||||
DISTANCE_CIE94_TEXTILES,
|
||||
DISTANCE_CIE2000,
|
||||
DISTANCE_CMC_1TO1, ///<In 1984, the Colour Measurement Committee of the Society of Dyers and Colourists defined a difference measure, also based on the L*C*h color model.
|
||||
DISTANCE_CMC_2TO1,
|
||||
DISTANCE_RGB, ///<Euclidean distance of rgb color space
|
||||
DISTANCE_RGBL ///<Euclidean distance of rgbl color space
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Applies gamma correction to the input image.
|
||||
* @param src Input image.
|
||||
* @param dst Output image.
|
||||
* @param gamma Gamma correction greater than zero.
|
||||
*/
|
||||
CV_EXPORTS_W void gammaCorrection(InputArray src, OutputArray dst, double gamma);
|
||||
|
||||
/** @brief Core class of ccm model
|
||||
|
||||
Produce a ColorCorrectionModel instance for inference
|
||||
*/
|
||||
class CV_EXPORTS_W ColorCorrectionModel
|
||||
{
|
||||
public:
|
||||
CV_WRAP ColorCorrectionModel();
|
||||
|
||||
/** @brief Color Correction Model
|
||||
|
||||
Supported list of color cards:
|
||||
- @ref COLORCHECKER_MACBETH, the Macbeth ColorChecker
|
||||
- @ref COLORCHECKER_VINYL, the DKK ColorChecker
|
||||
- @ref COLORCHECKER_DIGITAL_SG, the DigitalSG ColorChecker with 140 squares
|
||||
|
||||
@param src detected colors of ColorChecker patches;
|
||||
the color type is RGB not BGR, and the color values are in [0, 1];
|
||||
@param constColor the Built-in color card
|
||||
*/
|
||||
CV_WRAP ColorCorrectionModel(InputArray src, int constColor);
|
||||
|
||||
/** @brief Color Correction Model
|
||||
@param src detected colors of ColorChecker patches;
|
||||
the color type is RGB not BGR, and the color values are in [0, 1];
|
||||
@param colors the reference color values, the color values are in [0, 1].
|
||||
@param refColorSpace the corresponding color space
|
||||
If the color type is some RGB, the format is RGB not BGR;
|
||||
*/
|
||||
CV_WRAP ColorCorrectionModel(InputArray src, InputArray colors, ColorSpace refColorSpace);
|
||||
|
||||
/** @brief Color Correction Model
|
||||
@param src detected colors of ColorChecker patches;
|
||||
the color type is RGB not BGR, and the color values are in [0, 1];
|
||||
@param colors the reference color values, the color values are in [0, 1].
|
||||
@param refColorSpace the corresponding color space
|
||||
If the color type is some RGB, the format is RGB not BGR;
|
||||
@param coloredPatchesMask binary mask indicating which patches are colored (non-gray) patches
|
||||
*/
|
||||
CV_WRAP ColorCorrectionModel(InputArray src, InputArray colors, ColorSpace refColorSpace, InputArray coloredPatchesMask);
|
||||
|
||||
/** @brief set ColorSpace
|
||||
@note It should be some RGB color space;
|
||||
Supported list of color cards:
|
||||
- @ref COLOR_SPACE_SRGB
|
||||
- @ref COLOR_SPACE_ADOBE_RGB
|
||||
- @ref COLOR_SPACE_WIDE_GAMUT_RGB
|
||||
- @ref COLOR_SPACE_PRO_PHOTO_RGB
|
||||
- @ref COLOR_SPACE_DCI_P3_RGB
|
||||
- @ref COLOR_SPACE_APPLE_RGB
|
||||
- @ref COLOR_SPACE_REC_709_RGB
|
||||
- @ref COLOR_SPACE_REC_2020_RGB
|
||||
@param cs the absolute color space that detected colors convert to;
|
||||
default: @ref COLOR_SPACE_SRGB
|
||||
*/
|
||||
CV_WRAP void setColorSpace(ColorSpace cs);
|
||||
|
||||
/** @brief set ccmType
|
||||
@param ccmType the shape of color correction matrix(CCM);
|
||||
default: @ref CCM_LINEAR
|
||||
*/
|
||||
CV_WRAP void setCcmType(CcmType ccmType);
|
||||
|
||||
/** @brief set Distance
|
||||
@param distance the type of color distance;
|
||||
default: @ref DISTANCE_CIE2000
|
||||
*/
|
||||
CV_WRAP void setDistance(DistanceType distance);
|
||||
|
||||
/** @brief set Linear
|
||||
@param linearizationType the method of linearization;
|
||||
default: @ref LINEARIZATION_GAMMA
|
||||
*/
|
||||
CV_WRAP void setLinearization(LinearizationType linearizationType);
|
||||
|
||||
/** @brief set Gamma
|
||||
|
||||
@note only valid when linear is set to "gamma";
|
||||
|
||||
@param gamma the gamma value of gamma correction;
|
||||
default: 2.2;
|
||||
*/
|
||||
CV_WRAP void setLinearizationGamma(double gamma);
|
||||
|
||||
/** @brief set degree
|
||||
@note only valid when linear is set to
|
||||
- @ref LINEARIZATION_COLORPOLYFIT
|
||||
- @ref LINEARIZATION_GRAYPOLYFIT
|
||||
- @ref LINEARIZATION_COLORLOGPOLYFIT
|
||||
- @ref LINEARIZATION_GRAYLOGPOLYFIT
|
||||
|
||||
@param deg the degree of linearization polynomial
|
||||
default: 3
|
||||
|
||||
*/
|
||||
CV_WRAP void setLinearizationDegree(int deg);
|
||||
|
||||
/** @brief set SaturatedThreshold.
|
||||
The colors in the closed interval [lower, upper] are reserved to participate
|
||||
in the calculation of the loss function and initialization parameters
|
||||
@param lower the lower threshold to determine saturation;
|
||||
default: 0;
|
||||
@param upper the upper threshold to determine saturation;
|
||||
default: 0
|
||||
*/
|
||||
CV_WRAP void setSaturatedThreshold(double lower, double upper);
|
||||
|
||||
/** @brief set WeightsList
|
||||
@param weightsList the list of weight of each color;
|
||||
default: empty array
|
||||
*/
|
||||
CV_WRAP void setWeightsList(const Mat& weightsList);
|
||||
|
||||
/** @brief set WeightCoeff
|
||||
@param weightsCoeff the exponent number of L* component of the reference color in CIE Lab color space;
|
||||
default: 0
|
||||
*/
|
||||
CV_WRAP void setWeightCoeff(double weightsCoeff);
|
||||
|
||||
/** @brief set InitialMethod
|
||||
@param initialMethodType the method of calculating CCM initial value;
|
||||
default: INITIAL_METHOD_LEAST_SQUARE
|
||||
*/
|
||||
CV_WRAP void setInitialMethod(InitialMethodType initialMethodType);
|
||||
|
||||
/** @brief set MaxCount
|
||||
@param maxCount used in MinProblemSolver-DownhillSolver;
|
||||
Terminal criteria to the algorithm;
|
||||
default: 5000;
|
||||
*/
|
||||
CV_WRAP void setMaxCount(int maxCount);
|
||||
|
||||
/** @brief set Epsilon
|
||||
@param epsilon used in MinProblemSolver-DownhillSolver;
|
||||
Terminal criteria to the algorithm;
|
||||
default: 1e-4;
|
||||
*/
|
||||
CV_WRAP void setEpsilon(double epsilon);
|
||||
|
||||
/** @brief Set whether the input image is in RGB color space
|
||||
@param rgb If true, the model expects input images in RGB format.
|
||||
If false, input is assumed to be in BGR (default).
|
||||
*/
|
||||
CV_WRAP void setRGB(bool rgb);
|
||||
|
||||
/** @brief make color correction */
|
||||
CV_WRAP Mat compute();
|
||||
|
||||
CV_WRAP Mat getColorCorrectionMatrix() const;
|
||||
CV_WRAP double getLoss() const;
|
||||
CV_WRAP Mat getSrcLinearRGB() const;
|
||||
CV_WRAP Mat getRefLinearRGB() const;
|
||||
CV_WRAP Mat getMask() const;
|
||||
CV_WRAP Mat getWeights() const;
|
||||
|
||||
/** @brief Applies color correction to the input image using a fitted color correction matrix.
|
||||
*
|
||||
* The conventional ranges for R, G, and B channel values are:
|
||||
- 0 to 255 for CV_8U images
|
||||
- 0 to 65535 for CV_16U images
|
||||
- 0 to 1 for CV_32F images
|
||||
@param src Input 8-bit, 16-bit unsigned or 32-bit float 3-channel image..
|
||||
@param dst Output image of the same size and datatype as src.
|
||||
@param islinear default false.
|
||||
*/
|
||||
CV_WRAP void correctImage(InputArray src, OutputArray dst, bool islinear = false);
|
||||
|
||||
CV_WRAP void write(cv::FileStorage& fs) const;
|
||||
CV_WRAP void read(const cv::FileNode& node);
|
||||
|
||||
class Impl;
|
||||
private:
|
||||
std::shared_ptr<Impl> p;
|
||||
};
|
||||
|
||||
CV_EXPORTS void write(cv::FileStorage& fs, const std::string&, const ColorCorrectionModel& ccm);
|
||||
CV_EXPORTS void read(const cv::FileNode& node, ColorCorrectionModel& ccm, const ColorCorrectionModel& defaultValue = ColorCorrectionModel());
|
||||
|
||||
//! @} ccm
|
||||
} // namespace ccm
|
||||
} // namespace cv
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,103 @@
|
||||
// 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 "perf_precomp.hpp"
|
||||
#include "opencv2/photo.hpp"
|
||||
|
||||
namespace opencv_test {
|
||||
namespace {
|
||||
|
||||
using namespace cv;
|
||||
using namespace std;
|
||||
|
||||
PERF_TEST(CV_ccm_perf_480_640, correctImage)
|
||||
{
|
||||
string path = cvtest::findDataFile("cv/mcc/mcc_ccm_test.yml");
|
||||
FileStorage fs(path, FileStorage::READ);
|
||||
Mat chartsRGB;
|
||||
fs["chartsRGB"] >> chartsRGB;
|
||||
fs.release();
|
||||
ASSERT_FALSE(chartsRGB.empty()) << "chartsRGB is empty after loading from: " << path;
|
||||
|
||||
cv::ccm::ColorCorrectionModel model(
|
||||
chartsRGB.col(1).clone().reshape(3, chartsRGB.rows/3) / 255.0,
|
||||
cv::ccm::COLORCHECKER_MACBETH
|
||||
);
|
||||
model.compute();
|
||||
Mat img(480, 640, CV_8UC3);
|
||||
randu(img, 0, 255);
|
||||
|
||||
Mat correctedImage;
|
||||
TEST_CYCLE() { model.correctImage(img, correctedImage); }
|
||||
SANITY_CHECK_NOTHING();
|
||||
}
|
||||
|
||||
PERF_TEST(CV_ccm_perf_720_1280, correctImage)
|
||||
{
|
||||
string path = cvtest::findDataFile("cv/mcc/mcc_ccm_test.yml");
|
||||
FileStorage fs(path, FileStorage::READ);
|
||||
Mat chartsRGB;
|
||||
fs["chartsRGB"] >> chartsRGB;
|
||||
fs.release();
|
||||
ASSERT_FALSE(chartsRGB.empty()) << "chartsRGB is empty after loading from: " << path;
|
||||
|
||||
cv::ccm::ColorCorrectionModel model(
|
||||
chartsRGB.col(1).clone().reshape(3, chartsRGB.rows/3) / 255.0,
|
||||
cv::ccm::COLORCHECKER_MACBETH
|
||||
);
|
||||
model.compute();
|
||||
Mat img(720, 1280, CV_8UC3);
|
||||
randu(img, 0, 255);
|
||||
|
||||
Mat correctedImage;
|
||||
TEST_CYCLE() { model.correctImage(img, correctedImage); }
|
||||
SANITY_CHECK_NOTHING();
|
||||
}
|
||||
|
||||
PERF_TEST(CV_ccm_perf_1080_1920, correctImage)
|
||||
{
|
||||
string path = cvtest::findDataFile("cv/mcc/mcc_ccm_test.yml");
|
||||
FileStorage fs(path, FileStorage::READ);
|
||||
Mat chartsRGB;
|
||||
fs["chartsRGB"] >> chartsRGB;
|
||||
fs.release();
|
||||
ASSERT_FALSE(chartsRGB.empty()) << "chartsRGB is empty after loading from: " << path;
|
||||
|
||||
cv::ccm::ColorCorrectionModel model(
|
||||
chartsRGB.col(1).clone().reshape(3, chartsRGB.rows/3) / 255.0,
|
||||
cv::ccm::COLORCHECKER_MACBETH
|
||||
);
|
||||
model.compute();
|
||||
Mat img(1080, 1920, CV_8UC3);
|
||||
randu(img, 0, 255);
|
||||
|
||||
Mat correctedImage;
|
||||
TEST_CYCLE() { model.correctImage(img, correctedImage); }
|
||||
SANITY_CHECK_NOTHING();
|
||||
}
|
||||
|
||||
PERF_TEST(CV_ccm_perf_2160_3840, correctImage)
|
||||
{
|
||||
string path = cvtest::findDataFile("cv/mcc/mcc_ccm_test.yml");
|
||||
FileStorage fs(path, FileStorage::READ);
|
||||
Mat chartsRGB;
|
||||
fs["chartsRGB"] >> chartsRGB;
|
||||
fs.release();
|
||||
ASSERT_FALSE(chartsRGB.empty()) << "chartsRGB is empty after loading from: " << path;
|
||||
|
||||
cv::ccm::ColorCorrectionModel model(
|
||||
chartsRGB.col(1).clone().reshape(3, chartsRGB.rows/3) / 255.0,
|
||||
cv::ccm::COLORCHECKER_MACBETH
|
||||
);
|
||||
model.compute();
|
||||
Mat img(2160, 3840, CV_8UC3);
|
||||
randu(img, 0, 255);
|
||||
|
||||
Mat correctedImage;
|
||||
TEST_CYCLE() { model.correctImage(img, correctedImage); }
|
||||
SANITY_CHECK_NOTHING();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace opencv_test
|
||||
@@ -0,0 +1,557 @@
|
||||
// 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.
|
||||
//
|
||||
// Author: Longbu Wang <wanglongbu@huawei.com.com>
|
||||
// Jinheng Zhang <zhangjinheng1@huawei.com>
|
||||
// Chenqi Shan <shanchenqi@huawei.com>
|
||||
|
||||
#include "opencv2/photo.hpp"
|
||||
#include "linearize.hpp"
|
||||
#include <cmath>
|
||||
namespace cv {
|
||||
namespace ccm {
|
||||
|
||||
class ColorCorrectionModel::Impl
|
||||
{
|
||||
public:
|
||||
Mat src;
|
||||
|
||||
Color ref = Color();
|
||||
Mat dist;
|
||||
RGBBase_& cs;
|
||||
// Track initialization parameters for serialization
|
||||
ColorSpace csEnum;
|
||||
Mat mask;
|
||||
|
||||
// RGBl of detected data and the reference
|
||||
Mat srcRgbl;
|
||||
Mat dstRgbl;
|
||||
|
||||
// ccm type and shape
|
||||
CcmType ccmType;
|
||||
int shape;
|
||||
|
||||
// linear method and distance
|
||||
std::shared_ptr<Linear> linear = std::make_shared<Linear>();
|
||||
DistanceType distance;
|
||||
LinearizationType linearizationType;
|
||||
|
||||
Mat weights;
|
||||
Mat weightsList;
|
||||
Mat ccm;
|
||||
Mat ccm0;
|
||||
double gamma;
|
||||
int deg;
|
||||
std::vector<double> saturatedThreshold;
|
||||
InitialMethodType initialMethodType;
|
||||
double weightsCoeff;
|
||||
int maskedLen;
|
||||
double loss;
|
||||
int maxCount;
|
||||
double epsilon;
|
||||
bool rgb;
|
||||
Impl();
|
||||
|
||||
/** @brief Make no change for CCM_LINEAR.
|
||||
convert cv::Mat A to [A, 1] in CCM_AFFINE.
|
||||
@param inp the input array, type of cv::Mat.
|
||||
@return the output array, type of cv::Mat
|
||||
*/
|
||||
Mat prepare(const Mat& inp);
|
||||
|
||||
/** @brief Calculate weights and mask.
|
||||
@param weightsList the input array, type of cv::Mat.
|
||||
@param weightsCoeff type of double.
|
||||
@param saturateMask the input array, type of cv::Mat.
|
||||
*/
|
||||
void calWeightsMasks(const Mat& weightsList, double weightsCoeff, Mat saturateMask);
|
||||
|
||||
/** @brief Fitting nonlinear - optimization initial value by white balance.
|
||||
@return the output array, type of Mat
|
||||
*/
|
||||
void initialWhiteBalance(void);
|
||||
|
||||
/** @brief Fitting nonlinear-optimization initial value by least square.
|
||||
@param fit if fit is True, return optimalization for rgbl distance function.
|
||||
*/
|
||||
void initialLeastSquare(bool fit = false);
|
||||
|
||||
double calcLoss_(Color color);
|
||||
double calcLoss(const Mat ccm_);
|
||||
|
||||
/** @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);
|
||||
|
||||
void getColor(Mat& img_, bool islinear = false);
|
||||
void getColor(ColorCheckerType constColor);
|
||||
void getColor(Mat colors_, ColorSpace cs_, Mat colored_);
|
||||
void getColor(Mat colors_, ColorSpace refColorSpace_);
|
||||
|
||||
/** @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 MinProblemSolver::Function
|
||||
{
|
||||
public:
|
||||
ColorCorrectionModel::Impl* ccmLoss;
|
||||
LossFunction(ColorCorrectionModel::Impl* ccm)
|
||||
: ccmLoss(ccm) {};
|
||||
|
||||
/** @brief Reset dims to ccm->shape.
|
||||
*/
|
||||
int getDims() const CV_OVERRIDE
|
||||
{
|
||||
return ccmLoss->shape;
|
||||
}
|
||||
|
||||
/** @brief Reset calculation.
|
||||
*/
|
||||
double calc(const double* x) const CV_OVERRIDE
|
||||
{
|
||||
Mat ccm_(ccmLoss->shape, 1, CV_64F);
|
||||
for (int i = 0; i < ccmLoss->shape; i++)
|
||||
{
|
||||
ccm_.at<double>(i, 0) = x[i];
|
||||
}
|
||||
ccm_ = ccm_.reshape(0, ccmLoss->shape / 3);
|
||||
return ccmLoss->calcLoss(ccm_);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
ColorCorrectionModel::Impl::Impl()
|
||||
: cs(*GetCS::getInstance().getRgb(COLOR_SPACE_SRGB))
|
||||
, csEnum(COLOR_SPACE_SRGB)
|
||||
, ccmType(CCM_LINEAR)
|
||||
, distance(DISTANCE_CIE2000)
|
||||
, linearizationType(LINEARIZATION_GAMMA)
|
||||
, weights(Mat())
|
||||
, gamma(2.2)
|
||||
, deg(3)
|
||||
, saturatedThreshold({ 0, 0.98 })
|
||||
, initialMethodType(INITIAL_METHOD_LEAST_SQUARE)
|
||||
, weightsCoeff(0)
|
||||
, maxCount(5000)
|
||||
, epsilon(1.e-4)
|
||||
, rgb(true)
|
||||
{}
|
||||
|
||||
Mat ColorCorrectionModel::Impl::prepare(const Mat& inp)
|
||||
{
|
||||
switch (ccmType)
|
||||
{
|
||||
case cv::ccm::CCM_LINEAR:
|
||||
shape = 9;
|
||||
return inp;
|
||||
case cv::ccm::CCM_AFFINE:
|
||||
{
|
||||
shape = 12;
|
||||
Mat ones(inp.size(), CV_64F, Scalar(1));
|
||||
Mat out(inp.size(), CV_64FC4);
|
||||
const Mat srcs[] = { inp, ones };
|
||||
const int fromTo[] = { 0,0, 1,1, 2,2, 3,3 }; // inp[ch] → out[ch]
|
||||
mixChannels(srcs, 2, &out, 1, fromTo, 4);
|
||||
return out;
|
||||
}
|
||||
default:
|
||||
CV_Error(Error::StsBadArg, "Wrong ccmType!");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void ColorCorrectionModel::Impl::calWeightsMasks(const Mat& weightsList_, double weightsCoeff_, Mat saturateMask)
|
||||
{
|
||||
// weights
|
||||
if (!weightsList_.empty())
|
||||
{
|
||||
weights = weightsList_;
|
||||
}
|
||||
else if (weightsCoeff_ != 0)
|
||||
{
|
||||
pow(ref.toLuminant(cs.illumobserver), weightsCoeff_, weights);
|
||||
}
|
||||
|
||||
// masks
|
||||
Mat weight_mask = Mat::ones(src.rows, 1, CV_8U);
|
||||
if (!weights.empty())
|
||||
{
|
||||
weight_mask = weights > 0;
|
||||
}
|
||||
this->mask = (weight_mask) & (saturateMask);
|
||||
|
||||
// weights' mask
|
||||
if (!weights.empty())
|
||||
{
|
||||
Mat weights_masked = maskCopyTo(this->weights, this->mask);
|
||||
weights = weights_masked / mean(weights_masked)[0];
|
||||
}
|
||||
maskedLen = (int)sum(mask)[0];
|
||||
}
|
||||
|
||||
void ColorCorrectionModel::Impl::initialWhiteBalance()
|
||||
{
|
||||
// sum over all pixels – Scalar holds per-channel sums
|
||||
const cv::Scalar srcSum = cv::sum(srcRgbl);
|
||||
const cv::Scalar dstSum = cv::sum(dstRgbl);
|
||||
|
||||
// channel-wise gain factors
|
||||
const double gR = dstSum[0] / srcSum[0];
|
||||
const double gG = dstSum[1] / srcSum[1];
|
||||
const double gB = dstSum[2] / srcSum[2];
|
||||
|
||||
// shape == 9 for a 3×3 linear CCM, or 12 for a 3×4 affine CCM
|
||||
if (shape == 9) {
|
||||
// 3×3 diagonal matrix
|
||||
ccm0 = cv::Mat::zeros(3, 3, CV_64F);
|
||||
ccm0.at<double>(0, 0) = gR;
|
||||
ccm0.at<double>(1, 1) = gG;
|
||||
ccm0.at<double>(2, 2) = gB;
|
||||
}
|
||||
else {
|
||||
// 3×4 affine matrix (last column = zeros)
|
||||
ccm0 = cv::Mat::zeros(3, 4, CV_64F);
|
||||
ccm0.at<double>(0, 0) = gR;
|
||||
ccm0.at<double>(1, 1) = gG;
|
||||
ccm0.at<double>(2, 2) = gB;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void ColorCorrectionModel::Impl::initialLeastSquare(bool fit)
|
||||
{
|
||||
Mat A, B, w;
|
||||
if (weights.empty())
|
||||
{
|
||||
A = srcRgbl;
|
||||
B = dstRgbl;
|
||||
}
|
||||
else
|
||||
{
|
||||
pow(weights, 0.5, w);
|
||||
Mat w_;
|
||||
merge(std::vector<Mat> { w, w, w }, w_);
|
||||
A = w_.mul(srcRgbl);
|
||||
B = w_.mul(dstRgbl);
|
||||
}
|
||||
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;
|
||||
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 / maskedLen);
|
||||
}
|
||||
}
|
||||
|
||||
double ColorCorrectionModel::Impl::calcLoss_(Color color)
|
||||
{
|
||||
Mat distlist = color.diff(ref, distance);
|
||||
Color lab = color.to(COLOR_SPACE_LAB_D50_2);
|
||||
Mat dist_;
|
||||
pow(distlist, 2, dist_);
|
||||
if (!weights.empty())
|
||||
{
|
||||
dist_ = weights.mul(dist_);
|
||||
}
|
||||
Scalar ss = sum(dist_);
|
||||
return ss[0];
|
||||
}
|
||||
|
||||
double ColorCorrectionModel::Impl::calcLoss(const Mat ccm_)
|
||||
{
|
||||
Mat converted = srcRgbl.reshape(1, 0) * ccm_;
|
||||
Color color(converted.reshape(3, 0), *(cs.l));
|
||||
return calcLoss_(color);
|
||||
}
|
||||
|
||||
void ColorCorrectionModel::Impl::fitting(void)
|
||||
{
|
||||
cv::Ptr<DownhillSolver> solver = cv::DownhillSolver::create();
|
||||
cv::Ptr<LossFunction> ptr_F(new LossFunction(this));
|
||||
solver->setFunction(ptr_F);
|
||||
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, maxCount, epsilon);
|
||||
solver->setTermCriteria(termcrit);
|
||||
double res = solver->minimize(reshapeCcm);
|
||||
ccm = reshapeCcm.reshape(0, shape / 3);
|
||||
loss = sqrt(res / maskedLen);
|
||||
}
|
||||
|
||||
ColorCorrectionModel::ColorCorrectionModel()
|
||||
: p(std::make_shared<Impl>())
|
||||
{}
|
||||
|
||||
void ColorCorrectionModel::correctImage(InputArray src, OutputArray ref, bool islinear)
|
||||
{
|
||||
if (!p->ccm.data)
|
||||
{
|
||||
CV_Error(Error::StsBadArg, "No CCM values!" );
|
||||
}
|
||||
Mat img, normImg;
|
||||
if (p->rgb){
|
||||
cvtColor(src.getMat(), img, COLOR_BGR2RGB);
|
||||
} else {
|
||||
img = src.getMat();
|
||||
}
|
||||
|
||||
double scale;
|
||||
int type = img.type();
|
||||
switch (type) {
|
||||
case CV_8UC3:
|
||||
scale = 1.0 / 255.0;
|
||||
break;
|
||||
case CV_16UC3:
|
||||
scale = 1.0 / 65535.0;
|
||||
break;
|
||||
case CV_32FC3:
|
||||
scale = 1.0; // Already in [0,1] range
|
||||
break;
|
||||
default:
|
||||
CV_Error( cv::Error::StsUnsupportedFormat, "8-bit, 16-bit unsigned or 32-bit float 3-channel input images are supported");
|
||||
}
|
||||
|
||||
img.convertTo(normImg, CV_64F, scale);
|
||||
Mat linearImg = (p->linear)->linearize(normImg);
|
||||
Mat ccm = p->ccm.reshape(0, p->shape / 3);
|
||||
Mat imgCcm = multiple(p->prepare(linearImg), ccm);
|
||||
if (islinear == true)
|
||||
{
|
||||
imgCcm.copyTo(ref);
|
||||
}
|
||||
Mat imgCorrected = p->cs.fromLFunc(imgCcm, linearImg);
|
||||
|
||||
imgCorrected *= 1.0/scale;
|
||||
imgCorrected.convertTo(imgCorrected, type);
|
||||
|
||||
if (p->rgb)
|
||||
cvtColor(imgCorrected, imgCorrected, COLOR_RGB2BGR);
|
||||
imgCorrected.copyTo(ref);
|
||||
}
|
||||
|
||||
void ColorCorrectionModel::Impl::getColor(ColorCheckerType constColor)
|
||||
{
|
||||
ref = GetColor().getColor(constColor);
|
||||
}
|
||||
|
||||
void ColorCorrectionModel::Impl::getColor(Mat colors_, ColorSpace refColorSpace_)
|
||||
{
|
||||
ref = Color(colors_, *GetCS::getInstance().getCS(refColorSpace_));
|
||||
}
|
||||
|
||||
void ColorCorrectionModel::Impl::getColor(Mat colors_, ColorSpace cs_, Mat colored_)
|
||||
{
|
||||
ref = Color(colors_, *GetCS::getInstance().getCS(cs_), colored_);
|
||||
}
|
||||
|
||||
ColorCorrectionModel::ColorCorrectionModel(InputArray src_, int constColor): p(std::make_shared<Impl>())
|
||||
{
|
||||
p->src = src_.getMat();
|
||||
p->getColor(static_cast<ColorCheckerType>(constColor));
|
||||
}
|
||||
|
||||
ColorCorrectionModel::ColorCorrectionModel(InputArray src_, InputArray colors_, ColorSpace refColorSpace_): p(std::make_shared<Impl>())
|
||||
{
|
||||
p->src = src_.getMat();
|
||||
p->getColor(colors_.getMat(), refColorSpace_);
|
||||
}
|
||||
|
||||
ColorCorrectionModel::ColorCorrectionModel(InputArray src_, InputArray colors_, ColorSpace cs_, InputArray coloredPatchesMask_): p(std::make_shared<Impl>())
|
||||
{
|
||||
p->src = src_.getMat();
|
||||
p->getColor(colors_.getMat(), cs_, coloredPatchesMask_.getMat());
|
||||
}
|
||||
|
||||
void ColorCorrectionModel::setColorSpace(ColorSpace cs_)
|
||||
{
|
||||
p->cs = *GetCS::getInstance().getRgb(cs_);
|
||||
}
|
||||
void ColorCorrectionModel::setCcmType(CcmType ccmType_)
|
||||
{
|
||||
p->ccmType = ccmType_;
|
||||
}
|
||||
void ColorCorrectionModel::setDistance(DistanceType distance_)
|
||||
{
|
||||
p->distance = distance_;
|
||||
}
|
||||
void ColorCorrectionModel::setLinearization(LinearizationType linearizationType)
|
||||
{
|
||||
p->linearizationType = linearizationType;
|
||||
}
|
||||
void ColorCorrectionModel::setLinearizationGamma(double gamma)
|
||||
{
|
||||
p->gamma = gamma;
|
||||
}
|
||||
void ColorCorrectionModel::setLinearizationDegree(int deg)
|
||||
{
|
||||
p->deg = deg;
|
||||
}
|
||||
void ColorCorrectionModel::setSaturatedThreshold(double lower, double upper)
|
||||
{ //std::vector<double> saturatedThreshold
|
||||
p->saturatedThreshold = { lower, upper };
|
||||
}
|
||||
void ColorCorrectionModel::setWeightsList(const Mat& weightsList)
|
||||
{
|
||||
p->weightsList = weightsList;
|
||||
}
|
||||
void ColorCorrectionModel::setWeightCoeff(double weightsCoeff)
|
||||
{
|
||||
p->weightsCoeff = weightsCoeff;
|
||||
}
|
||||
void ColorCorrectionModel::setInitialMethod(InitialMethodType initialMethodType)
|
||||
{
|
||||
p->initialMethodType = initialMethodType;
|
||||
}
|
||||
void ColorCorrectionModel::setMaxCount(int maxCount_)
|
||||
{
|
||||
p->maxCount = maxCount_;
|
||||
}
|
||||
void ColorCorrectionModel::setEpsilon(double epsilon_)
|
||||
{
|
||||
p->epsilon = epsilon_;
|
||||
}
|
||||
void ColorCorrectionModel::setRGB(bool rgb_)
|
||||
{
|
||||
p->rgb = rgb_;
|
||||
}
|
||||
Mat ColorCorrectionModel::compute()
|
||||
{
|
||||
|
||||
Mat saturateMask = saturate(p->src, p->saturatedThreshold[0], p->saturatedThreshold[1]);
|
||||
p->linear = getLinear(p->gamma, p->deg, p->src, p->ref, saturateMask, (p->cs), p->linearizationType);
|
||||
p->calWeightsMasks(p->weightsList, p->weightsCoeff, saturateMask);
|
||||
p->srcRgbl = p->linear->linearize(maskCopyTo(p->src, p->mask));
|
||||
p->ref.colors = maskCopyTo(p->ref.colors, p->mask);
|
||||
p->dstRgbl = p->ref.to(*(p->cs.l)).colors;
|
||||
|
||||
// make no change for CCM_LINEAR, make change for CCM_AFFINE.
|
||||
p->srcRgbl = p->prepare(p->srcRgbl);
|
||||
|
||||
// distance function may affect the loss function and the fitting function
|
||||
switch (p->distance)
|
||||
{
|
||||
case cv::ccm::DISTANCE_RGBL:
|
||||
p->initialLeastSquare(true);
|
||||
break;
|
||||
default:
|
||||
switch (p->initialMethodType)
|
||||
{
|
||||
case cv::ccm::INITIAL_METHOD_WHITE_BALANCE:
|
||||
p->initialWhiteBalance();
|
||||
break;
|
||||
case cv::ccm::INITIAL_METHOD_LEAST_SQUARE:
|
||||
p->initialLeastSquare();
|
||||
break;
|
||||
default:
|
||||
CV_Error(Error::StsBadArg, "Wrong initial_methoddistance_type!" );
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
p->fitting();
|
||||
|
||||
return p->ccm;
|
||||
}
|
||||
Mat ColorCorrectionModel::getColorCorrectionMatrix() const
|
||||
{
|
||||
return p->ccm;
|
||||
}
|
||||
double ColorCorrectionModel::getLoss() const
|
||||
{
|
||||
return p->loss;
|
||||
}
|
||||
Mat ColorCorrectionModel::getSrcLinearRGB() const{
|
||||
return p->srcRgbl;
|
||||
}
|
||||
Mat ColorCorrectionModel::getRefLinearRGB() const{
|
||||
return p->dstRgbl;
|
||||
}
|
||||
Mat ColorCorrectionModel::getMask() const{
|
||||
return p->mask;
|
||||
}
|
||||
Mat ColorCorrectionModel::getWeights() const{
|
||||
return p->weights;
|
||||
}
|
||||
|
||||
void ColorCorrectionModel::write(FileStorage& fs) const
|
||||
{
|
||||
fs << "ColorCorrectionModel" << "{"
|
||||
<< "ccm" << p->ccm
|
||||
<< "loss" << p->loss
|
||||
<< "csEnum" << p->csEnum
|
||||
<< "ccm_type" << p->ccmType
|
||||
<< "shape" << p->shape
|
||||
<< "linear" << *p->linear
|
||||
<< "distance" << p->distance
|
||||
<< "linear_type" << p->linearizationType
|
||||
<< "gamma" << p->gamma
|
||||
<< "deg" << p->deg
|
||||
<< "saturated_threshold" << p->saturatedThreshold
|
||||
<< "}";
|
||||
}
|
||||
|
||||
void ColorCorrectionModel::read(const FileNode& node)
|
||||
{
|
||||
node["ccm"] >> p->ccm;
|
||||
node["loss"] >> p->loss;
|
||||
node["ccm_type"] >> p->ccmType;
|
||||
node["shape"] >> p->shape;
|
||||
node["distance"] >> p->distance;
|
||||
node["gamma"] >> p->gamma;
|
||||
node["deg"] >> p->deg;
|
||||
node["saturated_threshold"] >> p->saturatedThreshold;
|
||||
|
||||
ColorSpace csEnum;
|
||||
node["csEnum"] >> csEnum;
|
||||
setColorSpace(csEnum);
|
||||
|
||||
node["linear_type"] >> p->linearizationType;
|
||||
switch (p->linearizationType) {
|
||||
case cv::ccm::LINEARIZATION_GAMMA:
|
||||
p->linear = std::shared_ptr<Linear>(new LinearGamma());
|
||||
break;
|
||||
case cv::ccm::LINEARIZATION_COLORPOLYFIT:
|
||||
p->linear = std::shared_ptr<Linear>(new LinearColor<Polyfit>());
|
||||
break;
|
||||
case cv::ccm::LINEARIZATION_IDENTITY:
|
||||
p->linear = std::shared_ptr<Linear>(new LinearIdentity());
|
||||
break;
|
||||
case cv::ccm::LINEARIZATION_COLORLOGPOLYFIT:
|
||||
p->linear = std::shared_ptr<Linear>(new LinearColor<LogPolyfit>());
|
||||
break;
|
||||
case cv::ccm::LINEARIZATION_GRAYPOLYFIT:
|
||||
p->linear = std::shared_ptr<Linear>(new LinearGray<Polyfit>());
|
||||
break;
|
||||
case cv::ccm::LINEARIZATION_GRAYLOGPOLYFIT:
|
||||
p->linear = std::shared_ptr<Linear>(new LinearGray<LogPolyfit>());
|
||||
break;
|
||||
default:
|
||||
CV_Error(Error::StsBadArg, "Wrong linear_type!");
|
||||
break;
|
||||
}
|
||||
node["linear"] >> *p->linear;
|
||||
}
|
||||
|
||||
void write(FileStorage& fs, const std::string&, const cv::ccm::ColorCorrectionModel& ccm)
|
||||
{
|
||||
ccm.write(fs);
|
||||
}
|
||||
|
||||
void read(const cv::FileNode& node, cv::ccm::ColorCorrectionModel& ccm, const cv::ccm::ColorCorrectionModel& defaultValue)
|
||||
{
|
||||
if (node.empty())
|
||||
ccm = defaultValue;
|
||||
else
|
||||
ccm.read(node);
|
||||
}
|
||||
|
||||
}
|
||||
} // namespace cv::ccm
|
||||
@@ -0,0 +1,391 @@
|
||||
// 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.
|
||||
//
|
||||
// Author: Longbu Wang <wanglongbu@huawei.com.com>
|
||||
// Jinheng Zhang <zhangjinheng1@huawei.com>
|
||||
// Chenqi Shan <shanchenqi@huawei.com>
|
||||
|
||||
#include "color.hpp"
|
||||
|
||||
namespace cv {
|
||||
namespace ccm {
|
||||
Color::Color()
|
||||
: colors(Mat())
|
||||
, cs(std::make_shared<ColorSpaceBase>())
|
||||
{}
|
||||
Color::Color(Mat colors_, enum ColorSpace cs_)
|
||||
: colors(colors_)
|
||||
, cs(GetCS::getInstance().getCS(cs_))
|
||||
{}
|
||||
|
||||
Color::Color(Mat colors_, enum ColorSpace cs_, Mat colored_)
|
||||
: colors(colors_)
|
||||
, cs(GetCS::getInstance().getCS(cs_))
|
||||
, colored(colored_)
|
||||
{
|
||||
grays = ~colored;
|
||||
}
|
||||
Color::Color(Mat colors_, const ColorSpaceBase& cs_, Mat colored_)
|
||||
: colors(colors_)
|
||||
, cs(std::make_shared<ColorSpaceBase>(cs_))
|
||||
, colored(colored_)
|
||||
{
|
||||
grays = ~colored;
|
||||
}
|
||||
|
||||
Color::Color(Mat colors_, const ColorSpaceBase& cs_)
|
||||
: colors(colors_)
|
||||
, cs(std::make_shared<ColorSpaceBase>(cs_))
|
||||
{}
|
||||
|
||||
Color::Color(Mat colors_, std::shared_ptr<ColorSpaceBase> cs_)
|
||||
: colors(colors_)
|
||||
, cs(cs_)
|
||||
{}
|
||||
|
||||
Color Color::to(const ColorSpaceBase& other, ChromaticAdaptationType method, bool save)
|
||||
{
|
||||
auto it = history.find(other);
|
||||
if ( it != history.end() )
|
||||
{
|
||||
return *(it->second);
|
||||
}
|
||||
if (cs->relate(other))
|
||||
{
|
||||
return Color(cs->relation(other).run(colors), other);
|
||||
}
|
||||
Operations ops;
|
||||
ops.add(cs->to).add(XYZ(cs->illumobserver).cam(other.illumobserver, method)).add(other.from);
|
||||
Mat converted = ops.run(colors);
|
||||
if (save)
|
||||
{
|
||||
auto ptr = std::make_shared<Color>(converted, other);
|
||||
history[other] = ptr;
|
||||
return *ptr;
|
||||
}
|
||||
else
|
||||
{
|
||||
return Color(converted, other);
|
||||
}
|
||||
}
|
||||
|
||||
Color Color::to(ColorSpace other, ChromaticAdaptationType method, bool save)
|
||||
{
|
||||
return to(*GetCS::getInstance().getCS(other), method, save);
|
||||
}
|
||||
|
||||
Mat Color::channel(Mat m, int i)
|
||||
{
|
||||
Mat dchannels[3];
|
||||
split(m, dchannels);
|
||||
return dchannels[i];
|
||||
}
|
||||
|
||||
Mat Color::toGray(const IllumObserver& illumobserver, ChromaticAdaptationType method, bool save)
|
||||
{
|
||||
XYZ xyz = *XYZ::get(illumobserver);
|
||||
return channel(this->to(xyz, method, save).colors, 1);
|
||||
}
|
||||
|
||||
Mat Color::toLuminant(const IllumObserver& illumobserver, ChromaticAdaptationType method, bool save)
|
||||
{
|
||||
Lab lab = *Lab::get(illumobserver);
|
||||
return channel(this->to(lab, method, save).colors, 0);
|
||||
}
|
||||
|
||||
Mat Color::diff(Color& other, DistanceType method)
|
||||
{
|
||||
return diff(other, cs->illumobserver, method);
|
||||
}
|
||||
|
||||
Mat Color::diff(Color& other, const IllumObserver& illumobserver, DistanceType method)
|
||||
{
|
||||
Lab lab = *Lab::get(illumobserver);
|
||||
switch (method)
|
||||
{
|
||||
case cv::ccm::DISTANCE_CIE76:
|
||||
case cv::ccm::DISTANCE_CIE94_GRAPHIC_ARTS:
|
||||
case cv::ccm::DISTANCE_CIE94_TEXTILES:
|
||||
case cv::ccm::DISTANCE_CIE2000:
|
||||
case cv::ccm::DISTANCE_CMC_1TO1:
|
||||
case cv::ccm::DISTANCE_CMC_2TO1:
|
||||
return distance(to(lab).colors, other.to(lab).colors, method);
|
||||
case cv::ccm::DISTANCE_RGB:
|
||||
return distance(to(*cs->nl).colors, other.to(*cs->nl).colors, method);
|
||||
case cv::ccm::DISTANCE_RGBL:
|
||||
return distance(to(*cs->l).colors, other.to(*cs->l).colors, method);
|
||||
default:
|
||||
CV_Error(Error::StsBadArg, "Wrong method!" );
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void Color::getGray(double JDN)
|
||||
{
|
||||
if (!grays.empty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
Mat lab = to(COLOR_SPACE_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);
|
||||
Mat d = distance(lab, gray, DISTANCE_CIE2000);
|
||||
this->grays = d < JDN;
|
||||
this->colored = ~grays;
|
||||
}
|
||||
|
||||
Color Color::operator[](Mat mask)
|
||||
{
|
||||
return Color(maskCopyTo(colors, mask), cs);
|
||||
}
|
||||
|
||||
Mat GetColor::getColorChecker(const double* checker, int row)
|
||||
{
|
||||
Mat res(row, 1, CV_64FC3);
|
||||
for (int i = 0; i < row; ++i)
|
||||
{
|
||||
res.at<Vec3d>(i, 0) = Vec3d(checker[3 * i], checker[3 * i + 1], checker[3 * i + 2]);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
Mat GetColor::getColorCheckerMask(const uchar* checker, int row)
|
||||
{
|
||||
Mat res(row, 1, CV_8U);
|
||||
for (int i = 0; i < row; ++i)
|
||||
{
|
||||
res.at<uchar>(i, 0) = checker[i];
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
Color GetColor::getColor(ColorCheckerType const_color)
|
||||
{
|
||||
|
||||
/** @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.
|
||||
*/
|
||||
static const double ColorChecker2005_LAB_D50_2[24][3] = { { 37.986, 13.555, 14.059 },
|
||||
{ 65.711, 18.13, 17.81 },
|
||||
{ 49.927, -4.88, -21.925 },
|
||||
{ 43.139, -13.095, 21.905 },
|
||||
{ 55.112, 8.844, -25.399 },
|
||||
{ 70.719, -33.397, -0.199 },
|
||||
{ 62.661, 36.067, 57.096 },
|
||||
{ 40.02, 10.41, -45.964 },
|
||||
{ 51.124, 48.239, 16.248 },
|
||||
{ 30.325, 22.976, -21.587 },
|
||||
{ 72.532, -23.709, 57.255 },
|
||||
{ 71.941, 19.363, 67.857 },
|
||||
{ 28.778, 14.179, -50.297 },
|
||||
{ 55.261, -38.342, 31.37 },
|
||||
{ 42.101, 53.378, 28.19 },
|
||||
{ 81.733, 4.039, 79.819 },
|
||||
{ 51.935, 49.986, -14.574 },
|
||||
{ 51.038, -28.631, -28.638 },
|
||||
{ 96.539, -0.425, 1.186 },
|
||||
{ 81.257, -0.638, -0.335 },
|
||||
{ 66.766, -0.734, -0.504 },
|
||||
{ 50.867, -0.153, -0.27 },
|
||||
{ 35.656, -0.421, -1.231 },
|
||||
{ 20.461, -0.079, -0.973 } };
|
||||
|
||||
static const uchar ColorChecker2005_COLORED_MASK[24] = { 1, 1, 1, 1, 1, 1,
|
||||
1, 1, 1, 1, 1, 1,
|
||||
1, 1, 1, 1, 1, 1,
|
||||
0, 0, 0, 0, 0, 0 };
|
||||
static const double Vinyl_LAB_D50_2[18][3] = { { 100, 0.00520000001, -0.0104 },
|
||||
{ 73.0833969, -0.819999993, -2.02099991 },
|
||||
{ 62.493, 0.425999999, -2.23099995 },
|
||||
{ 50.4640007, 0.446999997, -2.32399988 },
|
||||
{ 37.7970009, 0.0359999985, -1.29700005 },
|
||||
{ 0, 0, 0 },
|
||||
{ 51.5880013, 73.5179977, 51.5690002 },
|
||||
{ 93.6989975, -15.7340002, 91.9420013 },
|
||||
{ 69.4079971, -46.5940018, 50.4869995 },
|
||||
{ 66.61000060000001, -13.6789999, -43.1720009 },
|
||||
{ 11.7110004, 16.9799995, -37.1759987 },
|
||||
{ 51.973999, 81.9440002, -8.40699959 },
|
||||
{ 40.5489998, 50.4399986, 24.8490009 },
|
||||
{ 60.8160019, 26.0690002, 49.4420013 },
|
||||
{ 52.2529984, -19.9500008, -23.9960003 },
|
||||
{ 51.2859993, 48.4700012, -15.0579996 },
|
||||
{ 68.70700069999999, 12.2959995, 16.2129993 },
|
||||
{ 63.6839981, 10.2930002, 16.7639999 } };
|
||||
static const uchar Vinyl_COLORED_MASK[18] = { 0, 0, 0, 0, 0, 0,
|
||||
1, 1, 1, 1, 1, 1,
|
||||
1, 1, 1, 1, 1, 1 };
|
||||
static const double DigitalSG_LAB_D50_2[140][3] = { { 96.55, -0.91, 0.57 },
|
||||
{ 6.43, -0.06, -0.41 },
|
||||
{ 49.7, -0.18, 0.03 },
|
||||
{ 96.5, -0.89, 0.59 },
|
||||
{ 6.5, -0.06, -0.44 },
|
||||
{ 49.66, -0.2, 0.01 },
|
||||
{ 96.52, -0.91, 0.58 },
|
||||
{ 6.49, -0.02, -0.28 },
|
||||
{ 49.72, -0.2, 0.04 },
|
||||
{ 96.43, -0.91, 0.67 },
|
||||
{ 49.72, -0.19, 0 },
|
||||
{ 32.6, 51.58, -10.85 },
|
||||
{ 60.75, 26.22, -18.6 },
|
||||
{ 28.69, 48.28, -39 },
|
||||
{ 49.38, -15.43, -48.48 },
|
||||
{ 60.63, -30.77, -26.23 },
|
||||
{ 19.29, -26.37, -6.15 },
|
||||
{ 60.15, -41.77, -12.6 },
|
||||
{ 21.42, 1.67, 8.79 },
|
||||
{ 49.69, -0.2, 0.01 },
|
||||
{ 6.5, -0.03, -0.67 },
|
||||
{ 21.82, 17.33, -18.35 },
|
||||
{ 41.53, 18.48, -37.26 },
|
||||
{ 19.99, -0.16, -36.29 },
|
||||
{ 60.16, -18.45, -31.42 },
|
||||
{ 19.94, -17.92, -20.96 },
|
||||
{ 60.68, -6.05, -32.81 },
|
||||
{ 50.81, -49.8, -9.63 },
|
||||
{ 60.65, -39.77, 20.76 },
|
||||
{ 6.53, -0.03, -0.43 },
|
||||
{ 96.56, -0.91, 0.59 },
|
||||
{ 84.19, -1.95, -8.23 },
|
||||
{ 84.75, 14.55, 0.23 },
|
||||
{ 84.87, -19.07, -0.82 },
|
||||
{ 85.15, 13.48, 6.82 },
|
||||
{ 84.17, -10.45, 26.78 },
|
||||
{ 61.74, 31.06, 36.42 },
|
||||
{ 64.37, 20.82, 18.92 },
|
||||
{ 50.4, -53.22, 14.62 },
|
||||
{ 96.51, -0.89, 0.65 },
|
||||
{ 49.74, -0.19, 0.03 },
|
||||
{ 31.91, 18.62, 21.99 },
|
||||
{ 60.74, 38.66, 70.97 },
|
||||
{ 19.35, 22.23, -58.86 },
|
||||
{ 96.52, -0.91, 0.62 },
|
||||
{ 6.66, 0, -0.3 },
|
||||
{ 76.51, 20.81, 22.72 },
|
||||
{ 72.79, 29.15, 24.18 },
|
||||
{ 22.33, -20.7, 5.75 },
|
||||
{ 49.7, -0.19, 0.01 },
|
||||
{ 6.53, -0.05, -0.61 },
|
||||
{ 63.42, 20.19, 19.22 },
|
||||
{ 34.94, 11.64, -50.7 },
|
||||
{ 52.03, -44.15, 39.04 },
|
||||
{ 79.43, 0.29, -0.17 },
|
||||
{ 30.67, -0.14, -0.53 },
|
||||
{ 63.6, 14.44, 26.07 },
|
||||
{ 64.37, 14.5, 17.05 },
|
||||
{ 60.01, -44.33, 8.49 },
|
||||
{ 6.63, -0.01, -0.47 },
|
||||
{ 96.56, -0.93, 0.59 },
|
||||
{ 46.37, -5.09, -24.46 },
|
||||
{ 47.08, 52.97, 20.49 },
|
||||
{ 36.04, 64.92, 38.51 },
|
||||
{ 65.05, 0, -0.32 },
|
||||
{ 40.14, -0.19, -0.38 },
|
||||
{ 43.77, 16.46, 27.12 },
|
||||
{ 64.39, 17, 16.59 },
|
||||
{ 60.79, -29.74, 41.5 },
|
||||
{ 96.48, -0.89, 0.64 },
|
||||
{ 49.75, -0.21, 0.01 },
|
||||
{ 38.18, -16.99, 30.87 },
|
||||
{ 21.31, 29.14, -27.51 },
|
||||
{ 80.57, 3.85, 89.61 },
|
||||
{ 49.71, -0.2, 0.03 },
|
||||
{ 60.27, 0.08, -0.41 },
|
||||
{ 67.34, 14.45, 16.9 },
|
||||
{ 64.69, 16.95, 18.57 },
|
||||
{ 51.12, -49.31, 44.41 },
|
||||
{ 49.7, -0.2, 0.02 },
|
||||
{ 6.67, -0.05, -0.64 },
|
||||
{ 51.56, 9.16, -26.88 },
|
||||
{ 70.83, -24.26, 64.77 },
|
||||
{ 48.06, 55.33, -15.61 },
|
||||
{ 35.26, -0.09, -0.24 },
|
||||
{ 75.16, 0.25, -0.2 },
|
||||
{ 44.54, 26.27, 38.93 },
|
||||
{ 35.91, 16.59, 26.46 },
|
||||
{ 61.49, -52.73, 47.3 },
|
||||
{ 6.59, -0.05, -0.5 },
|
||||
{ 96.58, -0.9, 0.61 },
|
||||
{ 68.93, -34.58, -0.34 },
|
||||
{ 69.65, 20.09, 78.57 },
|
||||
{ 47.79, -33.18, -30.21 },
|
||||
{ 15.94, -0.42, -1.2 },
|
||||
{ 89.02, -0.36, -0.48 },
|
||||
{ 63.43, 25.44, 26.25 },
|
||||
{ 65.75, 22.06, 27.82 },
|
||||
{ 61.47, 17.1, 50.72 },
|
||||
{ 96.53, -0.89, 0.66 },
|
||||
{ 49.79, -0.2, 0.03 },
|
||||
{ 85.17, 10.89, 17.26 },
|
||||
{ 89.74, -16.52, 6.19 },
|
||||
{ 84.55, 5.07, -6.12 },
|
||||
{ 84.02, -13.87, -8.72 },
|
||||
{ 70.76, 0.07, -0.35 },
|
||||
{ 45.59, -0.05, 0.23 },
|
||||
{ 20.3, 0.07, -0.32 },
|
||||
{ 61.79, -13.41, 55.42 },
|
||||
{ 49.72, -0.19, 0.02 },
|
||||
{ 6.77, -0.05, -0.44 },
|
||||
{ 21.85, 34.37, 7.83 },
|
||||
{ 42.66, 67.43, 48.42 },
|
||||
{ 60.33, 36.56, 3.56 },
|
||||
{ 61.22, 36.61, 17.32 },
|
||||
{ 62.07, 52.8, 77.14 },
|
||||
{ 72.42, -9.82, 89.66 },
|
||||
{ 62.03, 3.53, 57.01 },
|
||||
{ 71.95, -27.34, 73.69 },
|
||||
{ 6.59, -0.04, -0.45 },
|
||||
{ 49.77, -0.19, 0.04 },
|
||||
{ 41.84, 62.05, 10.01 },
|
||||
{ 19.78, 29.16, -7.85 },
|
||||
{ 39.56, 65.98, 33.71 },
|
||||
{ 52.39, 68.33, 47.84 },
|
||||
{ 81.23, 24.12, 87.51 },
|
||||
{ 81.8, 6.78, 95.75 },
|
||||
{ 71.72, -16.23, 76.28 },
|
||||
{ 20.31, 14.45, 16.74 },
|
||||
{ 49.68, -0.19, 0.05 },
|
||||
{ 96.48, -0.88, 0.68 },
|
||||
{ 49.69, -0.18, 0.03 },
|
||||
{ 6.39, -0.04, -0.33 },
|
||||
{ 96.54, -0.9, 0.67 },
|
||||
{ 49.72, -0.18, 0.05 },
|
||||
{ 6.49, -0.03, -0.41 },
|
||||
{ 96.51, -0.9, 0.69 },
|
||||
{ 49.7, -0.19, 0.07 },
|
||||
{ 6.47, 0, -0.38 },
|
||||
{ 96.46, -0.89, 0.7 } };
|
||||
|
||||
switch (const_color)
|
||||
{
|
||||
|
||||
case cv::ccm::COLORCHECKER_MACBETH:
|
||||
{
|
||||
Mat ColorChecker2005_LAB_D50_2_ = GetColor::getColorChecker(*ColorChecker2005_LAB_D50_2, 24);
|
||||
Mat ColorChecker2005_COLORED_MASK_ = GetColor::getColorCheckerMask(ColorChecker2005_COLORED_MASK, 24);
|
||||
Color Macbeth_D50_2 = Color(ColorChecker2005_LAB_D50_2_, COLOR_SPACE_LAB_D50_2, ColorChecker2005_COLORED_MASK_);
|
||||
return Macbeth_D50_2;
|
||||
}
|
||||
|
||||
case cv::ccm::COLORCHECKER_VINYL:
|
||||
{
|
||||
Mat Vinyl_LAB_D50_2__ = GetColor::getColorChecker(*Vinyl_LAB_D50_2, 18);
|
||||
Mat Vinyl_COLORED_MASK__ = GetColor::getColorCheckerMask(Vinyl_COLORED_MASK, 18);
|
||||
Color Vinyl_D50_2 = Color(Vinyl_LAB_D50_2__, COLOR_SPACE_LAB_D50_2, Vinyl_COLORED_MASK__);
|
||||
return Vinyl_D50_2;
|
||||
}
|
||||
|
||||
case cv::ccm::COLORCHECKER_DIGITAL_SG:
|
||||
{
|
||||
Mat DigitalSG_LAB_D50_2__ = GetColor::getColorChecker(*DigitalSG_LAB_D50_2, 140);
|
||||
Color DigitalSG_D50_2 = Color(DigitalSG_LAB_D50_2__, COLOR_SPACE_LAB_D50_2);
|
||||
return DigitalSG_D50_2;
|
||||
}
|
||||
}
|
||||
CV_Error(Error::StsNotImplemented, "");
|
||||
}
|
||||
|
||||
}
|
||||
} // namespace cv::ccm
|
||||
@@ -0,0 +1,108 @@
|
||||
// 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.
|
||||
//
|
||||
// Author: Longbu Wang <wanglongbu@huawei.com.com>
|
||||
// Jinheng Zhang <zhangjinheng1@huawei.com>
|
||||
// Chenqi Shan <shanchenqi@huawei.com>
|
||||
|
||||
#ifndef __OPENCV_CCM_COLOR_HPP__
|
||||
#define __OPENCV_CCM_COLOR_HPP__
|
||||
|
||||
#include "distance.hpp"
|
||||
#include "colorspace.hpp"
|
||||
#include "opencv2/photo.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
|
||||
*/
|
||||
Mat colors;
|
||||
std::shared_ptr<ColorSpaceBase> cs;
|
||||
Mat grays;
|
||||
Mat colored;
|
||||
std::map<ColorSpaceBase, std::shared_ptr<Color>> history;
|
||||
|
||||
Color();
|
||||
Color(Mat colors_, enum ColorSpace cs_);
|
||||
Color(Mat colors_, enum ColorSpace cs_, Mat colored);
|
||||
Color(Mat colors_, const ColorSpaceBase& cs, Mat colored);
|
||||
Color(Mat colors_, const ColorSpaceBase& cs);
|
||||
Color(Mat colors_, std::shared_ptr<ColorSpaceBase> cs_);
|
||||
virtual ~Color() {};
|
||||
|
||||
/** @brief Change to other color space.
|
||||
The conversion process incorporates linear transformations to speed up.
|
||||
@param other type of ColorSpaceBase.
|
||||
@param method the chromatic adapation method.
|
||||
@param save when save if True, get data from history first.
|
||||
@return Color.
|
||||
*/
|
||||
Color to(const ColorSpaceBase& other, ChromaticAdaptationType method = BRADFORD, bool save = true);
|
||||
|
||||
/** @brief Convert color to another color space using ColorSpace enum.
|
||||
@param other type of ColorSpace.
|
||||
@param method the method of chromatic adaptation.
|
||||
@param save whether to save the conversion history.
|
||||
@return the output array, type of Color.
|
||||
*/
|
||||
Color to(ColorSpace other, ChromaticAdaptationType method = BRADFORD, bool save = true);
|
||||
|
||||
/** @brief Channels split.
|
||||
@return each channel.
|
||||
*/
|
||||
Mat channel(Mat m, int i);
|
||||
|
||||
/** @brief To Gray.
|
||||
*/
|
||||
Mat toGray(const IllumObserver& illumobserver, ChromaticAdaptationType method = BRADFORD, bool save = true);
|
||||
|
||||
/** @brief To Luminant.
|
||||
*/
|
||||
Mat toLuminant(const IllumObserver& illumobserver, ChromaticAdaptationType method = BRADFORD, bool save = true);
|
||||
|
||||
/** @brief Diff without IllumObserver.
|
||||
@param other type of Color.
|
||||
@param method type of distance.
|
||||
@return distance between self and other
|
||||
*/
|
||||
Mat diff(Color& other, DistanceType method = DISTANCE_CIE2000);
|
||||
|
||||
/** @brief Diff with IllumObserver.
|
||||
@param other type of Color.
|
||||
@param illumobserver type of IllumObserver.
|
||||
@param method type of distance.
|
||||
@return distance between self and other
|
||||
*/
|
||||
Mat diff(Color& other, const IllumObserver& illumobserver, DistanceType method = DISTANCE_CIE2000);
|
||||
|
||||
/** @brief Calculate gray mask.
|
||||
*/
|
||||
void getGray(double JDN = 2.0);
|
||||
|
||||
/** @brief Operator for mask copy.
|
||||
*/
|
||||
Color operator[](Mat mask);
|
||||
};
|
||||
|
||||
class GetColor
|
||||
{
|
||||
public:
|
||||
Color getColor(ColorCheckerType const_color);
|
||||
static Mat getColorChecker(const double* checker, int row);
|
||||
static Mat getColorCheckerMask(const uchar* checker, int row);
|
||||
};
|
||||
|
||||
}
|
||||
} // namespace cv::ccm
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,769 @@
|
||||
// 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.
|
||||
//
|
||||
// Author: Longbu Wang <wanglongbu@huawei.com.com>
|
||||
// Jinheng Zhang <zhangjinheng1@huawei.com>
|
||||
// Chenqi Shan <shanchenqi@huawei.com>
|
||||
|
||||
#include "colorspace.hpp"
|
||||
#include "operations.hpp"
|
||||
#include "illumobserver.hpp"
|
||||
|
||||
namespace cv {
|
||||
namespace ccm {
|
||||
static const std::vector<double>& getIlluminants(const IllumObserver& illumobserver)
|
||||
{
|
||||
static const std::map<IllumObserver, std::vector<double>> illuminants = {
|
||||
{ IllumObserver::getIllumObservers(A_2), { 1.098466069456375, 1, 0.3558228003436005 } },
|
||||
{ IllumObserver::getIllumObservers(A_10), { 1.111420406956693, 1, 0.3519978321919493 } },
|
||||
{ IllumObserver::getIllumObservers(D50_2), { 0.9642119944211994, 1, 0.8251882845188288 } },
|
||||
{ IllumObserver::getIllumObservers(D50_10), { 0.9672062750333777, 1, 0.8142801513128616 } },
|
||||
{ IllumObserver::getIllumObservers(D55_2), { 0.956797052643698, 1, 0.9214805860173273 } },
|
||||
{ IllumObserver::getIllumObservers(D55_10), { 0.9579665682254781, 1, 0.9092525159847462 } },
|
||||
{ IllumObserver::getIllumObservers(D65_2), { 0.95047, 1., 1.08883 } },
|
||||
{ IllumObserver::getIllumObservers(D65_10), { 0.94811, 1., 1.07304 } },
|
||||
{ IllumObserver::getIllumObservers(D75_2), { 0.9497220898840717, 1, 1.226393520724154 } },
|
||||
{ IllumObserver::getIllumObservers(D75_10), { 0.9441713925645873, 1, 1.2064272211720228 } },
|
||||
{ IllumObserver::getIllumObservers(E_2), { 1., 1., 1. } },
|
||||
{ IllumObserver::getIllumObservers(E_10), { 1., 1., 1. } },
|
||||
};
|
||||
auto it = illuminants.find(illumobserver);
|
||||
CV_Assert(it != illuminants.end());
|
||||
return it->second;
|
||||
};
|
||||
|
||||
/* @brief Basic class for ColorSpaceBase.
|
||||
*/
|
||||
bool ColorSpaceBase::relate(const ColorSpaceBase& other) const
|
||||
{
|
||||
return (type == other.type) && (illumobserver == other.illumobserver);
|
||||
};
|
||||
|
||||
Operations ColorSpaceBase::relation(const ColorSpaceBase& /*other*/) const
|
||||
{
|
||||
return Operations::getIdentityOps();
|
||||
}
|
||||
|
||||
bool ColorSpaceBase::operator<(const ColorSpaceBase& other) const
|
||||
{
|
||||
return (illumobserver < other.illumobserver || (illumobserver == other.illumobserver && type < other.type) || (illumobserver == other.illumobserver && 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
|
||||
*/
|
||||
Operations RGBBase_::relation(const ColorSpaceBase& other) const
|
||||
{
|
||||
if (linear == other.linear)
|
||||
{
|
||||
return Operations::getIdentityOps();
|
||||
}
|
||||
if (linear)
|
||||
{
|
||||
return Operations({ Operation([this](Mat rgbl) -> Mat { return fromLFunc(rgbl); }) });
|
||||
}
|
||||
return Operations({ Operation([this](Mat rgb) -> Mat { return toLFunc(rgb); })});
|
||||
}
|
||||
|
||||
/* @brief Initial operations.
|
||||
*/
|
||||
void RGBBase_::init()
|
||||
{
|
||||
setParameter();
|
||||
calLinear();
|
||||
calM();
|
||||
calOperations();
|
||||
}
|
||||
|
||||
/* @brief Produce color space instance with linear and non-linear versions.
|
||||
* @param rgbl type of RGBBase_.
|
||||
*/
|
||||
void RGBBase_::bind(RGBBase_& rgbl)
|
||||
{
|
||||
init();
|
||||
rgbl.init();
|
||||
l = &rgbl;
|
||||
rgbl.l = &rgbl;
|
||||
nl = this;
|
||||
rgbl.nl = this;
|
||||
}
|
||||
|
||||
/* @brief Calculation of M_RGBL2XYZ_base.
|
||||
*/
|
||||
void RGBBase_::calM()
|
||||
{
|
||||
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<Mat> { XYZr, XYZg, XYZb }, XYZ_rgbl);
|
||||
XYZ_rgbl = XYZ_rgbl.reshape(1, (int)XYZ_rgbl.total());
|
||||
Mat XYZw = Mat(getIlluminants(illumobserver), true);
|
||||
XYZw = XYZw.reshape(1, (int)XYZw.total());
|
||||
solve(XYZ_rgbl, XYZw, Srgb);
|
||||
merge(std::vector<Mat> { Srgb.at<double>(0) * XYZr, Srgb.at<double>(1) * XYZg,
|
||||
Srgb.at<double>(2) * XYZb },
|
||||
M_to);
|
||||
M_to = M_to.reshape(1, (int)M_to.total());
|
||||
M_from = M_to.inv();
|
||||
};
|
||||
|
||||
/* @brief operations to or from XYZ.
|
||||
*/
|
||||
void RGBBase_::calOperations()
|
||||
{
|
||||
if (linear)
|
||||
{
|
||||
to = Operations({ Operation(M_to.t()) });
|
||||
from = Operations({ Operation(M_from.t()) });
|
||||
}
|
||||
else
|
||||
{
|
||||
// rgb -> rgbl
|
||||
to = Operations({ Operation([this](Mat rgb) -> Mat { return toLFunc(rgb); }), Operation(M_to.t()) });
|
||||
// rgbl -> rgb
|
||||
from = Operations({ Operation(M_from.t()), Operation([this](Mat rgbl) -> Mat { return fromLFunc(rgbl); }) });
|
||||
}
|
||||
}
|
||||
|
||||
Mat RGBBase_::toLFunc(Mat& /*rgb*/) const { return Mat(); }
|
||||
|
||||
Mat RGBBase_::fromLFunc(Mat& /*rgbl*/, Mat dst) const { return dst; }
|
||||
|
||||
/* @brief Base of Adobe RGB color space;
|
||||
*/
|
||||
|
||||
Mat AdobeRGBBase_::toLFunc(Mat& rgb) const
|
||||
{
|
||||
Mat out;
|
||||
gammaCorrection(rgb, out, gamma);
|
||||
return out;
|
||||
}
|
||||
|
||||
Mat AdobeRGBBase_::fromLFunc(Mat& rgbl, Mat dst) const
|
||||
{
|
||||
gammaCorrection(rgbl, dst, 1. / gamma);
|
||||
return dst;
|
||||
}
|
||||
|
||||
/* @brief Base of sRGB color space;
|
||||
*/
|
||||
|
||||
void sRGBBase_::calLinear()
|
||||
{
|
||||
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 sRGBBase_::toLFuncEW(double x) const
|
||||
{
|
||||
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.
|
||||
* @param rgb the input array, type of cv::Mat.
|
||||
* @return the output array, type of cv::Mat.
|
||||
*/
|
||||
Mat sRGBBase_::toLFunc(Mat& rgb) const
|
||||
{
|
||||
return elementWise(rgb,
|
||||
[this](double a_) -> double { return toLFuncEW(a_); });
|
||||
}
|
||||
|
||||
/* @brief Used by fromLFunc.
|
||||
*/
|
||||
double sRGBBase_::fromLFuncEW(double x) const
|
||||
{
|
||||
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.
|
||||
* @param rgbl the input array, type of cv::Mat.
|
||||
* @return the output array, type of cv::Mat.
|
||||
*/
|
||||
Mat sRGBBase_::fromLFunc(Mat& rgbl, Mat dst) const
|
||||
{
|
||||
return elementWise(rgbl, [this](double a_) -> double { return fromLFuncEW(a_); }, dst);
|
||||
}
|
||||
|
||||
/* @brief sRGB color space.
|
||||
* data from https://en.wikipedia.org/wiki/SRGB.
|
||||
*/
|
||||
void sRGB_::setParameter()
|
||||
{
|
||||
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.
|
||||
*/
|
||||
void AdobeRGB_::setParameter()
|
||||
{
|
||||
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.
|
||||
*/
|
||||
void WideGamutRGB_::setParameter()
|
||||
{
|
||||
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.
|
||||
*/
|
||||
void ProPhotoRGB_::setParameter()
|
||||
{
|
||||
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.
|
||||
*/
|
||||
|
||||
void DCI_P3_RGB_::setParameter()
|
||||
{
|
||||
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.
|
||||
*/
|
||||
void AppleRGB_::setParameter()
|
||||
{
|
||||
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.
|
||||
*/
|
||||
void REC_709_RGB_::setParameter()
|
||||
{
|
||||
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.
|
||||
*/
|
||||
|
||||
void REC_2020_RGB_::setParameter()
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
Operations XYZ::cam(IllumObserver dio, ChromaticAdaptationType method)
|
||||
{
|
||||
return (illumobserver == dio) ? Operations()
|
||||
: Operations({ Operation(cam_(illumobserver, dio, method).t()) });
|
||||
}
|
||||
Mat XYZ::cam_(IllumObserver sio, IllumObserver dio, ChromaticAdaptationType method) const
|
||||
{
|
||||
static std::map<std::tuple<IllumObserver, IllumObserver, ChromaticAdaptationType>, Mat> cams;
|
||||
|
||||
if (sio == dio)
|
||||
{
|
||||
return 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)];
|
||||
}
|
||||
/* @brief XYZ color space.
|
||||
* Chromatic adaption matrices.
|
||||
*/
|
||||
|
||||
static const Mat Von_Kries = (Mat_<double>(3, 3) << 0.40024, 0.7076, -0.08081, -0.2263, 1.16532, 0.0457, 0., 0., 0.91822);
|
||||
static const Mat Bradford = (Mat_<double>(3, 3) << 0.8951, 0.2664, -0.1614, -0.7502, 1.7135, 0.0367, 0.0389, -0.0685, 1.0296);
|
||||
static const std::map<ChromaticAdaptationType, std::vector<Mat>> MAs = {
|
||||
{ IDENTITY, { Mat::eye(Size(3, 3), CV_64FC1), Mat::eye(Size(3, 3), CV_64FC1) } },
|
||||
{ VON_KRIES, { Von_Kries, Von_Kries.inv() } },
|
||||
{ BRADFORD, { Bradford, Bradford.inv() } }
|
||||
};
|
||||
|
||||
// Function from http://www.brucelindbloom.com/index.html?ColorCheckerRGB.html.
|
||||
Mat XYZws = Mat(getIlluminants(dio));
|
||||
Mat XYZWd = Mat(getIlluminants(sio));
|
||||
XYZws = XYZws.reshape(1, (int)XYZws.total());
|
||||
XYZWd = XYZWd.reshape(1, (int)XYZWd.total());
|
||||
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;
|
||||
}
|
||||
|
||||
std::shared_ptr<XYZ> XYZ::get(IllumObserver illumobserver)
|
||||
{
|
||||
static std::map<IllumObserver, std::shared_ptr<XYZ>> xyz_cs;
|
||||
|
||||
if (xyz_cs.count(illumobserver) == 1)
|
||||
{
|
||||
return xyz_cs[illumobserver];
|
||||
}
|
||||
std::shared_ptr<XYZ> XYZ_CS = std::make_shared<XYZ>(illumobserver);
|
||||
xyz_cs[illumobserver] = XYZ_CS;
|
||||
return xyz_cs[illumobserver];
|
||||
}
|
||||
|
||||
/* @brief Lab color space.
|
||||
*/
|
||||
Lab::Lab(IllumObserver illumobserver_)
|
||||
: ColorSpaceBase(illumobserver_, "Lab", true)
|
||||
{
|
||||
to = { Operation([this](Mat src) -> Mat { return tosrc(src); }) };
|
||||
from = { Operation([this](Mat src) -> Mat { return fromsrc(src); }) };
|
||||
}
|
||||
|
||||
Vec3d Lab::fromxyz(const Vec3d& xyz)
|
||||
{
|
||||
auto& il = getIlluminants(illumobserver);
|
||||
double x = xyz[0] / il[0],
|
||||
y = xyz[1] / il[1],
|
||||
z = xyz[2] / il[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
|
||||
*/
|
||||
Mat Lab::fromsrc(Mat& src)
|
||||
{
|
||||
return channelWise(src,
|
||||
[this](cv::Vec3d a) -> cv::Vec3d { return fromxyz(a); });
|
||||
}
|
||||
|
||||
Vec3d Lab::tolab(const 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.;
|
||||
auto& il = getIlluminants(illumobserver);
|
||||
return { il[0] * f_inv(L + a),
|
||||
il[1] * f_inv(L),
|
||||
il[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
|
||||
*/
|
||||
Mat Lab::tosrc(Mat& src)
|
||||
{
|
||||
return channelWise(src,
|
||||
[this](cv::Vec3d a) -> cv::Vec3d { return tolab(a); });
|
||||
}
|
||||
|
||||
std::shared_ptr<Lab> Lab::get(IllumObserver illumobserver)
|
||||
{
|
||||
static std::map<IllumObserver, std::shared_ptr<Lab>> lab_cs;
|
||||
|
||||
if (lab_cs.count(illumobserver) == 1)
|
||||
{
|
||||
return lab_cs[illumobserver];
|
||||
}
|
||||
std::shared_ptr<Lab> Lab_CS(new Lab(illumobserver));
|
||||
lab_cs[illumobserver] = Lab_CS;
|
||||
return lab_cs[illumobserver];
|
||||
}
|
||||
|
||||
GetCS::GetCS()
|
||||
{
|
||||
// nothing
|
||||
}
|
||||
|
||||
GetCS& GetCS::getInstance()
|
||||
{
|
||||
static GetCS instance;
|
||||
return instance;
|
||||
}
|
||||
|
||||
std::shared_ptr<RGBBase_> GetCS::getRgb(enum ColorSpace cs_name)
|
||||
{
|
||||
switch (cs_name)
|
||||
{
|
||||
case cv::ccm::COLOR_SPACE_SRGB:
|
||||
if (map_cs.find(COLOR_SPACE_SRGB) == map_cs.end())
|
||||
{
|
||||
std::shared_ptr<sRGB_> sRGB_CS(new sRGB_(false));
|
||||
std::shared_ptr<sRGB_> sRGBL_CS(new sRGB_(true));
|
||||
(*sRGB_CS).bind(*sRGBL_CS);
|
||||
map_cs[COLOR_SPACE_SRGB] = sRGB_CS;
|
||||
map_cs[COLOR_SPACE_SRGBL] = sRGBL_CS;
|
||||
}
|
||||
return std::dynamic_pointer_cast<RGBBase_>(map_cs[COLOR_SPACE_SRGB]);
|
||||
|
||||
case cv::ccm::COLOR_SPACE_ADOBE_RGB:
|
||||
if (map_cs.find(COLOR_SPACE_ADOBE_RGB) == map_cs.end())
|
||||
{
|
||||
std::shared_ptr<AdobeRGB_> AdobeRGB_CS(new AdobeRGB_(false));
|
||||
std::shared_ptr<AdobeRGB_> AdobeRGBL_CS(new AdobeRGB_(true));
|
||||
(*AdobeRGB_CS).bind(*AdobeRGBL_CS);
|
||||
map_cs[COLOR_SPACE_ADOBE_RGB] = AdobeRGB_CS;
|
||||
map_cs[COLOR_SPACE_ADOBE_RGBL] = AdobeRGBL_CS;
|
||||
}
|
||||
return std::dynamic_pointer_cast<RGBBase_>(map_cs[COLOR_SPACE_ADOBE_RGB]);
|
||||
|
||||
case cv::ccm::COLOR_SPACE_WIDE_GAMUT_RGB:
|
||||
{
|
||||
if (map_cs.count(cs_name) < 1)
|
||||
{
|
||||
std::shared_ptr<WideGamutRGB_> WideGamutRGB_CS(new WideGamutRGB_(false));
|
||||
std::shared_ptr<WideGamutRGB_> WideGamutRGBL_CS(new WideGamutRGB_(true));
|
||||
(*WideGamutRGB_CS).bind(*WideGamutRGBL_CS);
|
||||
map_cs[COLOR_SPACE_WIDE_GAMUT_RGB] = WideGamutRGB_CS;
|
||||
map_cs[COLOR_SPACE_WIDE_GAMUT_RGBL] = WideGamutRGBL_CS;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case cv::ccm::COLOR_SPACE_PRO_PHOTO_RGB:
|
||||
{
|
||||
if (map_cs.count(cs_name) < 1)
|
||||
{
|
||||
std::shared_ptr<ProPhotoRGB_> ProPhotoRGB_CS(new ProPhotoRGB_(false));
|
||||
std::shared_ptr<ProPhotoRGB_> ProPhotoRGBL_CS(new ProPhotoRGB_(true));
|
||||
(*ProPhotoRGB_CS).bind(*ProPhotoRGBL_CS);
|
||||
map_cs[COLOR_SPACE_PRO_PHOTO_RGB] = ProPhotoRGB_CS;
|
||||
map_cs[COLOR_SPACE_PRO_PHOTO_RGBL] = ProPhotoRGBL_CS;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case cv::ccm::COLOR_SPACE_DCI_P3_RGB:
|
||||
{
|
||||
if (map_cs.count(cs_name) < 1)
|
||||
{
|
||||
std::shared_ptr<DCI_P3_RGB_> DCI_P3_RGB_CS(new DCI_P3_RGB_(false));
|
||||
std::shared_ptr<DCI_P3_RGB_> DCI_P3_RGBL_CS(new DCI_P3_RGB_(true));
|
||||
(*DCI_P3_RGB_CS).bind(*DCI_P3_RGBL_CS);
|
||||
map_cs[COLOR_SPACE_DCI_P3_RGB] = DCI_P3_RGB_CS;
|
||||
map_cs[COLOR_SPACE_DCI_P3_RGBL] = DCI_P3_RGBL_CS;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case cv::ccm::COLOR_SPACE_APPLE_RGB:
|
||||
{
|
||||
if (map_cs.count(cs_name) < 1)
|
||||
{
|
||||
std::shared_ptr<AppleRGB_> AppleRGB_CS(new AppleRGB_(false));
|
||||
std::shared_ptr<AppleRGB_> AppleRGBL_CS(new AppleRGB_(true));
|
||||
(*AppleRGB_CS).bind(*AppleRGBL_CS);
|
||||
map_cs[COLOR_SPACE_APPLE_RGB] = AppleRGB_CS;
|
||||
map_cs[COLOR_SPACE_APPLE_RGBL] = AppleRGBL_CS;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case cv::ccm::COLOR_SPACE_REC_709_RGB:
|
||||
{
|
||||
if (map_cs.count(cs_name) < 1)
|
||||
{
|
||||
std::shared_ptr<REC_709_RGB_> REC_709_RGB_CS(new REC_709_RGB_(false));
|
||||
std::shared_ptr<REC_709_RGB_> REC_709_RGBL_CS(new REC_709_RGB_(true));
|
||||
(*REC_709_RGB_CS).bind(*REC_709_RGBL_CS);
|
||||
map_cs[COLOR_SPACE_REC_709_RGB] = REC_709_RGB_CS;
|
||||
map_cs[COLOR_SPACE_REC_709_RGBL] = REC_709_RGBL_CS;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case cv::ccm::COLOR_SPACE_REC_2020_RGB:
|
||||
{
|
||||
if (map_cs.count(cs_name) < 1)
|
||||
{
|
||||
std::shared_ptr<REC_2020_RGB_> REC_2020_RGB_CS(new REC_2020_RGB_(false));
|
||||
std::shared_ptr<REC_2020_RGB_> REC_2020_RGBL_CS(new REC_2020_RGB_(true));
|
||||
(*REC_2020_RGB_CS).bind(*REC_2020_RGBL_CS);
|
||||
map_cs[COLOR_SPACE_REC_2020_RGB] = REC_2020_RGB_CS;
|
||||
map_cs[COLOR_SPACE_REC_2020_RGBL] = REC_2020_RGBL_CS;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case cv::ccm::COLOR_SPACE_SRGBL:
|
||||
case cv::ccm::COLOR_SPACE_ADOBE_RGBL:
|
||||
case cv::ccm::COLOR_SPACE_WIDE_GAMUT_RGBL:
|
||||
case cv::ccm::COLOR_SPACE_PRO_PHOTO_RGBL:
|
||||
case cv::ccm::COLOR_SPACE_DCI_P3_RGBL:
|
||||
case cv::ccm::COLOR_SPACE_APPLE_RGBL:
|
||||
case cv::ccm::COLOR_SPACE_REC_709_RGBL:
|
||||
case cv::ccm::COLOR_SPACE_REC_2020_RGBL:
|
||||
CV_Error(Error::StsBadArg, "linear RGB colorspaces are not supported, you should assigned as normal RGB color space");
|
||||
break;
|
||||
|
||||
default:
|
||||
CV_Error(Error::StsBadArg, "Only RGB color spaces are supported");
|
||||
}
|
||||
return (std::dynamic_pointer_cast<RGBBase_>)(map_cs[cs_name]);
|
||||
}
|
||||
|
||||
std::shared_ptr<ColorSpaceBase> GetCS::getCS(enum ColorSpace cs_name)
|
||||
{
|
||||
switch (cs_name)
|
||||
{
|
||||
case cv::ccm::COLOR_SPACE_SRGB:
|
||||
case cv::ccm::COLOR_SPACE_SRGBL:
|
||||
if (map_cs.find(COLOR_SPACE_SRGB) == map_cs.end())
|
||||
{
|
||||
std::shared_ptr<sRGB_> sRGB_CS(new sRGB_(false));
|
||||
std::shared_ptr<sRGB_> sRGBL_CS(new sRGB_(true));
|
||||
(*sRGB_CS).bind(*sRGBL_CS);
|
||||
map_cs[COLOR_SPACE_SRGB] = sRGB_CS;
|
||||
map_cs[COLOR_SPACE_SRGBL] = sRGBL_CS;
|
||||
}
|
||||
return map_cs[cs_name];
|
||||
|
||||
case cv::ccm::COLOR_SPACE_ADOBE_RGB:
|
||||
case cv::ccm::COLOR_SPACE_ADOBE_RGBL:
|
||||
{
|
||||
if (map_cs.count(cs_name) < 1)
|
||||
{
|
||||
std::shared_ptr<AdobeRGB_> AdobeRGB_CS(new AdobeRGB_(false));
|
||||
std::shared_ptr<AdobeRGB_> AdobeRGBL_CS(new AdobeRGB_(true));
|
||||
(*AdobeRGB_CS).bind(*AdobeRGBL_CS);
|
||||
map_cs[COLOR_SPACE_ADOBE_RGB] = AdobeRGB_CS;
|
||||
map_cs[COLOR_SPACE_ADOBE_RGBL] = AdobeRGBL_CS;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case cv::ccm::COLOR_SPACE_WIDE_GAMUT_RGB:
|
||||
case cv::ccm::COLOR_SPACE_WIDE_GAMUT_RGBL:
|
||||
{
|
||||
if (map_cs.count(cs_name) < 1)
|
||||
{
|
||||
std::shared_ptr<WideGamutRGB_> WideGamutRGB_CS(new WideGamutRGB_(false));
|
||||
std::shared_ptr<WideGamutRGB_> WideGamutRGBL_CS(new WideGamutRGB_(true));
|
||||
(*WideGamutRGB_CS).bind(*WideGamutRGBL_CS);
|
||||
map_cs[COLOR_SPACE_WIDE_GAMUT_RGB] = WideGamutRGB_CS;
|
||||
map_cs[COLOR_SPACE_WIDE_GAMUT_RGBL] = WideGamutRGBL_CS;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case cv::ccm::COLOR_SPACE_PRO_PHOTO_RGB:
|
||||
case cv::ccm::COLOR_SPACE_PRO_PHOTO_RGBL:
|
||||
{
|
||||
if (map_cs.count(cs_name) < 1)
|
||||
{
|
||||
std::shared_ptr<ProPhotoRGB_> ProPhotoRGB_CS(new ProPhotoRGB_(false));
|
||||
std::shared_ptr<ProPhotoRGB_> ProPhotoRGBL_CS(new ProPhotoRGB_(true));
|
||||
(*ProPhotoRGB_CS).bind(*ProPhotoRGBL_CS);
|
||||
map_cs[COLOR_SPACE_PRO_PHOTO_RGB] = ProPhotoRGB_CS;
|
||||
map_cs[COLOR_SPACE_PRO_PHOTO_RGBL] = ProPhotoRGBL_CS;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case cv::ccm::COLOR_SPACE_DCI_P3_RGB:
|
||||
case cv::ccm::COLOR_SPACE_DCI_P3_RGBL:
|
||||
{
|
||||
if (map_cs.count(cs_name) < 1)
|
||||
{
|
||||
std::shared_ptr<DCI_P3_RGB_> DCI_P3_RGB_CS(new DCI_P3_RGB_(false));
|
||||
std::shared_ptr<DCI_P3_RGB_> DCI_P3_RGBL_CS(new DCI_P3_RGB_(true));
|
||||
(*DCI_P3_RGB_CS).bind(*DCI_P3_RGBL_CS);
|
||||
map_cs[COLOR_SPACE_DCI_P3_RGB] = DCI_P3_RGB_CS;
|
||||
map_cs[COLOR_SPACE_DCI_P3_RGBL] = DCI_P3_RGBL_CS;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case cv::ccm::COLOR_SPACE_APPLE_RGB:
|
||||
case cv::ccm::COLOR_SPACE_APPLE_RGBL:
|
||||
{
|
||||
if (map_cs.count(cs_name) < 1)
|
||||
{
|
||||
std::shared_ptr<AppleRGB_> AppleRGB_CS(new AppleRGB_(false));
|
||||
std::shared_ptr<AppleRGB_> AppleRGBL_CS(new AppleRGB_(true));
|
||||
(*AppleRGB_CS).bind(*AppleRGBL_CS);
|
||||
map_cs[COLOR_SPACE_APPLE_RGB] = AppleRGB_CS;
|
||||
map_cs[COLOR_SPACE_APPLE_RGBL] = AppleRGBL_CS;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case cv::ccm::COLOR_SPACE_REC_709_RGB:
|
||||
case cv::ccm::COLOR_SPACE_REC_709_RGBL:
|
||||
{
|
||||
if (map_cs.count(cs_name) < 1)
|
||||
{
|
||||
std::shared_ptr<REC_709_RGB_> REC_709_RGB_CS(new REC_709_RGB_(false));
|
||||
std::shared_ptr<REC_709_RGB_> REC_709_RGBL_CS(new REC_709_RGB_(true));
|
||||
(*REC_709_RGB_CS).bind(*REC_709_RGBL_CS);
|
||||
map_cs[COLOR_SPACE_REC_709_RGB] = REC_709_RGB_CS;
|
||||
map_cs[COLOR_SPACE_REC_709_RGBL] = REC_709_RGBL_CS;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case cv::ccm::COLOR_SPACE_REC_2020_RGB:
|
||||
case cv::ccm::COLOR_SPACE_REC_2020_RGBL:
|
||||
{
|
||||
if (map_cs.count(cs_name) < 1)
|
||||
{
|
||||
std::shared_ptr<REC_2020_RGB_> REC_2020_RGB_CS(new REC_2020_RGB_(false));
|
||||
std::shared_ptr<REC_2020_RGB_> REC_2020_RGBL_CS(new REC_2020_RGB_(true));
|
||||
(*REC_2020_RGB_CS).bind(*REC_2020_RGBL_CS);
|
||||
map_cs[COLOR_SPACE_REC_2020_RGB] = REC_2020_RGB_CS;
|
||||
map_cs[COLOR_SPACE_REC_2020_RGBL] = REC_2020_RGBL_CS;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case cv::ccm::COLOR_SPACE_XYZ_D65_2:
|
||||
return XYZ::get(IllumObserver::getIllumObservers(D65_2));
|
||||
break;
|
||||
case cv::ccm::COLOR_SPACE_XYZ_D50_2:
|
||||
return XYZ::get(IllumObserver::getIllumObservers(D50_2));
|
||||
break;
|
||||
case cv::ccm::COLOR_SPACE_XYZ_D65_10:
|
||||
return XYZ::get(IllumObserver::getIllumObservers(D65_10));
|
||||
break;
|
||||
case cv::ccm::COLOR_SPACE_XYZ_D50_10:
|
||||
return XYZ::get(IllumObserver::getIllumObservers(D50_10));
|
||||
break;
|
||||
case cv::ccm::COLOR_SPACE_XYZ_A_2:
|
||||
return XYZ::get(IllumObserver::getIllumObservers(A_2));
|
||||
break;
|
||||
case cv::ccm::COLOR_SPACE_XYZ_A_10:
|
||||
return XYZ::get(IllumObserver::getIllumObservers(A_10));
|
||||
break;
|
||||
case cv::ccm::COLOR_SPACE_XYZ_D55_2:
|
||||
return XYZ::get(IllumObserver::getIllumObservers(D55_2));
|
||||
break;
|
||||
case cv::ccm::COLOR_SPACE_XYZ_D55_10:
|
||||
return XYZ::get(IllumObserver::getIllumObservers(D55_10));
|
||||
break;
|
||||
case cv::ccm::COLOR_SPACE_XYZ_D75_2:
|
||||
return XYZ::get(IllumObserver::getIllumObservers(D75_2));
|
||||
break;
|
||||
case cv::ccm::COLOR_SPACE_XYZ_D75_10:
|
||||
return XYZ::get(IllumObserver::getIllumObservers(D75_10));
|
||||
break;
|
||||
case cv::ccm::COLOR_SPACE_XYZ_E_2:
|
||||
return XYZ::get(IllumObserver::getIllumObservers(E_2));
|
||||
break;
|
||||
case cv::ccm::COLOR_SPACE_XYZ_E_10:
|
||||
return XYZ::get(IllumObserver::getIllumObservers(E_10));
|
||||
break;
|
||||
case cv::ccm::COLOR_SPACE_LAB_D65_2:
|
||||
return Lab::get(IllumObserver::getIllumObservers(D65_2));
|
||||
break;
|
||||
case cv::ccm::COLOR_SPACE_LAB_D50_2:
|
||||
return Lab::get(IllumObserver::getIllumObservers(D50_2));
|
||||
break;
|
||||
case cv::ccm::COLOR_SPACE_LAB_D65_10:
|
||||
return Lab::get(IllumObserver::getIllumObservers(D65_10));
|
||||
break;
|
||||
case cv::ccm::COLOR_SPACE_LAB_D50_10:
|
||||
return Lab::get(IllumObserver::getIllumObservers(D50_10));
|
||||
break;
|
||||
case cv::ccm::COLOR_SPACE_LAB_A_2:
|
||||
return Lab::get(IllumObserver::getIllumObservers(A_2));
|
||||
break;
|
||||
case cv::ccm::COLOR_SPACE_LAB_A_10:
|
||||
return Lab::get(IllumObserver::getIllumObservers(A_10));
|
||||
break;
|
||||
case cv::ccm::COLOR_SPACE_LAB_D55_2:
|
||||
return Lab::get(IllumObserver::getIllumObservers(D55_2));
|
||||
break;
|
||||
case cv::ccm::COLOR_SPACE_LAB_D55_10:
|
||||
return Lab::get(IllumObserver::getIllumObservers(D55_10));
|
||||
break;
|
||||
case cv::ccm::COLOR_SPACE_LAB_D75_2:
|
||||
return Lab::get(IllumObserver::getIllumObservers(D75_2));
|
||||
break;
|
||||
case cv::ccm::COLOR_SPACE_LAB_D75_10:
|
||||
return Lab::get(IllumObserver::getIllumObservers(D75_10));
|
||||
break;
|
||||
case cv::ccm::COLOR_SPACE_LAB_E_2:
|
||||
return Lab::get(IllumObserver::getIllumObservers(E_2));
|
||||
break;
|
||||
case cv::ccm::COLOR_SPACE_LAB_E_10:
|
||||
return Lab::get(IllumObserver::getIllumObservers(E_10));
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return map_cs[cs_name];
|
||||
}
|
||||
|
||||
}
|
||||
} // namespace cv::ccm
|
||||
@@ -0,0 +1,343 @@
|
||||
// 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.
|
||||
//
|
||||
// Author: Longbu Wang <wanglongbu@huawei.com.com>
|
||||
// Jinheng Zhang <zhangjinheng1@huawei.com>
|
||||
// Chenqi Shan <shanchenqi@huawei.com>
|
||||
|
||||
#ifndef __OPENCV_CCM_COLORSPACE_HPP__
|
||||
#define __OPENCV_CCM_COLORSPACE_HPP__
|
||||
|
||||
#include "operations.hpp"
|
||||
#include "illumobserver.hpp"
|
||||
#include "opencv2/photo.hpp"
|
||||
|
||||
namespace cv {
|
||||
namespace ccm {
|
||||
|
||||
/** @brief Basic class for ColorSpace.
|
||||
*/
|
||||
class ColorSpaceBase
|
||||
{
|
||||
public:
|
||||
typedef std::function<Mat(Mat)> MatFunc;
|
||||
IllumObserver illumobserver;
|
||||
std::string type;
|
||||
bool linear;
|
||||
Operations to;
|
||||
Operations from;
|
||||
ColorSpaceBase* l;
|
||||
ColorSpaceBase* nl;
|
||||
|
||||
ColorSpaceBase() {};
|
||||
|
||||
ColorSpaceBase(IllumObserver illumobserver_, std::string type_, bool linear_)
|
||||
: illumobserver(illumobserver_)
|
||||
, type(type_)
|
||||
, linear(linear_) {};
|
||||
|
||||
virtual ~ColorSpaceBase()
|
||||
{
|
||||
l = 0;
|
||||
nl = 0;
|
||||
};
|
||||
virtual bool relate(const ColorSpaceBase& other) const;
|
||||
|
||||
virtual Operations relation(const ColorSpaceBase& /*other*/) const;
|
||||
|
||||
bool operator<(const ColorSpaceBase& other) const;
|
||||
};
|
||||
|
||||
/** @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 ColorSpaceBase
|
||||
{
|
||||
public:
|
||||
// primaries
|
||||
double xr;
|
||||
double yr;
|
||||
double xg;
|
||||
double yg;
|
||||
double xb;
|
||||
double yb;
|
||||
Mat M_to;
|
||||
Mat M_from;
|
||||
|
||||
using ColorSpaceBase::ColorSpaceBase;
|
||||
|
||||
/** @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 ColorSpaceBase.
|
||||
@return Operations.
|
||||
*/
|
||||
Operations relation(const ColorSpaceBase& other) const CV_OVERRIDE;
|
||||
|
||||
/** @brief Initial operations.
|
||||
*/
|
||||
void init();
|
||||
/** @brief Produce color space instance with linear and non-linear versions.
|
||||
@param rgbl type of RGBBase_.
|
||||
*/
|
||||
void bind(RGBBase_& rgbl);
|
||||
|
||||
virtual Mat toLFunc(Mat& /*rgb*/) const;
|
||||
|
||||
virtual Mat fromLFunc(Mat& /*rgbl*/, Mat dst=Mat()) const;
|
||||
private:
|
||||
virtual void setParameter() {};
|
||||
|
||||
/** @brief Calculation of M_RGBL2XYZ_base.
|
||||
*/
|
||||
virtual void calM();
|
||||
|
||||
/** @brief operations to or from XYZ.
|
||||
*/
|
||||
virtual void calOperations();
|
||||
|
||||
virtual void calLinear() {};
|
||||
};
|
||||
|
||||
/** @brief Base of Adobe RGB color space;
|
||||
*/
|
||||
class AdobeRGBBase_ : public RGBBase_
|
||||
|
||||
{
|
||||
public:
|
||||
using RGBBase_::RGBBase_;
|
||||
double gamma;
|
||||
|
||||
private:
|
||||
Mat toLFunc(Mat& rgb) const CV_OVERRIDE;
|
||||
Mat fromLFunc(Mat& rgbl, Mat dst=Mat()) const CV_OVERRIDE;
|
||||
};
|
||||
|
||||
/** @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
|
||||
*/
|
||||
virtual void calLinear() CV_OVERRIDE;
|
||||
/** @brief Used by toLFunc.
|
||||
*/
|
||||
double toLFuncEW(double x) const;
|
||||
|
||||
/** @brief Linearization.
|
||||
@param rgb the input array, type of cv::Mat.
|
||||
@return the output array, type of cv::Mat.
|
||||
*/
|
||||
Mat toLFunc(Mat& rgb) const CV_OVERRIDE;
|
||||
|
||||
/** @brief Used by fromLFunc.
|
||||
*/
|
||||
double fromLFuncEW(double x) const;
|
||||
|
||||
/** @brief Delinearization.
|
||||
@param rgbl the input array, type of cv::Mat.
|
||||
@return the output array, type of cv::Mat.
|
||||
*/
|
||||
Mat fromLFunc(Mat& rgbl, Mat dst=Mat()) const CV_OVERRIDE;
|
||||
};
|
||||
|
||||
/** @brief sRGB color space.
|
||||
data from https://en.wikipedia.org/wiki/SRGB.
|
||||
*/
|
||||
class sRGB_ : public sRGBBase_
|
||||
|
||||
{
|
||||
public:
|
||||
sRGB_(bool linear_)
|
||||
: sRGBBase_(IllumObserver::getIllumObservers(D65_2), "sRGB", linear_) {};
|
||||
|
||||
private:
|
||||
void setParameter() CV_OVERRIDE;
|
||||
};
|
||||
|
||||
/** @brief Adobe RGB color space.
|
||||
*/
|
||||
class AdobeRGB_ : public AdobeRGBBase_
|
||||
{
|
||||
public:
|
||||
AdobeRGB_(bool linear_ = false)
|
||||
: AdobeRGBBase_(IllumObserver::getIllumObservers(D65_2), "AdobeRGB", linear_) {};
|
||||
|
||||
private:
|
||||
void setParameter() CV_OVERRIDE;
|
||||
};
|
||||
|
||||
/** @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_(IllumObserver::getIllumObservers(D50_2), "WideGamutRGB", linear_) {};
|
||||
|
||||
private:
|
||||
void setParameter() CV_OVERRIDE;
|
||||
};
|
||||
|
||||
/** @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_(IllumObserver::getIllumObservers(D50_2), "ProPhotoRGB", linear_) {};
|
||||
|
||||
private:
|
||||
void setParameter() CV_OVERRIDE;
|
||||
};
|
||||
|
||||
/** @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_(IllumObserver::getIllumObservers(D65_2), "DCI_P3_RGB", linear_) {};
|
||||
|
||||
private:
|
||||
void setParameter() CV_OVERRIDE;
|
||||
};
|
||||
|
||||
/** @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_(IllumObserver::getIllumObservers(D65_2), "AppleRGB", linear_) {};
|
||||
|
||||
private:
|
||||
void setParameter() CV_OVERRIDE;
|
||||
};
|
||||
|
||||
/** @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_(IllumObserver::getIllumObservers(D65_2), "REC_709_RGB", linear_) {};
|
||||
|
||||
private:
|
||||
void setParameter() CV_OVERRIDE;
|
||||
};
|
||||
|
||||
/** @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_(IllumObserver::getIllumObservers(D65_2), "REC_2020_RGB", linear_) {};
|
||||
|
||||
private:
|
||||
void setParameter() CV_OVERRIDE;
|
||||
};
|
||||
|
||||
/** @brief Enum of the possible types of Chromatic Adaptation Models.
|
||||
*/
|
||||
enum ChromaticAdaptationType
|
||||
{
|
||||
IDENTITY,
|
||||
VON_KRIES,
|
||||
BRADFORD
|
||||
};
|
||||
|
||||
|
||||
/** @brief XYZ color space.
|
||||
Chromatic adaption matrices.
|
||||
*/
|
||||
class XYZ : public ColorSpaceBase
|
||||
{
|
||||
public:
|
||||
XYZ(IllumObserver illumobserver_)
|
||||
: ColorSpaceBase(illumobserver_, "XYZ", true) {};
|
||||
Operations cam(IllumObserver dio, ChromaticAdaptationType method = BRADFORD);
|
||||
static std::shared_ptr<XYZ> get(IllumObserver illumobserver);
|
||||
|
||||
private:
|
||||
/** @brief Get cam.
|
||||
@param sio the input IllumObserver of src.
|
||||
@param dio the input IllumObserver of dst.
|
||||
@param method type of Chromatic Adaptation Model.
|
||||
@return the output array, type of cv::Mat.
|
||||
*/
|
||||
Mat cam_(IllumObserver sio, IllumObserver dio, ChromaticAdaptationType method = BRADFORD) const;
|
||||
};
|
||||
|
||||
/** @brief Lab color space.
|
||||
*/
|
||||
class Lab : public ColorSpaceBase
|
||||
{
|
||||
public:
|
||||
Lab(IllumObserver illumobserver_);
|
||||
static std::shared_ptr<Lab> get(IllumObserver illumobserver);
|
||||
|
||||
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.;
|
||||
|
||||
Vec3d fromxyz(const Vec3d& xyz);
|
||||
|
||||
/** @brief Calculate From.
|
||||
@param src the input array, type of cv::Mat.
|
||||
@return the output array, type of cv::Mat
|
||||
*/
|
||||
Mat fromsrc(Mat& src);
|
||||
|
||||
Vec3d tolab(const Vec3d& lab);
|
||||
|
||||
/** @brief Calculate To.
|
||||
@param src the input array, type of cv::Mat.
|
||||
@return the output array, type of cv::Mat
|
||||
*/
|
||||
Mat tosrc(Mat& src);
|
||||
};
|
||||
|
||||
class GetCS
|
||||
{
|
||||
protected:
|
||||
std::map<enum ColorSpace, std::shared_ptr<ColorSpaceBase>> map_cs;
|
||||
|
||||
GetCS(); // singleton, use getInstance()
|
||||
public:
|
||||
static GetCS& getInstance();
|
||||
|
||||
std::shared_ptr<RGBBase_> getRgb(enum ColorSpace cs_name);
|
||||
std::shared_ptr<ColorSpaceBase> getCS(enum ColorSpace cs_name);
|
||||
};
|
||||
|
||||
}
|
||||
} // namespace cv::ccm
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,204 @@
|
||||
// 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.
|
||||
//
|
||||
// Author: Longbu Wang <wanglongbu@huawei.com.com>
|
||||
// Jinheng Zhang <zhangjinheng1@huawei.com>
|
||||
// Chenqi Shan <shanchenqi@huawei.com>
|
||||
|
||||
#include "distance.hpp"
|
||||
|
||||
namespace cv {
|
||||
namespace ccm {
|
||||
|
||||
double deltaCIE76(const Vec3d& lab1, const Vec3d& lab2) { return norm(lab1 - lab2); };
|
||||
|
||||
double deltaCIE94(const Vec3d& lab1, const 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(const Vec3d& lab1, const Vec3d& lab2)
|
||||
{
|
||||
return deltaCIE94(lab1, lab2);
|
||||
}
|
||||
|
||||
double toRad(double degree) { return degree / 180 * CV_PI; };
|
||||
|
||||
double deltaCIE94Textiles(const Vec3d& lab1, const Vec3d& lab2)
|
||||
{
|
||||
return deltaCIE94(lab1, lab2, 1.0, 1.0, 2.0, 0.048, 0.014);
|
||||
}
|
||||
|
||||
double deltaCIEDE2000_(const Vec3d& lab1, const Vec3d& lab2, double kL,
|
||||
double kC, double kH)
|
||||
{
|
||||
double deltaLApo = lab2[0] - lab1[0];
|
||||
double lBarApo = (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 cBar = (C1 + C2) / 2.0;
|
||||
double G = sqrt(pow(cBar, 7) / (pow(cBar, 7) + pow(25, 7)));
|
||||
double a1Apo = lab1[1] + lab1[1] / 2.0 * (1.0 - G);
|
||||
double a2Apo = lab2[1] + lab2[1] / 2.0 * (1.0 - G);
|
||||
double c1Apo = sqrt(pow(a1Apo, 2) + pow(lab1[2], 2));
|
||||
double c2Apo = sqrt(pow(a2Apo, 2) + pow(lab2[2], 2));
|
||||
double cBarApo = (c1Apo + c2Apo) / 2.0;
|
||||
double deltaCApo = c2Apo - c1Apo;
|
||||
|
||||
double h1Apo;
|
||||
if (c1Apo == 0)
|
||||
{
|
||||
h1Apo = 0.0;
|
||||
}
|
||||
else
|
||||
{
|
||||
h1Apo = atan2(lab1[2], a1Apo);
|
||||
if (h1Apo < 0.0)
|
||||
h1Apo += 2. * CV_PI;
|
||||
}
|
||||
|
||||
double h2Apo;
|
||||
if (c2Apo == 0)
|
||||
{
|
||||
h2Apo = 0.0;
|
||||
}
|
||||
else
|
||||
{
|
||||
h2Apo = atan2(lab2[2], a2Apo);
|
||||
if (h2Apo < 0.0)
|
||||
h2Apo += 2. * CV_PI;
|
||||
}
|
||||
|
||||
double deltaHApo;
|
||||
if (abs(h2Apo - h1Apo) <= CV_PI)
|
||||
{
|
||||
deltaHApo = h2Apo - h1Apo;
|
||||
}
|
||||
else if (h2Apo <= h1Apo)
|
||||
{
|
||||
deltaHApo = h2Apo - h1Apo + 2. * CV_PI;
|
||||
}
|
||||
else
|
||||
{
|
||||
deltaHApo = h2Apo - h1Apo - 2. * CV_PI;
|
||||
}
|
||||
|
||||
double hBarApo;
|
||||
if (c1Apo == 0 || c2Apo == 0)
|
||||
{
|
||||
hBarApo = h1Apo + h2Apo;
|
||||
}
|
||||
else if (abs(h1Apo - h2Apo) <= CV_PI)
|
||||
{
|
||||
hBarApo = (h1Apo + h2Apo) / 2.0;
|
||||
}
|
||||
else if (h1Apo + h2Apo < 2. * CV_PI)
|
||||
{
|
||||
hBarApo = (h1Apo + h2Apo + 2. * CV_PI) / 2.0;
|
||||
}
|
||||
else
|
||||
{
|
||||
hBarApo = (h1Apo + h2Apo - 2. * CV_PI) / 2.0;
|
||||
}
|
||||
|
||||
double deltaH_Apo = 2.0 * sqrt(c1Apo * c2Apo) * sin(deltaHApo / 2.0);
|
||||
double T = 1.0 - 0.17 * cos(hBarApo - toRad(30.)) + 0.24 * cos(2.0 * hBarApo) + 0.32 * cos(3.0 * hBarApo + toRad(6.0)) - 0.2 * cos(4.0 * hBarApo - toRad(63.0));
|
||||
double sC = 1.0 + 0.045 * cBarApo;
|
||||
double sH = 1.0 + 0.015 * cBarApo * T;
|
||||
double sL = 1.0 + ((0.015 * pow(lBarApo - 50.0, 2.0)) / sqrt(20.0 + pow(lBarApo - 50.0, 2.0)));
|
||||
double rC = 2.0 * sqrt(pow(cBarApo, 7.0) / (pow(cBarApo, 7.0) + pow(25, 7)));
|
||||
double rT = -sin(toRad(60.0) * exp(-pow((hBarApo - toRad(275.0)) / toRad(25.0), 2.0))) * rC;
|
||||
double res = (pow(deltaLApo / (kL * sL), 2.0) + pow(deltaCApo / (kC * sC), 2.0) + pow(deltaH_Apo / (kH * sH), 2.0) + rT * (deltaCApo / (kC * sC)) * (deltaH_Apo / (kH * sH)));
|
||||
return res > 0 ? sqrt(res) : 0;
|
||||
}
|
||||
|
||||
double deltaCIEDE2000(const Vec3d& lab1, const Vec3d& lab2)
|
||||
{
|
||||
return deltaCIEDE2000_(lab1, lab2);
|
||||
}
|
||||
|
||||
double deltaCMC(const Vec3d& lab1, const 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(const Vec3d& lab1, const Vec3d& lab2)
|
||||
{
|
||||
return deltaCMC(lab1, lab2);
|
||||
}
|
||||
|
||||
double deltaCMC2To1(const Vec3d& lab1, const Vec3d& lab2)
|
||||
{
|
||||
return deltaCMC(lab1, lab2, 2, 1);
|
||||
}
|
||||
|
||||
Mat distance(Mat src, Mat ref, DistanceType distanceType)
|
||||
{
|
||||
switch (distanceType)
|
||||
{
|
||||
case cv::ccm::DISTANCE_CIE76:
|
||||
return distanceWise(src, ref, deltaCIE76);
|
||||
case cv::ccm::DISTANCE_CIE94_GRAPHIC_ARTS:
|
||||
return distanceWise(src, ref, deltaCIE94GraphicArts);
|
||||
case cv::ccm::DISTANCE_CIE94_TEXTILES:
|
||||
return distanceWise(src, ref, deltaCIE94Textiles);
|
||||
case cv::ccm::DISTANCE_CIE2000:
|
||||
return distanceWise(src, ref, deltaCIEDE2000);
|
||||
case cv::ccm::DISTANCE_CMC_1TO1:
|
||||
return distanceWise(src, ref, deltaCMC1To1);
|
||||
case cv::ccm::DISTANCE_CMC_2TO1:
|
||||
return distanceWise(src, ref, deltaCMC2To1);
|
||||
case cv::ccm::DISTANCE_RGB:
|
||||
return distanceWise(src, ref, deltaCIE76);
|
||||
case cv::ccm::DISTANCE_RGBL:
|
||||
return distanceWise(src, ref, deltaCIE76);
|
||||
default:
|
||||
CV_Error(Error::StsBadArg, "Wrong distanceType!" );
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
} // namespace ccm
|
||||
@@ -0,0 +1,80 @@
|
||||
// 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.
|
||||
//
|
||||
// Author: Longbu Wang <wanglongbu@huawei.com.com>
|
||||
// Jinheng Zhang <zhangjinheng1@huawei.com>
|
||||
// Chenqi Shan <shanchenqi@huawei.com>
|
||||
|
||||
#ifndef __OPENCV_CCM_DISTANCE_HPP__
|
||||
#define __OPENCV_CCM_DISTANCE_HPP__
|
||||
|
||||
#include "utils.hpp"
|
||||
#include "opencv2/photo.hpp"
|
||||
|
||||
namespace cv {
|
||||
namespace ccm {
|
||||
/** possibale functions to calculate the distance between
|
||||
colors.see https://en.wikipedia.org/wiki/Color_difference for details;*/
|
||||
|
||||
/** @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(const Vec3d& lab1, const Vec3d& 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(const Vec3d& lab1, const Vec3d& lab2, double kH = 1.0,
|
||||
double kC = 1.0, double kL = 1.0, double k1 = 0.045,
|
||||
double k2 = 0.015);
|
||||
|
||||
double deltaCIE94GraphicArts(const Vec3d& lab1, const Vec3d& lab2);
|
||||
|
||||
double toRad(double degree);
|
||||
|
||||
double deltaCIE94Textiles(const Vec3d& lab1, const Vec3d& lab2);
|
||||
|
||||
/** @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_(const Vec3d& lab1, const Vec3d& lab2, double kL = 1.0,
|
||||
double kC = 1.0, double kH = 1.0);
|
||||
double deltaCIEDE2000(const Vec3d& lab1, const Vec3d& 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(const Vec3d& lab1, const Vec3d& lab2, double kL = 1, double kC = 1);
|
||||
|
||||
double deltaCMC1To1(const Vec3d& lab1, const Vec3d& lab2);
|
||||
|
||||
double deltaCMC2To1(const Vec3d& lab1, const Vec3d& lab2);
|
||||
|
||||
Mat distance(Mat src,Mat ref, DistanceType distanceType);
|
||||
|
||||
}
|
||||
} // namespace cv::ccm
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,114 @@
|
||||
// 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.
|
||||
//
|
||||
// Author: Longbu Wang <wanglongbu@huawei.com.com>
|
||||
// Jinheng Zhang <zhangjinheng1@huawei.com>
|
||||
// Chenqi Shan <shanchenqi@huawei.com>
|
||||
|
||||
#include "illumobserver.hpp"
|
||||
namespace cv {
|
||||
namespace ccm {
|
||||
IllumObserver::IllumObserver(std::string illuminant_, std::string observer_)
|
||||
: illuminant(illuminant_)
|
||||
, observer(observer_) {};
|
||||
|
||||
bool IllumObserver::operator<(const IllumObserver& other) const
|
||||
{
|
||||
return (illuminant < other.illuminant || ((illuminant == other.illuminant) && (observer < other.observer)));
|
||||
}
|
||||
|
||||
bool IllumObserver::operator==(const IllumObserver& other) const
|
||||
{
|
||||
return illuminant == other.illuminant && observer == other.observer;
|
||||
};
|
||||
|
||||
IllumObserver IllumObserver::getIllumObservers(IllumObserverType illumobserver)
|
||||
{
|
||||
switch (illumobserver)
|
||||
{
|
||||
case cv::ccm::A_2:
|
||||
{
|
||||
IllumObserver A_2_IllumObserver("A", "2");
|
||||
return A_2_IllumObserver;
|
||||
break;
|
||||
}
|
||||
case cv::ccm::A_10:
|
||||
{
|
||||
IllumObserver A_1O_IllumObserver("A", "10");
|
||||
return A_1O_IllumObserver;
|
||||
break;
|
||||
}
|
||||
case cv::ccm::D50_2:
|
||||
{
|
||||
IllumObserver D50_2_IllumObserver("D50", "2");
|
||||
return D50_2_IllumObserver;
|
||||
break;
|
||||
}
|
||||
case cv::ccm::D50_10:
|
||||
{
|
||||
IllumObserver D50_10_IllumObserver("D50", "10");
|
||||
return D50_10_IllumObserver;
|
||||
break;
|
||||
}
|
||||
case cv::ccm::D55_2:
|
||||
{
|
||||
IllumObserver D55_2_IllumObserver("D55", "2");
|
||||
return D55_2_IllumObserver;
|
||||
break;
|
||||
}
|
||||
case cv::ccm::D55_10:
|
||||
{
|
||||
IllumObserver D55_10_IllumObserver("D55", "10");
|
||||
return D55_10_IllumObserver;
|
||||
break;
|
||||
}
|
||||
case cv::ccm::D65_2:
|
||||
{
|
||||
IllumObserver D65_2_IllumObserver("D65", "2");
|
||||
return D65_2_IllumObserver;
|
||||
}
|
||||
case cv::ccm::D65_10:
|
||||
{
|
||||
IllumObserver D65_10_IllumObserver("D65", "10");
|
||||
return D65_10_IllumObserver;
|
||||
break;
|
||||
}
|
||||
case cv::ccm::D75_2:
|
||||
{
|
||||
IllumObserver D75_2_IllumObserver("D75", "2");
|
||||
return D75_2_IllumObserver;
|
||||
break;
|
||||
}
|
||||
case cv::ccm::D75_10:
|
||||
{
|
||||
IllumObserver D75_10_IllumObserver("D75", "10");
|
||||
return D75_10_IllumObserver;
|
||||
break;
|
||||
}
|
||||
case cv::ccm::E_2:
|
||||
{
|
||||
IllumObserver E_2_IllumObserver("E", "2");
|
||||
return E_2_IllumObserver;
|
||||
break;
|
||||
}
|
||||
case cv::ccm::E_10:
|
||||
{
|
||||
IllumObserver E_10_IllumObserver("E", "10");
|
||||
return E_10_IllumObserver;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
return IllumObserver();
|
||||
break;
|
||||
}
|
||||
}
|
||||
// data from https://en.wikipedia.org/wiki/Standard_illuminant.
|
||||
std::vector<double> xyY2XYZ(const std::vector<double>& xyY)
|
||||
{
|
||||
double Y = xyY.size() >= 3 ? xyY[2] : 1;
|
||||
return { Y * xyY[0] / xyY[1], Y, Y / xyY[1] * (1 - xyY[0] - xyY[1]) };
|
||||
}
|
||||
|
||||
}
|
||||
} // namespace cv::ccm
|
||||
@@ -0,0 +1,53 @@
|
||||
// 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.
|
||||
//
|
||||
// Author: Longbu Wang <wanglongbu@huawei.com.com>
|
||||
// Jinheng Zhang <zhangjinheng1@huawei.com>
|
||||
// Chenqi Shan <shanchenqi@huawei.com>
|
||||
|
||||
#ifndef __OPENCV_CCM_IllumObserver_HPP__
|
||||
#define __OPENCV_CCM_IllumObserver_HPP__
|
||||
|
||||
#include <opencv2/core.hpp>
|
||||
#include <map>
|
||||
|
||||
namespace cv {
|
||||
namespace ccm {
|
||||
|
||||
enum IllumObserverType
|
||||
{
|
||||
A_2,
|
||||
A_10,
|
||||
D50_2,
|
||||
D50_10,
|
||||
D55_2,
|
||||
D55_10,
|
||||
D65_2,
|
||||
D65_10,
|
||||
D75_2,
|
||||
D75_10,
|
||||
E_2,
|
||||
E_10
|
||||
};
|
||||
|
||||
/** @brief IllumObserver is the meaning of illuminant and observer. See notes of ccm.hpp
|
||||
for supported list for illuminant and observer*/
|
||||
class IllumObserver
|
||||
{
|
||||
public:
|
||||
std::string illuminant;
|
||||
std::string observer;
|
||||
IllumObserver() {};
|
||||
IllumObserver(std::string illuminant, std::string observer);
|
||||
virtual ~IllumObserver() {};
|
||||
bool operator<(const IllumObserver& other) const;
|
||||
bool operator==(const IllumObserver& other) const;
|
||||
static IllumObserver getIllumObservers(IllumObserverType illumobserver);
|
||||
};
|
||||
std::vector<double> xyY2XYZ(const std::vector<double>& xyY);
|
||||
|
||||
}
|
||||
} // namespace cv::ccm
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,284 @@
|
||||
// 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.
|
||||
//
|
||||
// Author: Longbu Wang <wanglongbu@huawei.com.com>
|
||||
// Jinheng Zhang <zhangjinheng1@huawei.com>
|
||||
// Chenqi Shan <shanchenqi@huawei.com>
|
||||
|
||||
#include "linearize.hpp"
|
||||
|
||||
namespace cv {
|
||||
namespace ccm {
|
||||
|
||||
Polyfit::Polyfit() : deg(0) {}
|
||||
|
||||
void Polyfit::write(cv::FileStorage& fs) const {
|
||||
fs << "{" << "deg" << deg << "p" << p << "}";
|
||||
}
|
||||
|
||||
void Polyfit::read(const cv::FileNode& node) {
|
||||
node["deg"] >> deg;
|
||||
node["p"] >> p;
|
||||
}
|
||||
|
||||
// Global functions to support FileStorage for Polyfit
|
||||
void write(cv::FileStorage& fs, const std::string&, const Polyfit& polyfit) {
|
||||
polyfit.write(fs);
|
||||
}
|
||||
void read(const cv::FileNode& node, Polyfit& polyfit, const Polyfit& defaultValue) {
|
||||
if(node.empty())
|
||||
polyfit = defaultValue;
|
||||
else
|
||||
polyfit.read(node);
|
||||
}
|
||||
|
||||
Polyfit::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);
|
||||
Mat_<double> A = Mat_<double>::ones(n, deg + 1);
|
||||
for (int i = 0; i < n; ++i)
|
||||
{
|
||||
for (int j = 1; j < A.cols; ++j)
|
||||
{
|
||||
A.at<double>(i, j) = x.at<double>(i) * A.at<double>(i, j - 1);
|
||||
}
|
||||
}
|
||||
Mat y_(y);
|
||||
cv::solve(A, y_, p, DECOMP_SVD);
|
||||
}
|
||||
|
||||
Mat Polyfit::operator()(const Mat& inp)
|
||||
{
|
||||
return elementWise(inp, [this](double x) -> double { return fromEW(x); });
|
||||
};
|
||||
|
||||
double Polyfit::fromEW(double x)
|
||||
{
|
||||
double res = 0;
|
||||
for (int d = 0; d <= deg; ++d)
|
||||
{
|
||||
res += pow(x, d) * p.at<double>(d, 0);
|
||||
}
|
||||
return res;
|
||||
};
|
||||
|
||||
// Default constructor for LogPolyfit
|
||||
LogPolyfit::LogPolyfit() : deg(0) {}
|
||||
|
||||
void LogPolyfit::write(cv::FileStorage& fs) const {
|
||||
fs << "{" << "deg" << deg << "p" << p << "}";
|
||||
}
|
||||
|
||||
void LogPolyfit::read(const cv::FileNode& node) {
|
||||
node["deg"] >> deg;
|
||||
node["p"] >> p;
|
||||
}
|
||||
|
||||
// Global functions to support FileStorage for LogPolyfit
|
||||
void write(cv::FileStorage& fs, const std::string&, const LogPolyfit& logpolyfit) {
|
||||
logpolyfit.write(fs);
|
||||
}
|
||||
void read(const cv::FileNode& node, LogPolyfit& logpolyfit, const LogPolyfit& defaultValue) {
|
||||
if(node.empty())
|
||||
logpolyfit = defaultValue;
|
||||
else
|
||||
logpolyfit.read(node);
|
||||
}
|
||||
|
||||
LogPolyfit::LogPolyfit(Mat x, Mat y, int deg_)
|
||||
: deg(deg_)
|
||||
{
|
||||
Mat mask_ = (x > 0) & (y > 0);
|
||||
Mat src_, dst_, s_, d_;
|
||||
src_ = maskCopyTo(x, mask_);
|
||||
dst_ = maskCopyTo(y, mask_);
|
||||
log(src_, s_);
|
||||
log(dst_, d_);
|
||||
p = Polyfit(s_, d_, deg);
|
||||
}
|
||||
|
||||
Mat LogPolyfit::operator()(const Mat& inp)
|
||||
{
|
||||
Mat mask_ = inp >= 0;
|
||||
Mat y, y_, res;
|
||||
log(inp, y);
|
||||
y = p(y);
|
||||
exp(y, y_);
|
||||
y_.copyTo(res, mask_);
|
||||
return res;
|
||||
};
|
||||
|
||||
void LinearIdentity::write(cv::FileStorage& fs) const
|
||||
{
|
||||
fs << "{" << "}";
|
||||
}
|
||||
|
||||
void LinearIdentity::read(const cv::FileNode&)
|
||||
{
|
||||
}
|
||||
|
||||
void LinearGamma::write(cv::FileStorage& fs) const
|
||||
{
|
||||
fs << "{" << "gamma" << gamma << "}";
|
||||
}
|
||||
|
||||
void LinearGamma::read(const cv::FileNode& node)
|
||||
{
|
||||
node["gamma"] >> gamma;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void LinearColor<T>::write(cv::FileStorage& fs) const
|
||||
{
|
||||
fs << "{" << "deg" << deg << "pr" << pr << "pg" << pg << "pb" << pb << "}";
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void LinearColor<T>::read(const cv::FileNode& node)
|
||||
{
|
||||
node["deg"] >> deg;
|
||||
node["pr"] >> pr;
|
||||
node["pg"] >> pg;
|
||||
node["pb"] >> pb;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void LinearGray<T>::write(cv::FileStorage& fs) const
|
||||
{
|
||||
fs << "{" << "deg" << deg << "p" << p << "}";
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void LinearGray<T>::read(const cv::FileNode& node)
|
||||
{
|
||||
node["deg"] >> deg;
|
||||
node["p"] >> p;
|
||||
}
|
||||
|
||||
void Linear::write(cv::FileStorage&) const
|
||||
{
|
||||
CV_Error(Error::StsNotImplemented, "This is a base class, so this shouldn't be called");
|
||||
}
|
||||
|
||||
void Linear::read(const cv::FileNode&)
|
||||
{
|
||||
CV_Error(Error::StsNotImplemented, "This is a base class, so this shouldn't be called");
|
||||
}
|
||||
|
||||
void write(cv::FileStorage& fs, const std::string&, const Linear& linear)
|
||||
{
|
||||
linear.write(fs);
|
||||
}
|
||||
|
||||
void read(const cv::FileNode& node, Linear& linear, const Linear& defaultValue)
|
||||
{
|
||||
if (node.empty())
|
||||
linear = defaultValue;
|
||||
else
|
||||
linear.read(node);
|
||||
}
|
||||
|
||||
void write(cv::FileStorage& fs, const std::string&, const LinearIdentity& linearidentity)
|
||||
{
|
||||
linearidentity.write(fs);
|
||||
}
|
||||
|
||||
void read(const cv::FileNode& node, LinearIdentity& linearidentity, const LinearIdentity& defaultValue)
|
||||
{
|
||||
if (node.empty())
|
||||
linearidentity = defaultValue;
|
||||
else
|
||||
linearidentity.read(node);
|
||||
}
|
||||
|
||||
void write(cv::FileStorage& fs, const std::string&, const LinearGamma& lineargamma)
|
||||
{
|
||||
lineargamma.write(fs);
|
||||
}
|
||||
|
||||
void read(const cv::FileNode& node, LinearGamma& lineargamma, const LinearGamma& defaultValue)
|
||||
{
|
||||
if (node.empty())
|
||||
lineargamma = defaultValue;
|
||||
else
|
||||
lineargamma.read(node);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void write(cv::FileStorage& fs, const std::string&, const LinearColor<T>& linearcolor)
|
||||
{
|
||||
linearcolor.write(fs);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void read(const cv::FileNode& node, LinearColor<T>& linearcolor, const LinearColor<T>& defaultValue)
|
||||
{
|
||||
if (node.empty())
|
||||
linearcolor = defaultValue;
|
||||
else
|
||||
linearcolor.read(node);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void write(cv::FileStorage& fs, const std::string&, const LinearGray<T>& lineargray)
|
||||
{
|
||||
lineargray.write(fs);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void read(const cv::FileNode& node, LinearGray<T>& lineargray, const LinearGray<T>& defaultValue)
|
||||
{
|
||||
if (node.empty())
|
||||
lineargray = defaultValue;
|
||||
else
|
||||
lineargray.read(node);
|
||||
}
|
||||
|
||||
Mat Linear::linearize(Mat inp)
|
||||
{
|
||||
return inp;
|
||||
};
|
||||
|
||||
Mat LinearGamma::linearize(Mat inp)
|
||||
{
|
||||
Mat out;
|
||||
gammaCorrection(inp, out, gamma);
|
||||
return out;
|
||||
};
|
||||
|
||||
std::shared_ptr<Linear> getLinear(double gamma, int deg, Mat src, Color dst, Mat mask, RGBBase_ cs, LinearizationType linearizationType)
|
||||
{
|
||||
std::shared_ptr<Linear> p = std::make_shared<Linear>();
|
||||
switch (linearizationType)
|
||||
{
|
||||
case cv::ccm::LINEARIZATION_IDENTITY:
|
||||
p = std::make_shared<LinearIdentity>();
|
||||
break;
|
||||
case cv::ccm::LINEARIZATION_GAMMA:
|
||||
p = std::make_shared<LinearGamma>(gamma);
|
||||
break;
|
||||
case cv::ccm::LINEARIZATION_COLORPOLYFIT:
|
||||
p = std::make_shared<LinearColor<Polyfit>>(deg, src, dst, mask, cs);
|
||||
break;
|
||||
case cv::ccm::LINEARIZATION_COLORLOGPOLYFIT:
|
||||
p = std::make_shared<LinearColor<LogPolyfit>>(deg, src, dst, mask, cs);
|
||||
break;
|
||||
case cv::ccm::LINEARIZATION_GRAYPOLYFIT:
|
||||
p = std::make_shared<LinearGray<Polyfit>>(deg, src, dst, mask, cs);
|
||||
break;
|
||||
case cv::ccm::LINEARIZATION_GRAYLOGPOLYFIT:
|
||||
p = std::make_shared<LinearGray<LogPolyfit>>(deg, src, dst, mask, cs);
|
||||
break;
|
||||
default:
|
||||
CV_Error(Error::StsBadArg, "Wrong linearizationType!" );
|
||||
break;
|
||||
}
|
||||
return p;
|
||||
};
|
||||
|
||||
}
|
||||
} // namespace cv::ccm
|
||||
@@ -0,0 +1,260 @@
|
||||
// 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.
|
||||
//
|
||||
// Author: Longbu Wang <wanglongbu@huawei.com.com>
|
||||
// Jinheng Zhang <zhangjinheng1@huawei.com>
|
||||
// Chenqi Shan <shanchenqi@huawei.com>
|
||||
|
||||
#ifndef __OPENCV_CCM_LINEARIZE_HPP__
|
||||
#define __OPENCV_CCM_LINEARIZE_HPP__
|
||||
|
||||
#include <opencv2/core.hpp>
|
||||
#include <map>
|
||||
#include "color.hpp"
|
||||
#include "opencv2/photo.hpp"
|
||||
namespace cv {
|
||||
namespace ccm {
|
||||
|
||||
/** @brief Polyfit model.
|
||||
*/
|
||||
class Polyfit
|
||||
{
|
||||
public:
|
||||
int deg;
|
||||
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
|
||||
*/
|
||||
Polyfit(Mat x, Mat y, int deg);
|
||||
virtual ~Polyfit() {};
|
||||
Mat operator()(const Mat& inp);
|
||||
|
||||
// Serialization support
|
||||
void write(cv::FileStorage& fs) const;
|
||||
void read(const cv::FileNode& node);
|
||||
|
||||
private:
|
||||
double fromEW(double x);
|
||||
};
|
||||
|
||||
// Global functions for FileStorage for Polyfit
|
||||
void write(cv::FileStorage& fs, const std::string&, const Polyfit& polyfit);
|
||||
void read(const cv::FileNode& node, Polyfit& polyfit, const Polyfit& defaultValue = Polyfit());
|
||||
|
||||
/** @brief Logpolyfit model.
|
||||
*/
|
||||
class LogPolyfit
|
||||
{
|
||||
public:
|
||||
int deg;
|
||||
Polyfit p;
|
||||
|
||||
LogPolyfit();
|
||||
|
||||
/** @brief Logpolyfit method.
|
||||
*/
|
||||
LogPolyfit(Mat x, Mat y, int deg);
|
||||
virtual ~LogPolyfit() {};
|
||||
Mat operator()(const Mat& inp);
|
||||
|
||||
// Serialization support
|
||||
void write(cv::FileStorage& fs) const;
|
||||
void read(const cv::FileNode& node);
|
||||
};
|
||||
|
||||
// Global functions for FileStorage for LogPolyfit
|
||||
void write(cv::FileStorage& fs, const std::string&, const LogPolyfit& logpolyfit);
|
||||
void read(const cv::FileNode& node, LogPolyfit& logpolyfit, const LogPolyfit& defaultValue = LogPolyfit());
|
||||
|
||||
/** @brief Linearization base.
|
||||
*/
|
||||
|
||||
class Linear
|
||||
{
|
||||
public:
|
||||
Linear() {};
|
||||
virtual ~Linear() {};
|
||||
|
||||
/** @brief Inference.
|
||||
@param inp the input array, type of cv::Mat.
|
||||
*/
|
||||
virtual Mat linearize(Mat inp);
|
||||
/** @brief Evaluate linearization model.
|
||||
*/
|
||||
virtual void value(void) {};
|
||||
|
||||
// Serialization support
|
||||
virtual void write(cv::FileStorage& fs) const;
|
||||
virtual void read(const cv::FileNode& node);
|
||||
};
|
||||
|
||||
// Global functions for FileStorage for Linear
|
||||
void write(cv::FileStorage& fs, const std::string&, const Linear& linear);
|
||||
void read(const cv::FileNode& node, Linear& linear, const Linear& defaultValue = Linear());
|
||||
|
||||
/** @brief Linearization identity.
|
||||
make no change.
|
||||
*/
|
||||
class LinearIdentity : public Linear
|
||||
{
|
||||
public:
|
||||
void write(cv::FileStorage& fs) const CV_OVERRIDE;
|
||||
void read(const cv::FileNode& node) CV_OVERRIDE;
|
||||
};
|
||||
|
||||
// Global functions for FileStorage for LinearIdentity
|
||||
void write(cv::FileStorage& fs, const std::string&, const LinearIdentity& linearidentity);
|
||||
void read(const cv::FileNode& node, LinearIdentity& linearidentity, const LinearIdentity& defaultValue = LinearIdentity());
|
||||
|
||||
/** @brief Linearization gamma correction.
|
||||
*/
|
||||
class LinearGamma : public Linear
|
||||
{
|
||||
public:
|
||||
double gamma;
|
||||
|
||||
LinearGamma()
|
||||
: gamma(1.0) {};
|
||||
|
||||
LinearGamma(double gamma_)
|
||||
: gamma(gamma_) {};
|
||||
|
||||
Mat linearize(Mat inp) CV_OVERRIDE;
|
||||
|
||||
// Serialization support
|
||||
void write(cv::FileStorage& fs) const CV_OVERRIDE;
|
||||
void read(const cv::FileNode& node) CV_OVERRIDE;
|
||||
};
|
||||
|
||||
// Global functions for FileStorage for LinearGamma
|
||||
void write(cv::FileStorage& fs, const std::string&, const LinearGamma& lineargamma);
|
||||
void read(const cv::FileNode& node, LinearGamma& lineargamma, const LinearGamma& defaultValue = LinearGamma());
|
||||
|
||||
/** @brief Linearization.
|
||||
Grayscale polynomial fitting.
|
||||
*/
|
||||
template <class T>
|
||||
class LinearGray : public Linear
|
||||
{
|
||||
public:
|
||||
int deg;
|
||||
T p;
|
||||
|
||||
LinearGray(): deg(3) {};
|
||||
|
||||
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.
|
||||
Mat gray;
|
||||
cvtColor(src, gray, COLOR_RGB2GRAY);
|
||||
gray.copyTo(src);
|
||||
|
||||
Mat dst_ = maskCopyTo(dst.toGray(cs.illumobserver), 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 Mat& src, const Mat& dst)
|
||||
{
|
||||
p = T(src, dst, deg);
|
||||
};
|
||||
|
||||
Mat linearize(Mat inp) CV_OVERRIDE
|
||||
{
|
||||
return p(inp);
|
||||
};
|
||||
|
||||
// Serialization support
|
||||
void write(cv::FileStorage& fs) const CV_OVERRIDE;
|
||||
void read(const cv::FileNode& node) CV_OVERRIDE;
|
||||
};
|
||||
|
||||
// Global functions for FileStorage for LinearGray
|
||||
template <typename T>
|
||||
void write(cv::FileStorage& fs, const std::string&, const LinearGray<T>& lineargray);
|
||||
template <typename T>
|
||||
void read(const cv::FileNode& node, LinearGray<T>& lineargray, const LinearGray<T>& defaultValue = LinearGray<T>());
|
||||
|
||||
/** @brief Linearization.
|
||||
Fitting channels respectively.
|
||||
*/
|
||||
template <class T>
|
||||
class LinearColor : public Linear
|
||||
{
|
||||
public:
|
||||
int deg;
|
||||
T pr;
|
||||
T pg;
|
||||
T pb;
|
||||
|
||||
LinearColor(): deg(3) {};
|
||||
|
||||
LinearColor(int deg_, Mat src_, Color dst, Mat mask, RGBBase_ cs)
|
||||
: deg(deg_)
|
||||
{
|
||||
Mat src = maskCopyTo(src_, mask);
|
||||
Mat dst_ = maskCopyTo(dst.to(*cs.l).colors, mask);
|
||||
calc(src, dst_);
|
||||
}
|
||||
|
||||
void calc(const Mat& src, const Mat& dst)
|
||||
{
|
||||
Mat schannels[3];
|
||||
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);
|
||||
};
|
||||
|
||||
Mat linearize(Mat inp) CV_OVERRIDE
|
||||
{
|
||||
Mat channels[3];
|
||||
split(inp, channels);
|
||||
std::vector<Mat> channel;
|
||||
Mat res;
|
||||
merge(std::vector<Mat> { pr(channels[0]), pg(channels[1]), pb(channels[2]) }, res);
|
||||
return res;
|
||||
};
|
||||
|
||||
// Serialization support
|
||||
void write(cv::FileStorage& fs) const CV_OVERRIDE;
|
||||
void read(const cv::FileNode& node) CV_OVERRIDE;
|
||||
};
|
||||
|
||||
// Global functions for FileStorage for LinearColor
|
||||
template <typename T>
|
||||
void write(cv::FileStorage& fs, const std::string&, const LinearColor<T>& linearcolor);
|
||||
template <typename T>
|
||||
void read(const cv::FileNode& node, LinearColor<T>& linearcolor, const LinearColor<T>& defaultValue = LinearColor<T>());
|
||||
|
||||
/** @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 linearizationType type of linear.
|
||||
*/
|
||||
|
||||
std::shared_ptr<Linear> getLinear(double gamma, int deg, Mat src, Color dst, Mat mask, RGBBase_ cs, LinearizationType linearizationType);
|
||||
|
||||
}
|
||||
} // namespace cv::ccm
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,71 @@
|
||||
// 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.
|
||||
//
|
||||
// Author: Longbu Wang <wanglongbu@huawei.com.com>
|
||||
// Jinheng Zhang <zhangjinheng1@huawei.com>
|
||||
// Chenqi Shan <shanchenqi@huawei.com>
|
||||
|
||||
#include "operations.hpp"
|
||||
#include "utils.hpp"
|
||||
namespace cv {
|
||||
namespace ccm {
|
||||
|
||||
Mat Operation::operator()(Mat& abc)
|
||||
{
|
||||
if (!linear)
|
||||
{
|
||||
return f(abc);
|
||||
}
|
||||
if (M.empty())
|
||||
{
|
||||
return abc;
|
||||
}
|
||||
return multiple(abc, M);
|
||||
};
|
||||
|
||||
void Operation::add(const Operation& other)
|
||||
{
|
||||
if (M.empty())
|
||||
{
|
||||
M = other.M.clone();
|
||||
}
|
||||
else
|
||||
{
|
||||
M = M * other.M;
|
||||
}
|
||||
};
|
||||
|
||||
void Operation::clear()
|
||||
{
|
||||
M = Mat();
|
||||
};
|
||||
|
||||
Operations& Operations::add(const Operations& other)
|
||||
{
|
||||
ops.insert(ops.end(), other.ops.begin(), other.ops.end());
|
||||
return *this;
|
||||
};
|
||||
|
||||
Mat Operations::run(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;
|
||||
}
|
||||
|
||||
}
|
||||
} // namespace cv::ccm
|
||||
@@ -0,0 +1,83 @@
|
||||
// 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.
|
||||
//
|
||||
// Author: Longbu Wang <wanglongbu@huawei.com.com>
|
||||
// Jinheng Zhang <zhangjinheng1@huawei.com>
|
||||
// Chenqi Shan <shanchenqi@huawei.com>
|
||||
|
||||
#ifndef __OPENCV_CCM_OPERATIONS_HPP__
|
||||
#define __OPENCV_CCM_OPERATIONS_HPP__
|
||||
|
||||
#include "utils.hpp"
|
||||
|
||||
namespace cv {
|
||||
namespace ccm {
|
||||
|
||||
/** @brief Operation class contains some operarions used for color space
|
||||
conversion containing linear transformation and non-linear transformation
|
||||
*/
|
||||
class Operation
|
||||
{
|
||||
public:
|
||||
typedef std::function<Mat(Mat)> MatFunc;
|
||||
bool linear;
|
||||
Mat M;
|
||||
MatFunc f;
|
||||
|
||||
Operation()
|
||||
: linear(true)
|
||||
, M(Mat()) {};
|
||||
Operation(Mat M_)
|
||||
: linear(true)
|
||||
, M(M_) {};
|
||||
Operation(MatFunc f_)
|
||||
: linear(false)
|
||||
, f(f_) {};
|
||||
virtual ~Operation() {};
|
||||
|
||||
/** @brief operator function will run operation
|
||||
*/
|
||||
Mat operator()(Mat& abc);
|
||||
|
||||
/** @brief add function will conbine this operation
|
||||
with other linear transformation operation
|
||||
*/
|
||||
void add(const Operation& other);
|
||||
|
||||
void clear();
|
||||
static Operation& getIdentityOp()
|
||||
{
|
||||
static Operation identity_op([](Mat x) { return x; });
|
||||
return identity_op;
|
||||
}
|
||||
};
|
||||
|
||||
class Operations
|
||||
{
|
||||
public:
|
||||
std::vector<Operation> ops;
|
||||
Operations()
|
||||
: ops {} {};
|
||||
Operations(std::initializer_list<Operation> op)
|
||||
: ops { op } {};
|
||||
virtual ~Operations() {};
|
||||
|
||||
/** @brief add function will conbine this operation with other transformation operations
|
||||
*/
|
||||
Operations& add(const Operations& other);
|
||||
|
||||
/** @brief run operations to make color conversion
|
||||
*/
|
||||
Mat run(Mat abc);
|
||||
static const Operations& getIdentityOps()
|
||||
{
|
||||
static Operations Operation_op {Operation::getIdentityOp()};
|
||||
return Operation_op;
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
} // namespace cv::ccm
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,113 @@
|
||||
// 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.
|
||||
//
|
||||
// Author: Longbu Wang <wanglongbu@huawei.com.com>
|
||||
// Jinheng Zhang <zhangjinheng1@huawei.com>
|
||||
// Chenqi Shan <shanchenqi@huawei.com>
|
||||
|
||||
#include "utils.hpp"
|
||||
|
||||
namespace cv {
|
||||
namespace ccm {
|
||||
|
||||
void gammaCorrection(InputArray _src, OutputArray _dst, double gamma)
|
||||
{
|
||||
Mat src = _src.getMat();
|
||||
CV_Assert(gamma > 0);
|
||||
|
||||
double maxVal;
|
||||
int depth = src.depth();
|
||||
switch (depth)
|
||||
{
|
||||
case CV_8U: maxVal = 255.0; break;
|
||||
case CV_16U: maxVal = 65535.0; break;
|
||||
case CV_16S: maxVal = 32767.0; break;
|
||||
case CV_32F: maxVal = 1.0; break;
|
||||
case CV_64F: maxVal = 1.0; break;
|
||||
default:
|
||||
CV_Error(Error::StsUnsupportedFormat,
|
||||
"gammaCorrection: unsupported image depth");
|
||||
}
|
||||
|
||||
// Special‐case for uint8 with a LUT
|
||||
if (depth == CV_8U)
|
||||
{
|
||||
Mat lut(1, 256, CV_8U);
|
||||
uchar* p = lut.ptr<uchar>();
|
||||
for (int i = 0; i < 256; ++i)
|
||||
{
|
||||
double fn = std::pow(i / 255.0, gamma) * 255.0;
|
||||
p[i] = cv::saturate_cast<uchar>(fn + 0.5);
|
||||
}
|
||||
_dst.create(src.size(), src.type());
|
||||
Mat dst = _dst.getMat();
|
||||
cv::LUT(src, lut, dst);
|
||||
return;
|
||||
}
|
||||
|
||||
Mat f;
|
||||
src.convertTo(f, CV_64F, 1.0 / maxVal);
|
||||
cv::pow(f, gamma, f);
|
||||
|
||||
_dst.create(src.size(), src.type());
|
||||
Mat dst = _dst.getMat();
|
||||
f.convertTo(dst, src.type(), maxVal);
|
||||
}
|
||||
|
||||
|
||||
Mat maskCopyTo(const Mat& src, const Mat& mask)
|
||||
{
|
||||
Mat fullMasked;
|
||||
src.copyTo(fullMasked, mask);
|
||||
|
||||
std::vector<Point> nonZeroLocations;
|
||||
findNonZero(mask, nonZeroLocations);
|
||||
|
||||
Mat dst(static_cast<int>(nonZeroLocations.size()), 1, src.type());
|
||||
|
||||
int channels = src.channels();
|
||||
if (channels == 1)
|
||||
{
|
||||
for (size_t i = 0; i < nonZeroLocations.size(); i++)
|
||||
{
|
||||
dst.at<double>(static_cast<int>(i), 0) = fullMasked.at<double>(nonZeroLocations[i]);
|
||||
}
|
||||
}
|
||||
else if (channels == 3)
|
||||
{
|
||||
for (size_t i = 0; i < nonZeroLocations.size(); i++)
|
||||
{
|
||||
dst.at<Vec3d>(static_cast<int>(i), 0) = fullMasked.at<Vec3d>(nonZeroLocations[i]);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
CV_Error(Error::StsBadArg, "Unsupported number of channels");
|
||||
}
|
||||
|
||||
return dst;
|
||||
}
|
||||
|
||||
Mat multiple(const Mat& xyz, const Mat& ccm)
|
||||
{
|
||||
Mat tmp = xyz.reshape(1, xyz.rows * xyz.cols);
|
||||
Mat res = tmp * ccm;
|
||||
res = res.reshape(res.cols, xyz.rows);
|
||||
return res;
|
||||
}
|
||||
|
||||
Mat saturate(Mat& src, double low, double up)
|
||||
{
|
||||
CV_Assert(src.type() == CV_64FC3);
|
||||
Scalar lower_bound(low, low, low);
|
||||
Scalar upper_bound(up, up, up);
|
||||
|
||||
Mat mask;
|
||||
inRange(src, lower_bound, upper_bound, mask);
|
||||
mask /= 255;
|
||||
|
||||
return mask;
|
||||
}
|
||||
}
|
||||
} // namespace cv::ccm
|
||||
@@ -0,0 +1,145 @@
|
||||
// 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.
|
||||
//
|
||||
// Author: Longbu Wang <wanglongbu@huawei.com.com>
|
||||
// Jinheng Zhang <zhangjinheng1@huawei.com>
|
||||
// Chenqi Shan <shanchenqi@huawei.com>
|
||||
|
||||
#ifndef __OPENCV_CCM_UTILS_HPP__
|
||||
#define __OPENCV_CCM_UTILS_HPP__
|
||||
|
||||
#include <opencv2/core.hpp>
|
||||
#include <opencv2/imgproc.hpp>
|
||||
|
||||
namespace cv {
|
||||
namespace ccm {
|
||||
/** @brief gamma correction.
|
||||
\f[
|
||||
C_l=C_n^{\gamma},\qquad C_n\ge0\\
|
||||
C_l=-(-C_n)^{\gamma},\qquad C_n<0\\\\
|
||||
\f]
|
||||
@param src the input array,type of Mat.
|
||||
@param gamma a constant for gamma correction greater than zero.
|
||||
@param dst the output array, type of Mat.
|
||||
*/
|
||||
CV_EXPORTS_W void gammaCorrection(InputArray src, OutputArray dst, double gamma);
|
||||
|
||||
/** @brief maskCopyTo a function to delete unsatisfied elementwise.
|
||||
@param src the input array, type of Mat.
|
||||
@param mask operation mask that used to choose satisfided elementwise.
|
||||
*/
|
||||
Mat maskCopyTo(const Mat& src, const Mat& mask);
|
||||
|
||||
/** @brief multiple the function used to compute an array with n channels
|
||||
mulipied by ccm.
|
||||
@param xyz the input array, type of Mat.
|
||||
@param ccm the ccm matrix to make color correction.
|
||||
*/
|
||||
Mat multiple(const Mat& xyz, const Mat& ccm);
|
||||
|
||||
/** @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 Mat.
|
||||
@param low the threshold to choose saturated colors
|
||||
@param up the threshold to choose saturated colors
|
||||
*/
|
||||
Mat saturate(Mat& src, double low, double up);
|
||||
|
||||
/** @brief function for elementWise operation
|
||||
@param src the input array, type of Mat
|
||||
@param lambda a for operation
|
||||
*/
|
||||
template <typename F>
|
||||
Mat elementWise(const Mat& src, F&& lambda, Mat dst=Mat())
|
||||
{
|
||||
if (dst.empty() || !dst.isContinuous() || dst.total() != src.total() || dst.type() != src.type())
|
||||
dst = Mat(src.rows, src.cols, src.type());
|
||||
const int channel = src.channels();
|
||||
if (src.isContinuous()) {
|
||||
const int num_elements = (int)src.total()*channel;
|
||||
const double *psrc = (double*)src.data;
|
||||
double *pdst = (double*)dst.data;
|
||||
const int batch = getNumThreads() > 1 ? 128 : num_elements;
|
||||
const int N = (num_elements / batch) + ((num_elements % batch) > 0);
|
||||
parallel_for_(Range(0, N),[&](const Range& range) {
|
||||
const int start = range.start * batch;
|
||||
const int end = std::min(range.end*batch, num_elements);
|
||||
for (int i = start; i < end; i++) {
|
||||
pdst[i] = lambda(psrc[i]);
|
||||
}
|
||||
});
|
||||
return dst;
|
||||
}
|
||||
switch (channel)
|
||||
{
|
||||
case 1:
|
||||
{
|
||||
|
||||
MatIterator_<double> it, end;
|
||||
for (it = dst.begin<double>(), end = dst.end<double>(); it != end; ++it)
|
||||
{
|
||||
(*it) = lambda((*it));
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 3:
|
||||
{
|
||||
MatIterator_<Vec3d> it, end;
|
||||
for (it = dst.begin<Vec3d>(), end = dst.end<Vec3d>(); it != end; ++it)
|
||||
{
|
||||
for (int j = 0; j < 3; j++)
|
||||
{
|
||||
(*it)[j] = lambda((*it)[j]);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
CV_Error(Error::StsBadArg, "Wrong channel!" );
|
||||
break;
|
||||
}
|
||||
return dst;
|
||||
}
|
||||
|
||||
/** @brief function for channel operation
|
||||
@param src the input array, type of Mat
|
||||
@param lambda the function for operation
|
||||
*/
|
||||
template <typename F>
|
||||
Mat channelWise(const Mat& src, F&& lambda)
|
||||
{
|
||||
Mat dst = src.clone();
|
||||
MatIterator_<Vec3d> it, end;
|
||||
for (it = dst.begin<Vec3d>(), end = dst.end<Vec3d>(); it != end; ++it)
|
||||
{
|
||||
*it = lambda(*it);
|
||||
}
|
||||
return dst;
|
||||
}
|
||||
|
||||
/** @brief function for distance operation.
|
||||
@param src the input array, type of Mat.
|
||||
@param ref another input array, type of Mat.
|
||||
@param lambda the computing method for distance .
|
||||
*/
|
||||
template <typename F>
|
||||
Mat distanceWise(Mat& src, Mat& ref, F&& lambda)
|
||||
{
|
||||
Mat dst = Mat(src.size(), CV_64FC1);
|
||||
MatIterator_<Vec3d> it_src = src.begin<Vec3d>(), end_src = src.end<Vec3d>(),
|
||||
it_ref = ref.begin<Vec3d>();
|
||||
MatIterator_<double> it_dst = dst.begin<double>();
|
||||
for (; it_src != end_src; ++it_src, ++it_ref, ++it_dst)
|
||||
{
|
||||
*it_dst = lambda(*it_src, *it_ref);
|
||||
}
|
||||
return dst;
|
||||
}
|
||||
|
||||
Mat multiple(const Mat& xyz, const Mat& ccm);
|
||||
|
||||
}
|
||||
} // namespace cv::ccm
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,302 @@
|
||||
// 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_<Vec3d>(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(Photo_ColorCorrection, test_model)
|
||||
{
|
||||
cv::ccm::ColorCorrectionModel model(s / 255, cv::ccm::COLORCHECKER_MACBETH);
|
||||
Mat colorCorrectionMat = model.compute();
|
||||
Mat srcRgbl = (Mat_<Vec3d>(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));
|
||||
EXPECT_MAT_NEAR(srcRgbl, model.getSrcLinearRGB(), 1e-4);
|
||||
|
||||
Mat dstRgbl = (Mat_<Vec3d>(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));
|
||||
EXPECT_MAT_NEAR(dstRgbl, model.getRefLinearRGB(), 1e-4);
|
||||
|
||||
Mat mask = Mat::ones(24, 1, CV_8U);
|
||||
EXPECT_MAT_NEAR(model.getMask(), mask, 0.0);
|
||||
|
||||
Mat refColorMat = (Mat_<double>(3, 3) <<
|
||||
0.37406520, 0.02066507, 0.05804047,
|
||||
0.12719672, 0.77389268, -0.01569404,
|
||||
-0.27627010, 0.00603427, 2.74272981);
|
||||
EXPECT_MAT_NEAR(colorCorrectionMat, refColorMat, 1e-4);
|
||||
}
|
||||
|
||||
TEST(Photo_ColorCorrection, test_model_with_color_patches_mask)
|
||||
{
|
||||
Mat dstData = (Mat_<Vec3d>(24, 1) <<
|
||||
Vec3d(37.986, 13.555, 14.059),
|
||||
Vec3d(65.711, 18.13, 17.81),
|
||||
Vec3d(49.927, -4.88, -21.925),
|
||||
Vec3d(43.139, -13.095, 21.905),
|
||||
Vec3d(55.112, 8.843999999999999, -25.399),
|
||||
Vec3d(70.71899999999999, -33.397, -0.199),
|
||||
Vec3d(62.661, 36.067, 57.096),
|
||||
Vec3d(40.02, 10.41, -45.964),
|
||||
Vec3d(51.124, 48.239, 16.248),
|
||||
Vec3d(30.325, 22.976, -21.587),
|
||||
Vec3d(72.532, -23.709, 57.255),
|
||||
Vec3d(71.941, 19.363, 67.857),
|
||||
Vec3d(28.778, 14.179, -50.297),
|
||||
Vec3d(55.261, -38.342, 31.37),
|
||||
Vec3d(42.101, 53.378, 28.19),
|
||||
Vec3d(81.733, 4.039, 79.819),
|
||||
Vec3d(51.935, 49.986, -14.574),
|
||||
Vec3d(51.038, -28.631, -28.638),
|
||||
Vec3d(96.539, -0.425, 1.186),
|
||||
Vec3d(81.25700000000001, -0.638, -0.335),
|
||||
Vec3d(66.76600000000001, -0.734, -0.504),
|
||||
Vec3d(50.867, -0.153, -0.27),
|
||||
Vec3d(35.656, -0.421, -1.231),
|
||||
Vec3d(20.461, -0.079, -0.973)
|
||||
);
|
||||
|
||||
Mat coloredMask = (Mat_<uchar>(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);
|
||||
|
||||
cv::ccm::ColorCorrectionModel model(s/255, dstData, cv::ccm::COLOR_SPACE_LAB_D50_2, coloredMask);
|
||||
Mat colorCorrectionMat = model.compute();
|
||||
|
||||
Mat refColorMat = (Mat_<double>(3, 3) <<
|
||||
0.37406520, 0.02066507, 0.05804047,
|
||||
0.12719672, 0.77389268, -0.01569404,
|
||||
-0.27627010, 0.00603427, 2.74272981);
|
||||
EXPECT_MAT_NEAR(colorCorrectionMat, refColorMat, 1e-4);
|
||||
}
|
||||
|
||||
TEST(Photo_ColorCorrection, test_masks_weights_1)
|
||||
{
|
||||
Mat weightsList_ = (Mat_<double>(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);
|
||||
cv::ccm::ColorCorrectionModel model1(s / 255,cv::ccm::COLORCHECKER_MACBETH);
|
||||
model1.setColorSpace(cv::ccm::COLOR_SPACE_SRGB);
|
||||
model1.setCcmType(cv::ccm::CCM_LINEAR);
|
||||
model1.setDistance(cv::ccm::DISTANCE_CIE2000);
|
||||
model1.setLinearization(cv::ccm::LINEARIZATION_GAMMA);
|
||||
model1.setLinearizationGamma(2.2);
|
||||
model1.setLinearizationDegree(3);
|
||||
model1.setSaturatedThreshold(0, 0.98);
|
||||
model1.setWeightsList(weightsList_);
|
||||
model1.setWeightCoeff(1.5);
|
||||
Mat colorCorrectionMat = model1.compute();
|
||||
Mat weights = (Mat_<double>(8, 1) <<
|
||||
1.15789474, 1.26315789, 1.36842105, 1.47368421,
|
||||
0.52631579, 0.63157895, 0.73684211, 0.84210526);
|
||||
EXPECT_MAT_NEAR(model1.getWeights(), weights, 1e-4);
|
||||
|
||||
Mat mask = (Mat_<uchar>(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);
|
||||
EXPECT_MAT_NEAR(model1.getMask(), mask, 0.0);
|
||||
}
|
||||
|
||||
TEST(Photo_ColorCorrection, test_masks_weights_2)
|
||||
{
|
||||
cv::ccm::ColorCorrectionModel model2(s / 255, cv::ccm::COLORCHECKER_MACBETH);
|
||||
model2.setCcmType(cv::ccm::CCM_LINEAR);
|
||||
model2.setDistance(cv::ccm::DISTANCE_CIE2000);
|
||||
model2.setLinearization(cv::ccm::LINEARIZATION_GAMMA);
|
||||
model2.setLinearizationGamma(2.2);
|
||||
model2.setLinearizationDegree(3);
|
||||
model2.setSaturatedThreshold(0.05, 0.93);
|
||||
model2.setWeightsList(Mat());
|
||||
model2.setWeightCoeff(1.5);
|
||||
Mat colorCorrectionMat = model2.compute();
|
||||
Mat weights = (Mat_<double>(20, 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);
|
||||
EXPECT_MAT_NEAR(model2.getWeights(), weights, 1e-4);
|
||||
|
||||
Mat mask = (Mat_<uchar>(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);
|
||||
EXPECT_MAT_NEAR(model2.getMask(), mask, 0.0);
|
||||
}
|
||||
|
||||
TEST(Photo_ColorCorrection, compute_color_correction_matrix)
|
||||
{
|
||||
// read gold chartsRGB
|
||||
string path = cvtest::findDataFile("mcc/mcc_ccm_test.yml");
|
||||
FileStorage fs(path, FileStorage::READ);
|
||||
Mat chartsRGB;
|
||||
FileNode node = fs["chartsRGB"];
|
||||
node >> chartsRGB;
|
||||
ASSERT_FALSE(chartsRGB.empty()) << "chartsRGB is empty after loading from: " << path;
|
||||
|
||||
// compute CCM
|
||||
cv::ccm::ColorCorrectionModel model(chartsRGB.col(1).clone().reshape(3, chartsRGB.rows/3) / 255., cv::ccm::COLORCHECKER_MACBETH);
|
||||
Mat colorCorrectionMat = model.compute();
|
||||
|
||||
// read gold CCM
|
||||
node = fs["ccm"];
|
||||
ASSERT_FALSE(node.empty());
|
||||
Mat gold_ccm;
|
||||
node >> gold_ccm;
|
||||
fs.release();
|
||||
|
||||
// check CCM
|
||||
EXPECT_MAT_NEAR(gold_ccm, colorCorrectionMat, 1e-8);
|
||||
|
||||
const double gold_loss = 4.6386569120323129;
|
||||
// check loss
|
||||
const double loss = model.getLoss();
|
||||
EXPECT_NEAR(gold_loss, loss, 1e-8);
|
||||
}
|
||||
|
||||
TEST(Photo_ColorCorrection, correct_image)
|
||||
{
|
||||
string path = cvtest::findDataFile("mcc/mcc_ccm_test.jpg");
|
||||
Mat img = imread(path, IMREAD_COLOR);
|
||||
// read gold calibrate img
|
||||
path = cvtest::findDataFile("mcc/mcc_ccm_test_res.png");
|
||||
Mat gold_img = imread(path);
|
||||
|
||||
// read gold chartsRGB
|
||||
path = cvtest::findDataFile("mcc/mcc_ccm_test.yml");
|
||||
FileStorage fs(path, FileStorage::READ);
|
||||
Mat chartsRGB;
|
||||
FileNode node = fs["chartsRGB"];
|
||||
node >> chartsRGB;
|
||||
fs.release();
|
||||
ASSERT_FALSE(chartsRGB.empty()) << "chartsRGB is empty after loading from: " << path;
|
||||
|
||||
// compute CCM
|
||||
cv::ccm::ColorCorrectionModel model(chartsRGB.col(1).clone().reshape(3, chartsRGB.rows/3) / 255., cv::ccm::COLORCHECKER_MACBETH);
|
||||
Mat colorCorrectionMat = model.compute();
|
||||
|
||||
// compute calibrate image
|
||||
Mat calibratedImage;
|
||||
model.correctImage(img, calibratedImage);
|
||||
// check calibrated image
|
||||
EXPECT_MAT_NEAR(gold_img, calibratedImage, 0.1);
|
||||
}
|
||||
|
||||
TEST(Photo_ColorCorrection, serialization)
|
||||
{
|
||||
auto path = cvtest::findDataFile("mcc/mcc_ccm_test.yml");
|
||||
FileStorage fs(path, FileStorage::READ);
|
||||
Mat chartsRGB;
|
||||
FileNode node = fs["chartsRGB"];
|
||||
node >> chartsRGB;
|
||||
fs.release();
|
||||
ASSERT_FALSE(chartsRGB.empty()) << "chartsRGB is empty after loading from: " << path;
|
||||
|
||||
// compute CCM
|
||||
cv::ccm::ColorCorrectionModel model(chartsRGB.col(1).clone().reshape(3, chartsRGB.rows/3) / 255., cv::ccm::COLORCHECKER_MACBETH);
|
||||
Mat colorCorrectionMat = model.compute();
|
||||
|
||||
//--- 1. write model to memory -------------------------------------------
|
||||
FileStorage fs1("", FileStorage::WRITE | FileStorage::MEMORY);
|
||||
model.write(fs1);
|
||||
std::string yaml1 = fs1.releaseAndGetString();
|
||||
|
||||
//--- 2. read model back from memory -------------------------------------
|
||||
cv::ccm::ColorCorrectionModel model1;
|
||||
FileStorage fs2(yaml1, FileStorage::READ | FileStorage::MEMORY);
|
||||
model1.read(fs2["ColorCorrectionModel"]);
|
||||
fs2.release();
|
||||
|
||||
//--- 3. write the re-loaded model again to memory -----------------------
|
||||
FileStorage fs3("", FileStorage::WRITE | FileStorage::MEMORY);
|
||||
model1.write(fs3);
|
||||
std::string yaml2 = fs3.releaseAndGetString();
|
||||
|
||||
//--- 4. compare the two YAML strings ------------------------------------
|
||||
EXPECT_EQ(yaml1, yaml2);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace opencv_test
|
||||
@@ -1,11 +1,15 @@
|
||||
// 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.
|
||||
#ifndef __OPENCV_TEST_PRECOMP_HPP__
|
||||
#define __OPENCV_TEST_PRECOMP_HPP__
|
||||
#ifndef OPENCV_PHOTO_TEST_PRECOMP_HPP
|
||||
#define OPENCV_PHOTO_TEST_PRECOMP_HPP
|
||||
|
||||
#include "opencv2/ts.hpp"
|
||||
#include "opencv2/ts/ocl_test.hpp"
|
||||
#include "opencv2/photo.hpp"
|
||||
|
||||
namespace opencv_test
|
||||
{
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,336 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
from __future__ import print_function
|
||||
|
||||
import numpy as np
|
||||
import cv2 as cv
|
||||
import tempfile
|
||||
|
||||
from tests_common import NewOpenCVTests
|
||||
|
||||
class photo_test(NewOpenCVTests):
|
||||
|
||||
def setUp(self):
|
||||
super(photo_test, self).setUp()
|
||||
self.image_cache = {}
|
||||
|
||||
def test_model(self):
|
||||
s = np.array([
|
||||
[214.11, 98.67, 37.97],
|
||||
[231.94, 153.1, 85.27],
|
||||
[204.08, 143.71, 78.46],
|
||||
[190.58, 122.99, 30.84],
|
||||
[230.93, 148.46, 100.84],
|
||||
[228.64, 206.97, 97.5],
|
||||
[229.09, 137.07, 55.29],
|
||||
[189.21, 111.22, 92.66],
|
||||
[223.5, 96.42, 75.45],
|
||||
[201.82, 69.71, 50.9],
|
||||
[240.52, 196.47, 59.3],
|
||||
[235.73, 172.13, 54.],
|
||||
[131.6, 75.04, 68.86],
|
||||
[189.04, 170.43, 42.05],
|
||||
[222.23, 74., 71.95],
|
||||
[241.01, 199.1, 61.15],
|
||||
[224.99, 101.4, 100.24],
|
||||
[174.58, 152.63, 91.52],
|
||||
[248.06, 227.69, 140.5],
|
||||
[241.15, 201.38, 115.58],
|
||||
[236.49, 175.87, 88.86],
|
||||
[212.19, 133.49, 54.79],
|
||||
[181.17, 102.94, 36.18],
|
||||
[115.1, 53.77, 15.23]
|
||||
], dtype=np.float64)
|
||||
|
||||
src = (s / 255.).astype(np.float64).reshape(-1, 1, 3)
|
||||
model = cv.ccm.ColorCorrectionModel(src, cv.ccm.COLORCHECKER_MACBETH)
|
||||
colorCorrectionMat = model.compute()
|
||||
|
||||
src_rgbl = np.array([
|
||||
[0.68078957, 0.12382801, 0.01514889],
|
||||
[0.81177942, 0.32550452, 0.089818],
|
||||
[0.61259378, 0.2831933, 0.07478902],
|
||||
[0.52696493, 0.20105976, 0.00958657],
|
||||
[0.80402284, 0.30419523, 0.12989841],
|
||||
[0.78658646, 0.63184111, 0.12062068],
|
||||
[0.78999637, 0.25520249, 0.03462853],
|
||||
[0.51866697, 0.16114393, 0.1078387],
|
||||
[0.74820768, 0.11770076, 0.06862177],
|
||||
[0.59776825, 0.05765816, 0.02886627],
|
||||
[0.8793145, 0.56346033, 0.0403954],
|
||||
[0.84124847, 0.42120746, 0.03287592],
|
||||
[0.23333214, 0.06780408, 0.05612276],
|
||||
[0.5176423, 0.41210976, 0.01896255],
|
||||
[0.73888613, 0.06575388, 0.06181293],
|
||||
[0.88326036, 0.58018751, 0.04321991],
|
||||
[0.75922531, 0.13149072, 0.1282041],
|
||||
[0.4345097, 0.32331019, 0.10494139],
|
||||
[0.94110142, 0.77941419, 0.26946323],
|
||||
[0.88438952, 0.5949049, 0.17536928],
|
||||
[0.84722687, 0.44160449, 0.09834799],
|
||||
[0.66743106, 0.24076803, 0.03394333],
|
||||
[0.47141286, 0.13592419, 0.01362205],
|
||||
[0.17377101, 0.03256864, 0.00203026]
|
||||
], dtype=np.float64)
|
||||
np.testing.assert_allclose(src_rgbl, model.getSrcLinearRGB().reshape(-1, 3), rtol=1e-4, atol=1e-4)
|
||||
|
||||
dst_rgbl = np.array([
|
||||
[0.17303173, 0.08211037, 0.05672686],
|
||||
[0.56832031, 0.29269488, 0.21835529],
|
||||
[0.10365019, 0.19588357, 0.33140475],
|
||||
[0.10159676, 0.14892193, 0.05188294],
|
||||
[0.22159627, 0.21584476, 0.43461196],
|
||||
[0.10806379, 0.51437196, 0.41264213],
|
||||
[0.74736423, 0.20062878, 0.02807988],
|
||||
[0.05757947, 0.10516793, 0.40296109],
|
||||
[0.56676218, 0.08424805, 0.11969461],
|
||||
[0.11099515, 0.04230796, 0.14292554],
|
||||
[0.34546869, 0.50872001, 0.04944204],
|
||||
[0.79461323, 0.35942459, 0.02051968],
|
||||
[0.01710416, 0.05022043, 0.29220674],
|
||||
[0.05598012, 0.30021149, 0.06871162],
|
||||
[0.45585457, 0.03033727, 0.04085654],
|
||||
[0.85737614, 0.56757335, 0.0068503],
|
||||
[0.53348585, 0.08861148, 0.30750446],
|
||||
[-0.0374061, 0.24699498, 0.40041217],
|
||||
[0.91262695, 0.91493909, 0.89367049],
|
||||
[0.57981916, 0.59200418, 0.59328881],
|
||||
[0.35490581, 0.36544831, 0.36755375],
|
||||
[0.19007357, 0.19186587, 0.19308397],
|
||||
[0.08529188, 0.08887994, 0.09257601],
|
||||
[0.0303193, 0.03113818, 0.03274845]
|
||||
], dtype=np.float64)
|
||||
np.testing.assert_allclose(dst_rgbl, model.getRefLinearRGB().reshape(-1, 3), rtol=1e-4, atol=1e-4)
|
||||
|
||||
mask = np.ones((24, 1), dtype=np.uint8)
|
||||
np.testing.assert_allclose(model.getMask(), mask, rtol=0.0, atol=0.0)
|
||||
|
||||
# Test reference color matrix
|
||||
refColorMat = np.array([
|
||||
[0.37406520, 0.02066507, 0.05804047],
|
||||
[0.12719672, 0.77389268, -0.01569404],
|
||||
[-0.27627010, 0.00603427, 2.74272981]
|
||||
], dtype=np.float64)
|
||||
np.testing.assert_allclose(colorCorrectionMat, refColorMat, rtol=1e-4, atol=1e-4)
|
||||
|
||||
def test_masks_weights_1(self):
|
||||
s = np.array([
|
||||
[214.11, 98.67, 37.97],
|
||||
[231.94, 153.1, 85.27],
|
||||
[204.08, 143.71, 78.46],
|
||||
[190.58, 122.99, 30.84],
|
||||
[230.93, 148.46, 100.84],
|
||||
[228.64, 206.97, 97.5],
|
||||
[229.09, 137.07, 55.29],
|
||||
[189.21, 111.22, 92.66],
|
||||
[223.5, 96.42, 75.45],
|
||||
[201.82, 69.71, 50.9],
|
||||
[240.52, 196.47, 59.3],
|
||||
[235.73, 172.13, 54.],
|
||||
[131.6, 75.04, 68.86],
|
||||
[189.04, 170.43, 42.05],
|
||||
[222.23, 74., 71.95],
|
||||
[241.01, 199.1, 61.15],
|
||||
[224.99, 101.4, 100.24],
|
||||
[174.58, 152.63, 91.52],
|
||||
[248.06, 227.69, 140.5],
|
||||
[241.15, 201.38, 115.58],
|
||||
[236.49, 175.87, 88.86],
|
||||
[212.19, 133.49, 54.79],
|
||||
[181.17, 102.94, 36.18],
|
||||
[115.1, 53.77, 15.23]
|
||||
], dtype=np.float64)
|
||||
|
||||
weightsList = np.array([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], dtype=np.float64)
|
||||
weightsList = weightsList.reshape(-1, 1)
|
||||
|
||||
src = (s / 255.).astype(np.float64).reshape(-1, 1, 3)
|
||||
model = cv.ccm.ColorCorrectionModel(src, cv.ccm.COLORCHECKER_MACBETH)
|
||||
model.setColorSpace(cv.ccm.COLOR_SPACE_SRGB)
|
||||
model.setCcmType(cv.ccm.CCM_LINEAR)
|
||||
model.setDistance(cv.ccm.DISTANCE_CIE2000)
|
||||
model.setLinearization(cv.ccm.LINEARIZATION_GAMMA)
|
||||
model.setLinearizationGamma(2.2)
|
||||
model.setLinearizationDegree(3)
|
||||
model.setSaturatedThreshold(0, 0.98)
|
||||
model.setWeightsList(weightsList)
|
||||
model.setWeightCoeff(1.5)
|
||||
_ = model.compute()
|
||||
|
||||
weights = np.array([1.15789474, 1.26315789, 1.36842105, 1.47368421,
|
||||
0.52631579, 0.63157895, 0.73684211, 0.84210526], dtype=np.float64)
|
||||
np.testing.assert_allclose(model.getWeights(), weights.reshape(-1, 1), rtol=1e-4, atol=1e-4)
|
||||
|
||||
mask = np.array([True, False, False, True, False, False,
|
||||
True, False, False, True, False, False,
|
||||
True, False, False, True, False, False,
|
||||
True, False, False, True, False, False], dtype=np.uint8)
|
||||
np.testing.assert_allclose(model.getMask(), mask.reshape(-1, 1), rtol=0.0, atol=0.0)
|
||||
|
||||
def test_masks_weights_2(self):
|
||||
s = np.array([
|
||||
[214.11, 98.67, 37.97],
|
||||
[231.94, 153.1, 85.27],
|
||||
[204.08, 143.71, 78.46],
|
||||
[190.58, 122.99, 30.84],
|
||||
[230.93, 148.46, 100.84],
|
||||
[228.64, 206.97, 97.5],
|
||||
[229.09, 137.07, 55.29],
|
||||
[189.21, 111.22, 92.66],
|
||||
[223.5, 96.42, 75.45],
|
||||
[201.82, 69.71, 50.9],
|
||||
[240.52, 196.47, 59.3],
|
||||
[235.73, 172.13, 54.],
|
||||
[131.6, 75.04, 68.86],
|
||||
[189.04, 170.43, 42.05],
|
||||
[222.23, 74., 71.95],
|
||||
[241.01, 199.1, 61.15],
|
||||
[224.99, 101.4, 100.24],
|
||||
[174.58, 152.63, 91.52],
|
||||
[248.06, 227.69, 140.5],
|
||||
[241.15, 201.38, 115.58],
|
||||
[236.49, 175.87, 88.86],
|
||||
[212.19, 133.49, 54.79],
|
||||
[181.17, 102.94, 36.18],
|
||||
[115.1, 53.77, 15.23]
|
||||
], dtype=np.float64)
|
||||
|
||||
src = (s / 255.).astype(np.float64).reshape(-1, 1, 3)
|
||||
model = cv.ccm.ColorCorrectionModel(src, cv.ccm.COLORCHECKER_MACBETH)
|
||||
model.setCcmType(cv.ccm.CCM_LINEAR)
|
||||
model.setDistance(cv.ccm.DISTANCE_CIE2000)
|
||||
model.setLinearization(cv.ccm.LINEARIZATION_GAMMA)
|
||||
model.setLinearizationGamma(2.2)
|
||||
model.setLinearizationDegree(3)
|
||||
model.setSaturatedThreshold(0.05, 0.93)
|
||||
model.setWeightsList(np.array([]))
|
||||
model.setWeightCoeff(1.5)
|
||||
_ = model.compute()
|
||||
|
||||
weights = np.array([
|
||||
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
|
||||
], dtype=np.float64)
|
||||
np.testing.assert_allclose(model.getWeights(), weights.reshape(-1, 1), rtol=1e-4, atol=1e-4)
|
||||
|
||||
# Test mask
|
||||
mask = np.array([True, True, True, True, True, True,
|
||||
True, True, True, True, False, True,
|
||||
True, True, True, False, True, True,
|
||||
False, False, True, True, True, True], dtype=np.uint8)
|
||||
np.testing.assert_allclose(model.getMask(), mask.reshape(-1, 1), rtol=0.0, atol=0.0)
|
||||
|
||||
def test_compute_color_correction_matrix(self):
|
||||
path = self.find_file('cv/mcc/mcc_ccm_test.yml')
|
||||
fs = cv.FileStorage(path, cv.FileStorage_READ)
|
||||
chartsRGB = fs.getNode("chartsRGB").mat()
|
||||
|
||||
src = (chartsRGB[:, 1].reshape(-1, 1, 3) / 255.).astype(np.float64)
|
||||
|
||||
model = cv.ccm.ColorCorrectionModel(src, cv.ccm.COLORCHECKER_MACBETH)
|
||||
colorCorrectionMat = model.compute()
|
||||
|
||||
gold_ccm = fs.getNode("ccm").mat()
|
||||
fs.release()
|
||||
|
||||
np.testing.assert_allclose(gold_ccm, colorCorrectionMat, rtol=1e-8, atol=1e-8)
|
||||
|
||||
gold_loss = 4.6386569120323129
|
||||
loss = model.getLoss()
|
||||
self.assertAlmostEqual(gold_loss, loss, places=8)
|
||||
|
||||
def test_correctImage(self):
|
||||
img = self.get_sample('cv/mcc/mcc_ccm_test.jpg')
|
||||
self.assertIsNotNone(img, "Test image can't be loaded: ")
|
||||
|
||||
gold_img = self.get_sample('cv/mcc/mcc_ccm_test_res.png')
|
||||
self.assertIsNotNone(gold_img, "Ground truth for test image can't be loaded: ")
|
||||
|
||||
path = self.find_file("cv/mcc/mcc_ccm_test.yml")
|
||||
fs = cv.FileStorage(path, cv.FileStorage_READ)
|
||||
chartsRGB = fs.getNode("chartsRGB").mat()
|
||||
fs.release()
|
||||
|
||||
src = (chartsRGB[:, 1].reshape(-1, 1, 3) / 255.).astype(np.float64)
|
||||
|
||||
np.savetxt('src_test_correct.txt',src.reshape(-1,3),fmt="%.2f")
|
||||
model = cv.ccm.ColorCorrectionModel(src, cv.ccm.COLORCHECKER_MACBETH)
|
||||
_ = model.compute()
|
||||
|
||||
calibratedImage = np.zeros_like(img)
|
||||
model.correctImage(img, calibratedImage)
|
||||
|
||||
np.testing.assert_allclose(gold_img, calibratedImage, rtol=0.1, atol=0.1)
|
||||
|
||||
def test_mcc_ccm_combined(self):
|
||||
detector = cv.mcc_CCheckerDetector.create()
|
||||
|
||||
img = self.get_sample('cv/mcc/mcc_ccm_test.jpg')
|
||||
self.assertIsNotNone(img, "Test image can't be loaded: ")
|
||||
|
||||
gold_img = self.get_sample('cv/mcc/mcc_ccm_test_res.png')
|
||||
self.assertIsNotNone(gold_img, "Ground truth for test image can't be loaded: ")
|
||||
|
||||
detector.setColorChartType(cv.mcc.MCC24)
|
||||
self.assertTrue(detector.process(img))
|
||||
|
||||
checkers = detector.getListColorChecker()
|
||||
# Get colors from detector and save for debugging
|
||||
src = checkers[0].getChartsRGB(False).reshape(-1, 1, 3) / 255.
|
||||
src = src.astype(np.float64)
|
||||
|
||||
# Load reference colors from file for comparison
|
||||
path = self.find_file('cv/mcc/mcc_ccm_test.yml')
|
||||
fs = cv.FileStorage(path, cv.FileStorage_READ)
|
||||
chartsRGB = fs.getNode("chartsRGB").mat()
|
||||
ref_src = (chartsRGB[:, 1].reshape(-1, 1, 3) / 255.).astype(np.float64)
|
||||
fs.release()
|
||||
|
||||
# Verify that detected colors are close to reference colors
|
||||
np.testing.assert_allclose(src, ref_src, rtol=0.01, atol=0.01)
|
||||
|
||||
# Use reference colors for model computation
|
||||
model = cv.ccm.ColorCorrectionModel(ref_src, cv.ccm.COLORCHECKER_MACBETH)
|
||||
_ = model.compute()
|
||||
|
||||
calibratedImage = np.zeros_like(img)
|
||||
model.correctImage(img, calibratedImage)
|
||||
|
||||
np.testing.assert_allclose(gold_img, calibratedImage, rtol=0.1, atol=0.1)
|
||||
|
||||
def test_serialization(self):
|
||||
path1 = self.find_file("cv/mcc/mcc_ccm_test.yml")
|
||||
fs = cv.FileStorage(path1, cv.FileStorage_READ)
|
||||
chartsRGB = fs.getNode("chartsRGB").mat()
|
||||
fs.release()
|
||||
|
||||
model = cv.ccm.ColorCorrectionModel(chartsRGB[:, 1].reshape(-1, 1, 3) / 255., cv.ccm.COLORCHECKER_MACBETH)
|
||||
_ = model.compute()
|
||||
|
||||
path1 = tempfile.mktemp(suffix='.yaml')
|
||||
fs1 = cv.FileStorage(path1, cv.FileStorage_WRITE)
|
||||
model.write(fs1)
|
||||
fs1.release()
|
||||
|
||||
model1 = cv.ccm.ColorCorrectionModel()
|
||||
fs2 = cv.FileStorage(path1, cv.FileStorage_READ)
|
||||
modelNode = fs2.getNode("ColorCorrectionModel")
|
||||
model1.read(modelNode)
|
||||
fs2.release()
|
||||
|
||||
path2 = tempfile.mktemp(suffix='.yaml')
|
||||
fs3 = cv.FileStorage(path2, cv.FileStorage_WRITE)
|
||||
model1.write(fs3)
|
||||
fs3.release()
|
||||
|
||||
with open(path1, 'r') as file1:
|
||||
str1 = file1.read()
|
||||
with open(path2, 'r') as file2:
|
||||
str2 = file2.read()
|
||||
self.assertEqual(str1, str2)
|
||||
|
||||
if __name__ == '__main__':
|
||||
NewOpenCVTests.bootstrap()
|
||||
@@ -0,0 +1,224 @@
|
||||
//! [tutorial]
|
||||
#include <opencv2/core.hpp>
|
||||
#include <opencv2/highgui.hpp>
|
||||
#include <opencv2/imgcodecs.hpp>
|
||||
#include <opencv2/imgproc.hpp>
|
||||
#include <opencv2/photo.hpp>
|
||||
#include <opencv2/objdetect.hpp>
|
||||
#include <opencv2/dnn.hpp>
|
||||
#include <iostream>
|
||||
#include "../dnn/common.hpp"
|
||||
|
||||
using namespace std;
|
||||
using namespace cv;
|
||||
using namespace cv::dnn;
|
||||
using namespace cv::ccm;
|
||||
using namespace mcc;
|
||||
|
||||
const string about =
|
||||
"This sample detects Macbeth color checker using DNN or thresholding and applies color correction."
|
||||
"To run default:\n"
|
||||
"\t ./example_cpp_color_correction_model --input=path/to/your/input/image --query=path/to/your/query/image\n"
|
||||
"With DNN model:\n"
|
||||
"\t ./example_cpp_color_correction_model mcc --input=path/to/your/input/image --query=path/to/your/query/image\n\n"
|
||||
"Using pre-computed CCM:\n"
|
||||
"\t ./example_cpp_color_correction_model mcc --ccm_file=path/to/ccm_output.yaml --query=path/to/your/query/image\n\n"
|
||||
"Model path can also be specified using --model argument. And config path can be specified using --config. Download it using python download_models.py mcc from dnn samples directory\n\n";
|
||||
|
||||
const string param_keys =
|
||||
"{ help h | | Print help message. }"
|
||||
"{ @alias | | An alias name of model to extract preprocessing parameters from models.yml file. }"
|
||||
"{ zoo | ../dnn/models.yml | An optional path to file with preprocessing parameters }"
|
||||
"{ input i | mcc_ccm_test.jpg | Path to input image for computing CCM.}"
|
||||
"{ query q | baboon.jpg | Path to query image to apply color correction. If not provided, input image will be used. }"
|
||||
"{ type | 0 | chartType: 0-Standard, 1-DigitalSG, 2-Vinyl }"
|
||||
"{ num_charts | 1 | Maximum number of charts in the image }"
|
||||
"{ ccm_file | | Path to YAML file containing pre-computed CCM parameters}";
|
||||
|
||||
const string backend_keys = format(
|
||||
"{ backend | default | Choose one of computation backends: "
|
||||
"default: automatically (by default), "
|
||||
"openvino: Intel's Deep Learning Inference Engine (https://software.intel.com/openvino-toolkit), "
|
||||
"opencv: OpenCV implementation, "
|
||||
"vkcom: VKCOM, "
|
||||
"cuda: CUDA, "
|
||||
"webnn: WebNN }");
|
||||
|
||||
const string target_keys = format(
|
||||
"{ target | cpu | Choose one of target computation devices: "
|
||||
"cpu: CPU target (by default), "
|
||||
"opencl: OpenCL, "
|
||||
"opencl_fp16: OpenCL fp16 (half-float precision), "
|
||||
"vpu: VPU, "
|
||||
"vulkan: Vulkan, "
|
||||
"cuda: CUDA, "
|
||||
"cuda_fp16: CUDA fp16 (half-float preprocess) }");
|
||||
|
||||
string keys = param_keys + backend_keys + target_keys;
|
||||
|
||||
static bool processFrame(const Mat& frame, Ptr<CCheckerDetector> detector, Mat& src, int nc){
|
||||
if (!detector->process(frame, nc))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
vector<Ptr<CChecker>> checkers = detector->getListColorChecker();
|
||||
src = checkers[0]->getChartsRGB(false);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
CommandLineParser parser(argc, argv, keys);
|
||||
parser.about(about);
|
||||
|
||||
if (parser.has("help")) {
|
||||
cout << about << endl;
|
||||
parser.printMessage();
|
||||
return 0;
|
||||
}
|
||||
|
||||
string modelName = parser.get<String>("@alias");
|
||||
string zooFile = parser.get<String>("zoo");
|
||||
const char* path = getenv("OPENCV_SAMPLES_DATA_PATH");
|
||||
|
||||
if ((path != NULL) || parser.has("@alias")) {
|
||||
zooFile = findFile(zooFile);
|
||||
}
|
||||
else{
|
||||
cout<<"[WARN] set the environment variables or pass the arguments --model, --config and models.yml file using --zoo for using dnn based detector. Continuing with default detector.\n\n";
|
||||
}
|
||||
keys += genPreprocArguments(modelName, zooFile);
|
||||
parser = CommandLineParser(argc, argv, keys);
|
||||
|
||||
int t = parser.get<int>("type");
|
||||
if (t < 0 || t > 2)
|
||||
{
|
||||
cout << "Error: --type must be 0, 1 or 2" << endl;
|
||||
parser.printMessage(); // prints full usage
|
||||
return -1;
|
||||
}
|
||||
|
||||
ColorChart chartType = ColorChart(t);
|
||||
|
||||
const string sha1 = parser.get<String>("sha1");
|
||||
const string modelPath = findModel(parser.get<string>("model"), sha1);
|
||||
const string config_sha1 = parser.get<String>("config_sha1");
|
||||
const string configPath = findModel(parser.get<string>("config"), config_sha1);
|
||||
const string backend = parser.get<String>("backend");
|
||||
const string target = parser.get<String>("target");
|
||||
|
||||
int nc = parser.get<int>("num_charts");
|
||||
|
||||
// Get input and target image paths
|
||||
const string inputFile = parser.get<String>("input");
|
||||
const string queryFile = parser.get<String>("query");
|
||||
const string ccmFile = parser.get<String>("ccm_file");
|
||||
|
||||
if (!ccmFile.empty()) {
|
||||
// When ccm_file is provided, only query is required
|
||||
if (queryFile.empty()) {
|
||||
cout << "Error: Query image path must be provided when using pre-computed CCM." << endl;
|
||||
parser.printMessage();
|
||||
return -1;
|
||||
}
|
||||
} else {
|
||||
// Original validation for when computing new CCM
|
||||
if (inputFile.empty()) {
|
||||
cout << "Error: Input image path must be provided." << endl;
|
||||
parser.printMessage();
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
ColorCorrectionModel model;
|
||||
Mat queryImage;
|
||||
|
||||
if (!ccmFile.empty()) {
|
||||
// Load CCM from YAML file
|
||||
FileStorage fs(ccmFile, FileStorage::READ);
|
||||
if (!fs.isOpened()) {
|
||||
cout << "Error: Unable to open CCM file: " << ccmFile << endl;
|
||||
return -1;
|
||||
}
|
||||
model.read(fs["ColorCorrectionModel"]);
|
||||
fs.release();
|
||||
cout << "Loaded CCM from file: " << ccmFile << endl;
|
||||
|
||||
// Read query image when using pre-computed CCM
|
||||
queryImage = imread(findFile(queryFile));
|
||||
if (queryImage.empty()) {
|
||||
cout << "Error: Unable to read query image." << endl;
|
||||
return -1;
|
||||
}
|
||||
} else {
|
||||
// Read input image for computing new CCM
|
||||
Mat originalImage = imread(findFile(inputFile));
|
||||
if (originalImage.empty()) {
|
||||
cout << "Error: Unable to read input image." << endl;
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Process first image to compute CCM
|
||||
Mat image = originalImage.clone();
|
||||
Mat src;
|
||||
|
||||
Ptr<CCheckerDetector> detector;
|
||||
if (!modelPath.empty() && !configPath.empty()) {
|
||||
Net net = readNetFromTensorflow(modelPath, configPath);
|
||||
net.setPreferableBackend(getBackendID(backend));
|
||||
net.setPreferableTarget(getTargetID(target));
|
||||
detector = CCheckerDetector::create(net);
|
||||
cout << "Using DNN-based checker detector." << endl;
|
||||
} else {
|
||||
detector = CCheckerDetector::create();
|
||||
cout << "Using thresholding-based checker detector." << endl;
|
||||
}
|
||||
detector->setColorChartType(chartType);
|
||||
|
||||
if (!processFrame(image, detector, src, nc)) {
|
||||
cout << "No chart detected in the input image!" << endl;
|
||||
return -1;
|
||||
}
|
||||
// Convert to double and normalize
|
||||
src.convertTo(src, CV_64F, 1.0/255.0);
|
||||
|
||||
// Color correction model
|
||||
model = ColorCorrectionModel(src, COLORCHECKER_MACBETH);
|
||||
model.setCcmType(CCM_LINEAR);
|
||||
model.setDistance(DISTANCE_CIE2000);
|
||||
model.setLinearization(LINEARIZATION_GAMMA);
|
||||
model.setLinearizationGamma(2.2);
|
||||
|
||||
Mat ccm = model.compute();
|
||||
cout << "Computed CCM Matrix:\n" << ccm << endl;
|
||||
cout << "Loss: " << model.getLoss() << endl;
|
||||
|
||||
// Save model parameters to YAML file
|
||||
FileStorage fs("ccm_output.yaml", FileStorage::WRITE);
|
||||
model.write(fs);
|
||||
fs.release();
|
||||
cout << "Model parameters saved to ccm_output.yaml" << endl;
|
||||
|
||||
// Set query image for correction
|
||||
if (queryFile.empty()) {
|
||||
cout << "[WARN] No query image provided, applying color correction on input image" << endl;
|
||||
queryImage = originalImage.clone();
|
||||
} else {
|
||||
queryImage = imread(findFile(queryFile));
|
||||
if (queryImage.empty()) {
|
||||
cout << "Error: Unable to read query image." << endl;
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Mat calibratedImage;
|
||||
model.correctImage(queryImage, calibratedImage);
|
||||
|
||||
imshow("Original Image", queryImage);
|
||||
imshow("Corrected Image", calibratedImage);
|
||||
waitKey(0);
|
||||
|
||||
return 0;
|
||||
}
|
||||
//! [tutorial]
|
||||
@@ -0,0 +1,24 @@
|
||||
0.380463 0.31696 0.210053
|
||||
0.649781 0.520561 0.452553
|
||||
0.323114 0.37593 0.50123
|
||||
0.314785 0.396522 0.258116
|
||||
0.452971 0.418602 0.578767
|
||||
0.34908 0.608649 0.652283
|
||||
0.691127 0.517818 0.144984
|
||||
0.208668 0.224391 0.485851
|
||||
0.657849 0.378126 0.304115
|
||||
0.285762 0.229671 0.31913
|
||||
0.513422 0.685031 0.337381
|
||||
0.786459 0.676133 0.246303
|
||||
0.11751 0.135079 0.383441
|
||||
0.190745 0.470513 0.296844
|
||||
0.587832 0.299132 0.196117
|
||||
0.783908 0.746261 0.294357
|
||||
0.615481 0.359983 0.471403
|
||||
0.107095 0.370516 0.573142
|
||||
0.708598 0.718936 0.740915
|
||||
0.593812 0.612474 0.63222
|
||||
0.489774 0.510077 0.521757
|
||||
0.380591 0.398499 0.393662
|
||||
0.27461 0.293267 0.275244
|
||||
0.180753 0.194968 0.145006
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 72 KiB |
@@ -0,0 +1,185 @@
|
||||
import cv2 as cv
|
||||
import numpy as np
|
||||
import argparse
|
||||
import sys
|
||||
import os
|
||||
sys.path.append(os.path.join(os.path.dirname(__file__), ".."))
|
||||
from dnn.common import *
|
||||
|
||||
def get_args_parser(func_args):
|
||||
backends = ("default", "openvino", "opencv", "vkcom", "cuda", "webnn")
|
||||
targets = ("cpu", "opencl", "opencl_fp16", "vpu", "vulkan", "cuda", "cuda_fp16")
|
||||
|
||||
parser = argparse.ArgumentParser(add_help=False)
|
||||
parser.add_argument('--zoo', default=os.path.join(os.path.dirname(os.path.abspath(__file__)), '../dnn', 'models.yml'),
|
||||
help='An optional path to file with preprocessing parameters.')
|
||||
parser.add_argument('--input', default='mcc_ccm_test.jpg', help='Path to input image for computing CCM')
|
||||
parser.add_argument('--query', default='baboon.jpg', help='Path to query image to apply color correction')
|
||||
parser.add_argument('--ccm_file', help='Path to YAML file containing pre-computed CCM parameters')
|
||||
parser.add_argument('--chart_type', type=int, default=0,
|
||||
help='chartType: 0-Standard, 1-DigitalSG, 2-Vinyl, default:0')
|
||||
parser.add_argument('--num_charts', type=int, default=1,
|
||||
help='Maximum number of charts in the image')
|
||||
parser.add_argument('--backend', default="default", type=str, choices=backends,
|
||||
help="Choose one of computation backends: "
|
||||
"default: automatically (by default), "
|
||||
"openvino: Intel's Deep Learning Inference Engine (https://software.intel.com/openvino-toolkit), "
|
||||
"opencv: OpenCV implementation, "
|
||||
"vkcom: VKCOM, "
|
||||
"cuda: CUDA, "
|
||||
"webnn: WebNN")
|
||||
parser.add_argument('--target', default="cpu", type=str, choices=targets,
|
||||
help="Choose one of target computation devices: "
|
||||
"cpu: CPU target (by default), "
|
||||
"opencl: OpenCL, "
|
||||
"opencl_fp16: OpenCL fp16 (half-float precision), "
|
||||
"vpu: VPU, "
|
||||
"vulkan: Vulkan, "
|
||||
"cuda: CUDA, "
|
||||
"cuda_fp16: CUDA fp16 (half-float preprocess)")
|
||||
|
||||
args, _ = parser.parse_known_args()
|
||||
add_preproc_args(args.zoo, parser, 'mcc', 'mcc')
|
||||
parser = argparse.ArgumentParser(parents=[parser],
|
||||
description='''
|
||||
To run:
|
||||
Default (compute new CCM):
|
||||
python color_correction_model.py --input=path/to/your/input/image --query=path/to/query/image
|
||||
DNN model:
|
||||
python color_correction_model.py mcc --input=path/to/your/input/image --query=path/to/query/image
|
||||
Using pre-computed CCM:
|
||||
python color_correction_model.py --ccm_file=path/to/ccm_output.yaml --query=path/to/query/image
|
||||
|
||||
Model path can also be specified using --model argument. And config path can be specified using --config.
|
||||
''', formatter_class=argparse.RawTextHelpFormatter)
|
||||
return parser.parse_args(func_args)
|
||||
|
||||
def process_frame(frame, detector, num_charts):
|
||||
if not detector.process(frame, num_charts):
|
||||
return None
|
||||
|
||||
checkers = detector.getListColorChecker()
|
||||
src = checkers[0].getChartsRGB(False)
|
||||
|
||||
return src
|
||||
|
||||
def main(func_args=None):
|
||||
args = get_args_parser(func_args)
|
||||
|
||||
if not (0 <= args.chart_type <= 2):
|
||||
raise ValueError("chartType must be 0, 1, or 2")
|
||||
|
||||
# Validate arguments based on whether using pre-computed CCM
|
||||
if args.ccm_file:
|
||||
if not args.query:
|
||||
print("[ERROR] Query image path must be provided when using pre-computed CCM.")
|
||||
return -1
|
||||
else:
|
||||
if not args.input:
|
||||
print("[ERROR] Input image path must be provided when computing new CCM.")
|
||||
return -1
|
||||
|
||||
# Read query image
|
||||
query_image = None
|
||||
if args.query:
|
||||
query_image = cv.imread(findFile(args.query))
|
||||
if query_image is None:
|
||||
print("[ERROR] Unable to read query image.")
|
||||
return -1
|
||||
|
||||
if os.getenv('OPENCV_SAMPLES_DATA_PATH') is not None:
|
||||
try:
|
||||
args.model = findModel(args.model, args.sha1)
|
||||
args.config = findModel(args.config, args.config_sha1)
|
||||
except:
|
||||
print("[WARN] Model file not provided, using default detector. Pass model using --model and config using --config to use dnn based detector.\n\n")
|
||||
args.model = None
|
||||
args.config = None
|
||||
else:
|
||||
args.model = None
|
||||
args.config = None
|
||||
print("[WARN] Model file not provided, using default detector. Pass model using --model and config using --config to use dnn based detector. Or, set OPENCV_SAMPLES_DATA_PATH environment variable.\n\n")
|
||||
|
||||
# Create color correction model
|
||||
model = cv.ccm.ColorCorrectionModel()
|
||||
|
||||
if args.ccm_file:
|
||||
# Load CCM from YAML file
|
||||
fs = cv.FileStorage(args.ccm_file, cv.FileStorage_READ)
|
||||
if not fs.isOpened():
|
||||
print(f"[ERROR] Unable to open CCM file: {args.ccm_file}")
|
||||
return -1
|
||||
model.read(fs.getNode("ColorCorrectionModel"))
|
||||
fs.release()
|
||||
print(f"Loaded CCM from file: {args.ccm_file}")
|
||||
else:
|
||||
# Read input image for computing new CCM
|
||||
image = cv.imread(findFile(args.input))
|
||||
if image is None:
|
||||
print("[ERROR] Unable to read input image.")
|
||||
return -1
|
||||
|
||||
# Create color checker detector
|
||||
if args.model and args.config:
|
||||
# Load the DNN from TensorFlow model
|
||||
engine = cv.dnn.ENGINE_AUTO
|
||||
if args.backend != "default" or args.target != "cpu":
|
||||
engine = cv.dnn.ENGINE_CLASSIC
|
||||
net = cv.dnn.readNetFromTensorflow(args.model, args.config, engine)
|
||||
net.setPreferableBackend(get_backend_id(args.backend))
|
||||
net.setPreferableTarget(get_target_id(args.target))
|
||||
|
||||
detector = cv.mcc_CCheckerDetector.create(net)
|
||||
print("Detecting checkers using neural network.")
|
||||
else:
|
||||
detector = cv.mcc_CCheckerDetector.create()
|
||||
print("Detecting checkers using default method (no DNN).")
|
||||
|
||||
detector.setColorChartType(args.chart_type)
|
||||
|
||||
# Process image to detect color checker
|
||||
src = process_frame(image, detector, args.num_charts)
|
||||
if src is None:
|
||||
print("No chart detected in the input image!")
|
||||
return -1
|
||||
|
||||
print("Actual colors:", src)
|
||||
|
||||
# Convert to double and normalize
|
||||
src = src.astype(np.float64) / 255.0
|
||||
|
||||
# Create and configure color correction model
|
||||
model = cv.ccm.ColorCorrectionModel(src, cv.ccm.COLORCHECKER_MACBETH)
|
||||
model.setCcmType(cv.ccm.CCM_LINEAR)
|
||||
model.setDistance(cv.ccm.DISTANCE_CIE2000)
|
||||
model.setLinearization(cv.ccm.LINEARIZATION_GAMMA)
|
||||
model.setLinearizationGamma(2.2)
|
||||
|
||||
# Compute color correction matrix
|
||||
ccm = model.compute()
|
||||
print("Computed CCM Matrix:\n", ccm)
|
||||
print("Loss:", model.getLoss())
|
||||
|
||||
# Save model parameters to YAML file
|
||||
fs = cv.FileStorage("ccm_output.yaml", cv.FileStorage_WRITE)
|
||||
model.write(fs)
|
||||
fs.release()
|
||||
print("Model parameters saved to ccm_output.yaml")
|
||||
|
||||
# Set query image for correction if not provided
|
||||
if query_image is None:
|
||||
print("[WARN] No query image provided, applying color correction on input image")
|
||||
query_image = image.copy()
|
||||
|
||||
# Apply correction to query image
|
||||
calibrated_image = np.empty_like(query_image)
|
||||
model.correctImage(query_image, calibrated_image)
|
||||
|
||||
cv.imshow("Original Image", query_image)
|
||||
cv.imshow("Corrected Image", calibrated_image)
|
||||
cv.waitKey(0)
|
||||
|
||||
return 0
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user