YUV色彩格式總結(jié)

YUV色彩格式總結(jié)

上一篇文章結(jié)合OpenCV的源代碼介紹了BGR轉(zhuǎn)YUV的方法(YUV444)。本文主要介紹YUV的3種采樣,YUV444,YUV422, YUV420,以及后兩種格式轉(zhuǎn)BGR的方法,和BGR轉(zhuǎn)YUV系列的方法。本系列介紹的公式都是結(jié)合OpenCV根據(jù)OpenCV的計算方法提供的。

YUV格式的采樣方式

YUV格式有3中采樣方式,分別是YUV444、YUV422、YUV420;其中YUV444也就是我們通常意義上的YUV,YUV420就是平時使用的NV21和NV12,其中NV12和NV21僅僅是存儲順序的差異。YUV422平時使用的相對較少。
上篇文章中介紹過BGR轉(zhuǎn)YUV,我們知道每一組BGR都會獲得一組YUV,所以YUV444就是原始的YUV,是不經(jīng)過采樣的。

YUV444

上一篇文章介紹了BGR轉(zhuǎn)YUV444,每一組BGR轉(zhuǎn)換為一組YUV;轉(zhuǎn)換公式如下:

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直接轉(zhuǎn)換,不進行采樣的結(jié)果;而YUV422以及YUV420是在YUV444的基礎(chǔ)上進行采樣得到的。如下圖所示,展示了YUV444的一種演示方式:

image

其中實心黑圈作為整體表示UV分量,空心圈表示Y分量;所以每一個Y擁有一組UV分量。需要注意的是,這僅僅是示意圖,表示采樣方式,不表示數(shù)據(jù)的真是存儲方式。444可以理解為第一行Y和UV的比是4(第一個4):4(第二個4);第二行Y和UV的比是4(第一個4):4(第三個4);因此使用YUV444表示這種采樣方式。這也表示水平采樣是4:4;垂直采樣是4:4.

YUV422

BGR轉(zhuǎn)YUV422的的公式是一樣的,只是對YUV444進行采樣,便可以得到Y(jié)UV422. YUV422的表示如下圖所示:

image

可以看到,第一行Y與UV的比例是4:2(第一個2);第二行也是4:2(第二個2);也可以理解為,水平方向上的采樣比例為4:2;垂直方向是也為4:2.
以上是YUV422的采樣方式。
所以在計算的時候,就可以少計算一半的UV分量;數(shù)據(jù)量也少一半的UV分量,也就是說,YUV422的數(shù)據(jù)量只有YUV444的2/3.

YUV420

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

image

可以看到,每4個Y擁有一組UV;第一行的采樣是4:2;第二行的采樣是4:0;所以取名YUV420.但是YUV420又有NV21和NV12兩種格式,這兩種格式的區(qū)別,僅僅是UV分量存儲方式上的區(qū)別。同時,在數(shù)據(jù)量上,YUV420僅僅是YUV444的1/2.
以上是關(guān)于YUV的3種格式的采樣方式的介紹,下面會介紹這YUV格式數(shù)據(jù)的存儲方式。

YUV格式的存儲方式

YUV與YCr、Cb

YUV444的存儲比較單一,Y單獨存儲,UV交叉存儲,這里主要區(qū)分一下YUV444和YCrCb;YCrCb和YUV的區(qū)別在兩方面:

  • 計算系數(shù)
  • 存儲順序

下面是RGB轉(zhuǎn)YUV的代碼

{
    typedef _Tp channel_type;

    RGB2YCrCb_i(int _srccn, int _blueIdx, bool _isCrCb)
        : srccn(_srccn), blueIdx(_blueIdx), isCrCb(_isCrCb)
    {
        //設(shè)置系數(shù)
        static const int coeffs_crb[] = { R2Y, G2Y, B2Y, YCRI, YCBI };
        static const int coeffs_yuv[] = { R2Y, G2Y, B2Y, R2VI, B2UI };
        //yuv和YCrCb的系數(shù)不同
        memcpy(coeffs, isCrCb ? coeffs_crb : coeffs_yuv, 5*sizeof(coeffs[0]));
        //RGB和BGR的區(qū)別,需要交換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;
        //區(qū)分是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計算系數(shù)不同
            dst[i+1+yuvOrder] = saturate_cast<_Tp>(Cr);
            dst[i+2-yuvOrder] = saturate_cast<_Tp>(Cb);
        }
    }
    int srccn, blueIdx;
    bool isCrCb;
    int coeffs[5];
};

具體區(qū)別在代碼中注釋了,首先看計算公式:

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剛好對應(yīng)YVU,所以僅僅是UV分量的存儲順序有區(qū)別;

YUV420

YUV格式的存儲方式有很多,YUV格式的數(shù)據(jù)存儲分為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轉(zhuǎn)YUV420的代碼,其中有兩個標(biāo)志位interleaved和swapUV,分別用于區(qū)分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;
            //區(qū)分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);
            }
            //區(qū)分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轉(zhuǎn)YUV420的轉(zhuǎn)換公式為:

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采樣,等等;
另外關(guān)于轉(zhuǎn)換系數(shù),根據(jù)精度不同,系數(shù)也會有出入。表現(xiàn)在移動位數(shù)不同,比如OpenCV中,目前移動的位數(shù)是20;上一篇文章中介紹BGR轉(zhuǎn)YUV,移動的位數(shù)是14;所以在自定義的實現(xiàn)中,可以根據(jù)對精度的需求進行修改,當(dāng)然如果移動位數(shù)變少,精度也會下降。

本文轉(zhuǎn)自鏈接YUV色彩格式總結(jié)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容