VideoHelp Forum
+ Reply to Thread
Page 1 of 3
1 2 3 LastLast
Results 1 to 30 of 71
Thread
  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 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)
    Last edited by raffriff42; 17th Nov 2016 at 06:14. Reason: code
    Quote 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 Quote  
  4. Member Cornucopia's Avatar
    Join Date
    Oct 2001
    Location
    Deep in the Heart of Texas
    Search PM
    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 Quote  
  5. Originally Posted by Cornucopia View Post
    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 Quote  
  6. Originally Posted by Cornucopia View Post
    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 Quote  
  7. Member Cornucopia's Avatar
    Join Date
    Oct 2001
    Location
    Deep in the Heart of Texas
    Search PM
    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 Quote  
  8. Originally Posted by Cornucopia View Post
    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 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 Quote  
  10. Originally Posted by chris319 View Post
    Originally Posted by Cornucopia View Post
    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.
    Last edited by jagabo; 18th Nov 2016 at 21:32.
    Quote Quote  
  11. Is it realistic to think in terms of 10-bit RGB?
    Quote Quote  
  12. Originally Posted by chris319 View Post
    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 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 Quote  
  14. Originally Posted by jagabo View Post
    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 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;
    Last edited by chris319; 6th Feb 2017 at 07:41.
    Quote 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);
    
    }
    Last edited by chris319; 6th Feb 2017 at 08:05.
    Quote 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 Quote  
  18. Originally Posted by jagabo View Post
    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?
    Last edited by chris319; 6th Feb 2017 at 16:27.
    Quote Quote  
  19. Member Cornucopia's Avatar
    Join Date
    Oct 2001
    Location
    Deep in the Heart of Texas
    Search PM
    Enough to be noticeable.
    Quote Quote  
  20. Originally Posted by chris319 View Post
    Originally Posted by jagabo View Post
    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 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 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 Quote  
  23. Originally Posted by raffriff42 View Post
    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)
    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 Quote  
  24. Originally Posted by chris319 View Post
    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.

    Originally Posted by jagabo View Post
    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 Quote  
  25. Originally Posted by raffriff42 View Post
    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 Quote  
  26. Originally Posted by chris319 View Post
    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.
    Last edited by jagabo; 6th Feb 2017 at 22:28.
    Quote Quote  
  27. Originally Posted by jagabo View Post
    Originally Posted by chris319 View Post
    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 Quote  
  28. I posted ANSI C code to perform 8 bit RGB to 10 bit YUV and back here:

    https://forum.videohelp.com/threads/376996-Lossless-Workflows-using-Premiere-Pro-Virtua...=1#post2446709

    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 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.
    Last edited by chris319; 7th Feb 2017 at 04:50.
    Quote 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 Quote  



Similar Threads

Visit our sponsor! Try DVDFab and backup Blu-rays!