欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

YUV数据格式

程序员文章站 2022-07-02 08:55:37
...

YUV存储方式
YUV存储方式主要分为两种:Packeted 和 Planar。
Packeted方式类似RGB的存储方式,以像素矩阵为存储方式。
Planar方式将YUV分量分别存储到矩阵,每一个分量矩阵称为一个平面。
YUV420即以平面方式存储,色度抽样为4:2:0的色彩编码格式。其中YUV420P为三平面存储,YUV420SP为两平面存储。
常用的I420(YUV420P),NV12(YUV420SP),YV12(YUV420P),NV21(YUV420SP)等都是属于YUV420,NV12是一种两平面存储方式,Y为一个平面,交错的UV为另一个平面。

由此,I420就是存储方式为Planar,抽样方式为4:2:0,数据组成为YYYYYYYYUUVV的一种色彩编码格式。
除此之外,NV12的数据组成:YYYYYYYYUVUV 。YV12的数据组成:YYYYYYYYVVUU。NV21的数据组成:YYYYYYYYVUVU。
通常,用来远程传输的是I420数据,而本地摄像头采集的是NV12数据。(iOS)

YV12和I420的区别 一般来说,直接采集到的视频数据是RGB24的格式,RGB24一帧的大小size=width×heigth×3 Bit,RGB32的size=width×heigth×4,如果是I420(即YUV标准格式4:2:0)的数据量是 size=width×heigth×1.5 Bit。 在采集到RGB24数据后,需要对这个格式的数据进行第一次压缩。即将图像的颜色空间由RGB2YUV。因为,X264在进行编码的时候需要标准的YUV(4:2:0)。但是这里需要注意的是,虽然YV12也是(4:2:0),但是YV12和I420的却是不同的,在存储空间上面有些区别。

如下: YV12 : 亮度(行×列) + U(行×列/4) + V(行×列/4)
I420 : 亮度(行×列) + V(行×列/4) + U(行×列/4)
可以看出,YV12和I420基本上是一样的,就是UV的顺序不同。

yuv420p 和 YUV420的区别 在存储格式上有区别
yuv420p:yyyyyyyy uuuuuuuu vvvvv
yuv420: yuv yuv yuv

YUV420P,Y,U,V三个分量都是平面格式,分为I420和YV12。I420格式和YV12格式的不同处在U平面和V平面的位置不同。在I420格式中,U平面紧跟在Y平面之后,然后才是V平面(即:YUV);但YV12则是相反(即:YVU)。

YUV420SP, Y分量平面格式,UV打包格式, 即NV12。 NV12与NV21类似,U 和 V 交错排列,不同在于UV顺序。
I420: YYYYYYYY UU VV =>YUV420P
YV12: YYYYYYYY VV UU =>YUV420P
NV12: YYYYYYYY UVUV =>YUV420SP
NV21: YYYYYYYY VUVU =>YUV420SP

YUV格式通常有两大类:打包(packed)格式和平面(planar)格式。前者将YUV分量存放在同一个数组中,通常是几个相邻的像素组成一个宏像素(macro-pixel);而后者使用三个数组分开存放YUV三个分量,就像是一个三维平面一样。表2.3中的YUY2到Y211都是打包格式,而IF09到YVU9都是平面格式。(注意:在介绍各种具体格式时,YUV各分量都会带有下标,如Y0、U0、V0表示第一个像素的YUV分量,Y1、U1、V1表示第二个像素的YUV分量,以此类推。)

YUV与RGB之间的转换

//RGB --> YUV
Y = 0.299 R + 0.587 G + 0.114 B

U = - 0.1687 R - 0.3313 G + 0.5 B + 128

V = 0.5 R - 0.4187 G - 0.0813 B + 128
//YUV --> RGB
//由于U、V可能出现负数,单存储为了方便就用一个字节表示:0-255,读取时要-128回归原值。
R = Y + 1.402 (Cr-128)

G = Y - 0.34414 (Cb-128) - 0.71414 (Cr-128)

B = Y + 1.772 (Cb-128)

NV12转RGB

