欢迎您访问 最编程 本站为您分享编程语言代码,编程技术文章!
您现在的位置是: 首页

YUV色彩格式总结-YUV格式的存储方式

最编程 2024-08-15 15:36:30
...

YUV与YCrCb

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;所以在自定义的实现中,可以根据对精度的需求进行修改,当然如果移动位数变少,精度也会下降。

以上是关于YUV系列常用色彩空间的简单总结,算是抛砖引玉吧~
OpenCV在YUV系列提供了丰富多样的色彩空间,关于细节,大家可以在根据上篇文章中源码的路径,进行探索!