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?
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 isuint8
whereY
is in the range[16 235]
, andCb
andCr
are in the range[16 240]
.
In OCV (implementation) on the other hand, this is performed using the following relations:
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.