8

How do I correctly convert colors stored as Y'CrCb (using rec. 709) to sRGB?

I'm working with HDTV video and I'm extracting the raw data using libavcodec. While I have succeeded in making the conversion, I have yet to be confident that I'm doing it correctly. VLC provides one result, converting in Gimp using 'compose' results in another and using code from the web is not consistent either. So I haven't found a reliable reference to compare to.

My research and current best bet is below. (Values are in floating-point with the range 0.0-1.0) What I'm most uncertain about is the gamma-correction. It makes it a bit lighter than I would expect, but I can't say it looks wrong either...

Studio-swing removal

Y' ranges from 16 to 235 for 8-bit. Cr and Cb ranges from 16 to 240, and centers on 128.

y = (y - (16 / 255.0)) * ( 1 + 16.0 / 255.0 + (256-235) / 255.0 );
u = (u - (16 / 255.0)) * ( 1 + 16.0 / 255.0 + (256-240) / 255.0 );
v = (v - (16 / 255.0)) * ( 1 + 16.0 / 255.0 + (256-240) / 255.0 );

//Move chroma
u -= 0.5;
v -= 0.5;

I'm not sure if it is safe to assume that you will never get values outside the ranges, or if you need to clamp it.

For higher bit-depths the spec says that the LSBs are ignored. What does that mean? I'm also working with material encoded in 10-bit, so this is of interest to me.

From Y'CrCb to RGB

The rec. 709 specification tells how to convert RGB to Y'CrCb:

E'y = 0.2126 * E'r + 0.7152 * E'g + 0.0722 * E'b
E'cb = 0.5389 * ( E'b - E'y )
E'cr = 0.6350 * ( E'r - E'y )

Wikipedia provides what appears to be a slightly more accurate definition for Cb and Cr:

Pb = 0.5 * (B' - Y') / (1 - Kb)
Pr = 0.5 * (R' - Y') / (1 - Kr)

where Kb and Kr are the factors for E'b and E'r. The values from the spec. appears to be rounded from these equations.

RGB can be found by reversing the equations (using Wikipedia version):

double r = y + 2*(1.0-kr) * v;
double b = y + 2*(1.0-kb) * u;
double g = ( y - kr * rr - kb*rb ) / kg;

G can be done using Cr and Cb directly:

double g = y - 2*kr*(1-kr)/kg * v - 2*kb*(1-kb)/kg * u;

(The factor for y is (1-kr-kb)/kg, which is kg/kg as kr+kb+kg=1)

RGB to sRGB

I haven't seen any code examples including this step at all. We need to convert the color space specified by rec. 709 to the one specified in sRGB. AFAIK, the only difference between the two is the transfer function (i.e. gamma). The XY coordinates specified by rec. 709 matches sRGB, but I don't know why sRGB includes the 'Z' coordinate while rec. 709 does not. Does this make a difference? (I know nothing about CIE XYZ.)

The rec. 709 specifies how to gamma-encode linear RGB:

V = 1.099 * L^0.45 - 0.099    for    1 >= L >= 0.018
V = 4.500 * L                 for 0.018 > L >= 0

We need to reverse it, however the linear cutoff 0.018 does not give the same value for V in both equations. So what are the ranges for the reversed version?:

L = ( ( V + 0.099 ) / 1.099 ) ^ (1/0.45)    for  1 >= V >= ?
L = V / 4.5000                              for  ? >  V >= 0

sRGB had the same issue, but was revised to be 0.0031308 which is more accurate. I remember someone devised a fraction which precisely represented it for sRGB, but I cannot find it again...

I'm currently using the following:

double cutoff = 1.099 * pow( 0.018, 0.45 ) - 0.099;
v = ( v < cutoff ) ? 1.0/4.5 * v : pow( (v+0.099)/1.099, 1.0/0.45 );
v = ( v <= 0.0031308 ) ? 12.92 * v : 1.055*pow( v, 1.0/2.4 ) - 0.055;
Tom Pažourek
  • 9,582
  • 8
  • 66
  • 107
