YUV格式

YUV、RGB、YcbCr是色彩空间的模型,而平常听到的BMP、PNG、JPEG、GIF是文件储存的形式。

提出YUV格式的原因,是为了解决彩色电视和黑白电视兼容性问题,因此从rgb的颜色空间,转换为yuv的颜色空间,其中y代表亮度,u和v代表色度。

YUV种类分为很多,可以理解是一个二维的,即空间间,和空间内,这样的表述,借鉴了h264中的帧间和帧内的思想。

空间-间:不同空间,即描述一个像素的bit数不同,比如yuv444,yuv422,yuv411,yuv420

空间-内:相同空间,即描述一个像素的bit数相同,但是存储方式不同,比如对于yuv420而言,又可细分为yuv420p,yuv420sp,nv21,nv12,yv12,yu12,I420

为什么yuv444,yuv420,yuv422,yuv411都是用的4呢?

因为用到了共享的思想,这是yuv和rgb的本质区别

RGB与YUV的转换公式:

Y= 0.299*R+0.587*G+0.114*B
U=-0.147*R-0.289*G+0.463*B
V= 0.615*R-0.515*G-0.100*B

rgb是一个像素是一个家庭,家庭成员是r,g,b,但是yuv是若干像素是一个家庭,不同像素的y共享同一个u和v,这样,引入了共享的思想,虽然最小单位是一个字节,但实际上描述一个像素点的字节,不一定是字节的整数倍。

1、YUV444

[ 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 ]

1920*1080文件的大小:1920*1080*3(B)

2、YUV422

[ y u ] [ y v ] [ y u ] [ y v ]
[ y v ] [ y u ] [ y v ] [ y u ]
[ y u ] [ y v ] [ y u ] [ y v ]
[ y v ] [ y u ] [ y v ] [ y u ]

显然一个家庭的成员为

[ y u ] [ y v ]

1920*1080文件的大小:1920*1080+1920*1080*0.5+1920*1080*0.5(B)因为UV的数量减少了一半,相对于YUV444空间节省了1/3

3、 YUV420

yuv420的意思似乎是在yuv422的基础上,再拿掉两个v,这样不就没有v了吗?其实yuv420的取名方式不是很高明,更确切的命名为yuv420yuv402,也就是第一行只有U,而第二行只有V

[ y u ] [ y ] [ y u ] [ y ]
[ y v ] [ y ] [ y v ] [ y ]
[ y u ] [ y ] [ y u ] [ y ]

对于yuv420而言,这个家庭的成员为

[ y u ] [ y ]
[ y v ] [ y ]

1920*1080文件的大小:1920*1080+1920*1080*0.25+1920*1080*0.25(B)相对于YUV444空间节省1/2,因此也是比较主流的采样方式。

三种格式packet,planar,semi-plane

packet是打包格式,即存储yuv,然后再存储下一个yuv。
planar是平面格式,即先存储y平面,再存储u平面,最后存储v平面。
semi-planar即先存储y平面,再存储uv平面。

1、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 ]

2、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 ]

常见的有nv12,nv21,nv16,nv61等,这里代表什么意思呢?
其实nv系列,都属于semi-plane系列
这里nv12表示正常的顺序,即uv plane,先是u,然后是v
而nv21表示相反的顺序,即uv plane,先是v,然后是u

同样,nv16和nv61的区别也是仅仅是uv的次序而已

这里的12和16又代表什么呢?实际上代表的是一个像素所占的位数!
以nv12为例,表示一个像素占用12bit,其中y是定死的占8bit,也就是u占2bit,v占2bit,实际上就是yuv420格式,具体而言是yuv420sp格式
nv16,则表示一个像素占用16bit,其中y是定死的8bit,也即是u占4bit,v占4bit,实际上就是yuv422格式,具体而言是yuv422sp格式

出处:https://www.jianshu.com/p/6a361e86ccd5

编码测试

将YUV422P中的Y、U、V分别分离出来

