将 YCbCr 转换为 RGB 是否可逆?
Is converting YCbCr to RGB reversible?
我正在尝试一些不同的图像格式,运行 发现一些奇怪的东西。当从 RGB 转换为 YCbCr 然后再转换回 RGB 时,结果与我开始时的结果非常相似(像素值的差异几乎总是小于 4)。但是,当我从 YCbCr 转换为 RGB 然后再转换回 YCbCr 时,我经常会得到截然不同的值。有时一个值会相差超过 40。
我不确定这是为什么。我的印象是,可以通过 YCbCr 表示的颜色是 RGB 颜色的子集,但看起来这是完全错误的。 YCbCr 中是否有一些已知的颜色子集可以转换为 RGB,然后再恢复为原始值?
我用来转换的代码(基于this site):
def yuv2rgb(yuv):
ret = []
for rows in yuv:
row = []
for y, u, v in rows:
c = y - 16
d = u - 128
e = v - 128
r = clamp(1.164*c + 1.596*e, 16, 235)
g = clamp(1.164*c - 0.392*d - 0.813*e, 16, 240)
b = clamp(1.164*c + 2.017*d , 16, 240)
row.append([r, g, b])
ret.append(row)
return ret
def rgb2yuv(rgb):
ret = []
for rows in rgb:
row = []
for r, g, b in rows:
y = int( 0.257*r + 0.504*g + 0.098*b + 16)
u = int(-0.148*r - 0.291*g + 0.439*b + 128)
v = int( 0.439*r - 0.368*g - 0.071*b + 128)
row.append([y, u, v])
ret.append(row)
return ret
编辑:
我创建了这个问题的基本 3D 图。所有的点都是值差小于 10 的点。它构成了一个非常有趣的形状。 X是Cb,Y是Cr,Z是Y。
据我所知,您应该能够以最小的精度损失来回转换这两种格式。
站点 you have mentioned 有另一组转换公式,称为 "RGB to full-range YCbCr" 和 "Full-range YCbCr to RGB",我相信这些是你应该使用的,我认为它应该能让你向前转换没有任何问题回来。
编辑:
由于这些公式对您不起作用,我将在 android 中分享我用于 RGB 和 YUV 之间转换的公式:
R = clamp(1 * Y + 0 * (U - 128) + 1.13983 * (V - 128), 0, 255);
G = clamp(1 * Y + -0.39465 * (U - 128) + -0.5806 * (V - 128), 0, 255);
B = clamp(1 * Y + 2.03211 * (U - 128) + 0 * (V - 128), 0, 255);
Y = clamp(0.299 * R + 0.587 * G + 0.114 * B, 0, 255);
U = clamp(-0.14713 * R + -0.28886 * G + 0.436 * B + 128, 0, 255);
V = clamp(0.615 * R + -0.51499 * G + -0.10001 * B + 128, 0, 255);
我刚刚尝试过,它似乎来回有效。注意 128 的加法和减法,因为这个 YUV 表示由无符号字节范围 (0..255) 组成,RGB 也是如此(像往常一样),所以如果你真的需要 (16..235) 和 (16.. 240) YCbCr 的范围,您可能需要另一个公式。
正如我在评论中所说,您的第一个问题是您在 yuv2rgb
.
的循环内使用 y
而不是 c
进行计算
第二个问题是您将 RGB 值限制在错误的范围内:RGB 应该是 0..255。
RGB 计算应如下所示:
r = clamp(1.164*c + 1.596*e, 0, 255)
g = clamp(1.164*c - 0.392*d - 0.813*e, 0, 255)
b = clamp(1.164*c + 2.017*d , 0, 255)
不,根本不是。 [上面讨论的所有内容都是针对 8 位的。]在全范围 R'G'B' 到有限范围 YCbCr 的情况下很明显(没有 bijection)。比如你可以在这里测试:
https://res18h39.netlify.app/color
全范围 R'G'B' 值 238、77、45 被编码为带有 BT.601 矩阵的有限 YCbCr:学校舍入后你将得到有限范围 120、90、201,但如果你将它舍入你会得到 238, 77, 44 回到 R'G'B'。和 238, 77, 44 值会去到相同的值。哎呀。这是:游戏结束。
在全范围 RGB 到全范围 YCbCr 的情况下... YCbCr 中有一些值为负 R'、G'、B'。 (例如,在有限范围内的 YCbCr BT.709 值 139、151、24 将是 RGB -21、182、181,只需转换为全范围 YCbCr。)同样,没有双射。
接下来,限制范围 R'G'B' 到限制范围 YCbCr...同样,没有双射。 YCbCr 中的黑色实际上是 16、128、128,而且只有这个。其他的16,x,y都不允许[在xvYCC里,不标准],而在R,G,B里,235,128,128都一样。而前面的负数R'、G'、B'当然也适用
从有限范围到全范围,我不知道。
我正在尝试一些不同的图像格式,运行 发现一些奇怪的东西。当从 RGB 转换为 YCbCr 然后再转换回 RGB 时,结果与我开始时的结果非常相似(像素值的差异几乎总是小于 4)。但是,当我从 YCbCr 转换为 RGB 然后再转换回 YCbCr 时,我经常会得到截然不同的值。有时一个值会相差超过 40。
我不确定这是为什么。我的印象是,可以通过 YCbCr 表示的颜色是 RGB 颜色的子集,但看起来这是完全错误的。 YCbCr 中是否有一些已知的颜色子集可以转换为 RGB,然后再恢复为原始值?
我用来转换的代码(基于this site):
def yuv2rgb(yuv):
ret = []
for rows in yuv:
row = []
for y, u, v in rows:
c = y - 16
d = u - 128
e = v - 128
r = clamp(1.164*c + 1.596*e, 16, 235)
g = clamp(1.164*c - 0.392*d - 0.813*e, 16, 240)
b = clamp(1.164*c + 2.017*d , 16, 240)
row.append([r, g, b])
ret.append(row)
return ret
def rgb2yuv(rgb):
ret = []
for rows in rgb:
row = []
for r, g, b in rows:
y = int( 0.257*r + 0.504*g + 0.098*b + 16)
u = int(-0.148*r - 0.291*g + 0.439*b + 128)
v = int( 0.439*r - 0.368*g - 0.071*b + 128)
row.append([y, u, v])
ret.append(row)
return ret
编辑:
我创建了这个问题的基本 3D 图。所有的点都是值差小于 10 的点。它构成了一个非常有趣的形状。 X是Cb,Y是Cr,Z是Y。
据我所知,您应该能够以最小的精度损失来回转换这两种格式。
站点 you have mentioned 有另一组转换公式,称为 "RGB to full-range YCbCr" 和 "Full-range YCbCr to RGB",我相信这些是你应该使用的,我认为它应该能让你向前转换没有任何问题回来。
编辑:
由于这些公式对您不起作用,我将在 android 中分享我用于 RGB 和 YUV 之间转换的公式:
R = clamp(1 * Y + 0 * (U - 128) + 1.13983 * (V - 128), 0, 255);
G = clamp(1 * Y + -0.39465 * (U - 128) + -0.5806 * (V - 128), 0, 255);
B = clamp(1 * Y + 2.03211 * (U - 128) + 0 * (V - 128), 0, 255);
Y = clamp(0.299 * R + 0.587 * G + 0.114 * B, 0, 255);
U = clamp(-0.14713 * R + -0.28886 * G + 0.436 * B + 128, 0, 255);
V = clamp(0.615 * R + -0.51499 * G + -0.10001 * B + 128, 0, 255);
我刚刚尝试过,它似乎来回有效。注意 128 的加法和减法,因为这个 YUV 表示由无符号字节范围 (0..255) 组成,RGB 也是如此(像往常一样),所以如果你真的需要 (16..235) 和 (16.. 240) YCbCr 的范围,您可能需要另一个公式。
正如我在评论中所说,您的第一个问题是您在 yuv2rgb
.
y
而不是 c
进行计算
第二个问题是您将 RGB 值限制在错误的范围内:RGB 应该是 0..255。
RGB 计算应如下所示:
r = clamp(1.164*c + 1.596*e, 0, 255)
g = clamp(1.164*c - 0.392*d - 0.813*e, 0, 255)
b = clamp(1.164*c + 2.017*d , 0, 255)
不,根本不是。 [上面讨论的所有内容都是针对 8 位的。]在全范围 R'G'B' 到有限范围 YCbCr 的情况下很明显(没有 bijection)。比如你可以在这里测试:
https://res18h39.netlify.app/color
全范围 R'G'B' 值 238、77、45 被编码为带有 BT.601 矩阵的有限 YCbCr:学校舍入后你将得到有限范围 120、90、201,但如果你将它舍入你会得到 238, 77, 44 回到 R'G'B'。和 238, 77, 44 值会去到相同的值。哎呀。这是:游戏结束。
在全范围 RGB 到全范围 YCbCr 的情况下... YCbCr 中有一些值为负 R'、G'、B'。 (例如,在有限范围内的 YCbCr BT.709 值 139、151、24 将是 RGB -21、182、181,只需转换为全范围 YCbCr。)同样,没有双射。
接下来,限制范围 R'G'B' 到限制范围 YCbCr...同样,没有双射。 YCbCr 中的黑色实际上是 16、128、128,而且只有这个。其他的16,x,y都不允许[在xvYCC里,不标准],而在R,G,B里,235,128,128都一样。而前面的负数R'、G'、B'当然也适用
从有限范围到全范围,我不知道。