Sebastian Wahl
  • 324
  • 4
  • 11
  • have a look at section 6.5 in http://www.martinreddy.net/gfx/faqs/colorconv.faq I currently only have edge-connection on my mobile so it's a bit hard to provide a proper answer right now... – Fredrik Pihl Jul 09 '13 at 20:55
  • I'm not interested in a random conversion function, I have lots of those. I want someone who can argument for why a particular one is correct. (This one looks wrong btw. Look at the equation for Y', it uses coefficients that does not match those specified in rec. 709. Furthermore, they don't even sum up to 1.0, which changes the range of Y'.) – Sebastian Wahl Jul 10 '13 at 12:29
  • It's pretty clear by the definitions that Y'CbCr is already gamma corrected, there's no need to do it a second time. If you're trying to be super accurate you may want to convert to linear and then back to corrected sRGB, but that's overkill for most applications. – Mark Ransom May 03 '14 at 02:47
  • I do want to be quite accurate as my application is mainly doing image processing. I am however seeing (in my opinion) a significant difference from converting to and from linear RGB, especially in the dark colours. Perhaps I'm doing it incorrectly, which is partially why I asked this question. The other reason is that I think that you should know the correct method, before you can do approximations for e.g. real time applications. That is the only way you can accurately measure the error. (Not that speed is a big concern for me.) – Sebastian Wahl May 03 '14 at 09:14
  • The reason the limits aren't 0 and 255 is because they *expect* values to go beyond those limits and they wanted some headroom. – Mark Ransom May 13 '21 at 04:34

2 Answers2

1

For correct transformation from linear sRGB to non-linear sRGB (companding process) and the reverse process (inverse companding), I use the following functions:

public double Companding(double channel)
{
    double v = channel;
    double V = v <= 0.0031308 ? 12.92 * v : 1.055 * Math.Pow(v, 1 / 2.4d) - 0.055;
    return V;
}

public double InverseCompanding(double channel)
{
    double V = channel;
    double v = V <= 0.04045 ? V / 12.92 : Math.Pow((V + 0.055) / 1.055, 2.4);
    return v;
}

Note: v is linear, V is non-linear.

These functions are based on an equation found here: http://www.brucelindbloom.com/index.html?Eqn_RGB_to_XYZ.html

There is also an option to use simplified sRGB with companding function v = V ^ gamma, where gamma is 2.2, as noted on the website.

Tom Pažourek
  • 9,582
  • 8
  • 66
  • 107
  • Even though the sRGB formulas were carefully crafted to emulate a simple gamma of 2.2, there's no need to use the simplified formula when the official one is so straight-forward. – Mark Ransom May 13 '21 at 04:48
-1

The XY coordinates specified by rec. 709 matches sRGB,

Those are xy, not XY, it it not the same as XY from XYZ.

Sigh, first of all XYZ is step after linearisation, you do not need to go there, since sRGB uses BT.709 primaries already, as you said. RGB linear, R'G'B' is non-linear. Y'Cb'Cr' is non-linear too.

I'm also working with material encoded in 10-bit, so this is of interest to me.

That means that you can just round it to get correct values for 8 bit. If last two bits of 10 bit values are 10 or 11 you round up to next 8 bit value, otherwise down (00, 01 are round down). LSB means least significant bits. Just do not forget that 1023 should be rounded to 255, not overflow.

We need to reverse it, however the linear cutoff 0.018 does not give the same value for V in both equations.

No, you do not need to reverse anything. EOTF of REC.601/REC.709/REC.2020 is not the reverse of OETF, EOTF is specified in BT.1886 and is 2.4 perfect gamma for ideal OLED display and almost sRGB EOTF for not perfect LCD in 200 lux ambient light. That is why Chrome just uses sRGB EOTF for BT.709, which means "no" EOTF at all, since windows defaults to that.

I remember someone devised a fraction which precisely represented it for sRGB,

It is just 0.04045/12.92 == 0.003130804954, 809/258400.