c++matlabopencvrgbcolor-conversion

Different color conversion from RGB to YCbCr with OpenCV and Matlab


I'm trying to replicate the jpeg algorithm with openCV, after I successfully implemented it in MATLAB. I noticed that MATLAB and OpenCV give different results for the color space convertion from RGB to YCbCr.

Looking at the documentation of OpenCV, seems that the only function to use is cv::cvtColor, but printing the first 8x8 submatrix of Y, Cb and Cr, they are not the same.

Here's my code both for MATLAB and for C++ (with OpenCV 4.0.1).

Matlab:

% Read rgb image
imgrgb = imread('lena512color.bmp');

% Convert to ycbcr
imgycbcr = rgb2ycbcr(imgrgb);

% Extract the 3 components
luma = imgycbcr (:,:,1);
cb = imgycbcr (:,:,2);
cr = imgycbcr (:,:,3);

C++:

// Load img
cv::Mat bgrImg = imread( "lena512color.bmp", cv::IMREAD_COLOR );
assert( bgrImg.data && "No image data");

// Declare an empty Mat for dst image
cv::Mat ycrcbImg;

// Convert to ycrcb
cv::cvtColor(bgrImg, ycrcbImg, cv::COLOR_BGR2YCrCb);

// Split bgr into 3 channels
cv::Mat bgrChan[3];
cv::split(bgrImg, bgrChan);

// Split ycrcb into 3 channels
cv::Mat ycrcbChan[3];
cv::split(ycrcbImg, ycrcbChan);

// Print first block for each channel
PRINT_MAT(ycrcbChan[0](cv::Rect(0, 0, 8, 8)), "LUMA (first 8x8 block)")
PRINT_MAT(ycrcbChan[1](cv::Rect(0, 0, 8, 8)), "Cr (first 8x8 block)")
PRINT_MAT(ycrcbChan[2](cv::Rect(0, 0, 8, 8)), "Cb (first 8x8 block)")

PRINT_MAT(bgrChan[0](cv::Rect(0, 0, 8, 8)), "Blue (first 8x8 block)")
PRINT_MAT(bgrChan[1](cv::Rect(0, 0, 8, 8)), "Green (first 8x8 block)")
PRINT_MAT(bgrChan[2](cv::Rect(0, 0, 8, 8)), "Red (first 8x8 block)")

where PRINT_MAT is the following macro:

#define PRINT_MAT(mat, msg) std::cout<< std::endl <<msg <<":" <<std::endl <<mat <<std::endl;

Printing out the RGB channels I obtain the same values (for the first 8x8 block) both for Matlab and OpenCV, while for Y, Cb and Cr I obtain different values. E.g., for Luma component:

Matlab:

155 155 155 154 155 150 156 154
155 155 155 154 155 150 156 154
155 155 155 154 155 150 156 154
155 155 155 154 155 150 156 154
155 155 155 154 155 150 156 154
157 157 151 149 154 153 152 153
154 154 156 152 154 155 153 150
152 152 149 150 152 152 150 151

OpenCV:

162 162 162 161 162 157 163 161
162 162 162 161 162 157 163 161
162 162 162 161 162 157 163 161
162 162 162 161 162 157 163 161
162 162 162 161 162 157 163 161
164 164 158 155 161 159 159 160
160 160 163 158 160 162 159 156
159 159 155 157 158 159 156 157

What is correct conversion? And why the results are different?


Solution

  • By looking at the source code of MATLAB's rgb2ycbcr, the conversion is performed according to an equation taken from "Poynton's "Introduction to Digital Video" (p. 176, equations 9.6)":

    origT = [ ...
         65.481 128.553  24.966;...
        -37.797 -74.203 112    ;...
        112     -93.786 -18.214];
    origOffset = [16; 128; 128];
    ycbcr = origT * rgb + origOffset;
    

    And it is also mentioned that:

    If the input is uint8, then YCBCR is uint8 where Y is in the range [16 235], and Cb and Cr are in the range [16 240].

    In OCV (implementation) on the other hand, this is performed using the following relations:

    enter image description here

    Note how it says "Y, Cr, and Cb cover the whole value range.".

    If we use the same equations in MATLAB, we get a result much closer to OCV's (perhaps I'm using a different source image). For example, for Y:

    OCV_Y = 0.299*imgrgb(:,:,1) + 0.587*imgrgb(:,:,2) + 0.114*imgrgb(:,:,3);
    

    Gives this first 8x8:

       162   162   162   162   163   157   163   161
       162   162   162   162   163   157   163   161
       162   162   162   162   163   157   163   161
       162   162   162   162   163   157   163   161
       162   162   162   162   163   157   163   161
       164   164   158   155   162   159   159   160
       161   161   163   158   160   161   158   155
       159   159   156   156   159   158   157   157
    

    According to the wikipedia article on YCbCr, it seems like OCV implements the "JPEG Conversion" variant, whereas MATLAB's implements ITU-R BT.601's variant for for "standard-definition television".

    To conclude: I would say that both definitions are correct, but if you care specifically about the correct implementation for JPEG, I would say that OCV's way is better. In any case, it's very easy to implement any other variant in MATLAB.