引言
因为项目中要做画面共享,所以需要学一点图像相关的知识,首当其冲就是RGB转YUV了,因为图像处理压缩这一块是由专业对口的同事做的,所以呢,我这就是写一下自己的理解,如有不对的地方,还望指正,谢谢。
正文
知识准备
RGB
三原色光模式(RGB color model),又称RGB颜色模型或红绿蓝颜色模型,是一种加色模型,将红(Red)、绿(Green)、蓝(Blue)三原色的色光以不同的比例相加,以合成产生各种色彩光。
RGB32
RGB32使用32位来表示一个像素,RGB分量各用去8位,剩下的8位用作Alpha通道或者不用。(ARGB32就是带Alpha通道的RGB24。)注意在内存中RGB各分量的排列顺序为:BGRA BGRA BGRA…。通常可以使用RGBQUAD数据结构来操作一个像素,它的定义为:
1 | typedef struct tagRGBQUAD { |
YUV
YUV,是一种颜色编码方法。常使用在各个影像处理组件中。 YUV在对照片或影片编码时,考虑到人类的感知能力,允许降低色度的带宽。
YUV是编译true-color颜色空间(color space)的种类,Y’UV, YUV, YCbCr,YPbPr等专有名词都可以称为YUV,彼此有重叠。“Y”表示明亮度(Luminance、Luma),“U”和“V”则是色度、浓度(Chrominance、Chroma)。
YUV Formats分成两个格式:
- 紧缩格式(packed formats):将Y、U、V值存储成Macro Pixels数组,和RGB的存放方式类似。
- 平面格式(planar formats):将Y、U、V的三个分量分别存放在不同的矩阵中。
YV12
YV12是每个像素都提取Y,在UV提取时,将图像2 x 2的矩阵,每个矩阵提取一个U和一个V。YV12格式和I420格式的不同处在V平面和U平面的位置不同。在YV12格式中,V平面紧跟在Y平面之后,然后才是U平面(即:YVU);但I420则是相反(即:YUV)。NV12与YV12类似,效果一样,YV12中U和V是连续排列的,而在NV12中,U和V就交错排列的。
排列举例: 2*2图像YYYYVU; 4*4图像YYYYYYYYYYYYYYYYVVVVUUUU。
ps:以上介绍摘自于维基百科、百度百科。
进入正题
获取RGBA数据
在这里主要介绍从Image中获取RGBA数据,会用到CoreGraphics库。
首先我们需要创建bitmap context
1 | + (CGContextRef) newBitmapRGBA8ContextFromImage:(CGImageRef) image { |
接下来需要向image绘制到bitmap context,然后从context中获取bitmap data。代码如下:
1 | + (unsigned char *) convertUIImageToBitmapRGBA8:(UIImage *) image { |
看了这段代码,你可能会有疑问,为什么不直接返回bitmapData呢?这是因为在我们释放bitmap context后,会释放掉bitmapData,所以这就需要我们从新申请空间将数据拷贝到重新开辟的空间了。
格式化图像数据
因为YV12要求以像素的2 * 2矩阵来做转换,所以在做RGB转换YV12之前,我们需要先格式化图像数据,以满足要求。
首先需要格式化图片size,在这里我们以8来对齐,代码如下:
1 | + (CGSize)fromatImageSizeToYV12Size:(CGSize)originalSize |
在上个步骤我们计算得到了满足YV12格式的size,在这里还需要根据计算到的size来格式化image data,以满足YV12格式要求,代码如下:
1 | + (void)formatImageDataToYV12:(unsigned char *)originalData outputData:(unsigned char *)outputData originalWidth:(CGFloat)originalWidth targetSize:(CGSize)targetSize |
数据准备好了,接下来,可以进入真正的主题,RGBA转换为YV12了。
RGBA转YV12
对于RGB转换为对应的YV12,转换规则都是大佬们研究出来的,只是实现的方式各有不同,我在这里罗列了我找到的几种方式。
1 | void rgb2yuv(int r, int g, int b, int *y, int *u, int *v){ |
以上的转换方式都是可行的,当然要提升效率的话,还有查表法什么的,这些有兴趣的可以自行搜索。既然转换规则固定了,那么不考虑效率的前提下,我们需要做的就是如何从rgba数组中按照2 * 2矩阵来获取YUV数据并存储下来了。
在这里介绍采用紧缩格式存储YV12的数据。为了便于理解,在这里先举个栗子:
下图中的表示一张图中的像素排列,每个像素都包含RGBA通道,将RGBA转换为YV12需要按照2 * 2的像素矩阵为单位来处理,在图中就是按颜色分块中的像素来获取YUV数据,YV12是每4个像素获取一次U、V分量的数据,每个像素都要获取Y分量。
要看转换后的YUV的样子,这里以2 * 2为例,8 个像素生成的YUV如下:
1 | YYYY YYYY VV UU |
有了上面的知识,就可以直接上代码看看了。在这里我们以数组的方式来存储yuv数据,输入的rgba数据我们设置为int型数组,因为每个像素包含4个通道,每个通道都是一个byte,这样每个像素是4个byte,正好是一个int。需要注意的是,在int数组中,按理说每个int的数据应该是:RGBA,但是在iOS上,由于iOS是小端,所以实际上每个int的内容为ABGR,所以在取数据的时候需要注意,避免弄错顺序。还有需要注意的是,计算出来的UV数据需要避免负数,不然颜色值会有问题。
1 | void rgbaConvert2YV12(int *rgbData, uint8_t *yuv, int width, int height) { |
YV12转RGBA
首先还是yuv转rgb的方法,这与RGB转YV12是对应的。
1 | void yuv2rgb(int y, int u, int v, int *r, int *g, int *b){ |
重点来了,将YUV数组恢复为RGBA数组
1 | void YV12Convert2RGB(uint8_t *yuv, uint8_t *rgb, int width, int height){ |
以上就是iOS中RGB与YUV互转的方式了,你可以在这里下载demo