接触到了一些yuv相关的信息。从Camera中拿到的每一帧,它的默认格式是NV21,它是一种yuv格式,然后转成OpenCV所需的BGR。Camera的每一帧的数据格式还可以指定成别的格式。因此开始关注了yuv这个名词,后面还有yuv的各种衍生,因此仔细地梳理一下自己的理解,以及与RGB对比。
对RGB,并不陌生,从初中开始接触的色光的三原色,告诉我们我们可以看到的光可以由这三种颜色按一定的比例去混合得到;后来在HTML以及Android开发中设置元素/控件的颜色时,可以通过一串数字,得到某个特定的颜色。这就是RGB的应用。
RGB 模型是目前常用的一种彩色信息表达方式,它使用红、绿、蓝三原色的亮度来定量表示颜色。该模型也称为加色混色模型,是以RGB三色光互相叠加来实现混色的方法,因而适合于显示器等发光体的显示。
RGB 颜色模型可以看做三维直角坐标颜色系统中的一个单位正方体。任何一种颜色在RGB 颜色空间中都可以用三维空间中的一个点来表示。在RGB 颜色空间上,当任何一个基色的亮度值为零时,即在原点处,就显示为黑色。当三种基色都达到最高亮度时,就表现为白色。在连接黑色与白色的对角线上,是亮度等量的三基色混合而成的灰色,该线称为灰色线。
与RGB类似,只是存储时B位与R位的位置进行调换。
Y表示亮度,CbCr表示颜色。怎么表示颜色,可以看下面这幅坐标图:
Y要如何表示亮度呢,下面是Y在不同的情况下的表现:
因此可以这样理解,同样是使用三个数来表示某个像素点的颜色,但是这三个数的意义变了,与RGB相比。然后接下来是各种yuv的衍生物。
The scope of the terms Y′UV, YUV, YCbCr, YPbPr, etc., is sometimes ambiguous and overlapping. Historically, the terms YUV and Y′UV were used for a specific analog encoding of color information in television systems, while YCbCr was used for digital encoding of color information suited for video and still-image compression and transmission such as MPEG and JPEG. Today, the term YUV is commonly used in the computer industry to describe file-formats that are encoded using YCbCr.
上面的意思是,这些术语有时真的很难区分,因为定义也是模糊不清。不过重要的是最后面那一句话:现在的YUV是通常用于计算机领域用来表示使用YCbCr编码的文件。所以可以粗浅地视YUV为YCbCr。
不过我在Camera预览中的每一帧中,除默认格式NV21外,还发现了其它的格式如YV12。去搜一些关于他们的资料时,发现都是yuv420系列的。具体有什么差异呢?
膜拜一下大佬的博客(稍作编辑与修改):
planar和packed
对于planar的YUV格式,先连续存储所有像素点的Y,紧接着存储所有像素点的U,随后是所有像素点的V。
对于packed的YUV格式,每个像素点的Y,U,V是连续交*存储的。
YUV,分为三个分量,“Y”表示明亮度(Luminance或Luma),也就是灰度值;而“U”和“V” 表示的则是色度(Chrominance或Chroma),作用是描述影像色彩及饱和度,用于指定像素的颜色。
与我们熟知的RGB类似,YUV也是一种颜色编码方法,主要用于电视系统以及模拟视频领域,它将亮度信息(Y)与色彩信息(UV)分离,没有UV信息一样可以显示完整的图像(是不是写错了),只不过是黑白的,这样的设计很好地解决了彩色电视机与黑白电视的兼容问题。并且,YUV不像RGB那样要求三个独立的视频信号同时传输,所以用YUV方式传送占用极少的频宽。
YUV码流的存储格式其实与其采样的方式密切相关,主流的采样方式有三种,YUV4:4:4,YUV4:2:2,YUV4:2:0,关于其详细原理,可以通过网上其它文章了解,这里我想强调的是如何根据其采样格式来从码流中还原每个像素点的YUV值,因为只有正确地还原了每个像素点的YUV值,才能通过YUV与RGB的转换公式提取出每个像素点的RGB值,然后显示出来。
用三个图来直观地表示采集的方式吧,以黑点表示采样该像素点的Y分量,以空心圆圈表示采用该像素点的UV分量。
先记住下面这段话,以后提取每个像素的YUV分量会用到。
YUV 4:4:4采样,每一个Y对应一组UV分量。
YUV 4:2:2采样,每两个Y共用一组UV分量。
YUV 4:2:0采样,每四个Y共用一组UV分量。
下面我用图的形式给出常见的YUV码流的存储方式,并在存储方式后面附有取样每个像素点的YUV数据的方法,其中,Cb、Cr的含义等同于U、V。
YUYV为YUV422采样的存储格式中的一种,相邻的两个Y共用其相邻的两个Cb、Cr,分析,对于像素点Y’00、Y’01 而言,其Cb、Cr的值均为 Cb00、Cr00,其他的像素点的YUV取值依次类推。
UYVY格式也是YUV422采样的存储格式中的一种,只不过与YUYV不同的是UV的排列顺序不一样而已,还原其每个像素点的YUV值的方法与上面一样。
YUV422P也属于YUV422的一种,它是一种Plane模式,即平面模式,并不是将YUV数据交错存储,而是先存放所有的Y分量,然后存储所有的U(Cb)分量,最后存储所有的V(Cr)分量,如上图所示。其每一个像素点的YUV值提取方法也是遵循YUV422格式的最基本提取方法,即两个Y共用一个UV。比如,对于像素点Y’00、Y’01 而言,其Cb、Cr的值均为 Cb00、Cr00。
YU12和YV12属于YUV420格式,也是一种Plane模式,将Y、U、V分量分别打包,依次存储。其每一个像素点的YUV数据提取遵循YUV420格式的提取方式,即4个Y分量共用一组UV。注意,上图中,Y’00、Y’01、Y’10、Y’11共用Cr00、Cb00,其他依次类推。
NV12和NV21属于YUV420格式,是一种two-plane模式,即Y和UV分为两个Plane,但是UV(CbCr)为交错存储,而不是分为三个plane。其提取方式与上一种类似,即Y’00、Y’01、Y’10、Y’11共用Cr00、Cb00
以720×488大小图象YUV420 planar为例,其存储格式是: 共大小为(720×480×3>>1)字节,
分为三个部分:Y,U和V
Y分量: (720×480)个字节
U(Cb)分量:(720×480>>2)个字节
V(Cr)分量:(720×480>>2)个字节
三个部分内部均是行优先存储,三个部分之间是Y,U,V 顺序存储。
即
0--720×480字节是Y分量值,
720×480--720×480×5/4字节是U分量
720×480×5/4 --720×480×3/2字节是V分量。
最简单的方式:
YUV4:2:2 —> YUV4:2:0 Y不变,将U和V信号值在行(垂直方向)在进行一次隔行抽样。 YUV4:2:0 —> YUV4:2:2 Y不变,将U和V信号值的每一行分别拷贝一份形成连续两行数据。
在YUV420中,一个像素点对应一个Y,一个4X4的小方块对应一个U和V。对于所有YUV420图像,它们的Y值排列是完全相同的,因为只有Y的图像就是灰度图像。YUV420sp与YUV420p的数据格式它们的UV排列在原理上是完全不同的。420p它是先把U存放完后,再存放V,也就是说UV它们是连续的。而420sp它是UV、UV这样交替存放的。(见下图) 有了上面的理论,我就可以准确的计算出一个YUV420在内存中存放的大小。 width * hight =Y(总和) U = Y / 4 V = Y / 4。所以YUV420 数据在内存中的长度是 width * hight * 3 / 2,
假设一个分辨率为8X4的YUV图像,它们的格式如下图:
YUV420p数据格式如下图
public static void rotateYUV240SP(byte[] src,byte[] des,int width,int height){
int wh = width * height;
//旋转Y
int k = 0;
for(int i=0;ifor(int j=0;jfor(int i=0;i2) {
for(int j=0;j2;j++) {
des[k] = src[wh+ width*j + i];
des[k+1]=src[wh + width*j + i+1];
k+=2;
}
}
}
一般来说,直接采集到的视频数据是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的顺序不同。
继续我们的话题,经过第一次数据压缩后RGB24->YUV(I420)。这样,数据量将减少一半,为什么呢?呵呵,这个就太基础了,我就不多写了。同样,如果是RGB24->YUV(YV12),也是减少一半。但是,虽然都是一半,如果是YV12的话效果就有很大损失。然后,经过X264编码后,数据量将大大减少。将编码后的数据打包,通过RTP实时传送。到达目的地后,将数据取出,进行解码。完成解码后,数据仍然是YUV格式的,所以,还需要一次转换,这样windows的驱动才可以处理,就是YUV2RGB24。
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
Y′UV420p is a planar format, meaning that the Y′, U, and V values are grouped together instead of interspersed. The reason for this is that by grouping the U and V values together, the image becomes much more compressible. When given an array of an image in the Y′UV420p format, all the Y′ values come first, followed by all the U values, followed finally by all the V values.
The Y′V12 format is essentially the same as Y′UV420p, but it has the U and V data switched: the Y′ values are followed by the V values, with the U values last. As long as care is taken to extract U and V values from the proper locations, both Y′UV420p and Y′V12 can be processed using the same algorithm.
As with most Y′UV formats, there are as many Y′ values as there are pixels. Where X equals the height multiplied by the width, the first X indices in the array are Y′ values that correspond to each individual pixel. However, there are only one fourth as many U and V values. The U and V values correspond to each 2 by 2 block of the image, meaning each U and V entry applies to four pixels. After the Y′ values, the next X/4 indices are the U values for each 2 by 2 block, and the next X/4 indices after that are the V values that also apply to each 2 by 2 block.
Translating Y′UV420p to RGB is a more involved process compared to the previous formats. Lookup of the Y′, U and V values can be done using the following method:
size.total = size.width * size.height;
y = yuv[position.y * size.width + position.x];
u = yuv[(position.y / 2) * (size.width / 2) + (position.x / 2) + size.total];
v = yuv[(position.y / 2) * (size.width / 2) + (position.x / 2) + size.total + (size.total / 4)];
rgb = Y′UV420toRGB888(y, u, v);
As shown in the above image, the Y′, U and V components in Y′UV420 are encoded separately in sequential blocks. A Y′ value is stored for every pixel, followed by a U value for each 2×2 square block of pixels, and finally a V value for each 2×2 block. Corresponding Y′, U and V values are shown using the same color in the diagram above. Read line-by-line as a byte stream from a device, the Y′ block would be found at position 0, the U block at position x×y (6×4 = 24 in this example) and the V block at position x×y + (x×y)/4 (here, 6×4 + (6×4)/4 = 30).
This format (NV21) is the standard picture format on Android camera preview. YUV 4:2:0 planar image, with 8 bit Y samples, followed by interleaved V/U plane with 8bit 2x2 subsampled chroma samples.
C++ code used on Android to convert pixels of YUVImage:
void YUVImage::yuv2rgb(uint8_t yValue, uint8_t uValue, uint8_t vValue,
uint8_t *r, uint8_t *g, uint8_t *b) const {
int rTmp = yValue + (1.370705 * (vValue-128));
int gTmp = yValue - (0.698001 * (vValue-128)) - (0.337633 * (uValue-128));
int bTmp = yValue + (1.732446 * (uValue-128));
*r = clamp(rTmp, 0, 255);
*g = clamp(gTmp, 0, 255);
*b = clamp(bTmp, 0, 255);
}
参考:
http://www.cnblogs.com/azraelly/archive/2013/01/01/2841269.html
https://www.jianshu.com/p/a91502c00fb0
https://en.wikipedia.org/wiki/YUV