int simplest_yuv420_split(char *url, int w, int h, int num) {
    FILE *fp = fopen(url, "r");
    if (fp < 0)
        cerr << "File doesn't exist.";

    FILE *fy, *fu, *fv;
    fy = fopen("output_420_y.y", "w");
    fu = fopen("output_420_u.y", "w");
    fv = fopen("output_420_v.y", "w");
    if (fv < 0 || fy < 0 || fu < 0)
        cerr << "Cannot create file.";

    //YUV420:Y->w*h + U->w*h*1/4 + V->w*h*1/4
    unsigned char *pic = (unsigned char*)malloc(w * h * 3 / 2);
    for (int i = 0; i < num; ++i)
    {
        fread(pic, 1, w * h * 3 / 2, fp);
        fwrite(pic, 1, w*h, fy);
        fwrite(pic + w * h, 1, w*h * 1 / 4, fu);
        fwrite(pic + w * h * 5 / 4, 1, w*h * 1 / 4, fv);
    }

    free(pic);
    fclose(fp);
    fclose(fy);
    fclose(fu);
    fclose(fv);

    return 0;
}
w、h分别为图像的宽高(px),那么图像的大小就是w*h*3/2(B),那么Y的数据储存位置便是w*h,紧接着的1/4便是U,最后1/4是V。

将YUV420p转换成灰度图

int simplest_yuv420_gray(char* path, int w, int h)
{
    FILE* fo = fopen(path, "r");
    FILE* fw = fopen("out_gray.yuv", "wb+");

    unsigned char* picbuf = (unsigned char*)malloc(w*h * 3 / 2);
    fread(picbuf, 1, w*h * 3 / 2, fo);
    memset(picbuf + w * h, 128, w*h * 1 / 2);
    fwrite(picbuf, 1, w* h * 3 / 2, fw);
    free(picbuf);
    fclose(fo);
    fclose(fw);
    return 0;
}

只要将uv设置成120。这是因为U、V是图像中的经过偏置处理的色度分量。

色度分量在偏置处理前的取值范围是-128至127,

这时候的无色对应的是“0”值

经过偏置后色度分量取值变成了0至255,

因而此时的无色对应的就是128了。

 

 YUV420p 亮度增强与减半
 
int simplest_yuv420_halfy(char* path, int w, int h)
{
    FILE* fo = fopen(path, "r");
    FILE* fw = fopen("out_halfy.yuv", "wb+");

    unsigned char* picbuf = (unsigned char*)malloc(w*h * 3 / 2);
    fread(picbuf, 1, w * h * 3 / 2, fo);
    for (int i = 0; i < (w * h); i++)
    {
        unsigned char temp = picbuf[i] / 2;
        printf("%d \n", temp);
        picbuf[i] = temp;
    }
    fwrite(picbuf, 1, w*h * 3 / 2, fw);
    free(picbuf);
    fclose(fo);
    fclose(fw);

    return 0;
}

int simplest_yuv420_stready(char* path, int w, int h)
{
    FILE* fo = fopen(path, "r");
    FILE* fw = fopen("out_stready.yuv", "wb+");

    unsigned char* picbuf = (unsigned char*)malloc(w*h * 3 / 2);
    fread(picbuf, 1, w * h * 3 / 2, fo);
    for (int i = 0; i < (w * h); i++)
    {
        unsigned char temp = picbuf[i] * 2;
        printf("%d \n", temp);
        picbuf[i] = temp;
    }

    fwrite(picbuf, 1, w*h * 3 / 2, fw);

    free(picbuf);
    fclose(fo);
    fclose(fw);

    return 0;
}

如果打算将图像的亮度减半(增强),
只要将图像的每个像素的Y值取出来分别进行除以2(乘以2)的工作就可以了。
图像的每个Y值占用1 Byte,
取值范围是0至255。

YUV420图像增加边框

int simplest_yuv420_border(char *url, int w, int h, int border) {
    FILE *fp = fopen(url, "rb+");
    FILE *fp1 = fopen("output_border.yuv", "wb+");
    unsigned char *pic = (unsigned char *)malloc(w*h * 3 / 2);
    fread(pic, 1, w*h * 3 / 2, fp);
    for (int j = 0; j < h; j++) {
        for (int k = 0; k < w; k++) {
            if (k<border || k>(w - border) || j<border || j>(h - border)) {
                pic[j*w + k] = 0;
            }
        }
    }
    fwrite(pic, 1, w*h * 3 / 2, fp1);
    free(pic);
    fclose(fp);
    fclose(fp1);
    return 0;
}

