YUV
YUV 简介
YUV 是一种彩色编码系统,相对于 RGB 颜色空间,设计 YUV 的目的就是为了编码、传输的方便,减少带宽占用和信息出错。
YUV 设计的初衷是为了使彩色电视能够兼容黑白电视。对于黑白电视信号,没有色度信息也就是(UV),那么在彩色电视显示的时候指显示亮度信息。
Y 为亮度信息,UV 为色度(Chroma)信息。U/V分别等于 blue–luminance/red–luminance。Y 信号分量为黑白灰度图。U、V信号分量为单色彩色图。
YUV 编码
人眼的视觉特点是对亮度更敏感,对位置、色彩相对来说不敏感。在视频编码系统中为了降低带宽,可以保存更多的亮度信息(luma),保存较少的色差信息(chroma)。这叫做色度二次采样。原则,数字图像中:
- 每一个图形像素都要包含 luma(亮度)值。
- 几个图形像素共用一个 Cb + Cr 值,一般是 2、4、8 个像素。
常见的 YUV 格式以及其对应的采样方式:
如上图中所示,左侧一列,每一个小矩形是图形像素表示,黑框矩形是色度像素表示,小黑点是表示色度像素值(Cb+Cr),表示图形像素和色度像素在水平和垂直方向的比例关系。比如:
- 4:4:0 水平方向是1/1,垂直方向是1/2,表示一个色度像素对应了两个图形像素。
- 4:2:2 水平方向是1/2,垂直方向是1/1,表示一个色度像素对应了两个图形像素。
- 4:2:0 水平方向是1/2,垂直方向是1/2,表示一个色度像素对应了四个图形像素。
右侧一列是二次采样模式记号表示, 是 J:a:b 模式,实心黑色圆圈表示包含色度像素(Cb+Cr),空心圆圈表示不包含色度像素。对于 J:a:b 模式,主要是围绕参考块的概念定义的,这个参考块是一个 J x 2 的矩形,J 通常是 4。这样,此参考块就是宽度有 4 个像素、高度有 2 个像素的矩形。a 表示参考块的第一行包含的色度像素样本数,b 表示在参考块的第二行包含的色度像素样本数。
-
4:4:0 参考块第一行包含四个色度样本,第二行没有包含色度样本。
-
4:2:2 参考块第一行包含两个色度样本,第二行也包含两个色度样本,他们是交替出现。
-
4:2:0 参考块第一行包含两个色度样本,第二行没有包含色度样本。
yuv444,yuv422,yuv420 等像素格式的本质是:每个图形像素都会包含亮度值,但是某几个图形像素会共用一个色度值,这个比例关系就是通过 4 x 2 的矩形参考块来定的。这样很容易理解类似 yuv440,yuv420 这样的格式了。
存储格式
-
平面格式(planar formats) :先连续存储所有像素点的Y,紧接着存储所有像素点的U,随后是所有像素点的 V,UV的顺序可能会调换。
-
紧缩格式(packed formats):对于 packed 的YUV格式,每个像素点的Y,U,V是连续交替存储的,如,yuv444 可能是YUV YUV YUV YUV, yuv420 可能是YYUV,YYUV,UV的顺序可能会调换,有些代码里面packed 也称为 Interleaved。
YUV420SP, YUV420P中的 P 表示的都是planar, SP 是 semi-Planar 他们的区别是:
- YUV420P: YUV都是planer格式, 即 YYYY UU VV的顺序(UV的顺序可能会调换)。
- YUV420SP: Y是 planer 格式, UV 是 packet 格式,即 YYYY YYYY UV UV的顺序(UV的顺序可能会调换)。
yuv422
yuyv(yuy2)
[ y u ] [ y v ] [ y u ] [ y v ]
[ y u ] [ y v ] [ y u ] [ y v ]
[ y u ] [ y v ] [ y u ] [ y v ]
[ y u ] [ y v ] [ y u ] [ y v ]
uyvy
[ u y ] [ v y ] [ u y ] [ v y ]
[ u y ] [ v y ] [ u y ] [ v y ]
[ u y ] [ v y ] [ u y ] [ v y ]
[ u y ] [ v y ] [ u y ] [ v y ]
yuv422p (yu16) 或 (yv16)
[ y y y y ]
[ y y y y ]
[ y y y y ]
[ y y y y ]
[ u u u u ]
[ u u u u ]
[ v v v v ]
[ v v v v ]
--------------
[ y y y y ]
[ y y y y ]
[ y y y y ]
[ y y y y ]
[ v v v v ]
[ v v v v ]
[ u u u u ]
[ u u u u ]
yuv422sp (nv16) 或 (nv61)
[ y y y y ]
[ y y y y ]
[ y y y y ]
[ y y y y ]
[ u v u v ]
[ u v u v ]
[ u v u v ]
[ u v u v ]
------------------
[ y y y y ]
[ y y y y ]
[ y y y y ]
[ y y y y ]
[ v u v u ]
[ v u v u ]
[ v u v u ]
[ v u v u ]
yuv420
yuv420p(yu12)或(yv12)
[ y y y y ]
[ y y y y ]
[ y y y y ]
[ y y y y ]
[ u u ]
[ u u ]
[ v v ]
[ v v ]
--------------
[ y y y y ]
[ y y y y ]
[ y y y y ]
[ y y y y ]
[ v v ]
[ v v ]
[ u u ]
[ u u ]
yuv420sp(nv12)或(nv21)
[ y y y y ]
[ y y y y ]
[ y y y y ]
[ y y y y ]
[ u v u v ]
[ u v u v ]
---------------
[ y y y y ]
[ y y y y ]
[ y y y y ]
[ y y y y ]
[ v u v u ]
[ v u v u ]
小结
- nv 都是 semi-plane 系列
- nv12 表示正常的顺序,即uv plane,先是u,然后是v,nv 21 表示相反的顺序,即uv plane,先是v,然后是u,nv16 和 nv61 也类似此含义。
- 12 表示一个像素占用12bit,其中y是定死的占8bit,也就是u占2bit,v占2bit。
- 16 表示一个像素占用16bit,其中y是定死的8bit,也即是u占4bit,v占4bit。
分解 YUV 数据
分解 YUV420P
/*
* @brief:splict y u v from yuv420p
* param[in] src: srource file name
* param[in] w: srource file width
* param[in] h: srource file height
* param[in] frames: srource frame numbers
*/
int split_yuv420P(const char* src, int w, int h, int frames)
{
assert(src != nullptr);
FILE* fsrc = fopen(src, "rb");
if (fsrc == nullptr)
return -1;
const char* out_y = "out_y.y";
const char* out_u = "out_u.u";
const char* out_v = "out_v.v";
FILE* fy = fopen(out_y, "wb");
FILE* fu = fopen(out_u, "wb");
FILE* fv = fopen(out_v, "wb");
assert(fy && fu && fv);
const int frame_len = w * h * 3 / 2;
uint8_t* buf = new uint8_t[frame_len];
for (int i = 0; i < frames; ++i)
{
int n = fread(buf, 1, frame_len, fsrc);
if (n != frame_len)
break;
fwrite(buf, 1, w * h, fy);
fwrite(buf + w * h, 1, w * h / 4, fu);
fwrite(buf + w * h * 5 / 4, 1, w * h / 4, fv);
}
free(buf);
fclose(fsrc);
fclose(fy);
fclose(fu);
fclose(fv);
return 0;
}
分解 YUV444P
/*
* @brief:splict y u v from yuv444p
* param[in] src: srource file name
* param[in] w: srource file width
* param[in] h: srource file height
* param[in] frames: srource frame numbers
*/
int split_yuv444P(const char* src, int w, int h, int frames)
{
assert(src != nullptr);
FILE* fsrc = fopen(src, "rb");
if (fsrc == nullptr)
return -1;
const char* out_y = "out_y.y";
const char* out_u = "out_u.u";
const char* out_v = "out_v.v";
FILE* fy = fopen(out_y, "wb");
FILE* fu = fopen(out_u, "wb");
FILE* fv = fopen(out_v, "wb");
assert(fy && fu && fv);
const int frame_len = w * h * 3;
uint8_t* buf = new uint8_t[frame_len];
for (int i = 0; i < frames; ++i)
{
int n = fread(buf, 1, frame_len, fsrc);
if (n != frame_len)
break;
fwrite(buf, 1, w * h, fy);
fwrite(buf + w * h, 1, w * h, fu);
fwrite(buf + w * h * 2, 1, w * h, fv);
}
delete(buf);
fclose(fsrc);
fclose(fy);
fclose(fu);
fclose(fv);
return 0;
}
测试:
下载 Lena 图转换成 YUV 格式:
.\ffmpeg.exe -i Lena.jpg -s 480*420 -pix_fmt yuv42p Lena480_420_420p.yuv
.\ffmpeg.exe -i Lena.jpg -s 480*420 -pix_fmt yuv444p Lena480_420_444p.yuv