YUV颜色编码解析

作者: 皮皮Warrior | 来源:发表于2016-10-16 20:30 被阅读4095次

YUV

YUV是一种颜色空间,基于YUV的颜色编码是流媒体的常用编码方式。Y表示流明,U、V表示色度、浓度,这种表达方式起初是为了彩色电视与黑白电视之间的信号兼容。 对于图像每一点,Y确定其亮度,UV确认其彩度。

Y'CbCr也称为YUV,是YUV的压缩版本,不同之处在于Y'CbCr用于数字图像领域,YUV用于模拟信号领域,MPEG、DVD、摄像机中常说的YUV其实是Y'CbCr,二者转换为RGBA的转换矩阵是不同的。Y'为亮度,Cb、Cr分量代表当前颜色对蓝色和红色的偏移程度。

注意4:2:0并不是只抽样第一行的色度,是第一行和第二行轮番抽样的:4:2:0 --> 4:0:2 --> 4:2:0 ……
可以看到,不管是哪种抽样方式,亮度都是全抽样的,不同之处在于U、V分量的抽样率。可以看到常用的4:2:0的U、V都是半抽样,所以抽样后的数据量是RGB24一半。(RGB24相当于全抽样)

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)


摄像头采集得到的数据是NV12

YUV与RGB之间的转换

在渲染时,不管是OpenGL还是iOS,都不支持直接渲染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)

YUV数据渲染

以NV12为例:

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;
        }
    }
}

有时不同的YUV格式需要互相转换

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;
}

或者需要旋转获得的数据

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++];
        }
    }
}
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;
        }
    }
}

在iOS中,可以使用core graphics将RGB数据画成UIImage。

- (UIImage *) convertBitmapRGBA8ToUIImage:(unsigned char *) buffer
                                withWidth:(int) width
                               withHeight:(int) height {
    
    //转为RGBA32
    char* rgba = (char*)malloc(width*height*4);
    for(int i=0; i < width*height; ++i) {
        rgba[4*i] = buffer[3*i];
        rgba[4*i+1] = buffer[3*i+1];
        rgba[4*i+2] = buffer[3*i+2];
        rgba[4*i+3] = 255;
    }
    
    size_t bufferLength = width * height * 4;
    CGDataProviderRef provider = CGDataProviderCreateWithData(NULL, rgba, bufferLength, NULL);
    size_t bitsPerComponent = 8;
    size_t bitsPerPixel = 32;
    size_t bytesPerRow = 4 * width;
    
    CGColorSpaceRef colorSpaceRef = CGColorSpaceCreateDeviceRGB();
    if(colorSpaceRef == NULL) {
        NSLog(@"Error allocating color space");
        CGDataProviderRelease(provider);
        return nil;
    }
    
    CGBitmapInfo bitmapInfo = kCGBitmapByteOrderDefault | kCGImageAlphaPremultipliedLast;
    CGColorRenderingIntent renderingIntent = kCGRenderingIntentDefault;
    
    CGImageRef iref = CGImageCreate(width,
                                    height,
                                    bitsPerComponent,
                                    bitsPerPixel,
                                    bytesPerRow,
                                    colorSpaceRef,
                                    bitmapInfo,
                                    provider,   // data provider
                                    NULL,       // decode
                                    YES,            // should interpolate
                                    renderingIntent);
    
    uint32_t* pixels = (uint32_t*)malloc(bufferLength);
    
    if(pixels == NULL) {
        NSLog(@"Error: Memory not allocated for bitmap");
        CGDataProviderRelease(provider);
        CGColorSpaceRelease(colorSpaceRef);
        CGImageRelease(iref);
        return nil;
    }
    
    CGContextRef context = CGBitmapContextCreate(pixels,
                                                 width,
                                                 height,
                                                 bitsPerComponent,
                                                 bytesPerRow,
                                                 colorSpaceRef,
                                                 bitmapInfo);
    
    if(context == NULL) {
        NSLog(@"Error context not created");
        free(pixels);
    }
    
    UIImage *image = nil;
    if(context) {
        
        CGContextDrawImage(context, CGRectMake(0.0f, 0.0f, width, height), iref);
        
        CGImageRef imageRef = CGBitmapContextCreateImage(context);
        
        image = [UIImage imageWithCGImage:imageRef scale:[UIScreen mainScreen].scale orientation:UIImageOrientationUp];
        if([UIImage respondsToSelector:@selector(imageWithCGImage:scale:orientation:)]) {
            image = [UIImage imageWithCGImage:imageRef scale:1.0 orientation:UIImageOrientationUp];
        } else {
            image = [UIImage imageWithCGImage:imageRef];
        }
        
        CGImageRelease(imageRef);
        CGContextRelease(context);
    }
    
    CGColorSpaceRelease(colorSpaceRef);
    CGImageRelease(iref);
    CGDataProviderRelease(provider);
    
    if(pixels) {
        free(pixels);
    }
    return image;
}