border的像素的Y分量调整为0

计算两个YUV420P像素数据的PSNR

PSNR(峰值信噪比)解释: https://www.cnblogs.com/seniusen/p/10012656.html 

int simplest_yuv420_psnr(char *url1, char *url2, int w, int h) {
    FILE *fp1 = fopen(url1, "rb+");
    FILE *fp2 = fopen(url2, "rb+");
    unsigned char *pic1 = (unsigned char *)malloc(w*h);
    unsigned char *pic2 = (unsigned char *)malloc(w*h);

    fread(pic1, 1, w*h, fp1);
    fread(pic2, 1, w*h, fp2);

    double mse_sum = 0, mse = 0, psnr = 0;
    for (int j = 0; j < w*h; j++)
    {
        mse_sum += pow((double)(pic1[j] - pic2[j]), 2);
    }
    mse = mse_sum / (w*h);
    psnr = 10 * log10(255.0*255.0 / mse);
    printf("%f \n", psnr);

    fseek(fp1, w*h / 2, SEEK_CUR);
    fseek(fp2, w*h / 2, SEEK_CUR);



    free(pic1);
    free(pic2);
    fclose(fp1);
    fclose(fp2);
    return 0;

}

RGB保存成bmp

int simplest_rgb24_to_bmp(const char *rgb24path, int width, int height, const char *bmppath) {
    typedef struct
    {
        long imageSize;
        long blank;
        long startPosition;
    }BmpHead;

    typedef struct
    {
        long  Length;
        long  width;
        long  height;
        unsigned short  colorPlane;
        unsigned short  bitColor;
        long  zipFormat;
        long  realSize;
        long  xPels;
        long  yPels;
        long  colorUse;
        long  colorImportant;
    }InfoHead;

    int i = 0, j = 0;
    BmpHead m_BMPHeader = { 0 };
    InfoHead  m_BMPInfoHeader = { 0 };
    char bfType[2] = { 'B','M' };
    int header_size = sizeof(bfType) + sizeof(BmpHead) + sizeof(InfoHead);
    unsigned char *rgb24_buffer = NULL;
    FILE *fp_rgb24 = NULL, *fp_bmp = NULL;

    if ((fp_rgb24 = fopen(rgb24path, "rb")) == NULL) {
        printf("Error: Cannot open input RGB24 file.\n");
        return -1;
    }
    if ((fp_bmp = fopen(bmppath, "wb")) == NULL) {
        printf("Error: Cannot open output BMP file.\n");
        return -1;
    }

    rgb24_buffer = (unsigned char *)malloc(width*height * 3);
    fread(rgb24_buffer, 1, width*height * 3, fp_rgb24);

    m_BMPHeader.imageSize = 3 * width*height + header_size;
    m_BMPHeader.startPosition = header_size;

    m_BMPInfoHeader.Length = sizeof(InfoHead);
    m_BMPInfoHeader.width = width;
    //BMP storage pixel data in opposite direction of Y-axis (from bottom to top).
    m_BMPInfoHeader.height = -height;
    m_BMPInfoHeader.colorPlane = 1;
    m_BMPInfoHeader.bitColor = 24;
    m_BMPInfoHeader.realSize = 3 * width*height;

    fwrite(bfType, 1, sizeof(bfType), fp_bmp);
    fwrite(&m_BMPHeader, 1, sizeof(m_BMPHeader), fp_bmp);
    fwrite(&m_BMPInfoHeader, 1, sizeof(m_BMPInfoHeader), fp_bmp);

    //BMP save R1|G1|B1,R2|G2|B2 as B1|G1|R1,B2|G2|R2
    //It saves pixel data in Little Endian
    //So we change 'R' and 'B'
    for (j = 0; j < height; j++) {
        for (i = 0; i < width; i++) {
            char temp = rgb24_buffer[(j*width + i) * 3 + 2];
            rgb24_buffer[(j*width + i) * 3 + 2] = rgb24_buffer[(j*width + i) * 3 + 0];
            rgb24_buffer[(j*width + i) * 3 + 0] = temp;
        }
    }
    fwrite(rgb24_buffer, 3 * width*height, 1, fp_bmp);
    fclose(fp_rgb24);
    fclose(fp_bmp);
    free(rgb24_buffer);
    printf("Finish generate %s!\n", bmppath);
    return 0;
    return 0;
}

