YUV学习
- 1.概念
- 2.分类
- 2.1 I420(属于 YUV 420 Plannar)
- 2.2 YV12(属于 YUV 420 Plannar)
- 2.3 NV12(属于 YUV 420 Semi-Planar)
- 2.4 NV21(属于 YUV 420 Semi-Planar)
- 2.6 I422(属于 YUV 422 Plannar)
- 2.7 YV16(属于 YUV 422 Plannar)
- 2.8 NV16(属于 YUV 422 Semi-Planar)
- 2.9 NV61(属于 YUV 422 Semi-Planar)
- 2.10 YUVY(属于 YUV 422 Interleaved)
- 2.11 VYUY(属于 YUV 422 Interleaved)
- 2.12 UYVY(属于 YUV 422 Interleaved)
- 2.13 I444(属于 YUV 444 Plannar)
- 2.14 YV24(属于 YUV 444 Plannar)
- 2.15 NV24(属于 YUV 444 Semi-Planar)
- 2.16 NV42(属于 YUV 444 Semi-Planar)
- 2.17 YUV 444 Packed
- 3.类型转换--代码实现
1.概念
YUV,是一种颜色编码方法。常使用在各个视频处理组件中。 YUV在对照片或视频编码时,考虑到人类的感知能力,允许降低色度的带宽。Y'UV, YUV, YCbCr,YPbPr等专有名词都可以称为YUV。
其中 Y 表示明亮度(Luminance、Luma),也就是灰阶值。U、V 表示色度(Chrominance 或 Chroma),描述的是色调和饱和度。
YCbCr 其实是 YUV 经过缩放和偏移的翻版。其中 Y 与 YUV 中的 Y 含义一致,Cb,Cr 同样都指色彩,只是在表示方法上不同而已。YCbCr 其中 Y 是指亮度分量,Cb 指蓝色色度分量,而 Cr 指红色色度分量。
YUV格式的图片查看工具:
windows系统:7yuv
ubuntu系统:vooya
RGB
RGB 分别表示红(R)、绿(G)、蓝(B),也就是三原色,将它们以不同的比例叠加,可以产生不同的颜色。
比如一张 w * h 的图片,代表着有 w * h 个像素点。如果采用 RGB 编码方式,每个像素点都有红、绿、蓝三个原色,其中每个原色占用 8 位(1个字节),每个像素占用 24 位(3个字节),每个像素的取值范围为0-255。
那么,一张w * h大小的图片,就占用 w * h * 3 字节
YUV优点
对于 YUV 所表示的图像,Y 和 UV 分量是分离的。如果只有 Y 分量而没有 UV 分离,那么图像表示的就是黑白图像。彩色电视机采用的就是 YUV 图像,解决与和黑白电视机的兼容问题,使黑白电视机也能接受彩色电视信号。
人眼对色度的敏感程度低于对亮度的敏感程度。主要原因是视网膜杆细胞多于视网膜锥细胞,其中视网膜杆细胞的作用就是识别亮度,视网膜锥细胞的作用就是识别色度。所以,眼睛对于亮度的分辨要比对颜色的分辨精细一些。
利用这个原理,可以把色度信息减少一点,人眼也无法查觉这一点。
所以,并不是每个像素点都需要包含了 Y、U、V 三个分量,根据不同的采样格式,可以每个 Y 分量都对应自己的 UV 分量,也可以几个 Y 分量共用 UV 分量。相比 RGB,能够节约不少存储空间。
备注:
比特bit: 也称为位,是计算机信息中的最小单位,是 binary digit 缩写, 指二进制中的一位,所以比特就是一些0,1二进制
字节byte:8个二进制位构成一个字节,它是存储空间的基本计算单位。1个字节可以存储一个英文字母,或者半个汉字,换句话就是一 个汉字占据2个字节的存储空间。
转换关系:
1 byte (B) = 8 bits (b)
1 Kilobyte(K/KB) = 2^10 bytes = 1,024 B
1 Megabyte(M/MB) = 2^10 KB = 1,024 KB
1 Gigabyte(G/GB) = 2^10 MB = 1,024 MB
1 Terabyte(T/TB) = 2^10 GB = 1,024 GB
2.分类
我们可以将 YUV 格式按照数据大小分为三个格式:
- YUV 420,由 4 个 Y 分量共用一套 UV 分量。 占的字节大小 = (w * h * 8 + w * h * 0.25 * 8 * 2 ) / 8 字节 = w * h * 3 / 2 字节
- YUV 422,由 2 个 Y 分量共用一套 UV 分量。 占的字节大小 = (w * h * 8 + w * h * 0.5 * 8 * 2 ) / 8 字节 = w * h * 2 字节
- YUV 444,不共用,一个 Y 分量使用一套 UV 分量。 占的字节大小 = (w * h * 8 + w * h * 8 * 2 ) / 8 字节 = w * h * 3 字节
由于人眼对 Y 的敏感度远超于对 U 和 V 的敏感,所以有时候可以多个 Y 分量共用一组 UV,这样既可以极大得节省空间,又可以不太损失质量。这三种格式就是按照人眼的特性制定的。
按照 YUV 的排列方式,再次将 YUV 分成三个大类,Planar,Semi-Planar 和 Packed。
- Planar YUV : 三个分量分开存放
- Semi-Planar : Y 分量单独存放,UV 分量交错存放
- Packed YUV : 三个分量全部交错存放
2.1 I420(属于 YUV 420 Plannar)
I420 是 YUV 420 Planar 的一种,YUV 分量分别存放,先是 w * h 长度的 Y,后面跟 w * h * 0.25 长度的 U, 最后是 w * h * 0.25 长度的 V,总长度为 w * h * 1.5。
Y Y Y Y Y Y
Y Y Y Y Y Y
Y Y Y Y Y Y
Y Y Y Y Y Y
Y Y Y Y Y Y
Y Y Y Y Y Y
U U U
U U U
U U U
V V V
V V V
V V V
2.2 YV12(属于 YUV 420 Plannar)
YV12 是 YUV 420 Planar 的一种,YUV 分量分别存放,先是 w * h 长度的 Y,后面跟 w * h * 0.25 长度的 V, 最后是 w * h * 0.25 长度的 U,总长度为 w * h * 1.5。与 I420 不同的是,YV12 是先 V 后 U
Y Y Y Y Y Y
Y Y Y Y Y Y
Y Y Y Y Y Y
Y Y Y Y Y Y
Y Y Y Y Y Y
Y Y Y Y Y Y
V V V
V V V
V V V
U U U
U U U
U U U
2.3 NV12(属于 YUV 420 Semi-Planar)
NV12 是 YUV 420 Semi-Planar 的一种,Y 分量单独存放,UV 分量交错存放,UV 在排列的时候,从 U 开始。总长度为 w * h * 1.5。
Y Y Y Y Y Y
Y Y Y Y Y Y
Y Y Y Y Y Y
Y Y 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 U V
2.4 NV21(属于 YUV 420 Semi-Planar)
NV21 是 YUV 420 Semi-Planar 的一种,Y 分量单独存放,UV 分量交错存放,与 NV12 不同的是,UV 在排列的时候,从 V 开始。总长度为 w * h * 1.5。
Y Y Y Y Y Y
Y Y Y Y Y Y
Y Y Y Y Y Y
Y Y 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 V U
2.6 I422(属于 YUV 422 Plannar)
I422 是 YUV 422 Planar 的一种,YUV 分量分别存放,先是 w * h 长度的 Y,后面跟 w * h * 0.5 长度的 U, 最后是 w * h * 0.5 长度的 V,总长度为 w * h * 2。
Y Y Y Y Y Y
Y Y Y Y Y Y
Y Y Y Y Y Y
Y Y Y Y Y Y
Y Y Y Y Y Y
Y Y Y Y Y Y
U U U U U U
U U U U U U
U U U U U U
V V V V V V
V V V V V V
V V V V V V
2.7 YV16(属于 YUV 422 Plannar)
YV16 是 YUV 422 Planar 的一种,YUV 分量分别存放,先是 w * h 长度的 Y,后面跟 w * h * 0.5 长度的 V, 最后是 w * h * 0.5 长度的 U,总长度为 w * h * 2。与 I422 不同的是,YV16 是先 V 后 U
Y Y Y Y Y Y
Y Y Y Y Y Y
Y Y Y Y Y Y
Y Y Y Y Y Y
Y Y Y Y Y Y
Y Y Y Y Y Y
V V V V V V
V V V V V V
V V V V V V
U U U U U U
U U U U U U
U U U U U U
2.8 NV16(属于 YUV 422 Semi-Planar)
NV16 是 YUV 422 Semi-Planar 的一种,Y 分量单独存放,UV 分量交错存放,UV 在排列的时候,从 U 开始。总长度为 w * h * 2。
Y Y Y Y Y Y
Y Y Y Y Y Y
Y Y Y Y Y Y
Y Y 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 U V
U V U V U V
U V U V U V
U V U V U V
2.9 NV61(属于 YUV 422 Semi-Planar)
NV61 是 YUV 422 Semi-Planar 的一种,Y 分量单独存放,UV 分量交错存放,UV 在排列的时候,从 V 开始。总长度为 w * h * 2。
Y Y Y Y Y Y
Y Y Y Y Y Y
Y Y Y Y Y Y
Y Y 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 V U
V U V U V U
V U V U V U
V U V U V U
2.10 YUVY(属于 YUV 422 Interleaved)
YUVY 属于 YUV 422 Interleaved 的一种。事实上,Interleaved 是属于 Packed 的,但是在 422 中,用 Interleaved 更加形象一些。在 Packed 内部,YUV 的排列顺序是 Y U V Y,两个 Y 共用一组 UV。
Y U V Y Y U V Y Y U V Y
Y U V Y Y U V Y Y U V Y
Y U V Y Y U V Y Y U V Y
Y U V Y Y U V Y Y U V Y
Y U V Y Y U V Y Y U V Y
Y U V Y Y U V Y Y U V Y
2.11 VYUY(属于 YUV 422 Interleaved)
VYUY 属于 YUV 422 Interleaved 的一种。在 Packed 内部,YUV 的排列顺序是 VYUY,两个 Y 共用一组 UV。
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 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 U Y V Y U Y V Y U Y
2.12 UYVY(属于 YUV 422 Interleaved)
UYVY 属于 YUV 422 Interleaved 的一种。在 Packed 内部,YUV 的排列顺序是 UYVY,两个 Y 共用一组 UV。
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 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 U Y V Y U Y V Y
2.13 I444(属于 YUV 444 Plannar)
I444 属于 YUV 444 Plannar 的一种。YUV 分量分别存放,先是 w * h 长度的 Y,后面跟 w * h 长度的 U, 最后是 w * h 长度的 V,总长度为 w * h * 3。
Y Y Y Y Y Y
Y Y Y Y Y Y
Y Y Y Y Y Y
Y Y Y Y Y Y
Y Y Y Y Y Y
Y Y Y Y Y Y
U U U U U U
U U U U U U
U U U U U U
U U U U U U
U U U U U U
U U U U U U
V V V V V V
V V V V V V
V V V V V V
V V V V V V
V V V V V V
V V V V V V
2.14 YV24(属于 YUV 444 Plannar)
YV24 属于 YUV 444 Plannar 的一种。YUV 分量分别存放,先是 w * h 长度的 Y,后面跟 w * h 长度的 V, 最后是 w * h 长度的 U,总长度为 w * h * 3。与 I444 不同的是,YV24 是先排列 V。
Y Y Y Y Y Y
Y Y Y Y Y Y
Y Y Y Y Y Y
Y Y Y Y Y Y
Y Y Y Y Y Y
Y Y Y Y Y Y
V V V V V V
V V V V V V
V V V V V V
V V V V V V
V V V V V V
V V V V V V
U U U U U U
U U U U U U
U U U U U U
U U U U U U
U U U U U U
U U U U U U
2.15 NV24(属于 YUV 444 Semi-Planar)
NV24 是 YUV 444 Semi-Planar 的一种,Y 分量单独存放,UV 分量交错存放,UV 在排列的时候,从 U 开始。总长度为 w * h * 3。
Y Y Y Y Y Y
Y Y Y Y Y Y
Y Y Y Y Y Y
Y Y 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 U V U V U V U V
U V U V U V U V U V U V
U V U V U V U V U V U V
U V U V U V U V U V U V
U V U V U V U V U V U V
2.16 NV42(属于 YUV 444 Semi-Planar)
NV42 是 YUV 444 Semi-Planar 的一种,Y 分量单独存放,UV 分量交错存放,UV 在排列的时候,从 V 开始。总长度为 w * h * 3。
Y Y Y Y Y Y
Y Y Y Y Y Y
Y Y Y Y Y Y
Y Y 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 V U V U V U V U
V U V U V U V U V U V U
V U V U V U V U V U V U
V U V U V U V U V U V U
V U V U V U V U V U V U
2.17 YUV 444 Packed
Y U V Y U V Y U V Y U V Y U V Y U V
Y U V Y U V Y U V Y U V Y U V Y U V
Y U V Y U V Y U V Y U V Y U V Y U V
Y U V Y U V Y U V Y U V Y U V Y U V
Y U V Y U V Y U V Y U V Y U V Y U V
Y U V Y U V Y U V Y U V Y U V Y U V
3.类型转换--代码实现
3.1 opencv将png图片转换成YUV420_I420
#include <string>
#include <iostream>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>
int main()
{
cv::Mat img = cv::imread("xxx.png");
int w = img.cols;
int h = img.rows;
int imageLength = h * w * 3 / 2;
unsigned char*yuv = new unsigned char[imageLength];
cv::cvtColor(img, img, CV_BGR2YUV_IYUV); // CV_BGR2YUV_IYUV即I420: YYYYYYYY UU VV
memcpy(yuv, img.data, imageLength * sizeof(unsigned char));
/* 保存YUV420格式的yuv420 */
FILE *fp = NULL;
std::string outpath = "test.yuv";
// errno_t ret = fopen_s(&fp, outpath.c_str(), "wb+");
fp = fopen(outpath.c_str(), "wb+");
if (!fp)
{
printf("file does not exist\n");
}
fwrite(yuv, 1, sizeof(char)* imageLength, fp);
fclose(fp);
fp = NULL;
/* 释放内存 */
delete []yuv;
yuv = nullptr;
}
3.2 opencv将png图片转换成YUV420_NV12
#include <string>
#include <iostream>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>
int main()
{
cv::Mat img = cv::imread("xxx.png");
int w = img.cols; /* 宽 */
int h = img.rows; /* 高 */
int imageLength = h * w * 3 / 2; /* 图像y、u、v个数总和 */
unsigned char*yuv_nv12 = new unsigned char[imageLength]; /* 存储nv12数据 */
unsigned char*yuv = new unsigned char[imageLength]; /* 存储CV_BGR2YUV_I420数据 */
cv::cvtColor(img, img, CV_BGR2YUV_IYUV); /* BGR空间转换为CV_BGR2YUV_I420 */
memcpy(yuv_nv12, img.data, imageLength*sizeof(unsigned char)); /* 此时yuv_nv12中存储的是CV_BGR2YUV_I420类型数据 */
memcpy(yuv, img.data, imageLength*sizeof(unsigned char)); /* 此时yuv中存储的是CV_BGR2YUV_I420类型数据 */
int num = 0; /* 对u、v个数的计数 */
for (int j = w * h; j != imageLength; j += 2)
{
yuv_nv12[j] = yuv[w * h + num]; /* 对yuv_nv12中u分量进行赋值 */
yuv_nv12[j + 1] = yuv[w * h + w * h / 4 + num]; /* 对yuv_nv12中v分量进行赋值 */
++num;
}
/* 保存nv12格式的yuv420 */
FILE *fp = NULL;
std::string outpath = "test.yuv";
fp = fopen(outpath.c_str(), "wb+");
if (!fp)
{
printf("file does not exist\n");
}
fwrite(yuv_nv12, 1, sizeof(char)* imageLength, fp);
fclose(fp);
fp = NULL;
/* 释放内存 */
delete []yuv_nv12;
delete []yuv;
yuv_nv12 = nullptr;
yuv = nullptr;
}
注意:是首先用opencv的将图片转换成CV_BGR2YUV_IYUV(即I420),然后用I420再转换成NV12,opencv没有直接转换NV12的
3.2 opencv将png图片转换成UYVY
类似上面的转换成NV12,也是先将图片转换成I420,再转换成UYVY
方法1:输入yuv(I420),将之转换成uyvy
#include <stdio.h>
#include <stdint.h>
#include <malloc.h>
#include <iostream>
#include <vector>
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/imgcodecs.hpp>
#include <opencv2/videoio.hpp>
int main(int argc, char** argv)
{
std::string in_name = "xxx1.yuv";
std::string out_name = "xxx2.uyvy";
FILE *in_fp = fopen(in_name.c_str(), "rb");
FILE *out_fp = fopen(out_name.c_str(), "wb");
int width = 图像的宽;
int height = 图像的高;
int frames = 1;
/*
一张 w * h 大小的图片,采用 YUV 4:2:0 采样时的大小为:
(w * h * 8 + w * h * 0.25 * 8 * 2 ) / 8 byte = w * h * 3 / 2 字节
一张 w * h 大小的图片,采用 YUV 4:2:2 采样时的大小为:
(w * h * 8 + w * h * 0.5 * 8 * 2 ) / 8 byte = w * h * 2 字节
因此
输入图像是YUV420_I420,按照YUV 4:2:0格式存储,占的内存大小是 w * h * 3 / 2 (byte)
输出图像是UYVY,按照YUV 4:2:2格式存储,占的内存大小是 w * h * 2 (byte)
*/
int in_size = width * height * 3 / 2;
int out_size = width * height * 2;
uint8_t *in_image = (uint8_t *)malloc(in_size * sizeof(uint8_t));
uint8_t *out_image = (uint8_t *)malloc(out_size * sizeof(uint8_t));
/*
YUV420_I420 是基于 planar 平面模式进行存储,先存储所有的 Y 分量,然后存储所有的 U 分量或者 V 分量。
Y分量从第一个数据开始,所以指向in_image的头
U分量从Y分量存完开始,Y分量占整个图像的宽高大小,所以U分量指向in_image + width * height
V分量从U分量存完开始,U分量占整个图像的宽高大小*1/4,所以V分量指向in_image + width * height + width * height *1/4
*/
uint8_t *Y = in_image;
uint8_t *U = in_image + width * height;
uint8_t *V = in_image + width * height * 5 / 4;
// YUV420_I420 to YVYU
// int frame_cnt, r, c;
// for (frame_cnt = 0; frame_cnt < frames; frame_cnt++) {
// fread(in_image, sizeof(uint8_t), in_size, in_fp);
// for(r = 0; r < height; r++)
// for (c = 0; c < width; c++) {
// if((c & 0x01) == 0)
// {
// out_image[r * width * 2 + c * 2 + 1] = V[(r >> 1) * (width >> 1)+ (c >> 1)];
// }
// else
// {
// out_image[r * width * 2 + c * 2 + 1] = U[(r >> 1) * (width >> 1) + (c >> 1)];
// }
// out_image[r * width * 2 + c * 2] = Y[r * width + c];
// }
// fwrite(out_image, sizeof(uint8_t), out_size, out_fp);
// }
// YUV420_I420 to UYVY
int frame_cnt, r, c;
for (frame_cnt = 0; frame_cnt < frames; frame_cnt++) {
fread(in_image, sizeof(uint8_t), in_size, in_fp);
for(r = 0; r < height; r++)
for (c = 0; c < width; c++) {
if((c & 0x01) == 0) // 判断偶数奇数。满足该条件的是偶数
{
// (r >> 1)右移1位,相当于除2
out_image[r * width * 2 + c * 2] = U[(r >> 1) * (width >> 1) + (c >> 1)];
}
else
{
out_image[r * width * 2 + c * 2] = V[(r >> 1) * (width >> 1)+ (c >> 1)];
}
out_image[r * width * 2 + c * 2 + 1] = Y[r * width + c];
}
fwrite(out_image, sizeof(uint8_t), out_size, out_fp);
}
free(in_image);
free(out_image);
fclose(in_fp);
fclose(out_fp);
}
方法2:opencv读入png,先转成I420,再转成UYVY
#include <string>
#include <iostream>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>
int main()
{
cv::Mat img = cv::imread("xxx.png");
int width = img.cols;
int height = img.rows;
int imageLength = width * height * 3 / 2;
unsigned char*yuv = new unsigned char[imageLength];
cv::cvtColor(img, img, CV_BGR2YUV_IYUV); // CV_BGR2YUV_IYUV即I420: YYYYYYYY UU VV
memcpy(yuv, img.data, imageLength * sizeof(unsigned char));
// YUV420_I420 to UYVY
FILE *out_fp = fopen("xxx.uyvy", "wb");
int frames = 1;
int out_size = width * height * 2;
uint8_t *out_image = (uint8_t *)malloc(out_size * sizeof(uint8_t));
uint8_t *Y = yuv;
uint8_t *U = yuv + width * height;
uint8_t *V = yuv + width * height * 5 / 4;
for (int frame_cnt = 0; frame_cnt < frames; frame_cnt++) {
for (int r = 0; r < height; r++)
for (int c = 0; c < width; c++) {
if ((c & 0x01) == 0) // 判断偶数奇数。满足该条件的是偶数
{
// (r >> 1)右移1位,相当于除2
out_image[r * width * 2 + c * 2] = U[(r >> 1) * (width >> 1) + (c >> 1)];
}
else
{
out_image[r * width * 2 + c * 2] = V[(r >> 1) * (width >> 1)+ (c >> 1)];
}
out_image[r * width * 2 + c * 2 + 1] = Y[r * width + c];
}
fwrite(out_image, sizeof(uint8_t), out_size, out_fp);
}
free(out_image);
fclose(out_fp);
/* 释放内存 */
delete []yuv;
yuv = nullptr;
return 0;
}
源自
https://juejin.cn/post/6920848468797816846
https://zhuanlan.zhihu.com/p/75735751
代码参考
https://blog.csdn.net/liugan528/article/details/83341028
https://github.com/Jopast/I420_UYVY/blob/master/souce/I420_UYVY.cpp