void convertNv12ToRgb(unsigned char *rgbout, unsigned char *pdata,int DataWidth,int DataHeight)
{
    unsigned long  idx=0;
    unsigned char *ybase,*ubase;
    unsigned char y,u,v;
    ybase = pdata; //获取Y平面地址
    ubase = pdata+DataWidth * DataHeight; //获取U平面地址,由于NV12中U、V是交错存储在一个平民的,v是u+1
    for(int j=0;j<DataHeight;j++)
    {
        idx=(DataHeight-j-1)*DataWidth*3;//该值保证所生成的rgb数据逆序存放在rgbbuf中,位图是底朝上的
        for(int i=0;i<DataWidth;i++)
        {
            unsigned char r,g,b;
            y=ybase[i + j  * DataWidth];//一个像素对应一个y
            u=ubase[j/2 * DataWidth+(i/2)*2];// 每四个y对应一个uv
            v=ubase[j/2 * DataWidth+(i/2)*2+1];  //一定要注意是u+1
            
            b=(unsigned char)(y+1.779*(u- 128));
            g=(unsigned char)(y-0.7169*(v - 128)-0.3455*(u - 128));
            r=(unsigned char)(y+ 1.4075*(v - 128));
            
            rgbout[idx++]=b;
            rgbout[idx++]=g;
            rgbout[idx++]=r;
        }
    }
}

NV12转I420

unsigned char* convertNV12ToI420(unsigned char *data , int dataWidth, int dataHeight){
    unsigned char *ybase,*ubase;
    ybase = data;
    ubase = data + dataWidth*dataHeight;
    unsigned char* tmpData = (unsigned char*)malloc(dataWidth*dataHeight * 1.5);
    int offsetOfU = dataWidth*dataHeight;
    int offsetOfV = dataWidth*dataHeight* 5/4;
    memcpy(tmpData, ybase, dataWidth*dataHeight);
    for (int i = 0; i < dataWidth*dataHeight/2; i++) {
        if (i % 2 == 0) {
            tmpData[offsetOfU] = ubase[i];
            offsetOfU++;
        }else{
            tmpData[offsetOfV] = ubase[i];
            offsetOfV++;
        }
    }
    free(data);
    return tmpData;
}

nv12旋转

void rotate90NV12(unsigned char *dst, const unsigned char *src, int srcWidth, int srcHeight)
{
    int wh = srcWidth * srcHeight;
    int uvHeight = srcHeight / 2;
    int uvWidth = srcWidth / 2;
    //旋转Y
    int i = 0, j = 0;
    int srcPos = 0, nPos = 0;
    for(i = 0; i < srcHeight; i++) {
        nPos = srcHeight - 1 - i;
        for(j = 0; j < srcWidth; j++) {
            dst[j * srcHeight + nPos] = src[srcPos++];
        }
    }
    
    srcPos = wh;
    for(i = 0; i < uvHeight; i++) {
        nPos = (uvHeight - 1 - i) * 2;
        for(j = 0; j < uvWidth; j++) {
            dst[wh + j * srcHeight + nPos] = src[srcPos++];
            dst[wh + j * srcHeight + nPos + 1] = src[srcPos++];
        }
    }
}

yuv420sp旋转

void rotate270YUV420sp(unsigned char *dst, const unsigned char *src, int srcWidth, int srcHeight)
{
    int nWidth = 0, nHeight = 0;
    int wh = 0;
    int uvHeight = 0;
    if(srcWidth != nWidth || srcHeight != nHeight)
    {
        nWidth = srcWidth;
        nHeight = srcHeight;
        wh = srcWidth * srcHeight;
        uvHeight = srcHeight >> 1;//uvHeight = height / 2
    }
    
    //旋转Y
    int k = 0;
    for(int i = 0; i < srcWidth; i++){
        int nPos = srcWidth - 1;
        for(int j = 0; j < srcHeight; j++)
        {
            dst[k] = src[nPos - i];
            k++;
            nPos += srcWidth;
        }
    }
    
    for(int i = 0; i < srcWidth; i+=2){
        int nPos = wh + srcWidth - 1;
        for(int j = 0; j < uvHeight; j++) {
            dst[k] = src[nPos - i - 1];
            dst[k + 1] = src[nPos - i];
            k += 2;
            nPos += srcWidth;
        }
    }
}

RGBA转YYYYYYUVUV格式