BMP文件格式:

BITMAPFILEHEADER

BITMAPINFOHEADER

RGB像素数据

typedef  struct  tagBITMAPFILEHEADER
{
    unsigned short int  bfType;       //位图文件的类型,必须为BM 
    unsigned long       bfSize;       //文件大小,以字节为单位
    unsigned short int  bfReserverd1; //位图文件保留字,必须为0 
    unsigned short int  bfReserverd2; //位图文件保留字,必须为0 
    unsigned long       bfbfOffBits;  //位图文件头到数据的偏移量,以字节为单位
}BITMAPFILEHEADER;
typedef  struct  tagBITMAPINFOHEADER
{
    long biSize;                        //该结构大小,字节为单位
    long  biWidth;                     //图形宽度以象素为单位
    long  biHeight;                     //图形高度以象素为单位
    short int  biPlanes;               //目标设备的级别,必须为1 
    short int  biBitcount;             //颜色深度,每个象素所需要的位数
    short int  biCompression;        //位图的压缩类型
    long  biSizeImage;              //位图的大小,以字节为单位
    long  biXPelsPermeter;       //位图水平分辨率,每米像素数
    long  biYPelsPermeter;       //位图垂直分辨率,每米像素数
    long  biClrUsed;            //位图实际使用的颜色表中的颜色数
    long  biClrImportant;       //位图显示过程中重要的颜色数
}BITMAPINFOHEADER;

RGB24彩色色条图

int simplest_rgb24_colorbar(int width, int height, char *url_out) {

    unsigned char *data = NULL;
    int barwidth;
    char filename[100] = { 0 };
    FILE *fp = NULL;
    int i = 0, j = 0;

    data = (unsigned char *)malloc(width*height * 3);
    barwidth = width / 8;

    if ((fp = fopen(url_out, "wb+")) == NULL) {
        printf("Error: Cannot create file!");
        return -1;
    }

    for (j = 0; j < height; j++) {
        for (i = 0; i < width; i++) {
            int barnum = i / barwidth;
            switch (barnum) {
            case 0: {
                data[(j*width + i) * 3 + 0] = 255;
                data[(j*width + i) * 3 + 1] = 255;
                data[(j*width + i) * 3 + 2] = 255;
                break;
            }
            case 1: {
                data[(j*width + i) * 3 + 0] = 255;
                data[(j*width + i) * 3 + 1] = 255;
                data[(j*width + i) * 3 + 2] = 0;
                break;
            }
            case 2: {
                data[(j*width + i) * 3 + 0] = 0;
                data[(j*width + i) * 3 + 1] = 255;
                data[(j*width + i) * 3 + 2] = 255;
                break;
            }
            case 3: {
                data[(j*width + i) * 3 + 0] = 0;
                data[(j*width + i) * 3 + 1] = 255;
                data[(j*width + i) * 3 + 2] = 0;
                break;
            }
            case 4: {
                data[(j*width + i) * 3 + 0] = 255;
                data[(j*width + i) * 3 + 1] = 0;
                data[(j*width + i) * 3 + 2] = 255;
                break;
            }
            case 5: {
                data[(j*width + i) * 3 + 0] = 255;
                data[(j*width + i) * 3 + 1] = 0;
                data[(j*width + i) * 3 + 2] = 0;
                break;
            }
            case 6: {
                data[(j*width + i) * 3 + 0] = 0;
                data[(j*width + i) * 3 + 1] = 0;
                data[(j*width + i) * 3 + 2] = 255;

                break;
            }
            case 7: {
                data[(j*width + i) * 3 + 0] = 0;
                data[(j*width + i) * 3 + 1] = 0;
                data[(j*width + i) * 3 + 2] = 0;
                break;
            }
            }

        }
    }
    fwrite(data, width*height * 3, 1, fp);
    fclose(fp);
    free(data);

    return 0;
}

 

转自:https://blog.csdn.net/leixiaohua1020/article/details/50534150(雷霄骅CSDN)

posted @ 2020-05-13 14:20  zebra_彬  阅读(3092)  评论(0编辑  收藏  举报