图片YUV数据格式

相关博客:
[YUV图片原理]https://yangandmore.github.io/2019/03/14/%E5%9B%BE%E7%89%87YUV%E6%95%B0%E6%8D%AE%E6%A0%BC%E5%BC%8F/
[RGB图片原理]https://yangandmore.github.io/2019/03/27/%E5%9B%BE%E7%89%87RGB%E6%95%B0%E6%8D%AE%E6%A0%BC%E5%BC%8F/
[libyuv for android库编译和简单使用]https://yangandmore.github.io/2019/03/07/libYUV%E7%BC%96%E8%AF%91/
[libturbojpeg for android库编译和简单使用]https://yangandmore.github.io/2019/03/08/libjpeg-turbo%E7%BC%96%E8%AF%91/
[yuv-tool for android库的项目]https://yangandmore.github.io/2020/07/13/yuv-tool%E5%B7%A5%E5%85%B7%E7%B1%BB/
[turbo-jpeg-tool for android库的项目]<>

转:https://blog.csdn.net/leixiaohua1020/article/details/50534150
YUV格式(英文):https://linuxtv.org/downloads/v4l-dvb-apis/uapi/v4l/yuv-formats.html?highlight=yuv
YUV格式(中文):https://blog.csdn.net/airk000/article/details/25032901

一、YUV简介

YUV的原理是把亮度与色度分离,研究证明,人眼对亮度的敏感超过色度。利用这个原理,可以把色度信息减少一点,人眼也无法查觉这一点。YUV三个字母中,其中”Y”表示明亮度(Lumina nce或Luma),也就是灰阶值;而”U”和”V”表示的则是色度(Chrominance或Chroma),作用是描述影像色彩及饱和度,用于指定像素的颜色。用这个三个字母好象就是通道命令重点内容
其优点在于:与黑白YUV转换快、YUV数据大小小于RGB格式。

二、采样

色度通道(UV)的采样率可以低于亮度通道(Y),而不会显着降低感知质量。以A:B:C的表示法分别表示YUV的频率。

  • 4:4:4
  • 4:2:2
  • 4:2:0
  • 4:1:1
    如下图中表示:
    github

三、存储格式

YUV 在存储上通常分为平面格式(Planar)半平面格式(Semi-Planar)以及打包格式(Packed)

Planar平面格式(P)

YUV这三个分量分别使用单独的数组保存,在计算使用过程中比较方便。

YU12(I420)

YU12I420,也叫IYUV。如图,三个平面分别存储YUV三个分量。每四个Y分量共享一组UV分量。
github
从图中可看出,U、V 平面的每行字节数的宽、高都是 Y 平面的一半。
I420在音视频开发中非常常见且重要的一种格式。

YV12

他与I420非常像,只是改变了U、V平面的顺序。因此整体的排序如下图:
github

J4XX

注意JXXX的格式与I4XX非常像,不同之处在于JXX具有完整范围的亮度(Y)0-255。而不是像IXX的范围:16-240。在色度(UV)分量上完全一致

I422

每两个 Y 分量共享一组 UV 分量。U、V 平面的元素量, 宽度是 Y 平面的一半,但高度与 Y 平面一致。
github

Semi-Planar半平面格式(SP)

半平面格式具有两个平面而不是三个平面,一个平面存储亮度(Y)分量,另一个平面存储两个色度(UV)分量

NV12

两个平面,分别存储 Y 分量 和 UV 分量。其中 UV 分量共用一个平面并且以 U, V, U, V 的顺序交错排列。每四个 Y 分量共享一组 UV 分量。
github
可以看出,uv平面的每行字节数与Y平面一致,高度却是Y平面的一半。
NV12是IOS相机可直接输出的两种视频帧格式之一,另一种是BGRA32

NV21

NV12几乎一致,区别在于UV平面中的U、V的排列顺序颠倒。
github
NV21是Android相机的默认输出视频帧格式。

Packed打包格式

打包格式通常只有一个平面,所有亮度(Y)和色度(UV)数据都交织在一起。有点类似于 RGB 格式,只是使用了不同的色彩空间。
打包格式在网络摄像头中较为常见。硬件设备使用多平面格式效率较低,因为每个像素需要多次内存访问。而打包格式由于仅一个平面,访问内存的开销较小。

AYUV

AYUV 是 Packed 打包格式,其中每个像素编码为四个连续字节,每个像素在内存中按照 V, U, Y, A 的顺序排列(A 指 alpha 通道)。
github

YUYV

包括YUY2
YUY2 是 Packed 打包格式,其中两个像素共用一组 UV 分量,内存中按照 Y U Y V 的顺序排列,如下图所示:
github

UYVY

包括Y422
UYVYYUYV类似,只是亮度(Y)分量与色度(UV)分量排列顺序颠倒,如下图所示:
github

四、代码实例

例:分离YUV422像素中的Y、U、V分量

由于YUV的数据是以Planar的格式平铺的,因此可以将数据分段取出,并放置在文件中即可查看YUV各数据图片的样式。

#include <iostream>

int main() {
char* url = "../lena_256x256_yuv422p.yuv"
char* url_Y = "./0_Y.y"
char* url_U = "./0_U.y"
char* url_V = "./0_V.y"

FILE* file = fopen(url, "rb+");
FILE* file_Y= fopen(url_Y, "wb+");
FILE* file_U= fopen(url_U, "wb+");
FILE* file_V= fopen(url_V, "wb+");

unsigned char* pic = (unsigned char*)malloc(256 * 256 * 2);

// read
fread(pic, 1, 256 * 256 * 2, file);

// Y
fwrite(pic, 1, 256 * 256, file_Y);
// U
fwrite(pic + (256 * 256), 1 ,(256 * 256) >> 1, file_U)
// V
fwrite(pic + ((256 * 256 * 3) >> 1), 1, (256 * 256) >> 1, file_V);

free(pic);
fclose(file);
fclose(file_Y);
fclose(file_U);
fclose(file_V);

return 0;
}

