YUV色彩格式总结

简介: 笔记

YUV色彩格式总结


上一篇文章结合OpenCV的源代码介绍了BGR转YUV的方法(YUV444)。本文主要介绍YUV的3种采样,YUV444,YUV422, YUV420,以及后两种格式转BGR的方法,和BGR转YUV系列的方法。本系列介绍的公式都是结合OpenCV根据OpenCV的计算方法提供的。


YUV格式的采样方式


YUV格式有3中采样方式,分别是YUV444、YUV422、YUV420;其中YUV444也就是我们通常意义上的YUV,YUV420就是平时使用的NV21和NV12,其中NV12和NV21仅仅是存储顺序的差异。YUV422平时使用的相对较少。

上篇文章中介绍过BGR转YUV,我们知道每一组BGR都会获得一组YUV,所以YUV444就是原始的YUV,是不经过采样的。


YUV444


上一篇文章介绍了BGR转YUV444,每一组BGR转换为一组YUV;转换公式如下:

Y = (4899 * R + 9617 * G + 1868 * B) >> 14;           
V = ((R - Y) * 14369 + delta) >> 14;              
U = ((B - Y) * 8061 + delta) >> 14;              
delta = (255 / 2 + 1) * (1 << 14);

YUV444是BGR直接转换,不进行采样的结果;而YUV422以及YUV420是在YUV444的基础上进行采样得到的。如下图所示,展示了YUV444的一种演示方式:

5.png

image

其中实心黑圈作为整体表示UV分量,空心圈表示Y分量;所以每一个Y拥有一组UV分量。需要注意的是,这仅仅是示意图,表示采样方式,不表示数据的真是存储方式。444可以理解为第一行Y和UV的比是4(第一个4):4(第二个4);第二行Y和UV的比是4(第一个4):4(第三个4);因此使用YUV444表示这种采样方式。这也表示水平采样是4:4;垂直采样是4:4.


YUV422


BGR转YUV422的的公式是一样的,只是对YUV444进行采样,便可以得到YUV422. YUV422的表示如下图所示:

6.png

                      image

可以看到,第一行Y与UV的比例是4:2(第一个2);第二行也是4:2(第二个2);也可以理解为,水平方向上的采样比例为4:2;垂直方向是也为4:2.

以上是YUV422的采样方式。

所以在计算的时候,就可以少计算一半的UV分量;数据量也少一半的UV分量,也就是说,YUV422的数据量只有YUV444的2/3.


YUV420


YUV420的表示方式如下图所示:

7.png

                          image

可以看到,每4个Y拥有一组UV;第一行的采样是4:2;第二行的采样是4:0;所以取名YUV420.但是YUV420又有NV21和NV12两种格式,这两种格式的区别,仅仅是UV分量存储方式上的区别。同时,在数据量上,YUV420仅仅是YUV444的1/2.

以上是关于YUV的3种格式的采样方式的介绍,下面会介绍这YUV格式数据的存储方式。


YUV格式的存储方式


YUV与YCr、Cb

YUV444的存储比较单一,Y单独存储,UV交叉存储,这里主要区分一下YUV444和YCrCb;YCrCb和YUV的区别在两方面:

  • 计算系数
  • 存储顺序

下面是RGB转YUV的代码

{
    typedef _Tp channel_type;
    RGB2YCrCb_i(int _srccn, int _blueIdx, bool _isCrCb)
        : srccn(_srccn), blueIdx(_blueIdx), isCrCb(_isCrCb)
    {
        //设置系数
        static const int coeffs_crb[] = { R2Y, G2Y, B2Y, YCRI, YCBI };
        static const int coeffs_yuv[] = { R2Y, G2Y, B2Y, R2VI, B2UI };
        //yuv和YCrCb的系数不同
        memcpy(coeffs, isCrCb ? coeffs_crb : coeffs_yuv, 5*sizeof(coeffs[0]));
        //RGB和BGR的区别,需要交换B分量和R分量的位置
        if(blueIdx==0) std::swap(coeffs[0], coeffs[2]);
    }
    void operator()(const _Tp* src, _Tp* dst, int n) const
    {
        int scn = srccn, bidx = blueIdx;
        //区分是YUV还是YCrCb
        int yuvOrder = !isCrCb; //1 if YUV, 0 if YCrCb
        int C0 = coeffs[0], C1 = coeffs[1], C2 = coeffs[2], C3 = coeffs[3], C4 = coeffs[4];
        //color.hpp +26 : yuv_shift = 14
        int delta = ColorChannel<_Tp>::half()*(1 << yuv_shift);
        n *= 3;
        for(int i = 0; i < n; i += 3, src += scn)
        {
            int Y = CV_DESCALE(src[0]*C0 + src[1]*C1 + src[2]*C2, yuv_shift);
            int Cr = CV_DESCALE((src[bidx^2] - Y)*C3 + delta, yuv_shift);
            int Cb = CV_DESCALE((src[bidx] - Y)*C4 + delta, yuv_shift);
            dst[i] = saturate_cast<_Tp>(Y);
            //YUV和YCrCb计算系数不同
            dst[i+1+yuvOrder] = saturate_cast<_Tp>(Cr);
            dst[i+2-yuvOrder] = saturate_cast<_Tp>(Cb);
        }
    }
    int srccn, blueIdx;
    bool isCrCb;
    int coeffs[5];
};

