# RGB to YUV to RGB

1. I don't know what to make of this.

RGB is converted to YUV and back without error with the luma coefficients given (3, 6, 1). However, these coefficients put Y, U and V out of the range of 10-bit integers.

These coefficients were chosen because they are close to the NTSC coefficients 30% red, 59% green, 11% blue.

Code:
```#include <stdio.h>
#include <float.h>

#define KR 3
#define KG 6
#define KB 1

unsigned short R, G, B, Rd, Gd, Bd;
short Y,U,V;

void rgb_to_yuv(void)
{double fr, fg, fb, fy, fu, fv;

fr = (double)R;
fg = (double)G;
fb = (double)B;

fy = KR*fr + KG*fg + KB*fb;
Y = (int)fy;

fu = ((fb * KB) - fy);
U = (int)fu;

fv = (fr * KR) - fy;
V = (int)fv;
}

void yuv_to_rgb(void)
{double fy, fu, fv;
fy = (double)Y;

fu = (double)U;

fv = (double)V;

Rd = (int) ((fy+fv)/KR)+0.5;
Gd = (int) (-(fy + fv + fu)/KG)+0.5;
Bd = (int) ((fy + fu)/KB)+0.5;
}

int main(void) {

unsigned long count, diff;
count = 0;
diff = 0;

puts("Testing...");

for (R=0; R<=1023; R++)
{
for (G=0; G<=1023; G++)
{
for (B=0; B<=1023; B++)
{

count++;
rgb_to_yuv();
yuv_to_rgb();

if (R!=Rd || G!=Gd || B!=Bd) { diff++; }

}
}
}

printf("\n%d colors tested\n", count);
printf("%d colors differed\n", diff);

}```  Quote
2. Use floating point coefficients. Also U and V must be biased if they are to be stored in unsigned ints.

http://avisynth.nl/index.php/Color_conversions
Code:
```//Rec.601
Kr=0.299
Kg=0.587
Kb=0.114

//Rec.709
Kr=0.2126
Kg=0.7152
Kb=0.0722

y = Kr*r + Kg*g + Kb*b

v = 0.5*r - 0.5*g*Kg/(1-Kr) - 0.5*b*Kb/(1-Kr) + 128

u = -0.5*r*Kr/(1-Kb) - 0.5*g*Kg/(1-Kb) + 0.5*b + 128

r = y + 2*(v-128)*(1-Kr)

g = y - 2*(u-128)*(1-Kb)*Kb/Kg - 2*(v-128)*(1-Kr)*Kr/Kg

b = y + 2*(u-128)*(1-Kb)```  Quote
3. Too many errors:
1073741824 colors tested
1073739073 colors differed

My original code had zero errors.

Code:
```#include <stdio.h>
#include <float.h>

#define Kr 0.3
#define Kg 0.59
#define Kb 0.11

unsigned short R, G, B, Rd, Gd, Bd;
short Y,U,V;

void rgb_to_yuv(void)
{double fr, fg, fb, fy, fu, fv;

fr = (double)R;
fg = (double)G;
fb = (double)B;

fy = Kr*fr + Kg*fg + Kb*fb;

fv = 0.5*fr - 0.5*fg*Kg/(1-Kr) - 0.5*fb*Kb/(1-Kr) + 128;

fu = -0.5*fr*Kr/(1-Kb) - 0.5*fg*Kg/(1-Kb) + 0.5*fb + 128;

Y = (int)fy+0.5;
U = (int)fu+0.5;
V = (int)fv+0.5;
}

void yuv_to_rgb(void)
{double fy, fu, fv;
fy = (double)Y;
fu = (double)U;
fv = (double)V;

Rd = fy + 2*(fv-128)*(1-Kr);
Gd = fy - 2*(fu-128)*(1-Kb)*Kb/Kg - 2*(fv-128)*(1-Kr)*Kr/Kg;
Bd = fy + 2*(fu-128)*(1-Kb);

}

int main(void) {

unsigned long count, diff;
count = 0;
diff = 0;

puts("Testing...");

for (R=0; R<=1023; R++)
{
for (G=0; G<=1023; G++)
{
for (B=0; B<=1023; B++)
{

count++;
rgb_to_yuv();
yuv_to_rgb();

if (R!=Rd || G!=Gd || B!=Bd) { diff++; }

}
}
}

printf("\n%d colors tested\n", count);
printf("%d colors differed\n", diff);

}```  Quote
4. Error is error. Or maybe not.
Your calculations only test for exact matches, with no accounting for rounding. A more real-world test would include some range of tolerance.

But it has always been a given that round trip colorspace conversions are not lossless.
Scott  Quote
5. But it has always been a given that round trip colorspace conversions are not lossless.
Scott
I'm wondering if it need be so.