unsigned char *bufY = (unsigned char *)malloc(width*height);
    unsigned char *bufUV = (unsigned char *)malloc(width*height/2);

    size_t offset,p;

    int r,g,b,y,u,v;
    int a=255;
    for (int row = 0; row < height; ++row) {
      for (int col = 0; col < width; ++col) {
        //
        offset = ((width * row) + col);
        p = offset*4;
        //
        r = data[p + 0];
        g = data[p + 1];
        b = data[p + 2];
        a = data[p + 3];
        //
        y = 0.299*r + 0.587*g + 0.114*b;
        u = -0.1687*r - 0.3313*g + 0.5*b + 128;
        v = 0.5*r - 0.4187*g - 0.0813*b + 128;
        //
        bufY[offset] = y;
        bufUV[(row/2)*width + (col/2)*2] = u;
        bufUV[(row/2)*width + (col/2)*2 + 1] = v;
      }
    }

RGBA转YV12


typedef struct tagRGBQUAD {
    unsigned char rgbBlue; // 蓝色分量
    unsigned char rgbGreen; // 绿色分量
    unsigned char rgbRed; // 红色分量
    unsigned char rgbReserved; // 保留字节(用作Alpha通道或忽略)
} RGBQUAD;

void rgb2yuv(int r, int g, int b, int *y, int *u, int *v){
    // 1 常规转换标准 - 浮点运算,精度高
//    *y =  0.29882  * r + 0.58681  * g + 0.114363 * b;
//    *u = -0.172485 * r - 0.338718 * g + 0.511207 * b;
//    *v =  0.51155  * r - 0.42811  * g - 0.08343  * b;

    // 2 常规转换标准 通过位移来避免浮点运算,精度低
//    *y = ( 76  * r + 150 * g + 29  * b)>>8;
//    *u = (-44  * r - 87  * g + 131 * b)>>8;
//    *v = ( 131 * r - 110 * g - 21  * b)>>8;
    // 3 常规转换标准 通过位移来避免乘法运算,精度低
    *y = ( (r<<6) + (r<<3) + (r<<2) + (g<<7) + (g<<4) + (g<<2) + (g<<1) + (b<<4) + (b<<3) + (b<<2) + b)>>8;
    *u = (-(r<<5) - (r<<3) - (r<<2) - (g<<6) - (g<<4) - (g<<2) - (g<<1) - g + (b<<7) + (b<<1) + b)>>8;
    *v = ((r<<7) + (r<<1) + r - (g<<6) - (g<<5) - (g<<3) - (g<<2) - (g<<1) - (b<<4) - (b<<2) - b)>>8;
    
    // 4 高清电视标准:BT.709 常规方法:浮点运算,精度高
//    *y =  0.2126  * r + 0.7152  * g + 0.0722  * b;
//    *u = -0.09991 * r - 0.33609 * g + 0.436   * b;
//    *v =  0.615   * r - 0.55861 * g - 0.05639 * b;
    
    *v += 128;
    *u += 128;
}


void rgbaConvert2YV12(int *rgbData, uint8_t *yuv, int width, int height) {
    int frameSize = width * height;
    int yIndex = 0;
    int vIndex = frameSize;
    int uIndex = frameSize * 1.25;

    int R, G, B, Y, U, V, A;
    int index = 0;
    for (int j = 0; j < height; j++) {
        for (int i = 0; i < width; i++) {//RGBA
            //样式为ABGR iOS设备为小端
            A = (rgbData[index] >> 24) & 0xff;
            B = (rgbData[index] >> 16) & 0xff;
            G = (rgbData[index] >> 8) & 0xff;
            R = rgbData[index] & 0xff;
            
            //转换
            rgb2yuv(R, G, B, &Y, &U, &V);
            //避免负数
//            U += 128;
//            V += 128;
//
//            Y = RANG_CONTROL(Y, 0, 255);
//            U = RANG_CONTROL(U, 0, 255);
//            V = RANG_CONTROL(V, 0, 255);
            
            yuv[yIndex++] = Y;
            
            if (j % 2 == 0 && i % 2 == 0) {//按2 * 2矩阵取
                yuv[vIndex++] = V;
                yuv[uIndex++] = U;
            }
            index ++;
        }
    }
}

旋转90度的算法:



public static void rotateYUV240SP(byte[] src,byte[] des,int width,int height)
 {
    
  int wh = width * height;
  //旋转Y
  int k = 0;
  for(int i=0;i<width;i++) {
   for(int j=0;j<height;j++) 
   {
               des[k] = src[width*j + i];   
         k++;
   }
  }
  
  for(int i=0;i<width;i+=2) {
   for(int j=0;j<height/2;j++) 
   { 
               des[k] = src[wh+ width*j + i]; 
               des[k+1]=src[wh + width*j + i+1];
         k+=2;
   }
  }
  
  
 }