1.首先获取源文件和我们需要创建的三个文件
2.创建我们需要的最大字符缓存区(这里可以依据图片中的方式算得Y(256*256)、U(256*128)、V(256*128))
3.将源文件中的数据全部存储至缓存区
4.将缓存区中的数据,分段写入对应的Y、U、V文件中
5.关闭退出

提示:在显示YUV正常却无法显示.y文件的时候,需要注意显示器的像素格式分辨率图片如下
github
github

下述图片可以看到我们的源文件、Y、U、V各图片的显示效果。
github

例:获取纯Y、纯U、纯V的yuv格式图片

我们将Y数据写入yuv文件中,如果以420格式查看图片,那么剩余的U、V数据都以128来填充。

#include <iostream>

int main() {
char* url = "../lena_256x256_yuv422p.yuv"
char* url_Y = "./0_Y.yuv"

FILE* file = fopen(url, "rb+");
FILE* file_Y= fopen(url_Y, "wb+");

unsigned char* pic = (unsigned char*)malloc(256 * 256 * 2);

// read
fread(pic, 1, 256 * 256 * 2, file);

// 将128写入
memset(pic+(256*256), 128, 256*256);
fwrite(pic, 1, 256*256*2, file_y);

free(pic);
fclose(file);
fclose(file_Y);

return 0;
}

例:将原图YUV422的图片亮度减半

我们将Y数据减半来使图片颜色的亮度减半

#include <iostream>

int main() {
char* url = "../lena_256x256_yuv422p.yuv"
char* url_Y = "./0_YUV_halve.yuv"

FILE* file = fopen(url, "rb+");
FILE* file_Y= fopen(url_Y, "wb+");

unsigned char* pic = (unsigned char*)malloc(256 * 256 * 2);

// read
fread(pic, 1, 256 * 256 * 2, file);

for (int i = 0; i < width * height; ++i) {
// Y数据减半
pic[i] = pic[i] / 2;
}
fwrite(pic, 1, width*height*2, file_y);

free(pic);
fclose(file);
fclose(file_Y);

return 0;
}

例:获取YUV质量

根据公式来获取两张图片的质量差值
对于8bit量化的像素数据来说,PSNR的计算公式如下所示。
github
上述公式中mse的计算公式如下。
github

其中M,N分别为图像的宽高,xij和yij分别为两张图像的每一个像素值。PSNR通常用于质量评价,就是计算受损图像与原始图像之间的差别,以此来评价受损图像的质量。

PSNR取值通常情况下都在20-50的范围内,取值越高,代表两张图像越接近,反映出受损图像质量越好。

#include <iostream>

int main() {
char* url = "../lena_256x256_yuv422p.yuv"
char* url_Y = "./0_YUV_halve.yuv"

FILE* file = fopen(url, "rb+");
FILE* file_Y= fopen(url_Y, "rb+");

unsigned char* pic = (unsigned char*)malloc(256 * 256 * 2);
unsigned char* pic1 = (unsigned char*)malloc(256 * 256 * 2);

// read
fread(pic, 1, 256 * 256 * 2, file);
fread(pic1, 1, 256 * 256 * 2, file_Y);

double mse_sum = 0, mse = 0, psnr = 0;
for (int i = 0; i< width * height; i++>) {
mse_sum += pow(double(pic[i] - pic1[i]), 2);
}
mse = mse_sum / (width * height);
psnr = 10 * log10(255.0 * 255.0 / mse);

printf("PSNR is %5.3f", psnr);

free(pic);
free(pic1);
fclose(file);
fclose(file_Y);

return 0;
}

例:YUV图片旋转角度

在旋转上这里使用YUV420P调试,因为422P貌似无法做90、270的旋转工作。
由于从422文件转用成420文件,因此从数据总量上也有变化:
YUV422
Y(width*height)
U(width\*height>>1)
V(width\*height>>1)

YUV422
Y(width*height)
U(width\*height>>2)
V(width\*height>>2)
也即U、V分量的总量需要减半。

在旋转时,首先旋转Y分量,之后是U分量和V分量,最后拼接成字符串即可。

#include <iostream>

int main() {
char* url = "../lena_256x256_yuv420p.yuv"
char* url_Y = "./YUV_rotate.yuv"

FILE* file = fopen(url, "rb+");
FILE* file_Y= fopen(url_Y, "rb+");

unsigned char* pic = (unsigned char*)malloc(width*height*3>>1);
unsigned char* pic1 = (unsigned char*)malloc(width*height*3>>1);

fread(pic, 1, width*height*3>>1, file);

// Y
int k = 0;
for (int i = 0; i < width; ++i) {
for (int j = height - 1; j >= 0; --j) {
pic1[k] = pic[j*width+i];
k++;
}
}

// U
for (int i = 0; i < width/2; ++i) {
for (int j = height/2 - 1; j >= 0; --j) {
pic1[k] = pic[width*height + (j * (width/2) + i)];
k++;
}
}

// V
for (int i = 0; i < width/2; ++i) {
for (int j = height/2 - 1; j >= 0; --j) {
pic1[k] = pic[(width*height*5>>2) + (j * (width/2) + i)];
k++;
}
}

fwrite(pic1, 1, width*height*3>>1, file_rotate);

free(pic);
free(pic1);
fclose(file);
fclose(file_rotate);

return 0;
}