Question

I need to write a program that will take pictures, transfer them to YCbCr format, manipulate the picture and then return the result back in RGB format. Before I write the manipulation I wrote the transformation from RGB to YCbCr and back and wanted to test that it works. On most pictures I've tested it on it works, and returns back a picture that is identical to the original. I have problems with 2 pictures, where the transformation seems to change some of the pixels. the pictures are (cropped areas from larger pictures):

The original:

enter image description here

The result:

enter image description here

The original:

enter image description here

The result:

enter image description here

I'm taking the values for the conversion matrices and vectors from this site, and I've tried all the 3 available options, all of them give similar "errors".

The code I use:

//transformation matrix from RGB to YCbCr
const double rgb2YCbCrMatrix[3][3] = {
    { 0.257,  0.504,  0.098},
    {-0.148, -0.291,  0.439},
    { 0.439, -0.368, -0.071}
};
//transformation vector from RGB to YcBCr
const char rgb2YCbCrVector[3] = {
    16,
    128,
    128
};
//transformation matrix from YCbCr to RGB
const double TCbCr2rgbMatrix[3][3] = {
    {1.164,  0.000,  1.596},
    {1.164, -0.392, -0.813},
    {1.164,  2.017,  0.000}
};
//transformation vector from RGB to YcBCr
const char YCbCr2rgbVector[3] = {
    -16,
    -128,
    -128};

//calculate the values of the Y, Cb and Cr channels, used the formula for HDTV, as described at http://www.equasys.de/colorconversion.html
for (int i = 0; i < height*width; i++) {
    YChannel[i]  = redChannel[i]*rgb2YCbCrMatrix[0][0]+greenChannel[i]*rgb2YCbCrMatrix[0][1]+blueChannel[i]*rgb2YCbCrMatrix[0][2] + rgb2YCbCrVector[0];
    CbChannel[i] = redChannel[i]*rgb2YCbCrMatrix[1][0]+greenChannel[i]*rgb2YCbCrMatrix[1][1]+blueChannel[i]*rgb2YCbCrMatrix[1][2] + rgb2YCbCrVector[1];
    CrChannel[i] = redChannel[i]*rgb2YCbCrMatrix[2][0]+greenChannel[i]*rgb2YCbCrMatrix[2][1]+blueChannel[i]*rgb2YCbCrMatrix[2][2] + rgb2YCbCrVector[2];
}
//calculate the values of the RGB channels after the transformation, used the formula for HDTV, as described at http://www.equasys.de/colorconversion.html
for (int i = 0; i < height*width; i++) {
    redChannel[i]   = (YChannel[i] + YCbCr2rgbVector[0])*TCbCr2rgbMatrix[0][0]+(CbChannel[i] + YCbCr2rgbVector[1])*TCbCr2rgbMatrix[0][1]+(CrChannel[i] + YCbCr2rgbVector[2])*TCbCr2rgbMatrix[0][2];
    greenChannel[i] = (YChannel[i] + YCbCr2rgbVector[0])*TCbCr2rgbMatrix[1][0]+(CbChannel[i] + YCbCr2rgbVector[1])*TCbCr2rgbMatrix[1][1]+(CrChannel[i] + YCbCr2rgbVector[2])*TCbCr2rgbMatrix[1][2];
    blueChannel[i]  = (YChannel[i] + YCbCr2rgbVector[0])*TCbCr2rgbMatrix[2][0]+(CbChannel[i] + YCbCr2rgbVector[1])*TCbCr2rgbMatrix[2][1]+(CrChannel[i] + YCbCr2rgbVector[2])*TCbCr2rgbMatrix[2][2];
}

Where redChannel, greenChannel and blueChannel as well as YChannel, CbChannel and CrChannel are of unsigned char * type, and height and width are the dimensions of the picture. Why doesn't those pictures return the same, while all other tested pictures return perfectly fine.

p.s.

I've tested both pictures in Matlab using the commands rgb2ycbcr and ycbcr2rgb, and rounding them down with uint8 and it gave me good results.

in=imread(pic);
YCbCr = uint8(rgb2ycbcr(in));
out = uint8(ycbcr2rgb(YCbCr));
imshow(out);
Was it helpful?

Solution

There is no 1-to-1 conversion between YCbCr and RGB. Using the same range of values, there are colors in both color spaces that cannot be represented in the other.

When you do a conversion in either direction, you need to clamp the values to the range.

For example, an RGB to Y conversion needs to have something like this for every component conversion:

 double RGBtoY (double R, double G, double B)
{
   . . . . 
   if (result < 0.0)
     result = 0.0 ;
   else if (result > 255) // Or whatever maximum value
     result = 255 ;      
}

Otherwise, you are going to get wrap around.

OTHER TIPS

I observed that the glitched areas appear to be in areas that are likely either pure black or white.

Based on this, a guess: rgb2YCbCrVector has type char, for which the value '128' actually overflows to -128.

So in the case of r,g,b=0,0,0 you get Y,Cr,Cb=16,-128,-128 instead of 16,128,128. Then when converting back, the formula gives

redChannel = (16-16)*blah + (-128 - 128)*blah2 + (-128 - 128)*blah3

Test program:

char a = 128;
char b = -128;
printf("%d", a + b);

Output: -256

So when converting back, redChannel = 0*blah1 - 256*blah2 - 256*blah3

Which is clearly not 0 as you would originally expect.

I would consider storing the conversion vectors as float/double, or at least int. Maybe consider doing the same for the color channels too, though not sure that would make a difference.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top