I wonder about these ever-changing luma coefficients. How finely do they need to slice them? Is there a discrenable difference among 0.11, 0.114 and 0.0593, especially with all the error that is introduced.

Here is a more recent version. Anyone may try compiling it and monkey around with it. One thing is that it has a 48-bit YUV payload rather than a 32-bit payload.

Code:
```#include <stdio.h>
#include <float.h>

//BT.601 coefficients
#define Kr 0.299
#define Kg 0.587
#define Kb 0.114

//BT.2020 coefficients
//#define Kr 0.2627
//#define Kg 0.678
//#define Kb 0.0593

//#KR = 0.299:#KG = 0.587:#KB = 0.114 ;REC.601
//2020 coefficients 0.2627 for red, 0.6780 for green, and 0.0593 for blue

unsigned short R, G, B, Rd, Gd, Bd;
float Y,U,V;

short KR,KG,KB;

void rgb_to_yuv(void)
{
Y = (KR*R + KG*G + KB*B);
U = (B * KB) - Y;
V = (R * KR) - Y;
}

void yuv_to_rgb(void)
{
Rd = ((Y+V)/KR);
Gd = -(Y + U + V)/KG;
Bd = ((Y+U)/KB);
}

int main(void) {

KR= Kr*10;
KG= Kg*10;
KB= Kb*10;

unsigned long count, diff;
count = 0;
diff = 0;

puts("Testing...");

for (R=0; R<=1023; R++)
{
for (G=0; G<=1023; G++)
{
for (B=0; B<=1023; B++)
{

count++;
rgb_to_yuv();
yuv_to_rgb();

//printf("R:  %d \n",R);
//printf("G:  %d \n",G);
//printf("B:  %d \n",B);

//printf("Kr:  %f \n",Kr);

if (R!=Rd || G!=Gd || B!=Bd) { diff++; }

}
}
}

printf("\n%d colors tested\n", count);
printf("%d colors differed\n", diff);

printf("Y:  %f \n",Y);
printf("U:  %f \n",U);
printf("V:  %f \n",V);

}```  Quote
6. Error is error. Or maybe not.
Your calculations only test for exact matches, with no accounting for rounding. A more real-world test would include some range of tolerance.

But it has always been a given that round trip colorspace conversions are not lossless.
Scott
In working with this code, a lot of the error seems to be plain old floating-point error. So maybe as long as an RGB value is +/- 1 of the expected value, that's probably as good as it's going to get if we're dealing with floating-point coefficients.  Quote
7. It may seem counter-intuitive, but it is good practice to add (usu 1/3LSB triangular or Gaussian) dithering to any conversion equation prior to rounding or truncating in order to maintain overall fidelity vs. quantization.
This is particularly true in audio & photo/video realms.

Scott  Quote
8. It may seem counter-intuitive, but it is good practice to add (usu 1/3LSB triangular or Gaussian) dithering to any conversion equation prior to rounding or truncating in order to maintain overall fidelity vs. quantization.
This is particularly true in audio & photo/video realms.

Scott
I don't know how to code that. Do you?  Quote
9. If we're going to tolerate rounding error, why not simply round the coefficients to integers and do it all in integer arithmetic? That way, what comes out is what went in.

Here are the rounded coefficents as integers:

Code:
```601:   3,6,1

709:   2,7,1

NTSC:  3,6,1

2020:  3,7,1```  Quote
10. But it has always been a given that round trip colorspace conversions are not lossless.
Scott
I'm wondering if it need be so.
If you're using integers and the same number of bits, yes. Look at the RGB cube inside the YUV cube. https://software.intel.com/en-us/node/503873

With limited range rec.601 YUV the RGB cube only occupies about 1/6 of the volume of the YUV cube. So, for example, with 8 bit RGB you have 16 million different RGB values, but using an 8 bit YUV cube those 16 million RGB values map to only ~2.7 million different YUV values. Ie, on average, six different RGB values map to the same YUV value. Then when returning to RGB there's no way to tell which of those six RGB values was the original one.

About 2 more bits (on each axis) of YUV will let you encode all 16 million RGB values as different YUV values. So 8 bit RGB can be converted to 10 bit YUV, and back to 8 bit RGB without errors.  Quote
11. Is it realistic to think in terms of 10-bit RGB?  Quote
12. Is it realistic to think in terms of 10-bit RGB?
10 bit RGB to 10 bit YUV will suffer from the same problem. The YUV cube needs 2 more bits of precision to get a lossless round trip.  Quote
13. And by the way, if you use "random" coefficients (eg, 3, 6, 1) your YUV values will be different than the standard. Your YUV videos will not play back with the correct colors with other software.  Quote
14. 10 bit RGB to 10 bit YUV will suffer from the same problem. The YUV cube needs 2 more bits of precision to get a lossless round trip.
So for 10-bit RGB we're looking at a 36-bit payload, or 5 bytes with room to spare.