相关文章

  • 几篇有用的文章

    YUV颜色编码解析

  • YUV颜色编码解析

    转自http://www.jianshu.com/p/a91502c00fb0 YUV YUV是一种颜色空间,基于...

  • YUV颜色编码解析

    YUV YUV是一种颜色空间,基于YUV的颜色编码是流媒体的常用编码方式。Y表示流明,U、V表示色度、浓度,这种表...

  • YUV与RGB颜色编码

    YUV颜色编码 YUV颜色编码采用的是明亮度和色度来指定像素的颜色 其中Y表示明亮度(Luminance、Luma...

  • YUV排版

    YUV是一种颜色空间,基于YUV的颜色编码是流媒体的常用编码方式。Y表示流明,U、V表示色度、浓度,这种表达方式起...

  • YUV像素数据处理

    YUV简介 YUV,是一种颜色编码方法。常使用在各个视频处理组件中。 YUV在对照片或视频编码时,考虑到人类的感知...

  • YUV的采样与格式

    YUV 是一种颜色编码方法,和它等同的还有 RGB 颜色编码方法。 RGB 颜色编码 RGB 三个字母分别代表了 ...

  • iOS视频(基础篇)-YUV

    YUV简介 YUV是一种颜色编码方式,跟我们熟悉的RGB同样用户与编码颜色的一种数据格式。彩色图像常见的格式有RG...

  • YUV 颜色编码格式

    背景 音视频、直播、短视频等图像处理过程中涉及到 YUV 信息,在维基百科阅读 YUV 协议、编写代码实现、输出可...

  • RGB & YUV 颜色编码

    RGB 和 YUV 都是色彩空间模型,下面来分别介绍下 RGB 颜色编码 RGB颜色编码格式,是指每种颜色都可以用...

网友评论

  • 士拔鼠:写得很棒!另外色度抽样方式那里的J的说明多了一个“的”字。
  • sellse:YUV的图片展示很清晰
  • 骑老虎喊救命:请问怎么把像素格式为ARGB的CVPixelBufferRef转成 YUV(3个平面)格式的CVPixelBufferRef
  • 26a58be4dcf0:写的很好,谢谢分享!博主,请问您知道YUV数据中的Y分量包含什么信息吗?或者与灰度值有什么关系?感觉维基上写的有点模糊,谷歌上也大多说两者相同,可我具体用起来是不同的。
    26a58be4dcf0:@ooOlly 非常感谢楼主的回复!谢谢!
    皮皮Warrior:Y是流明,就是亮度。灰度图分二值图和单通道图、深度图。你应该说的是单通道灰度图,这种情况Y就是图像中的像素值,实际上RGB转灰度图就是用的:color = 0.299 R + 0.587 G + 0.114 B,
    这和YUV转换公式的Y分量一样。所以Y和灰度值的表现是一样的,不过不同框架中的转换公式有可能有区别(经验值系数不同)。
  • ricefun:楼主你好 我现在又一个需求 就是I420的用VideoToolBox硬编H264,我找的资料都是NV12encodeH264 并没有I420的 楼主 能提供下I420转NV12的吗 谢谢 (I420是以流的形式传输的)
  • 今天星期伍:NV21的数据组成是YYYYYYYYVUVU,你好像写错了
    皮皮Warrior:多谢提醒,已改正。
  • 477fceb0acaf:直接渲染yuv是可以的,不过要用三张纹理渲染三次。转换可以用gpu shader做
    皮皮Warrior:嗯,这是一个很好的思路,不过不算直接渲染YUV把,毕竟gl_FragColor接受的数据就是RGB。
  • akali:楼主,你知道怎么把一个image 转出YUV格式的CVPixelBufferRef吗
    皮皮Warrior:@Little_Shaun
    我写了RGB和YUV相互转换的公式
    //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
    Little_Shaun:@ooOlly rgb怎么转化为yuv呢?楼主能给详细讲下么?感谢感谢
    皮皮Warrior:图像如jpg、png都是压缩格式的,你需要把图像转成位图,通过每个像素的RGB值来算出YUV值,可以就可以构造一个CVPixelBufferRef了。
  • 改变自己_now:谢谢!学习了?
  • 科比布莱恩特:博主写的很棒,太详细了,赞一个,最近也在研究这块可以加下你的 qq 一起探讨下吗?
  • c1e0f39af20a:请问。。我现在又一个yuv420p的图片,或者是rgb565,我该怎么做图片显示呢(uiimage)
  • 郭秀才:写的很详细,很有参考价值
  • 82f1331a8a6e:写的很好很强大,学习啦!谢谢
    皮皮Warrior:@achilles_xushy 共勉!

本文标题:YUV颜色编码解析

本文链接:https://www.haomeiwen.com/subject/iehayttx.html