目录
简介
Full Range公式推导
Limit Range推导
验证测试
参考资料
简介
RGB与YUV之间的转换有很多种标准,不同标准系数不一样,而且经常容易搞混淆,另外还有full range和limitrange的不同。其实这些转换系数都是推导出来的,有理论支撑的,并不是标准里直接给出来的系数。本篇文章主要是来介绍公式的推导。
Full Range公式推导
从YUV的定义中可知Y代表红绿蓝的比例混合,U代表蓝色与亮度Y的差量,V代表红色与亮度Y的差量,那么求RGB转YUV就是求混合比例。求YUV转RGB就是它的逆变化。如下图所示用公式展现了如何用混合比例实现RGB转YUV,其中KrKgKb就是RGB的混合比例。
其中,KrKgKb三个系数是RGB转XYZ矩阵的第二行,RGB转XYZ可以参考【精选】颜色空间转换-从RGB到LCH-亮度饱和度色度_rgb转lch-CSDN博客。有了这三个系数,RGB转YUV的系数就确定了,所以RGB转YUV完全是由色域的色坐标决定的。计算RGB2YUV的系数只要对YUV2RGB求逆即可。
参考代码如下:
def GetFullRangeCoef(xy):
matRGB2XYZ, matXYZ2RGB = GetRGBXYZMatrices(xy)
rgb2yuv_coef = np.zeros((3,3), np.float32)
rgb2yuv_coef[0, :] = matRGB2XYZ[1, :]
kr = matRGB2XYZ[1, 0]
kg = matRGB2XYZ[1, 1]
kb = matRGB2XYZ[1, 2]
rgb2yuv_coef[1, 0] = kr / (2*(kb - 1))
rgb2yuv_coef[1, 1] = kg / (2*(kb - 1))
rgb2yuv_coef[1, 2] = 0.5
rgb2yuv_coef[2, 0] = 0.5
rgb2yuv_coef[2, 1] = kg / (2*(kr - 1))
rgb2yuv_coef[2, 2] = kb / (2 * (kr - 1))
yuv2rgb_coef = np.linalg.inv(rgb2yuv_coef)
return rgb2yuv_coef, yuv2rgb_coef
Limit Range推导
YCbCr的范围又叫limit range,tv range,YUV的范围又叫full range、pc range。
电视机中的YUV又叫YCbCr(Cb是ColorBlue缩写,Cr是ColorRed缩写),YCbCr为了解决吉布斯现象对范围进行了调整,下图说明了正弦函数模拟原始信号时波形的峰值超过原始信号8.9%,需要缩小YCbCr的范围防止溢出,譬如8位YUV的范围是[0,255],那么8位YCbCr的范围就是Y[16,235]UV[16,240],(255−235)/(235−16)=9.1略大于大于8.9%,16/(235−16)=7.3略小于8.9%。
limit range的YCbCr转RGB的流程如下:
以8bit为例,Yscale=255 / (235 - 16),Uscale=255 / (240 - 16),Vscale=255 / (240 - 16),Yoffset=-16,Uoffset=-128,Voffset=-128。RGB2YCbCr只要对其进行求逆即可。
limit range公式推导参考代码如下:
def GetLimitRangeCoef(yuv2rgb_full):
scale_mat = np.zeros((3, 3), np.float)
scale_mat[0, 0] = 255 / (235 - 16)
scale_mat[1, 1] = 255 / (240 - 16)
scale_mat[2, 2] = 255 / (240 - 16)
yuv2rgb_limit = yuv2rgb_full @ scale_mat
rgb2yuv_limit = np.linalg.inv(yuv2rgb_limit)
return rgb2yuv_limit, yuv2rgb_limit
验证测试
我们常用的YUV与RGB转换的标准主要有BT709,BT601,BT2020,他们的色坐标分别为:
#BT709
xysRGB = np.array([
[0.64, 0.33],
[0.30, 0.60],
[0.15, 0.06],
[0.3127, 0.3290]
])
#BT2020
xyBT2020 = np.array([
[0.708, 0.292],
[0.17, 0.797],
[0.131, 0.046],
[0.3127, 0.3290]
])
#BT601
xyNTSC = np.array([
[0.67, 0.33],
[0.21, 0.71],
[0.14, 0.08],
[0.3101, 0.3162]
])
然后参考代码如下:
def TestsRGB709():
#BT709
xysRGB = np.array([
[0.64, 0.33],
[0.30, 0.60],
[0.15, 0.06],
[0.3127, 0.3290]
])
#BT2020
xyBT2020 = np.array([
[0.708, 0.292],
[0.17, 0.797],
[0.131, 0.046],
[0.3127, 0.3290]
])
#BT601
xyNTSC = np.array([
[0.67, 0.33],
[0.21, 0.71],
[0.14, 0.08],
[0.3101, 0.3162]
])
rgb2yuv_coef, yuv2rgb_coef = GetFullRangeCoef(xyNTSC)
print('rgb2yuv_full:', np.round(rgb2yuv_coef, 4))
print('yuv2rgb_full:', np.round(yuv2rgb_coef, 4))
rgb2yuv_limit, yuv2rgb_limit = GetLimitRangeCoef(yuv2rgb_coef)
print('rgb2yuv_limit:', np.round(rgb2yuv_limit, 4))
print('yuv2rgb_limit:', np.round(yuv2rgb_limit, 4))
BT601得到的结果如下:
rgb2yuv_full:
[[ 0.2989 0.5866 0.1144]
[-0.1688 -0.3312 0.5 ]
[ 0.5 -0.4184 -0.0816]]
yuv2rgb_full:
[[ 1. -0. 1.4021]
[ 1. -0.3455 -0.7145]
[ 1. 1.7711 0. ]]
rgb2yuv_limit:
[[ 0.2567 0.5038 0.0983]
[-0.1483 -0.291 0.4392]
[ 0.4392 -0.3675 -0.0717]]
yuv2rgb_limit:
[[ 1.1644 -0. 1.5962]
[ 1.1644 -0.3933 -0.8134]
[ 1.1644 2.0162 0. ]]
BT709得到的结果:
rgb2yuv_full:
[[ 0.2126 0.7152 0.0722]
[-0.1146 -0.3854 0.5 ]
[ 0.5 -0.4542 -0.0458]]
yuv2rgb_full:
[[ 1. -0. 1.5747]
[ 1. -0.1873 -0.4682]
[ 1. 1.8556 -0. ]]
rgb2yuv_limit:
[[ 0.1826 0.6142 0.062 ]
[-0.1007 -0.3386 0.4392]
[ 0.4392 -0.3989 -0.0403]]
yuv2rgb_limit:
[[ 1.1644 -0. 1.7927]
[ 1.1644 -0.2132 -0.533 ]
[ 1.1644 2.1124 -0. ]]
BT2020得到的结果:
rgb2yuv_full:
[[ 0.2627 0.678 0.0593]
[-0.1396 -0.3604 0.5 ]
[ 0.5 -0.4598 -0.0402]]
yuv2rgb_full:
[[ 1. -0. 1.4746]
[ 1. -0.1646 -0.5714]
[ 1. 1.8814 0. ]]
rgb2yuv_limit:
[[ 0.2256 0.5823 0.0509]
[-0.1227 -0.3166 0.4392]
[ 0.4392 -0.4039 -0.0353]]
yuv2rgb_limit:
[[ 1.1644 -0. 1.6787]
[ 1.1644 -0.1873 -0.6504]
[ 1.1644 2.1418 0. ]]
可以看到得到的系数和我们常用的是一样的,这样只要给定色域,其实就有一组对应的转换系数,完全是由色域大小决定的。
参考资料:
HDR转SDR实践之旅(四)YUV转RGB矩阵推导 - 掘金
网上流传的矩阵错了?浅谈如何正确推导视频YUV转RGB矩阵 - 知乎