if you use "random" coefficients (eg, 3, 6, 1) your YUV values will be different than the standard. Your YUV videos will not play back with the correct colors with other software.
I wouldn't use the word "random", but "non-standard".

Due to the rounding errors we've been discussing, they're not going to have perfect colors anyway, so choose your poison.

I wonder if it's realistic to be worried about multi-generation duplication, where the quality is slightly degraded with each successive generation due to cumulative encoding errors. I'm old enough to remember that this was a big concern with analog videotape.

I would love to see the whole deal rethought but it'll never happen.  Quote
15. Your calculations only test for exact matches, with no accounting for rounding. A more real-world test would include some range of tolerance.Scott
Working from the code given here: http://avisynth.nl/index.php/Color_conversions

I have changed the calculations of U and V from:
Code:
```U = (B-Y)/(1-Kb) = - R * Kr/(1-Kb) - G * Kg/(1-Kb) + B
V = (R-Y)/(1-Kr) = R - G * Kg/(1-Kr) - B * Kb/(1-Kr)```
to

Code:
```U = B*Kb - Y;
V = R*Kr - Y;```  Quote
16. Here is code which gives zero errors, i.e. a perfect round trip from RGB to YUV and back.

Y, U and V must be floats.

Code:
```#include <stdio.h>

//BT.709 coefficients
//#define Kr 0.2126
//#define Kg 0.7152
//#define Kb 0.0722

//BT.2020 coefficients
#define Kr 0.2627
#define Kg 0.678
#define Kb 0.0593

unsigned short R, G, B, Rd, Gd, Bd;
float Y,U,V;

void rgb_to_yuv(void)
{
Y = Kr*R + Kg* G + Kb*B;
V = R*Kr - Y;
U = B*Kb - Y;
}

void yuv_to_rgb(void)
{
Rd = ((Y+V)/Kr)+0.5;
Gd = (-(Y + U + V)/Kg)+0.5;
Bd = ((Y+U)/Kb)+0.5;
}

int main(void) {

unsigned long diff;
diff = 0;

puts("Testing...");

for (R=0; R<=255; R++)
{
for (G=0; G<=255; G++)
{
for (B=0; B<=255; B++)
{

rgb_to_yuv();
yuv_to_rgb();

if (R != Rd || G != Gd || B != Bd) { diff++; }
}
}
}

printf("%d colors differed\n", diff);

}```  Quote
17. You don't seem to understand the issue. There's no doubt you can convert 8 bit integer RGB to 32 bit floats and back to 8 bit integer RGB without errors. The issue is that you can't go from 8 bit integer RGB to 8 bit integer YUV and back to 8 bit integer RGB without errors.  Quote
18. The issue is that you can't go from 8 bit integer RGB to 8 bit integer YUV and back to 8 bit integer RGB without errors.
What is the magnitude of those errors? Are they a little or a lot?  Quote
19. Enough to be noticeable.  Quote
20. The issue is that you can't go from 8 bit integer RGB to 8 bit integer YUV and back to 8 bit integer RGB without errors.
What is the magnitude of those errors? Are they a little or a lot?
1 or 2 units of R, G, and/or B. Look at the image in post #10. The entire RGB cube occupies only about 1/6 of the limited range YUV cube. I.e. 16 million RGB colors map to only ~3 million different YUV values. So on average, 6 different RGB triplets map to each YUV triplet. On conversion back from YUV to RGB there's no way of knowing which of the six triplets is correct.  Quote
21. In practice, it's not that bad:
Code:
```A=ColorbarsHD
## use any YV24 source here

B=A.ConvertToRGB32(matrix="Rec709")
\  .ConvertToYV24( matrix="Rec709")

return Subtract(A, B)

## another test (view this frame-by-frame, not in real time)
return Interleave(
\   A.Subtitle("Original", align=5),
\   B.Subtitle("Converted", align=5)
\ )```
The differences are very slight - except where color gamut is clipped.
Watch for subtle banding issues.

Converting through many round trips; what will happen? The answer may surprise you...
Code:
```C=B.ConvertToRGB32(matrix="Rec709")
\  .ConvertToYV24( matrix="Rec709")
\  .ConvertToRGB32(matrix="Rec709")
\  .ConvertToYV24( matrix="Rec709")
\  .ConvertToRGB32(matrix="Rec709")
\  .ConvertToYV24( matrix="Rec709")
\  .ConvertToRGB32(matrix="Rec709")
\  .ConvertToYV24( matrix="Rec709")

return Subtract(B, C)```  Quote
22. raffriff42: Why don't you write some compilable low-level code to demonstrate this, preferably in C? I have posted several examples so you could modify one of them.  Quote
23. Converting through many round trips; what will happen?
It depends on the particular software. AviSynth is pretty good about avoiding incremental losses. Other software may not be. And one doesn't just convert back and forth for no reason. One does so because one needs to perform some operation in RGB or YUV that can't easily be done in the other. Consider this sequence:

