3

I converted an RGB matrix to YUV matrix using this formula:

Y  =      (0.257 * R) + (0.504 * G) + (0.098 * B) + 16
Cr = V =  (0.439 * R) - (0.368 * G) - (0.071 * B) + 128
Cb = U = -(0.148 * R) - (0.291 * G) + (0.439 * B) + 128

I then did a 4:2:0 chroma subsample on the matrix. I think I did this correctly, I took 2x2 submatrices from the YUV matrix, ordered the values from least to greatest, and took the average between the 2 values in the middle.

I then used this formula, from Wikipedia, to access the Y, U, and V planes:

size.total = size.width * size.height;
y = yuv[position.y * size.width + position.x];
u = yuv[(position.y / 2) * (size.width / 2) + (position.x / 2) + size.total];
v = yuv[(position.y / 2) * (size.width / 2) + (position.x / 2) + size.total + (size.total / 4)];

I'm using OpenCV so I tried to interpret this as best I can:

y = src.data[(i*channels)+(j*step)];
u = src.data[(j%4)*step + ((i%2)*channels+1) + max];
v = src.data[(j%4)*step + ((i%2)*channels+2) + max + (max%4)];

src is the YUV subsampled matrix. Did I interpret that formula correctly?

Here is how I converted the colours back to RGB:

bgr.data[(i*channels)+(j*step)] = (1.164 * (y - 16)) + (2.018 * (u - 128)); // B
bgr.data[(i*channels+1)+(j*step)] = (1.164 * (y - 16)) - (0.813 * (v - 128)) - (0.391 * (u - 128)); // G
bgr.data[(i*channels+2)+(j*step)] = (1.164 * (y - 16)) + (1.596 * (v - 128));   // R

The problem is my image does not return to its original colours.

Here are the images for reference: https://i.stack.imgur.com/vQkpT.jpg (Subsampled) https://i.stack.imgur.com/Oucc5.jpg (Output)

I see that I should be converting from YUV444 to RGB now but I don't quite I understand what the clip function does in the sample I found on Wiki.

C = Y' − 16
D = U − 128
E = V − 128

R = clip(( 298 * C           + 409 * E + 128) >> 8)
G = clip(( 298 * C - 100 * D - 208 * E + 128) >> 8)
B = clip(( 298 * C + 516 * D           + 128) >> 8)

Does the >> mean I should shift bits?

I'd appreciate any help/comments! Thanks

Update

Tried doing the YUV444 conversion but it just made my image appear in shades of green.

        y = src.data[(i*channels)+(j*step)];
        u = src.data[(j%4)*step + ((i%2)*channels+1) + max];
        v = src.data[(j%4)*step + ((i%2)*channels+2) + max + (max%4)];

        c = y - 16;
        d = u - 128;
        e = v - 128;

        bgr.data[(i*channels+2)+(j*step)] = clip((298*c + 409*e + 128)/256);
        bgr.data[(i*channels+1)+(j*step)] = clip((298*c - 100*d - 208*e + 128)/256);
        bgr.data[(i*channels)+(j*step)] = clip((298*c + 516*d + 128)/256);

And my clip function: int clip(double value) { return (value > 255) ? 255 : (value < 0) ? 0 : value; }

doc
  • 71
  • 1
  • 3
  • 7

3 Answers3

2

I had the same problem when decoding WebM frames to RGB. I finally found the solution after hours of searching.

Take SCALEYUV function from here: http://www.telegraphics.com.au/svn/webpformat/trunk/webpformat.h

Then to decode the RGB data from YUV, see this file: http://www.telegraphics.com.au/svn/webpformat/trunk/decode.c

Search for "py = img->planes[0];", there are two algorithms to convert the data. I only tried the simple one (after "// then fall back to cheaper method.").

Comments in the code also refer to this page: http://www.poynton.com/notes/colour_and_gamma/ColorFAQ.html#RTFToC30

Works great for me.

mrbrdo
  • 7,968
  • 4
  • 32
  • 36
1

You won't get back perfectly the same image since UV does compress the image.
You don't say if the result is completely wrong (ie an error) or just not perfect

R = clip(( 298 * C           + 409 * E + 128) >> 8)
G = clip(( 298 * C - 100 * D - 208 * E + 128) >> 8)
B = clip(( 298 * C + 516 * D           + 128) >> 8)

The >> 8 is a bit shift, equivalent to dividing by 256. This is just to allow you to do all the arithmatic in integer units rather than floating point for speed

Martin Beckett
  • 94,801
  • 28
  • 188
  • 263
  • 1
    Hi Martin, thanks for such a quick reply! What exactly should my clip function do? – doc Jul 03 '11 at 03:35
  • You need to end up with a value between 0-255 so the clip() needs to check this range. Note you can't just do (R & 0xff) because a value of 256 would be shown as black – Martin Beckett Jul 03 '11 at 03:38
  • Something like this would work, right? clip(x){ if x > 255 x = 255; else if(x < 0) x = 0; } – doc Jul 03 '11 at 03:40
  • Hm, my image is now showing up in shades of green. Could this be caused by an error in my subsampling or how I'm accessing the YUV values? – doc Jul 03 '11 at 05:07
0

Was experimenting with formulas present on wiki and found that mixed formula:

byte c = (byte) (y - 16);
byte d = (byte) (u - 128);
byte e = (byte) (v - 128);

byte r = (byte) (c + (1.370705 * (e))); 
byte g = (byte) (c - (0.698001 * (d)) - (0.337633 * (e)));
byte b = (byte) (c + (1.732446 * (d)));

produces "better" errors for my images, simply makes some black points pure green (i.e. rgb = 0x00FF00) which is better for detection and correction ...

wiki source: https://en.wikipedia.org/wiki/YUV#Y.27UV420p_.28and_Y.27V12_or_YV12.29_to_RGB888_conversion

tomasb
  • 1,663
  • 2
  • 22
  • 29