RGB和YUV是一种颜色编码格式。
这里简单介绍一下RGB、YUV和HSV。
出处:一文读懂 YUV 的采样与格式
https://glumes.com/post/ffmpeg/understand-yuv-format/
RGB与YUV
YUV 是一种颜色编码方法,和它等同的还有 RGB 颜色编码方法。
RGB 三个字母分别代表了 红(Red)、绿(Green)、蓝(Blue),这三种颜色称为 三原色,将它们以不同的比例相加,可以产生多种多样的颜色。
在图像显示中,一张 1280 * 720 大小的图片,就代表着它有 1280 * 720 个像素点。其中每一个像素点的颜色显示都采用 RGB 编码方法,将 RGB 分别取不同的值,就会展示不同的颜色。
RGB 图像中,每个像素点都有红、绿、蓝三个原色,其中每种原色都占用 8 bit,也就是一个字节,那么一个像素点也就占用 24 bit,也就是三个字节。
一张 1280 * 720 大小的图片,就占用 1280 * 720 * 3 / 1024 / 1024 = 2.63 MB 存储空间。
YUV 颜色编码
YUV 颜色编码采用的是 明亮度 和 色度 来 指定像素的颜色。
其中,Y 表示明亮度(Luminance、Luma),而 U 和 V 表示色度(Chrominance、Chroma)。
而色度又定义了颜色的两个方面:色调和饱和度。
使用 YUV 颜色编码表示一幅图像,它应该下面这样的:
和 RGB 表示图像类似,每个像素点都包含 Y、U、V 分量。 但是它的 Y 和 UV 分量是可以分离的,如果没有 UV 分量一样可以显示完整的图像,只不过是黑白的。
对于 YUV 图像来说,并不是每个像素点都需要包含了 Y、U、V 三个分量,根据不同的采样格式,可以每个 Y 分量都对应自己的 UV 分量,也可以几个 Y 分量共用 UV 分量。
这里有一个HSL和HSV模型的概念。
出处:百科
HSL和HSV都是一种将RGB色彩模型中的点在圆柱坐标系中的表示法。这两种表示法试图做到比基于笛卡尔坐标系的几何结构RGB更加直观。
HSL即色相、饱和度、亮度(英语:Hue, Saturation, Lightness)。HSV即色相、饱和度、明度(英语:Hue, Saturation, Value),又称HSB,其中B即英语:Brightness。
色相(H)是色彩的基本属性,就是平常所说的颜色名称,如红色、黄色等。
饱和度(S)是指色彩的纯度,越高色彩越纯,低则逐渐变灰,取0-100%的数值。
明度(V),亮度(L),取0-100%。
HSV模型通常用于计算机图形应用中。在用户必须选择一个颜色应用于特定图形元素各种应用环境中,经常使用HSV 色轮。在其中,色相表示为圆环;可以使用一个独立的三角形来表示饱和度和明度。典型的,这个三角形的垂直轴指示饱和度,而水平轴表示明度。在这种方式下,选择颜色可以首先在圆环中选择色相,在从三角形中选择想要的饱和度和明度。
这里其实我完全没有理解这几个编码格式的原理,为什么RGB,YUV 和HSV显示出颜色。只记住了YUV可以分离,没有了Y,UV显示为黑白。继续探究。找到颜色模式的概念。
颜色模式https://baike.baidu.com/item/%E9%A2%9C%E8%89%B2%E6%A8%A1%E5%BC%8F/7361869
颜色的实质是一种光波。它的存在是因为有三个实体:光线、被观察的对象以及观察者。
人眼是把颜色当作由被观察对象吸收或者反射不同波长的光波形成的。例如,当在一个晴朗的日子里,我们看到阳光下的某物体呈现红色时,那是因为该物体吸收了其它波长的光,而把红色波长的光反射到我们人眼里的缘故。当然,我们人眼所能感受到的只是波长在可见光范围内的光波信号。当各种不同波长的光信号一同进入我们的眼睛的某一点时,我们的视觉器官会将它们混合起来,作为一种颜色接受下来。同样我们在对图像进行颜色处理时,也要进行颜色的混合,但我们要遵循一定的规则,即我们是在不同颜色模式下对颜色进行处理的。
颜色模式,是将某种颜色表现为数字形式的模型,或者说是一种记录图像颜色的方式。分为:RGB模式、CMYK模式、HSB模式、Lab颜色模式、位图模式、灰度模式、索引颜色模式、双色调模式和多通道模式。
这里我还是有很多疑问,为什么RGB能表示颜色?所以这就又涉及到光学的范畴了。再想下去得去找本书去看了,所以暂且不管这些疑问。
上面一大堆文字关键点:
RGB:红绿蓝
YUV :Y= 明亮度 UV = 色度,Y和UV可以分离,仅有UV显示为黑白。
图像是由像素组成的。
那么这里有一个疑问。用YUV方法编码的数据就是YUV数据,用RGB方法编码的数据就是RGB数据,那么原始数据是什么?哪里来的?原始格式是采样后存储为YUV或RGB格式的数据。
下面是重点:YUV的格式。
出处:Android中的YUV格式解析
https://www.jianshu.com/p/0d5800b5d9a2
YUV格式有YUV444、 YUV422 和 YUV420 三种,差别在于:
1.YUV444: 每个Y分量对应一组UV分量
2.YUV422:每两个Y分量共用一组UV分量
3.YUV420:每四个Y分量共用一组UV分量
1.YUV444格式
YUV444是每个Y分量对应一组UV分量,也就是说Y1,U1和V1为一组数据,组成一个YUV像素点,Y2,U2和V2为一组数据,组成一个YUV像素点,依次类推,一直到Y32,U32和V32为一组数据,组成一个YUV像素点。一共32个像素点,这些像素点构成了一个图像。
2.YUV422格式分为:YCbYCr(YUYV)格式 (打包格式)、CbYCrY(UYVY)格式 (打包格式)、YUV422P格式 (平面格式 YUV422Planar) 和YUV422SP格式(平面格式YUV422SemiPlanar) 。
YUYV是每两个Y分量共用一组UV分量,也就是Y1、U1和V1为一组,Y2、U1和V1为一组。Y3、U2和V2为一组,Y4、U2和V2为一组。后面依次类推,Y31和U16和V16为一组,Y32、U16和V16为一组。
I422/UYVY是也是Y1、U1和V1为一组,Y2、U1和V1为一组。Y3、U2和V2为一组,Y4、U2和V2为一组。后面依次类推,Y31和U16和V16为一组,Y32、U16和V16为一组。
YUV422P是Y1、U1和V1为一组,Y2、U1和V1为一组。Y3、U2和V2为一组,Y4、U2和V2为一组。后面依次类推,Y31和U16和V16为一组,Y32、U16和V16为一组。
YUV422SP是Y1、U1和V1为一组,Y2、U1和V1为一组。Y3、U2和V2为一组,Y4、U2和V2为一组。后面依次类推,Y31和U16和V16为一组,Y32、U16和V16为一组。
思考:综合来看,这几种不同的YUV422格式所占的存储大小是一样的,都存储了32个像素点的YUV数据,不一样的是存储位置。那么为什么会有不同的存储位置?这么多格式的优劣点都是什么?
3.YUV420格式分为YUV420P 和 YUV420SP,YUV420P分为I420 和 YV12 两种。YUV420SP分为NV12 和 NV21 两种。
I420由于是每四个Y分量共用一组UV分量,也就是说Y1,U1和V1为一组数据,组成一个YUV像素点,Y2,U1和V1为一组数据,组成一个YUV像素点,Y3,U1和V1为一组数据,Y4,U1和V1为一组数据,依次类推,一直到Y29,U8和V8为一组数据,Y30,U8和V8为一组数据,Y31,U8和V8为一组数据,Y32,U8和V8为一组数据, 一共32个像素点。
YUV420P(YV12)像素存储, 跟I420的UV存储顺序相反:
YV12由于是每四个Y分量共用一组UV分量,也就是说Y1,U1和V1为一组数据,组成一个YUV像素点,Y2,U1和V1为一组数据,组成一个YUV像素点,Y3,U1和V1为一组数据,Y4,U1和V1为一组数据,依次类推,一直到Y29,U8和V8为一组数据,Y30,U8和V8为一组数据,Y31,U8和V8为一组数据,Y32,U8和V8为一组数据, 一共32个像素点。
NV12,UV分量交织存储
NV21 像素存储,UV分量交织存储,跟NV12的UV分量相反:
摄像头采集到的数据是RGB24的格式,RGB24 一帧的大小是 width x height x 3Bit, RGB32 一帧的大小则是width x height x 4Bit。YUV420 一帧的大小是 width x height x 1.5Bit。
RGB格式:
RGB16,RGB24和ARGB32。
RGB16
每个像素用16比特位表示,占2个字节,RGB分量分别使用5位、6位、5位。
RGB16数据格式主要有二种:RGB565和RGB555。
RGB565
颜色是由 rgb组成的,因此一个RGB565格式的颜色的rgb分离公式如下所示。
//获取高字节的5个bit
R = color & 0xF800;
//获取中间6个bit
G = color & 0x07E0;
//获取低字节5个bit
B = color & 0x001F;
使用window自带程序员计算器工具,将16进制转2进制。
0xF800转二进制为11111 000000 00000,0x07E0转二进制是00000 111111 00000,
0x001F转二进制是00000 000000 11111。
位与运算符&是从高位开始比较,如果两个数都为1则为1,否则为0。
假如一个color数据是01000 011111 10111,做&操作后,R值为01000 000000 00000,G值为00000 011111 00000,B值为00000 000000 10111。
这里其实还需要一步>>右移 。
这里复习一下基本操作符>>。下面的例子是5>>2 。
5的二进制是 0000 0000 0000 0101
右移两位 0000 0000 0000 0001
所以R值右移11位,G值右移5位后就是实际的值了。
RGB555
每个像素用16比特位表示,占2个字节,RGB分量都使用5位(最高位不用)。
//获取高字节的5个bit
R = color & 0x7C00;
//获取中间5个bit
G = color & 0x03E0;
//获取低字节5个bit
B = color & 0x001F;
RGB24
占3个字节。
注意:在内存中RGB各分量的排列顺序为:BGR BGR BGR ......。
R = color & 0x0000FF00;
G = color & 0x00FF0000;
B = color & 0xFF000000;
ARGB32
RGB32图像每个像素用32比特位表示,占4个字节,R,G,B分量分别用8个bit表示,存储顺序为B,G,R,最后8个字节保留。注意:在内存中RGB各分量的排列顺序为:BGRA BGRA BGRA ......。
RGB32本质就是带alpha通道的RGB24,与RGB32的区别在与,保留的8个bit用来表示透明,也就是alpha的值。
R = color & 0x0000FF00;
G = color & 0x00FF0000;
B = color & 0xFF000000;
A = color & 0x000000FF;
对比可以发现同一个图像YUV格式比RGB格式所占储存空间小。
android平台下的RGB格式
我们平时在android平台下处理Bitmap的时候,下面的几个参数应该接触的比较多:
Bitmap.Config.ALPHA_8:
每个像素用8比特位表示,占1个字节,只有透明度,没有颜色。
Bitmap.Config.RGB_565:
每个像素用16比特位表示,占2个字节,RGB分量分别使用5位、6位、5位,上面的图已经有作说明。
Bitmap.Config.ARGB_4444:
每个像素用16比特位表示,占2个字节,由4个4位组成,ARGB分量都是4位。
Bitmap.Config.ARGB_8888:
每个像素用32比特位表示,占4个字节,由4个8位组成,ARGB分量都是8位。
注意:java默认使用大端字节序,c/c++默认使用小端字节序,android平台下Bitmap.config.ARGB_8888的Bitmap默认是大端字节序,当需要把这个图片内存数据给小端语言使用的时候,就需要把大端字节序转换为小端字节序。例如:java层的ARGB_8888传递给jni层使用时,需要把java层的ARGB_8888的内存数据转换为BGRA8888。
yuv转rgb可以用libyuv这个库。libyuv是google 开源的用于实现对各种yuv数据之间的转换包括裁剪、缩放、旋转,以及yuv和rgb 之间的转换,底层有一部分代码是基于汇编实现的,大大提高了转换速度。
具体实现可以参考这个:
libyuv—libyuv测试使用ARGBToI420和ConvertToARGB接口
https://blog.csdn.net/xiaibiancheng/article/details/73065646
FOURCC_IYUV数据转为Argb,一般是用FOURCC_NV21转argb,把FOURCC_IYUV改成FOURCC_NV21就行了,具体需要转什么格式就填什么格式就好。
其实这部分应该也封装好了可以直接用。
Nv21转Bitmap(高效率转化)
https://blog.csdn.net/qq1137830424/article/details/81980673
我之前遇见过一个问题,使用UVC Camera使用android自带的函数Nv21转Bitmap不能转成功,后面使用rawByteArray2RGBABitmap2去转可以转出来,但是颜色不对。
public Bitmap rawByteArray2RGBABitmap2(byte[] data, int width, int height) {
int frameSize = width * height;
int[] rgba = new int[frameSize];
for (int i = 0; i < height; i++)
for (int j = 0; j < width; j++) {
int y = (0xff & ((int) data[i * width + j]));
int u = (0xff & ((int) data[frameSize + (i >> 1) * width + (j & ~1) + 0]));
int v = (0xff & ((int) data[frameSize + (i >> 1) * width + (j & ~1) + 1]));
y = y < 16 ? 16 : y;
int r = Math.round(1.164f * (y - 16) + 1.596f * (v - 128));
int g = Math.round(1.164f * (y - 16) - 0.813f * (v - 128) - 0.391f * (u - 128));
int b = Math.round(1.164f * (y - 16) + 2.018f * (u - 128));
r = r < 0 ? 0 : (r > 255 ? 255 : r);
g = g < 0 ? 0 : (g > 255 ? 255 : g);
b = b < 0 ? 0 : (b > 255 ? 255 : b);
rgba[i * width + j] = 0xff000000 + (b << 16) + (g << 8) + r;
}
Bitmap bmp = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
bmp.setPixels(rgba, 0 , width, 0, 0, width, height);
return bmp;
}
后面修改了一句话。
把rgba[i * width + j] = 0xff000000 + (b << 16) + (g << 8) + r;
改成0xff000000 + (r << 16) + (g << 8) + b;
,改后颜色就正常了。
假如 b : 0001 0001,g: 0010 0100,r:0100 1000
b << 16 左移16
0001 0001 00000000 00000000
g << 8
0010 0100 00000000
如果执行0xff000000 + (b << 16) + (g << 8) + r就是
这样导致数组中存的是ABGR。
如果执行0xff000000 + (r << 16) + (g << 8) + b;
跟上面一样b : 0001 0001,g: 0010 0100,r:0100 1000
r << 16 左移16
0100 1000 00000000 00000000
g << 8 左移8
0010 0100 00000000
这样数组中存储的才是真的的ARGB数据,也就是下面填的Bitmap.Config.ARGB_8888。
虽然最后没有用这种方法因为效率太低,最后用的是libyuv编译的so文件,但是现在想来,无法使用android中的函数去转换的原因大概是rgb存储顺序的问题导致的。
参考链接:
java按位运算符(&、|、~、^) 移位操作符(>> >> )
https://blog.csdn.net/mxiaoyem/article/details/78569782
音视频基础知识---像素格式RGB
https://zhuanlan.zhihu.com/p/68528311
https://github.com/byhook/ffmpeg4android/blob/master/readme/%E5%9B%BE%E8%A7%A3RGB565%E3%80%81RGB555%E3%80%81RGB16%E3%80%81RGB24%E3%80%81RGB32%E3%80%81ARGB32%E7%AD%89%E6%A0%BC%E5%BC%8F%E7%9A%84%E5%8C%BA%E5%88%AB.md
RGB、YUV和HSV颜色空间模型
能理解 RGB 模式中确定数值的各种颜色,但怎么理解「明度」、「饱和度」、「色相」等概念? - 豫涉川的回答 - 知乎
https://www.zhihu.com/question/35200358/answer/61833826
一文读懂 YUV 的采样与格式
https://glumes.com/post/ffmpeg/understand-yuv-format/
视频像素格式YUV和RGB
https://www.freehacker.cn/media/codec-yuv-rgb/
图文详解YUV420数据格式