Code:
```ConvertToYV24(matrix="rec709").ConvertToRGB(matrix="rec709").RGBAdjust(bb=1)
If the conversions were lossless only B=255 would be lost (becoming B=254). But banding is visible in blue gradients after only two round trips.

So the general rule is to keep YUV/RGB conversions to a minimum.  Quote
24. raffriff42: Why don't you write some compilable low-level code to demonstrate this, preferably in C? I have posted several examples so you could modify one of them.
Sorry chris319, I haven't the time or the talent.

And one doesn't just convert back and forth for no reason. One does so because one needs to perform some operation in RGB or YUV that can't easily be done in the other.
Good point. In the past I've destroyed video with too much convert-filter-convert; I'm more careful now   Quote
25. Sorry chris319, I haven't the time or the talent.
You have some pretty definite ideas about how these things work. I've done the coding, so just plug in your equations There are on-line C compilers so lack of a compiler is not an issue.

https://www.tutorialspoint.com/compile_c_online.php

You don't just plug numbers into what amounts to a black box and call it good.

one doesn't just convert back and forth for no reason.
This brings up the issue of cumulative errors, which I don't want to get into.

Here is a weird phenomenon: different errors depending on the channel examined. Try uncommenting the different error test lines.

Code:
```#include <stdio.h>

//BT.2020 coefficients
#define Kr 0.2627
#define Kg 0.678
#define Kb 0.0593

unsigned short R, G, B, Rd, Gd, Bd, Y;
short U;
short V;

void rgb_to_yuv(void)
{
//Y = ((Kr*R + Kg* G + Kb*B)+0.0);
Y = (unsigned short)((Kr*R + Kg* G + Kb*B)+0.0);
V = (short)(R*Kr - Y);
U = (short)(B*Kb - Y);
}

void yuv_to_rgb(void)
{
Rd = ((Y+V)/Kr)+0;
Gd = (-(Y + U + V)/Kg)+0;
Bd = ((Y+U)/Kb)+0;
}

int main(void) {

unsigned long diff;
diff = 0;

short err = 4;

puts("Testing...");

for (R=0; R<=255; R++)
{
for (G=0; G<=255; G++)
{
for (B=0; B<=255; B++)
{

rgb_to_yuv();
yuv_to_rgb();

if (R-Rd > err|| Rd-R > err)
//if (G-Gd > err|| Gd-G > err)
//if (B-Bd > err|| Bd-B > err)

{diff++;}

}
}
}

printf("%d colors differed\n", diff);

}```  Quote
26. Here is a weird phenomenon: different errors depending on the channel examined.
It's exactly what one expects. Not weird at all. The conversion is a rotation and scaling of the axis. Sometimes only one or two of the triplets will come back wrong.  Quote
27. Here is a weird phenomenon: different errors depending on the channel examined.
It's exactly what one expects. Not weird at all. The conversion is a rotation and scaling of the axis. Sometimes only one or two of the triplets will come back wrong.
I will have more later on, but some of the errors are quite large and don't seem right, again depending on the channel under examination.  Quote
28. I posted ANSI C code to perform 8 bit RGB to 10 bit YUV and back here:

You can easily modify that for 8 bit YUV by removing the *4 scaling that was used to multiply 8 bit values to get 10 bit values, and the /4 scaling to convert 10 bit YUV to 8 bit YUV.  Quote
29. I remember that code and was looking at it just today. It's quite complicated and I don't fully understand it. IIRC it works with BT.601 but I can't see how you would incorporate other color spaces such as BT.709 or BT.2020, plus I am not up to speed on matrix math.

I removed the *4 and /4 scaling and no joy.  Quote
30. What do you mean "no joy"? The code didn't compile? You got erroneous results? In what way?

It's not written as matrix operations, it's just algebra, the same as your code except that it uses limited range rec.601 where black is at Y=16 and white = 235. And U and V range from 16 to 240. The 4.0 multiplier at the end converts to 10 bit limited range YUV where Y ranges from 64 to 940. Adding 0.5 before converting to integers gives rounding instead of truncation (for example, 0.666 rounds up to 1 rather than truncating to 0).

For example, in the line that starts
Code:
`fy = ( 0.256789 * fr`
is the same as your code:

Code:
`fy = KR * fr`
except 0.256789 comes from prescaling KR by the limited Y range, 219, out of full range of 255.

Code:
`KR * (235-16) / (255-0)`
where KR is 0.299. 16.0 is added at the end because the Y range starts at 16 (in 8 bit rec.601 YUV), not 0. All the other coefficients were scaled similarly.  Quote

Statistics