具体区别在代码中注释了,首先看计算公式:

Y = (4899 * R + 9617 * G + 1868 * B) >> 14;           
Cr = ((R - Y) * 11682 + delta) >> 14;              
Cb = ((B - Y) * 9241 + delta) >> 14;              
delta = (255 / 2 + 1) * (1 << 14);

存储顺序:

dst[i+1+yuvOrder] = saturate_cast<_Tp>(Cr);
dst[i+2-yuvOrder] = saturate_cast<_Tp>(Cb);

可以看到,YCrCb刚好对应YVU,所以仅仅是UV分量的存储顺序有区别;


YUV420


YUV格式的存储方式有很多,YUV格式的数据存储分为two-plane和three-plane两种方式;所谓的two-plane是指Y单独存储一个plane,UV交叉存储,占用一个plane;three-plane是Y U V分别占用一个plane,一共三个plane.three-plane一般叫做YUV420p,two-plane叫做YUV420sp,我们熟知的NV21和NV12便是YUV420sp。

下面是OpenCV种RGB转YUV420的代码,其中有两个标志位interleaved和swapUV,分别用于区分YUV420p和YUV420sp以及NV21和NV12;NV21的存储是VU,而NV12是UV顺序。

struct RGB888toYUV420pInvoker: public ParallelLoopBody
{
    RGB888toYUV420pInvoker(const uchar * _src_data, size_t _src_step,
                           uchar * _y_data, uchar * _uv_data, size_t _dst_step,
                           int _src_width, int _src_height, int _scn, bool swapBlue_, bool swapUV_, bool interleaved_)
        : src_data(_src_data), src_step(_src_step),
          y_data(_y_data), uv_data(_uv_data), dst_step(_dst_step),
          src_width(_src_width), src_height(_src_height),
          scn(_scn), swapBlue(swapBlue_), swapUV(swapUV_), interleaved(interleaved_) { }
    void operator()(const Range& rowRange) const CV_OVERRIDE
    {
        const int w = src_width;
        const int h = src_height;
        const int cn = scn;
        for( int i = rowRange.start; i < rowRange.end; i++ )
        {
            const uchar* brow0 = src_data + src_step * (2 * i);
            const uchar* grow0 = brow0 + 1;
            const uchar* rrow0 = brow0 + 2;
            const uchar* brow1 = src_data + src_step * (2 * i + 1);
            const uchar* grow1 = brow1 + 1;
            const uchar* rrow1 = brow1 + 2;
            if (swapBlue)
            {
                std::swap(brow0, rrow0);
                std::swap(brow1, rrow1);
            }
            uchar* y = y_data + dst_step * (2*i);
            uchar* u;
            uchar* v;
            //区分two-plane or three-plane
            if (interleaved)
            {
                u = uv_data + dst_step * i;
                v = uv_data + dst_step * i + 1;
            }
            else
            {
                u = uv_data + dst_step * (i/2) + (i % 2) * (w/2);
                v = uv_data + dst_step * ((i + h/2)/2) + ((i + h/2) % 2) * (w/2);
            }
            //区分NV21 or NV12
            if (swapUV)
            {
                std::swap(u, v);
            }
            for( int j = 0, k = 0; j < w * cn; j += 2 * cn, k++ )
            {
                int r00 = rrow0[j];      int g00 = grow0[j];      int b00 = brow0[j];
                int r01 = rrow0[cn + j]; int g01 = grow0[cn + j]; int b01 = brow0[cn + j];
                int r10 = rrow1[j];      int g10 = grow1[j];      int b10 = brow1[j];
                int r11 = rrow1[cn + j]; int g11 = grow1[cn + j]; int b11 = brow1[cn + j];
                const int shifted16 = (16 << ITUR_BT_601_SHIFT);
                const int halfShift = (1 << (ITUR_BT_601_SHIFT - 1));
                int y00 = ITUR_BT_601_CRY * r00 + ITUR_BT_601_CGY * g00 + ITUR_BT_601_CBY * b00 + halfShift + shifted16;
                int y01 = ITUR_BT_601_CRY * r01 + ITUR_BT_601_CGY * g01 + ITUR_BT_601_CBY * b01 + halfShift + shifted16;
                int y10 = ITUR_BT_601_CRY * r10 + ITUR_BT_601_CGY * g10 + ITUR_BT_601_CBY * b10 + halfShift + shifted16;
                int y11 = ITUR_BT_601_CRY * r11 + ITUR_BT_601_CGY * g11 + ITUR_BT_601_CBY * b11 + halfShift + shifted16;
                y[2*k + 0]            = saturate_cast<uchar>(y00 >> ITUR_BT_601_SHIFT);
                y[2*k + 1]            = saturate_cast<uchar>(y01 >> ITUR_BT_601_SHIFT);
                y[2*k + dst_step + 0] = saturate_cast<uchar>(y10 >> ITUR_BT_601_SHIFT);
                y[2*k + dst_step + 1] = saturate_cast<uchar>(y11 >> ITUR_BT_601_SHIFT);
                const int shifted128 = (128 << ITUR_BT_601_SHIFT);
                int u00 = ITUR_BT_601_CRU * r00 + ITUR_BT_601_CGU * g00 + ITUR_BT_601_CBU * b00 + halfShift + shifted128;
                int v00 = ITUR_BT_601_CBU * r00 + ITUR_BT_601_CGV * g00 + ITUR_BT_601_CBV * b00 + halfShift + shifted128;
                if (interleaved)
                {
                    u[k*2] = saturate_cast<uchar>(u00 >> ITUR_BT_601_SHIFT);
                    v[k*2] = saturate_cast<uchar>(v00 >> ITUR_BT_601_SHIFT);
                }
                else
                {
                    u[k] = saturate_cast<uchar>(u00 >> ITUR_BT_601_SHIFT);
                    v[k] = saturate_cast<uchar>(v00 >> ITUR_BT_601_SHIFT);
                }
            }
        }
    }
}

