除了支持 YUV 4:4:4 格式的颜色空间转换以外,OpenCV 还支持 RGB 直接转换为 YUV 下采样格式,或者 YUV 下采样格式直接转换为 RGB,也就是前面所说的第三类 code
。第三类 code
可表示为带有某个 FOCC
后缀的 COLOR_BGR2YUV_FOCC
以及反变换的 COLOR_YUV2BGR_FOCC
,其中 FOCC
指由 4 个 ASCII 字符所组成的代码,即 Four-Character Code,有点类似于 C/C++ 中的 enum
。FOCC
在个人程序中通常可以任意定义,但是也存在一些约定俗成或者标准化的命名,例如编解码标准中的 JPEG
, MPG4
, H264
等等。在第一节中,我们提到过 YUV 中的色度分量通常要进行下采样以节省系统带宽。进一步地,经过下采样后的 YUV 数据中亮度和色度的像素个数是不一样的,我们不能像 RGB 那样将其表示为一个 [H, W, 3] 的矩阵,所以下采样的 YUV 数据存在多种不同的存储方式,这些就构成了各种繁乱的关于 YUV 格式的 FOCC
。
为了正确使用 OpenCV 进行下采样格式的 YUV 数据颜色空间转换,这里简单地对 YUV 的存储方式进行介绍。常用的存储方式分为三种,即 Planar
, Semi-Planar
以及 Interleave
。
-
Planar
即平面式存储。其在内存空间中先完整地顺序存储所有 Y 像素,然后再完整地顺序存储所有经过下采样的 U 数据,最后完整地顺序存储所有经过下采样的 V 数据,例如 Y1 Y2 Y3 … Yend U1 U2 U3 … Uend V1 V2 V3 … Vend。它的优点是可以适应任意的下采样格式,并且在只对单个分量如 Y 分量进行操作时无需对其他分量进行存取,缺点是在内存中 Y / U / V 数据间的跨度太大,当需要同时处理 Y / U / V 数据时不利于数据的连续突发传输,或者需要多个数据并行存取分支。注意,有时候也会先存储 V 再存储 U,这时要通过具体的 FOCC
进行区分。
-
Semi-Planar
即半平面式存储。其在内存空间中也是先完整地顺序存储所有 Y 像素,但由于 U 和 V 的数据量是相等的,且绝大多数算法都需要同时用到 U 和 V,所以 U 和 V 之间并不需要分开存储,而是可以进行交叉,例如 Y1 Y2 Y3 … Yend U1 V1 U2 V2 U3 V3 … Uend Vend。它也具有 Planar 格式的适应性以及亮度与色度分量独立存储的优点,但是能够将 U 和 V 的数据访问操作进行合并,改善了内存突发传输性能,所以 Semi-Planar 格式在硬件中反而是更加常用的。和 Planar 同理,有时候也会先存储 V 再存储 U,这时要通过具体的 FOCC
进行区分。
-
Interleave
即交叉式存储。不同于 Semi-Planar 只将 U 和 V 进行交叉,Interleave 将 Y / U / V 数据都交织在一起,可以最大化 YUV 数据的突发传输性能,缺点是在只需要亮度分量时必须把色度分量也进行传输。它通常用于亮度与色度数据量相等的情况,如 YUV 4:2:2 格式,这时两个 Y 对应着一个 U 和一个 V,根据四者的顺序的不同存在着多种 Interleave 格式,并对应着不同的 FOCC
。YUV 4:2:2 常用的交叉式存储顺序有 YUYV
, YVYU
, UYVY
, VYUY
等等,其中的两个 Y 需按像素顺序排列。例如对于 YUYV
,在内存中有 Y1 U1 Y2 V1 Y3 U2 Y4 V2 …。实际上,大多数硬件都支持内存隔点并行存取的操作,因此对于 YUV 4:2:2 而言,将亮度与色度分量进行交叉存储并不会造成太大的数据传输负担,所以 YUV 4:2:2 数据的处理通常采用此存储格式。
在第一节中我们已经列出了 OpenCV 所支持的 FOCC
,但要注意 COLOR_BGR2YUV_FOCC
与 COLOR_YUV2BGR_FOCC
所支持的 FOCC
并不是对称的,例如 COLOR_BGR2YUV_FOCC
并不支持 RGB 到 YUV 4:2:2 的转换,如果有这方面的需求则需要利用第二节的 COLOR_BGR2YCrCb
将 RGB 转换到 YUV 4:4:4 并手动进行下采样。不过要注意的是,COLOR_BGR2YCrCb
和 COLOR_BGR2YUV_FOCC
的转换系数是有差异的,这个留到后面讨论。另外有些 FOCC
的意义是重复的。这里先对各个 FOCC
所对应的下采样及存储格式进行明确:
NV12
:YUV 4:2:0 Semi-Planar格式,其中色度分量先存 U 再存 V。NV21
/ 420S
:YUV 4:2:0 Semi-Planar格式,其中色度分量先存 V 再存 U。IYUV
/ I420
:YUV 4:2:0 Planar 格式,其中色度分量先存 U 再存 V。YV12
/ 420P
:YUV 4:2:0 Planar 格式,其中色度分量先存 V 再存 U。UYVY
/ Y222
/ UYNV
:YUV 4:2:2 Interleave 格式,存储顺序为 UYVY。YUY2
/ YUYV
/ YUNV
:YUV 4:2:2 Interleave 格式,存储顺序为 YUYV。YVYU
:YUV 4:2:2 Interleave 格式,存储顺序为 YVYU。
在 OpenCV 中,虽然 YUV 经过了下采样,但还是会把 YUV 数据打包为一个矩阵方便处理。对于 YUV 4:2:0,色度分量 U 和 V 的尺寸分别为亮度 Y 的 1/4,为了将它们打包在一起,OpenCV 使用一个 [H*3/2, W] 的二维矩阵进行存储,其中 [:H, :W] 存储的是 Y 数据,而 [H : H+H/2, :W] 则根据 Planar 或 Semi-Planar 格式顺序对 U 和 V 数据进行存储。根据以上的 FOCC
对应的格式,它们在二维矩阵中的分布如下图所示:
而对于 YUV 4:2:2,色度分量 U 和 V 的尺寸分别为亮度 Y 的 1/2,即色度分量的数据量和亮度分量是一样的,所以 OpenCV 使用一个 [H, W, 2] 的三维矩阵进行存储。因为矩阵在内存中的索引顺序通常从最后的维度开始,所以根据 FOCC
顺序对 YUV 4:2:2 数据进行存储时,应该在两个 [H, W] 平面上交替写入。以 UYVY
为例,它们在三维矩阵中的分布如下图所示,其他 FOCC
可自行推理,这里不赘述:
在了解了 YUV 下采样格式的存储方式及对应的 FOCC
以后,我们就能基于 OpenCV 中的 COLOR_BGR2YUV_FOCC
与 COLOR_YUV2BGR_FOCC
直接将下采样后的 YUV 数据转换为 RGB,或者直接将 RGB 转换为特定下采样及存储方式的 YUV 数据了。实际上,第三类 COLOR_BGR2YUV_FOCC
可看作是第二类 COLOR_BGR2YCrCb
即 BT.601 的继承,但是又有些不同。在信号处理中,一个很重要的概念是系统响应,频域的滤波等操作通常会在时域或空域中引入震荡或过冲,最常见的就是理想低通滤波器中的 Gibbs 现象。数字 YUV 信号最终需要转换为模拟电平信号才能驱动显示器工作,例如通过电压控制液晶的偏转角度等等,而离散时间信号在还原为模拟连续时间信号时需连接一个低通滤波器,以此过滤掉由采样定理所引入的高频延拓分量,因此这时就有可能出现信号过冲的问题。因为 COLOR_BGR2YCrCb
所得 YUV 数据的取值范围都为 [0, 255],如果数模转换的过程中把 0 映射为 0 电平,而 255 映射为最高电平,经过系统滤波响应以后,信号实际的电平就有可能超出系统所能处理的范围。为了解决这个问题,ITU-R 组织在 BT.601 转换系数的基础上,对 COLOR_BGR2YCrCb
所得结果进行缩放与偏移,保留大约 10% 的过冲余量,从而得到了 OpenCV 第三类 code
所对应的转换系数,其通常配合特定的 YUV 下采样及存储格式使用。通过查阅 OpenCV 源码的 color_yuv.simd.hpp
,可以看到
static const int ITUR_BT_601_SHIFT = 20;
static const int ITUR_BT_601_CRY = 269484;
static const int ITUR_BT_601_CGY = 528482;
static const int ITUR_BT_601_CBY = 102760;
static const int ITUR_BT_601_CRU = -155188;
static const int ITUR_BT_601_CGU = -305135;
static const int ITUR_BT_601_CBU = 460324;
static const int ITUR_BT_601_CGV = -385875;
static const int ITUR_BT_601_CBV = -74448;
其中未标明的 ITUR_BT_601_CRV 和 ITUR_BT_601_CBU 是一样的。转换为浮点后,可得带有 FOCC
后缀的 RGB 与 YUV 转换系数与如下
\left[ {\begin{array}{c} {Y'} \\ {U'} \\ {V'} \end{array}} \right] = \left[ {\begin{array}{c} {0.257}&{0.504}&{0.098} \\ { - 0.148}&{ - 0.291}&{0.439} \\ {0.439}&{ - 0.368}&{ - 0.071} \end{array}} \right]\left[ {\begin{array}{c} R \\ G \\ B \end{array}} \right] + \left[ {\begin{array}{c} {16} \\ {128} \\ {128} \end{array}} \right]
\left[ {\begin{array}{c} {0.257}&{0.504}&{0.098} \\ { - 0.148}&{ - 0.291}&{0.439} \\ {0.439}&{ - 0.368}&{ - 0.071} \end{array}} \right] \approx \frac{1}{{255}}\left[ {\begin{array}{c} {219}&{}&{} \\ {}&{224}&{} \\ {}&{}&{224} \end{array}} \right]\left[ {\begin{array}{c} {0.299}&{0.587}&{0.114} \\ { - 0.169}&{ - 0.331}&{0.500} \\ {0.500}&{ - 0.419}&{ - 0.081} \end{array}} \right]
rgb2yuv_470_full = np.array([
[ 0.299, 0.587, 0.114],
[-0.147, -0.289, 0.436],
[ 0.615, -0.515, -0.100]
rgb2yuv_601_full = np.array([
[ 0.299, 0.587, 0.114],
[-0.169, -0.331, 0.500],
[ 0.500, -0.419, -0.081]
rgb2yuv_709_full = np.array([
[ 0.2126, 0.7152, 0.0722],
[-0.1146, -0.3854, 0.5000],
[ 0.5000, -0.4542, -0.0458]
yuv2rgb_470_full = np.linalg.inv(rgb2yuv_470_full)
yuv2rgb_601_full = np.linalg.inv(rgb2yuv_601_full)
yuv2rgb_709_full = np.linalg.inv(rgb2yuv_709_full)
print('rgb2yuv_470_full')
print(rgb2yuv_601_full)
print('yuv2rgb_470_full')
print(yuv2rgb_601_full)
print('rgb2yuv_601_full')
print(rgb2yuv_601_full)
print('yuv2rgb_601_full')
print(yuv2rgb_601_full)
print('rgb2yuv_709_full')
print(rgb2yuv_709_full)
print('yuv2rgb_709_full')
print(yuv2rgb_709_full)
rgb2yuv_601_comp = 1./255 * np.diag([219, 224, 224]).dot(rgb2yuv_601_full)
rgb2yuv_709_comp = 1./255 * np.diag([219, 224, 224]).dot(rgb2yuv_709_full)
yuv2rgb_601_comp = np.linalg.inv(rgb2yuv_601_comp)
yuv2rgb_709_comp = np.linalg.inv(rgb2yuv_709_comp)
print('rgb2yuv_601_comp')
print(rgb2yuv_601_comp)
print('yuv2rgb_601_comp')
print(yuv2rgb_601_comp)
print('rgb2yuv_709_comp')
print(rgb2yuv_709_comp)
print('yuv2rgb_709_comp')
print(yuv2rgb_709_comp)
def convert_bgr_to_yuv(bgr):
h, w = bgr.shape[:2]
assert h % 4 == 0 and w % 4 == 0
b, g, r = bgr.transpose(2, 0, 1)
y = 0.299 * r + 0.587 * g + 0.114 * b
u = -0.147 * r - 0.289 * g + 0.436 * b + 128
v = 0.615 * r - 0.515 * g - 0.100 * b + 128
yuv_470 = np.clip(np.array([y, u, v]).transpose(1, 2, 0), 0, 255).astype('uint8')
u = -0.169 * r - 0.331 * g + 0.500 * b + 128
v = 0.500 * r - 0.419 * g - 0.081 * b + 128
yvu_601 = np.clip(np.array([y, v, u]).transpose(1, 2, 0), 0, 255).astype('uint8')
y = y * 219. / 255 + 16
u = u * 224. / 255 + 16
v = v * 224. / 255 + 16
yuv_fcc = np.clip(np.array([y, u, v]).transpose(1, 2, 0), 0, 255).astype('uint8')
return yuv_470, yvu_601, yuv_fcc
def convert_yuv_to_fcc(yuv, fcc):
fcc_list_420 = ['NV12', 'NV21', '420S', 'YV12', 'IYUV', 'I420', '420P']
fcc_list_422 = ['UYVY', 'Y422', 'UYNV', 'YUY2', 'YVYU', 'YUYV', 'YUNV']
assert fcc in fcc_list_420 + fcc_list_422
h, w = yuv.shape[:2]
assert h % 4 == 0 and w % 4 == 0
y, u, v = yuv.transpose(2, 0, 1)
if fcc in fcc_list_420:
yuv420 = np.zeros([3*h//2, w], dtype='uint8')
yuv420[:h] = y
if fcc == 'NV12':
yuv420[h:, 0::2] = u[::2, ::2]
yuv420[h:, 1::2] = v[::2, ::2]
elif fcc == 'NV21' or fcc == '420S':
yuv420[h:, 0::2] = v[::2, ::2]
yuv420[h:, 1::2] = u[::2, ::2]
elif fcc == 'YV12' or fcc == '420P':
yuv420[h: h+h//4] = v[::2, ::2].reshape([-1, w])
yuv420[-h//4: ] = u[::2, ::2].reshape([-1, w])
elif fcc == 'IYUV' or fcc == 'I420':
yuv420[h: h+h//4] = u[::2, ::2].reshape([-1, w])
yuv420[-h//4: ] = v[::2, ::2].reshape([-1, w])
return yuv420
elif fcc in fcc_list_422:
yuv422 = np.zeros([h, w, 2], dtype='uint8')
if fcc == 'UYVY' or fcc == 'Y422' or fcc == 'UYNV':
yuv422[:, 0::2, 0] = u[:, ::2]
yuv422[:, 1::2, 0] = v[:, ::2]
yuv422[:, :, 1] = y
elif fcc == 'YUY2' or fcc == 'YUYV' or fcc == 'YUNV':
yuv422[:, :, 0] = y
yuv422[:, 0::2, 1] = u[:, ::2]
yuv422[:, 1::2, 1] = v[:, ::2]
elif fcc == 'YVYU':
yuv422[:, :, 0] = y
yuv422[:, 0::2, 1] = v[:, ::2]
yuv422[:, 1::2, 1] = u[:, ::2]
return yuv422
else:
return yuv
def convert_yuv470_to_bgr(yuv470):
rgb = (yuv470 - np.array([0, 128, 128])).dot(yuv2rgb_470_full.T)
bgr = np.clip(rgb[..., ::-1], 0, 255).astype('uint8')
return bgr
def convert_yvu601_to_bgr(yvu601):
rgb = (yvu601[..., [0, 2, 1]] - np.array([0, 128, 128])).dot(yuv2rgb_601_full.T)
bgr = np.clip(rgb[..., ::-1], 0, 255).astype('uint8')
return bgr
def test_yuv2bgr_fcc(yuv):
fcc_dict_420 = {
'NV12': cv2.COLOR_YUV2BGR_NV12,
'NV21': cv2.COLOR_YUV2BGR_NV21, '420S': cv2.COLOR_YUV420sp2BGR,
'YV12': cv2.COLOR_YUV2BGR_YV12, '420P': cv2.COLOR_YUV420p2BGR,
'IYUV': cv2.COLOR_YUV2BGR_IYUV, 'I420': cv2.COLOR_YUV2BGR_I420
fcc_dict_422 = {
'UYVY': cv2.COLOR_YUV2BGR_UYVY, 'Y422': cv2.COLOR_YUV2BGR_Y422, 'UYNV': cv2.COLOR_YUV2BGR_UYNV,
'YUY2': cv2.COLOR_YUV2BGR_YUY2, 'YUYV': cv2.COLOR_YUV2BGR_YUYV, 'YUNV': cv2.COLOR_YUV2BGR_YUNV,
'YVYU': cv2.COLOR_YUV2BGR_YVYU
yuv420up = yuv.copy()
yuv420up[::2, 1::2, 1:] = yuv420up[::2, ::2, 1:]
yuv420up[1::2, :, 1:] = yuv420up[::2, :, 1:]
rgb = (yuv420up - np.array([16, 128, 128])).dot(yuv2rgb_601_comp.T)
bgr420 = np.clip(rgb[..., ::-1], 0, 255).astype('int')
yuv422up = yuv.copy()
yuv422up[:, 1::2, 1:] = yuv422up[:, ::2, 1:]
rgb = (yuv422up - np.array([16, 128, 128])).dot(yuv2rgb_601_comp.T)
bgr422 = np.clip(rgb[..., ::-1], 0, 255).astype('int')
for fcc, code in fcc_dict_420.items():
yuv_fcc = convert_yuv_to_fcc(yuv, fcc)
bgr_cv = cv2.cvtColor(yuv_fcc, code)
print('{} bgr max diff: {}'.format(fcc, np.max(np.abs(bgr420 - bgr_cv))))
for fcc, code in fcc_dict_422.items():
yuv_fcc = convert_yuv_to_fcc(yuv, fcc)
bgr_cv = cv2.cvtColor(yuv_fcc, code)
print('{} bgr max diff: {}'.format(fcc, np.max(np.abs(bgr422 - bgr_cv))))
if __name__ == '__main__':
w, h = 128, 128
bgr = np.random.randint(0, 256, [h, w, 3], dtype='uint8')
yuv470, yvu601, yuv_fcc = convert_bgr_to_yuv(bgr)
y0 = cv2.cvtColor(bgr, cv2.COLOR_BGR2YUV)
x0 = cv2.cvtColor(y0, cv2.COLOR_YUV2BGR)
x1 = convert_yuv470_to_bgr(yuv470)
d0 = np.max(yuv470.astype('int') - y0)
d1 = np.max(x1.astype('int') - x0)
print('470 max diff: yuv={}, bgr={}'.format(d0, d1))
y0 = cv2.cvtColor(bgr, cv2.COLOR_BGR2YCrCb)
x0 = cv2.cvtColor(y0, cv2.COLOR_YCrCb2BGR)
x1 = convert_yvu601_to_bgr(yvu601)
d0 = np.max(yvu601.astype('int') - y0)
d1 = np.max(x1.astype('int') - x0)
print('601 max diff: yuv={}, bgr={}'.format(d0, d1))
test_yuv2bgr_fcc(yuv_fcc)
y0 = cv2.cvtColor(bgr, cv2.COLOR_BGR2YUV_I420)
y1 = convert_yuv_to_fcc(yuv_fcc, 'I420')
print('I420 yuv max diff: {}'.format(np.max(np.abs(y1.astype('int') - y0))))
y0 = cv2.cvtColor(bgr, cv2.COLOR_BGR2YUV_IYUV)
y1 = convert_yuv_to_fcc(yuv_fcc, 'IYUV')
print('IYUV yuv max diff: {}'.format(np.max(np.abs(y1.astype('int') - y0))))
y0 = cv2.cvtColor(bgr, cv2.COLOR_BGR2YUV_YV12)
y1 = convert_yuv_to_fcc(yuv_fcc, 'YV12')
print('YV12 yuv max diff: {}'.format(np.max(np.abs(y1.astype('int') - y0))))
Mat rgb2;
cvtColor(rgb, nv12, COLOR_RGB2YUV_I420);
//imwrite("/home/ubuntu/nv12.yuv",nv12,0);
FILE* f = fopen(".
YUV色彩格式总结
上一篇文章结合OpenCV的源代码介绍了BGR转YUV的方法(YUV444)。本文主要介绍YUV的3种采样,YUV444,YUV422, YUV420,以及后两种格式转BGR的方法,和BGR转YUV系列的方法。本系列介绍的公式都是结合OpenCV根据OpenCV的计算方法提供的。
YUV格式的采样方式
YUV格式有3中采样方式,分别是YUV444、YUV422、YUV420;其...
计算机领域,
RGB被称为基色分量,组合后能显示的颜色叫做颜色空间,一般取值范围从0-255(2^8,可以显示1600万多种颜色;现在有的显示器为10位位深,即2^10,约可以显示10亿种颜色)
还有一种显示方法即
YUV显示法:
只黑白显示时,则只需要拿到Y值就可以了,彩色显示时则需要拿到
YUV三个值。
RGB与YUV的互相转换
一、实验目的:完成彩色空间的互相转换,即将.yuv图片转换为.rgb图片,将.rgb图片转换为.yuv文件。
先调试老师给出的.rgb文件转换至.yuv文件代码,理清思路,再写出.yuv文件转换至.rgb文件代码。
down.yuv(需转换的yuv文件)图片如上,其采样格式为4:2:0,分辨率为256×256.
down.rgb(需转换的rgb文件)图片如上,格式为RGB24,分辨率为256×256.
二、前导知识
1.转换公式
Y = 0.2990R + 0.5870G +
从前文已经知道,R,G,B,3个分量都跟 亮度密切相关,也就是 3个分量里面都有大量的亮度信息。
RGB 转 YUV 的过程实际上就是 把 RGB 3分量里面的亮度信息 提取出来,放到 Y 分量。再把 RGB 3分量里面的 色调 ,色饱和度 信息提取出来放到 U跟 V分量。
所以这是一个信息提取过程,需要经过大量的实验。
提取 Y 亮度信息的公式如下:
Y = Kr * R + Kg * G + Kb * B
上面公式中的 K 是一个权重因子,Kr 代表 红色通道的权重,Kg 代表
YUV系列之BGR2YUV
在OpenCV中imgproc模块下的cvtColor API。这个API的主要功能是对图片做色彩空间转换,使用起来很方便,但是背后的转换理论多少有些繁琐,但是也不难。因此今天在这篇文章中对色彩空间转换的理论进行梳理。
OpenCV支持的色彩非常丰富,我们会在以后的系列中逐步介绍,这个系列主要介绍YUV色彩空间与RGB或者BGR空间之间的转换,同时借此了解OpenCV中...
以下文章来源于疯狂的FPGA,作者CrazyBingo。文章仅用于学术分享。
正式开始前,我们有必要介绍一下色彩模型。色彩模型有很多种类,比如RGB三原色模型、CMYK四原色模型、YUV/YCbCr颜色模型等,由于我们本章要进行RGB转YCbCr算法的实现,因此这里我们重点介绍RGB和YCbCr色彩模型。1.1.RGB三原色模型
为了研究RGB三原色模型,我们需要从光线的底层物理组成开始分析。光也属于电磁波,有着同样的特性,这里给出了电磁波光谱图,如下图所示:
图. 电磁波光谱图
在电磁波波段...
在OpenCV中,可以使用cv2.cvtColor()函数来进行YUV422和RGB之间的转换。
假设有一张YUV422格式的图像,可以通过以下代码将其转换为RGB格式:
```python
import cv2
# 加载YUV422格式的图像
yuv_image = cv2.imread('yuv_image.jpg', cv2.IMREAD_UNCHANGED)
# 将YUV422图像转换为RGB图像
rgb_image = cv2.cvtColor(yuv_image, cv2.COLOR_YUV2RGB_Y422)
# 显示RGB图像
cv2.imshow('RGB Image', rgb_image)
cv2.waitKey(0)
cv2.destroyAllWindows()
相反地,如果想将一张RGB格式的图像转换为YUV422格式,可以使用以下代码:
```python
import cv2
# 加载RGB格式的图像
rgb_image = cv2.imread('rgb_image.jpg')
# 将RGB图像转换为YUV422图像
yuv_image = cv2.cvtColor(rgb_image, cv2.COLOR_RGB2YUV_Y422)
# 显示YUV422图像
cv2.imshow('YUV422 Image', yuv_image)
cv2.waitKey(0)
cv2.destroyAllWindows()
在进行YUV422和RGB之间的转换时,需要注意图像的颜色空间和通道顺序。YUV422图像通常具有4:2:2采样率,即每两个像素共享一个U和一个V分量,而RGB图像则没有这种采样率。因此,在进行转换时,需要确保正确的颜色空间和通道顺序,以避免图像质量的损失。