• 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 
    ", 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 
    ", 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 
    ", 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.
    ");
            return -1;
        }
        if ((fp_bmp = fopen(bmppath, "wb")) == NULL) {
            printf("Error: Cannot open output BMP file.
    ");
            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!
    ", 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)

  • 相关阅读:
    程序员如何在百忙中更有效地利用时间,如何不走岔路,不白忙(忙得要有效率,要有收获)
    最坏的不是面试被拒,而是没面试机会,以面试官视角分析哪些简历至少能有面试机会
    最近面了不少java开发,据此来说下我的感受:哪怕事先只准备1小时,成功概率也能大大提升
    Ribbon整合Eureka组件,以实现负载均衡
    时间对于程序员的价值,以及如何高效地利用时间,同时划分下勤奋度的等级
    面试过程中,可以通过提问环节的发挥,提升面试的成功率
    以技术面试官的经验分享毕业生和初级程序员通过面试的技巧(Java后端方向)
    和小鲜肉相比,老程序员该由哪些优势?同时说下我看到的老程序员的三窟
    通过软引用和弱引用提升JVM内存使用性能的方法(面试时找机会说出,一定能提升成功率)
    Spring Clould负载均衡重要组件:Ribbon中重要类的用法
  • 原文地址:https://www.cnblogs.com/zebra-bin/p/12882117.html
Copyright © 2020-2023  润新知