YUV是一种颜色编码方法,Y表示亮度(Luma),也就是灰度值。U分量和V分量重存储了是色度(Chroma)信息,主要作用是描述了视频的色彩及饱和度,用于指定每个像素的颜色。主要用于电视系统以及模拟视频领域,它将亮度信息Y与色彩信息UV分离,没有UV信息一样可以显示完整的图像,显示出来将是黑白效果,解决了彩色电视机和黑白电视机之间的兼容问题。早期的黑白电视机只有亮度值Y,后面彩色电视的出现引入了UV分量,形成了现有的YUV格式,又称为YCbCr格式。 YUV格式的优点。由于人眼对亮度敏感而对色度不敏感的特点,因此在YUV格式中减少了UV的数据量,在不影响用户观看的情况下有效的压缩了总体的数据量。所以YUV与RGB格式相比,占用更少的存储空间,相对应的在传输过程中也会减少带宽的消耗量。
常见的YUV格式有YUV444、YUV422、YUV420。 对于YUV444格式,一个Y分量对应一组UV分量,及完全采样。如下图所示: 对于YUV422格式,每两个Y公用一组UV分量。如下图所示:
对于YUV420格式,并不是说只有Y分量和U分量,没有V分量。U分量和V分量是交替出现的,例如第一行为4:2:0,则第二行为4:0:2,如此反复依次交替。 YUV444、YUV422、YUV420几种格式中最常用的就是YUV420格式了。YUV420格式又可以细分为YUV420P和YUV420SP两种类型。
YUV420P是三平面存储,Y、U、V三个分量分别占用一个平面。数据的排列方式有I420,YV12。 I420,YYYYYYYY UUVV YV12,YYYYYYYY VVUU
YUV420SP是两平面存储,Y分量占用一个平面,UV分量公用一个平面。有两种排列方式NV12、NV21。 NV12,YYYYYYYY UVUV NV21,YYYYYYYY VUVU
RGB是一种对颜色进行编码的方式。由RGB(红、绿、蓝)经过叠加组合可以展现出所有的色彩,每种颜色都可以由这三个变量的不同值组合而成。记录一张图像时,最常用的编码方式就是RGB。
YUV的主要优势在于可以兼容之前的黑白电视,单独只有Y数据就可以显示完整的黑白图像,UV是后期加入的色彩参数。并且经过多年的发展,YUV的压缩算法得到了改进,使得图像的压缩率大大增加。使得YUV比RGB占用更少的存储空间。虽然YUV很有优势,但是在最终显示的时候其本质上也都是显示的RGB数据,一些支持YUV输入的设备,内部也是做了相应的转换。
YUV格式在存储上存在两类布局:
YUV根据Y、U、V存储方式的不同,可以分成两个格式:
紧缩格式(packed):每个像素点的Y、U、V连续存储,Y1U1V1…YnUnVn。 平面格式(planar):先存储所有像素点的Y分量,再存储所有像素点的UV分量,Y和UV分别连续存储在不同矩阵当中。
Packed:把相邻的几个像素打包起来;比如把水平方向2个像素打包到一个DWORD Plannar:方式相反;Y分量和UV分量完全分开来保存
YUY2和YV12是最常用的两个代表
-
YUY2是packed方式的。水平方向两个像素打包到一个DWORD,并且UV采样率只有Y的一半,这符合人的视觉特征能有效的压缩数据,具体布局为[Y0, U0,Y1,V0]。 这种格式常见于MPEG1的解码器。 -
YV12则常见于H.264的解码器,它属于plannar方式。对于一个MxN大小的视频来说,数据布局为[Y:M x N] [V:M/2 x N/2] [U:M/2 x N/2]. 也就是说UV的采样率在水平和垂直方向上都只有Y的一半。 平面格式(planar)又分为:
平面格式(planar):先存储所有像素的Y,再存储所有像素点U或者V,最后存储V或者U。其中U、V分别连续存储:Y1…Yn U1…Un V1…Vn 或者 Y1…Yn V1…Vn U1…Un。 半平面格式(semi-planar):先存储所有像素的Y,再存储所有像素点UV或者VU。其中U、V交替存储:Y1…Yn U1V1…UnVn 或者 Y1…Yn V1U1…VnUn。 采样方式采用YUV420、存储方式采用平面格式(planar)称为YUV420P。YUV420P根据U和V顺序不同又分为:
I420: Y1…Y4n U1…Un V1…Vn (例如:YYYYYYYYUUVV) YV12:Y1…Y4n V1…Vn U1…Un (例如:YYYYYYYYVVUU) 采样方式采用YUV420、存储方式采用半平面格式(semi-planar)称为YUV420SP,YUV420SP根据U和V顺序不同又分为:
NV12: Y1…Y4n U1V1…UnVn (例如:YYYYYYYYUVUV) NV21:Y1…Y4n V1U1…VnUn (例如:YYYYYYYYVUVU)
YUV_420_888
多平面Android YUV 420格式
此格式是通用的YCbCr格式,能够描述任何4:2:0色度采样的平面或半平面缓冲区(但不完全交织),每个颜色样本有8位。
这种格式的图像始终由三个单独的数据缓冲区表示,每个颜色缓冲区一个。 缓冲区中始终会附带其他信息,描述每个平面的行步长和像素步长。
确保Image#getPlanes()返回的数组中平面的顺序,使得平面#0始终为Y,平面#1始终为U(Cb),平面#2始终为V(Cr)。
保证Y平面不与U / V平面交错(特别是yPlane.getPixelStride()中的像素步长始终为1)。
确保U / V平面具有相同的行步长和像素步长(尤其是uPlane.getRowStride()== vPlane.getRowStride()和uPlane.getPixelStride()== vPlane.getPixelStride();)。
YUV_420_888使用实践 根据API中的介绍,我们可以知道,YUV_420_888是可以兼容所有YUV420P和YUV420SP格式的。也就是说上面提到的I420、YV12、NV12、NV21都可以是YUV_420_888的具体实现
I420、YV12、NV12、N21转RGBA
I420、YV12、NV12、N21转换时都有一些共性:
每个像素有自己独立的Y分量,Y的数量与像素点数量相等。 4个像素共用一个U分量和V分量。 因此,我们只要找到每个像素Y、U、V分量的对应关系就可以进行转换。 https://github.com/tensorflow/tensorflow/blob/master/tensorflow/examples/android/src/org/tensorflow/demo/
opencv YUV -> RGB
https://github.com/qiuxintai/YUV420Converter
#include <stdint.h>
#include <opencv2/opencv.hpp>
#include <opencv2/core/core.hpp>
#include "logger.h"
#include "opencv_utils.h"
using namespace std;
using namespace cv;
void opencvI420ToRGBA(unsigned char *src, unsigned char *dst, int width, int height) {
Mat srcImg(height * 3 / 2, width, CV_8UC1, src);
Mat dstImg(height, width, CV_8UC4, dst);
cvtColor(srcImg, dstImg, CV_YUV2RGBA_I420);
}
void opencvYV12ToRGBA(unsigned char *src, unsigned char *dst, int width, int height) {
Mat srcImg(height * 3 / 2, width, CV_8UC1, src);
Mat dstImg(height, width, CV_8UC4, dst);
cvtColor(srcImg, dstImg, CV_YUV2RGBA_YV12);
}
void opencvNV12ToRGBA(unsigned char *src, unsigned char *dst, int width, int height) {
Mat srcImg(height * 3 / 2, width, CV_8UC1, src);
Mat dstImg(height, width, CV_8UC4, dst);
cvtColor(srcImg, dstImg, CV_YUV2RGBA_NV12);
}
void opencvNV21ToRGBA(unsigned char *src, unsigned char *dst, int width, int height) {
Mat srcImg(height * 3 / 2, width, CV_8UC1, src);
Mat dstImg(height, width, CV_8UC4, dst);
cvtColor(srcImg, dstImg, CV_YUV2RGBA_NV21);
}
libyuv YUV -> RGB
https://github.com/hzl123456/LibyuvDemo
#include <stdint.h>
#include <libyuv/convert.h>
#include <libyuv/convert_argb.h>
#include <libyuv/convert_from.h>
#include <libyuv/rotate.h>
#include <libyuv/rotate_argb.h>
#include "logger.h"
#include "libyuv_utils.h"
using namespace std;
using namespace libyuv;
void libyuvI420ToRGBA(unsigned char *src, unsigned char *dst, int width, int height) {
unsigned char *pY = src;
unsigned char *pU = src + width * height;
unsigned char *pV = src + width * height * 5 / 4;
I420ToABGR(pY, width, pU, width >> 1, pV, width >> 1, dst, width * 4, width, height);
}
void libyuvYV12ToRGBA(unsigned char *src, unsigned char *dst, int width, int height) {
unsigned char *pY = src;
unsigned char *pU = src + width * height * 5 / 4;
unsigned char *pV = src + width * height;
I420ToABGR(pY, width, pU, width >> 1, pV, width >> 1, dst, width * 4, width, height);
}
void libyuvNV12ToRGBA(unsigned char *src, unsigned char *dst, int width, int height) {
unsigned char *pY = src;
unsigned char *pUV = src + width * height;
NV12ToABGR(pY, width, pUV, width, dst, width * 4, width, height);
}
void libyuvNV21ToRGBA(unsigned char *src, unsigned char *dst, int width, int height) {
unsigned char *pY = src;
unsigned char *pUV = src + width * height;
NV21ToABGR(pY, width, pUV, width, dst, width * 4, width, height);
参考: https://www.jianshu.com/p/944ede616261
|