YUV学习

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

posted on 2021-11-03 16:14  JJ_S  阅读(367)  评论(0编辑  收藏  举报