BGR转YUV420的转换公式为:

Y = (R *   269484  + G *   528482  + B *   102760 + (1 << 19) + (1 << 16)) >> 20;
U = (R * (-155188) + G * (-305135) + B *   460324 + (1 << 19) + (128 << 20)) >> 20;
V = (R *   460324  + G * (-385875) + B * (-74448) + (1 << 19) + (128 << 20)) >> 20;

另外需要注意的是,YUV420在计算过程中是需要采样的,每4个Y共同使用一组UV,而这组UV则是取的2x2左上角的点——(0,0);代码如下:

int u00 = ITUR_BT_601_CRU * r00 + ITUR_BT_601_CGU * g00 + ITUR_BT_601_CBU * b00 + halfShift + shifted128;
int v00 = ITUR_BT_601_CBU * r00 + ITUR_BT_601_CGV * g00 + ITUR_BT_601_CBV * b00 + halfShift + shifted128;

可以看到OpenCV在计算的时候取用的是(0,0)位置的点。在别的代码中也可能采取其他的采样方式,比如水平方向上对U采样,垂直方向上对V采样,等等;

另外关于转换系数,根据精度不同,系数也会有出入。表现在移动位数不同,比如OpenCV中,目前移动的位数是20;上一篇文章中介绍BGR转YUV,移动的位数是14;所以在自定义的实现中,可以根据对精度的需求进行修改,当然如果移动位数变少,精度也会下降。


目录
相关文章
|
存储 编解码 Android开发
NV21、NV12、YV12、RGB、YUV、RGBA、RGBX8888等图像色彩编码格式区别
NV21、NV12、YV12、RGB、YUV、RGBA、RGBX8888都是常见的图像颜色编码格式,它们之间的主要区别在于色彩空间和数据排列方式。
230 0
|
7月前
|
存储 传感器 编解码
图像格式:常见图像格式RAW, RGB, YUV&&图像格式的解析、格式转换和看图软件
图像格式:常见图像格式RAW, RGB, YUV&&图像格式的解析、格式转换和看图软件
1478 1
|
7月前
|
存储 编解码 Java
视频渲染的推荐8位YUV格式
视频渲染的推荐8位YUV格式
201 0
|
编解码 API 开发工具
NV21、NV12、YV12、RGB565、YUV等颜色编码格式区别和接口设计探讨
NV21、NV12、YV12、RGB565、YUV分别是不同的颜色编码格式,这些颜色编码格式各有特点,适用于不同的应用场景。选择合适的颜色编码格式取决于具体的需求和环境:
254 1
|
容器
yuv色彩空间和色彩范围
yuv色彩空间和色彩范围
304 0
|
存储 编解码 vr&ar
色彩空间与像素格式
颜色是不同波长的光对人眼刺激产生的色彩感觉。色彩空间(Color Space)是颜色的数学表示,根据不同的表示方法分为不同的色彩模型。最常用的色彩模型有三类:RGB(用于计算机图形学), YUV(用于视频系统), CMYK(用于彩色印刷)。后文对色彩空间与色彩模型的叫法不作区分。本文仅讨论视频图像处理领域常用的 RGB 色彩空间和 YUV 色彩空间。
2853 0
色彩空间与像素格式
FreeSwitch中,RGB颜色转换为YUV
FreeSwitch中,RGB颜色转换为YUV
95 0
|
存储 vr&ar Android开发
纹理格式
纹理格式
150 0
YUV 与 RGB的转换
RGB 转换成 YUV Y = (0.257 * R) + (0.504 * G) + (0.
5568 0