从Rec. 709到sRGB的正确转换方法

9

如何正确将存储为Y'CrCb(使用rec. 709)的颜色转换为sRGB?

我正在处理HDTV视频,并使用libavcodec提取原始数据。虽然我成功地进行了转换,但我仍然不确定我是否做得正确。VLC提供一个结果,在Gimp中使用'compose'进行转换会得到另一个结果,而使用网上的代码也不一致。因此,我没有找到一个可靠的参考来进行比较。

我的研究和当前的最佳选择如下。(值以浮点数表示,范围为0.0-1.0)我最不确定的是伽马校正。它使它比我预期的要亮一些,但我不能说它看起来有错...

Studio-swing去除

8位Y'范围为16到235。Cr和Cb范围为16到240,并且居中于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;

我不确定是否安全假设你永远不会得到超出范围的值,或者你需要夹紧它。

对于更高的位深度,规格说明LSB将被忽略。那是什么意思?我也在使用10位编码的材料,所以这对我很有帮助。

从Y'CrCb到RGB

REC. 709规格说明如何将RGB转换为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 )

维基百科提供了一个似乎更准确的Cb和Cr的定义:

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

其中Kb和Kr是E'b和E'r的因子。规范中的值似乎是从这些方程式四舍五入得出的。

通过反转方程式(使用维基百科版本),可以找到RGB:

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

使用Cr和Cb直接进行G处理:

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

(y的系数为(1-kr-kb)/ kg,因为kr + kb + kg = 1,所以是kg / kg)

RGB转sRGB

我没有看到包括此步骤的任何代码示例。我们需要将由rec. 709指定的颜色空间转换为由sRGB指定的颜色空间。据我所知,两者之间唯一的区别是传输函数(即伽马值)。由rec. 709指定的XY坐标与sRGB相匹配,但我不知道为什么sRGB包括“Z”坐标而rec. 709则不包括。这会有影响吗?(我对CIE XYZ一无所知。)

rec. 709规定了如何对线性RGB进行伽马编码:

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

我们需要反转它,但是线性截止值0.018在两个方程中给出的V值不同。那么反转版本的范围是什么?

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

sRGB也存在相同的问题,但已经修订为0.0031308,更加准确。我记得有人为sRGB创造了一个精确表示它的分数,但我再也找不到它了...

我目前使用以下内容:

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;

请查看http://www.martinreddy.net/gfx/faqs/colorconv.faq中的第6.5节。我目前只有移动网络连接,所以现在提供一个适当的答案有点困难... - Fredrik Pihl
我对随机转换函数不感兴趣,我有很多这样的函数。我想要的是能够为什么一个特定的函数是正确的进行论证的人。(顺便说一句,这个看起来是错误的。看看Y'的方程式,它使用了与rec. 709中指定的系数不匹配的系数。此外,它们甚至没有加起来等于1.0,这改变了Y'的范围。) - Sebastian Wahl
从定义上来看,Y'CbCr已经进行了伽马校正,没有必要再进行第二次校正。如果您想要非常精确,可以将其转换为线性格式,然后再转换回校正的sRGB格式,但对于大多数应用程序来说,这是过度的。 - Mark Ransom
我希望我的应用程序能够非常准确,因为它主要进行图像处理。然而,在转换为和从线性RGB转换时,我认为存在显着差异,特别是在暗色中。也许我做错了什么,这也是我提出这个问题的部分原因。另一个原因是,我认为您应该知道正确的方法,然后才能对例如实时应用程序进行逼近。这是您可以准确测量误差的唯一方法。(速度并不是我关心的重点。) - Sebastian Wahl
限制值不是0和255的原因是因为它们预计值会超出这些限制,并且它们想要一些余量。 - Mark Ransom
2个回答

1

为了正确地将线性sRGB转换为非线性sRGB(压缩过程)以及反向过程(反向压缩),我使用以下函数:

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;
}

注意:v 是线性的,V 是非线性的。
这些函数基于在此处找到的方程式: http://www.brucelindbloom.com/index.html?Eqn_RGB_to_XYZ.html 还有一个选项可以使用简化的sRGB,使用压缩函数v = V ^ gamma,其中gamma为2.2,如网站上所述。

尽管sRGB公式经过精心设计以模拟简单的2.2伽马值,但既然官方公式如此直接,就没有必要使用简化公式。 - Mark Ransom

-1
由 rec. 709 指定的 XY 坐标与 sRGB 匹配,这些是 xy,不是 XYZ 中的 XY。唉,首先XYZ是线性化后的步骤,因为sRGB已经使用BT.709色彩主基准了,正如你所说的那样。RGB线性、R'G'B'是非线性的,Y'Cb'Cr'也是非线性的。
我还在处理10位编码的材料,所以这对我很有用。这意味着您只需四舍五入即可获得正确的8位值。如果10位值的最后两位是10或11,则向上舍入到下一个8位值,否则向下舍入(00,01向下舍入)。LSB表示最低有效位。只需要不要忘记,1023应该四舍五入为255,而不是溢出。
我们需要将其反转,但是线性截止值0.018在两个方程中给出的V值不相同。

不需要反转任何东西。REC.601/REC.709/REC.2020的EOTF并不是OETF的反转,EOTF在BT.1886中有规定,对于理想的OLED显示器来说是2.4的完美伽马值,对于200流明环境光下的非完美LCD来说几乎是sRGB EOTF。这就是为什么Chrome只使用sRGB EOTF用于BT.709,这意味着根本没有EOTF,因为Windows默认使用它。

我记得有人设计了一个精确表示sRGB的分数,

它只是0.04045/12.92 == 0.003130804954,809/258400。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接