zoukankan      html  css  js  c++  java
  • RGB and YUV

    在之前的一篇关于JPEG格式的文章里提到了YUV和RGB之间的变换,有读者后台回复我说没有理解,那么这次展开来讲一讲,并且会掺杂最近的视音频学习的一些内容。

    什么是RGB、YUV

    首先在上一篇关于JPEG编解码的文章中提到了YCbCr色彩空间(不用回去找原文,这里会简单总结一下)。YUV、RGB和YCbCr是色彩空间模型,而我们最常听到的BMP、PNG、JPEG、GIF都是文件存储格式。

    RGB就是red green blue三原色,任何颜色都可以通过这三原色按不同比例混合出来。所以应用很广泛。

    YUV则是亮度Y(灰度值)、色差信号U和V。我们人眼对色彩的亮度信息会比色差信息敏感,没有UV信息一样可以显示完整的图像,只不过是黑白的。在默认情况下是图像和视频压缩的标准。

    YCbCr 其实是YUV经过缩放和偏移的翻版。其中Y与YUV 中的Y含义一致,Cb,Cr 同样都指色彩,只是在表示方法上不同而已。YCbCr其中Y是指亮度分量,Cb指蓝色色度分量,而Cr指红色色度分量。JPEG、MPEG均采用此格式。

    这三种色彩空间模型都可以通过格式相互转换。

    RGB到YUV的转换

    RGB到YUV的转换,就是将图像所有像素点的R、G、B分量转换到Y、U、V分量:

    YUV和RGB的转换:
          Y = 0.299 R + 0.587 G + 0.114 B
          U = -0.1687 R - 0.3313 G + 0.5 B + 128
          V = 0.5 R - 0.4187 G - 0.0813 B + 128
    
          R = Y + 1.402 (V-128)
          G= Y - 0.34414 (U-128) - 0.71414 (V-128)
          B= Y + 1.772 (U-128)
    

    既然能够转换,为什么还需要YUV和RGB两种色彩模型呢?因为对于图像显示器来说,它是通过RGB模型来显示图像的。而在传输图像数据时是使用YUV模型的,因为YUV模型可以节省带宽。所以就需要采集图像时将RGB模型转换到YUV模型,显示时再将YUV模型转换为RGB模型。

    视频采样

    从视频采集与处理的角度来说,一般的视频采集芯片输出的码流一般都是YUV数据流的形式,而从视频处理(例如H.264、MPEG视频编解码)的角度来说,也是在原始YUV码流进行编码和解析 ;如果采集的资源时RGB的,也需要转换成YUV。

    我们必须根据采样格式从码流中还原每个像素点的YUV值,YUV格式有两大类:planar和packed。对于planar的YUV格式,先连续存储所有像素点的Y,紧接着存储所有像素点的U,随后是所有像素点的V。对于packed的YUV格式,每个像素点的Y,U,V是连续交叉存储的。

    而YUV码流的存储格式其实与其采样的方式密切相关,主流的采样方式有三种,YUV4:4:4,YUV4:2:2,YUV4:2:0 。

    YUV采样格式

    YUV 4:4:4采样

    意味着Y、U、V三个分量的采样比例相同,所以在生成的图像里,每个像素的三个分量信息都是8bit。(Y用×表示,UV用○表示)

    图片.png

    假如图像像素为:[Y0 U0 V0]、[Y1 U1 V1]、[Y2 U2 V2]、[Y3 U3 V3]
    
    那么采样的码流为:Y0 U0 V0 Y1 U1 V1 Y2 U2 V2 Y3 U3 V3 
    
    最后映射出的像素点依旧为 [Y0 U0 V0]、[Y1 U1 V1]、[Y2 U2 V2]、[Y3 U3 V3] 
    

    这种采样方式和RGB颜色模型的图像大小是一样的,并没有达到节省带宽的目的。

    YUV 4:2:2采样

    UV分量是Y分量的一般,Y分量和UV分量按照2:1的比例采样,如果水平方向有10个像素点,那么采样了10个Y分量,就只采样了5个UV分量。

    图片.png

    假如图像像素为:[Y0 U0 V0]、[Y1 U1 V1]、[Y2 U2 V2]、[Y3 U3 V3]
    
     那么采样的码流为:Y0 U0 Y1 V1 Y2 U2 Y3 V3 
    
     其中,每采样过一个像素点,都会采样其 Y 分量,而 U、V 分量就会间隔一个采集一个。
    
     最后映射出的像素点为 [Y0 U0 V1]、[Y1 U0 V1]、[Y2 U2 V3]、[Y3 U2 V3]
    

    通过这个例子就可以发现第一个像素点和第二个像素点共用了[U0、V1]分量,第三个像素点和第四个像素点共用了[U2、V3]分量,这样就节省了图像空间。比如一张1280*720大小的图片,如果按照RGB方式存储,会耗费:

    [(1280*720*8+1280*720*8+1280*720*8)、8/1024/1024=2.637MB ]

    其中1280*720是表示有多少个像素点。但如果采用了YUV4:2:采样格式:

    [(1280 * 720 * 8 + 1280 * 720 * 0.5 * 8 * 2)/ 8 / 1024 / 1024 = 1.76 MB ]

    节省了1/3的存储空间,适合进行图像传输。

    YUV 4:2:0采样

    YUV 4:2:0 采样,并不是指只采样 U 分量而不采样 V 分量。而是指,在每一行扫描时,只扫描一种色度分量(U 或者 V),和 Y 分量按照 2 : 1 的方式采样。比如,第一行扫描时,YU 按照 2 : 1 的方式采样,那么第二行扫描时,YV 分量按照 2:1 的方式采样。对于每个色度分量来说,它的水平方向和竖直方向的采样和 Y 分量相比都是 2:1 。

    图片.png

    假设图像像素为:
     
    [Y0 U0 V0]、[Y1 U1 V1]、 [Y2 U2 V2]、 [Y3 U3 V3]
    [Y4 U4 V4]、[Y5 U5 V5]、[Y6 U6 V6]、 [Y7 U7 V7]
     
    那么采样的码流为:Y0 U0 Y1 Y2 U2 Y3 Y4 V4 Y5 Y6 V6 Y7
     
    其中,每采样过一个像素点,都会采样其 Y 分量,而 U、V 分量就会间隔一行按照 2 : 1 进行采样。
     
    最后映射出的像素点为:
    
    [Y0 U0 V5]、[Y1 U0 V5]、[Y2 U2 V7]、[Y3 U2 V7]
    [Y5 U0 V5]、[Y6 U0 V5]、[Y7 U2 V7]、[Y8 U2 V7]
    

    通过YUV 4:2:0采样后的图片大小为:

    [(1280 * 720 * 8 + 1280 * 720 * 0.25 * 8 * 2)/ 8 / 1024 / 1024 = 1.32 MB ]

    采样的图像比RGB模型图像节省了一半的存储空间,因此也是比较主流的采样方式。

    YUV存储格式

    前面简单提到了YUV的存储格式有两种:

    planar 平面格式
        指先连续存储所有像素点的 Y 分量,然后存储 U 分量,最后是 V 分量。
    packed 打包模式
        指每个像素点的 Y、U、V 分量是连续交替存储的。
    

    根据采样方式和存储格式的不同,就有了多种 YUV 格式。这些格式主要是基于 YUV 4:2:2 和 YUV 4:2:0 采样。

    基于YUV 4:2:2采样的格式

    YUV 4:2:2 采样规定了 Y 和 UV 分量按照 2: 1 的比例采样,两个 Y 分量公用一组 UV 分量。

    YUYV格式

    YUYV 格式是采用打包格式进行存储的,指每个像素点都采用 Y 分量,但是每隔一个像素采样它的 UV 分量,排列顺序如下:

    Y0 UO Y1 V0  Y2 U2 Y3 V2
    

    Y0 和 Y1 公用 U0 V0 分量,Y2 和 Y3 公用 U2 V2 分量.

    图片.png

    UYVY格式

    UYVY 格式也是采用打包格式进行存储,它的顺序和 YUYV 相反,先采用 U 分量再采样 Y 分量,排列顺序如下:

    U0 Y0 V0 Y1 U2 Y2 V2 Y3
    

    Y0 和 Y1 公用 U0 V0 分量,Y2 和 Y3 公用 U2 V2 分量。

    图片.png

    YUV 422P格式

    YUV 422P 格式,又叫做 I422,采用的是平面格式进行存储,先存储所有的 Y 分量,再存储所有的 U 分量,再存储所有的 V 分量

    基于YUV 4:2:0采样的格式

    基于 YUV 4:2:0 采样的格式主要有 YUV 420P 和 YUV 420SP 两种类型 。YUV 420P 和 YUV 420SP 都是基于 Planar 平面模式 进行存储的,先存储所有的 Y 分量后, YUV420P 类型就会先存储所有的 U 分量或者 V 分量,而 YUV420SP 则是按照 UV 或者 VU 的交替顺序进行存储 。所以针对这两种类型的格式就为YU12格式、YV112格式和NV12格式、NV21格式。

    YU12和YV12格式

    YU12 和 YV12 格式都属于 YUV 420P 类型,即先存储 Y 分量,再存储 U、V 分量,区别在于:YU12 是先 Y 再 U 后 V,而 YV12 是先 Y 再 V 后 U 。

    YU12格式

    NV12和NV21格式

    NV12 和 NV21 格式都属于 YUV420SP 类型。它也是先存储了 Y 分量,但接下来并不是再存储所有的 U 或者 V 分量,而是把 UV 分量交替连续存储。

    NV12 是 IOS 中有的模式,它的存储顺序是先存 Y 分量,再 UV 进行交替存储。

    NV12格式

    RGB格式

    RGB565 每个像素用16位表示,RGB分量分别使用5位、6位、5位

    RGB555 每个像素用16位表示,RGB分量都使用5位(剩下1位不用)

    RGB24 每个像素用24位表示,RGB分量各使用8位

    RGB32 每个像素用32位表示,RGB分量各使用8位(剩下8位不用)

    ARGB32 每个像素用32位表示,RGB分量各使用8位(剩下的8位用于表示Alpha通道值)

    在这里介绍一下常用的格式:

    RGB565,使用16位表示一个像素,5位用于R,6位用于G,5位用于B。

    RGB24,使用24位表示一个像素,RGB分量都用8位表示,取值范围为0-255,排列顺序为:BGR

    typedef struct tagRGBTRIPLE {
        BYTE rgbtBlue; // 蓝色分量
        BYTE rgbtGreen; // 绿色分量
        BYTE rgbtRed; // 红色分量
    } RGBTRIPLE;
    

    一些应用

    这里参考了雷神的博客。

    分离YUV420P像素数据中的Y、U、V分量

    首先建立一个函数能够将根据格式将YUV420P格式的文件分离成三个分量的文件:

    output_420_y.y:纯Y数据,分辨率为256x256。
    output_420_u.y:纯U数据,分辨率为128x128。
    output_420_v.y:纯V数据,分辨率为128x128。
    

    函数如下:

    int yuv420_split(const char *path, int w, int h, int num)
    {
        FILE *fp = fopen(path, "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;
    }
    

    得到的效果图就是下图:

    图片.png

    分离YUV444P像素数据中的Y、U、V分量

    这就相当于提取一个根据RGB相同大小的像素数据转换而来的Y、U、V三个分量,稍微更改一下读取的位置就行了,函数如下:

    int yuv444_split(const char *path, int w, int h, int num)
    {
        FILE *fp = fopen(path, "r");
        if (fp < 0)
            cerr << "File doesn't exist.";
    
        FILE *fy, *fu, *fv;
        fy = fopen("output_444_y.y", "w");
        fu = fopen("output_444_u.y", "w");
        fv = fopen("output_444_v.y", "w");
        if (fv < 0 || fy < 0 || fu < 0)
            cerr << "Cannot create file.";
    
        //YUV420:Y->w*h + U->w*h + V->w*h
        unsigned char *pic = (unsigned char*)malloc(w * h * 3);
        for (int i = 0; i < num; ++i)
        {
            int size = w * h;
            fread(pic, 1, size * 3 , fp);
            fwrite(pic, 1, size, fy);
            fwrite(pic +size, 1, size, fu);
            fwrite(pic + 2*size, 1, size, fv);
        }
    
        free(pic);
        fclose(fp);
        fclose(fy);
        fclose(fu);
        fclose(fv);
    
        return 0;
    }
    

    将YUV420P像素数据变为灰度图

    这个只需要保存Y分量就可以了,最后保存的文件更改为.yuv格式即可。但是在生成.yuv文件的时候是需要uv分量的,这个只需要将U、V分量设置成128即可。

    这是因为U、V是图像中的经过偏置处理的色度分量。色度分量在偏置处理前的取值范围是-128至127,这时候的无色对应的是“0”值。经过偏置后色度分量取值变成了0至255,因而此时的无色对应的就是128了。

    程序如下:

    int yuv420_gray(const char *path, int w, int h, int num)
    {
        FILE *fp = fopen(path, "r");
        if (fp < 0)
            cerr << "File doesn't exist.";
    
        FILE *fgray;
        fgray = fopen("output_420_gray.yuv", "w");
        if (fgray < 0)
            cerr << "Cannot create file.";
    
        unsigned char *pic = (unsigned char*)malloc(w * h * 3);
        for (int i = 0; i < num; ++i)
        {
            fread(pic, 1, w*h * 3 / 2, fp);
            fwrite(pic, 1, w*h, fgray);
            memset(pic + w * h, 128, w*h/2);
            fwrite(pic + w * h, 1, w*h * 1 / 2, fgray);
        }
    
        free(pic);
        fclose(fp);
        fclose(fgray);
    
        return 0;
    }
    

    效果图如下:

    图片.png

    将YUV420P像素数据的亮度减半

    只需将亮度分量Y的数值减半就可以了,程序实现如下:

    int yuv420_halfy(const char *path, int w, int h, int num)
    {
        FILE *fp = fopen(path, "r");
        if (fp < 0)
            cerr << "File doesn't exist.";
    
        FILE *fhalfy;
        fhalfy = fopen("output_420_halfy.yuv", "w");
        if (fhalfy < 0)
            cerr << "Cannot create file.";
    
        unsigned char *pic = (unsigned char*)malloc(w * h * 3);
        for (int i = 0; i < num; ++i)
        {
            fread(pic, 1, w*h * 3 / 2, fp);
            for (int j = 0; j < w*h; ++j)
                pic[j] /= 2;
            fwrite(pic, 1, w*h, fhalfy);
            fwrite(pic + w * h, 1, w*h * 1 / 2, fhalfy);
        }
    
        free(pic);
        fclose(fp);
        fclose(fhalfy);
    
        return 0;
    }
    

    图片.png

    在YUV420P像素数据的周围加上边框

    修改YUV数据中特定位置(边框)的分量Y数值,给图像添加一个边框效果,程序如下:

    int yuv420_border(const char *path, int w, int h, int border, int num)
    {
        FILE *fp = fopen(path, "r");
        if (fp < 0)
            cerr << "File doesn't exist.";
    
        FILE *fborder;
        fborder = fopen("output_420_border.yuv", "w");
        if (fborder < 0)
            cerr << "Cannot create file.";
    
        unsigned char *pic = (unsigned char*)malloc(w * h * 3);
        for (int i = 0; i < num; ++i)
        {
            fread(pic, 1, w*h * 3 / 2, fp);
            for (int cy = 0; cy < h; ++cy)
            {
                for (int cx = 0; cx < w; ++cx)
                {
                    if (cx<border || cx>w - border || cy<border || cy>h - border)
                        pic[cx + cy * w] = 255; //二维数组的线性表示
                }
            }
            fwrite(pic, 1, w*h*3/2, fborder);
        }
    
        free(pic);
        fclose(fp);
        fclose(fborder);
    
        return 0;
    }
    

    图片.png

    生成YUV420P格式的灰阶测试图

    首先得确定灰阶测试图的亮度最小值和最大值、灰阶数量,然后生成相应的图,不妨设置成这样的取值:

    图片.png

    int yuv420_graybar(int w, int h, int ymin, int ymax, int barnum)
    {
        int barwidth = w/barnum;
        unsigned lum;
        float dlum = (float)(ymax - ymin) / (float)(barnum - 1);
        unsigned char *data_y, *data_u, *data_v;
    
        int uv_width = w / 2;
        int uv_height = h / 2;
    
        data_y = (unsigned char *)malloc(w*h);
        data_u = (unsigned char *)malloc(uv_width*uv_height);
        data_v = (unsigned char *)malloc(uv_width*uv_height);
    
        FILE *fp;
        fp = fopen("output_yun420_graybar.yuv", "w");
        if (fp < 0)
            cerr << "Cannot create file.";
    
        //for (int i = 0; i < w / barwidth; ++i)
        //{
        //    lum = ymin + (char)(i*dlum);
        //}
    
        for (int i = 0; i < h; ++i)
            for (int j = 0; j < w; ++j)
                data_y[i*w + j] = ymin + (char)(j / barwidth * dlum);
        for (int i = 0; i < uv_height; ++i)
            for (int j = 0; j < uv_width; ++j)
            {
                data_u[i*uv_width + j] = 128;
                data_v[i*uv_width + j] = 128;
            }
    
        fwrite(data_y, 1, w*h, fp);
        fwrite(data_u, 1, uv_width*uv_height, fp);
        fwrite(data_v, 1, uv_width*uv_height, fp);
        fclose(fp);
        free(data_y);
        free(data_u);
        free(data_v);
        return 0;  
    }
    

    图片.png

    计算两个YUV420P像素数据的PSNR

    PSNR通常用于图像质量评价,即计算受损图像与原始图像之间的区别,以此来评价受损图像的质量,其计算公式为:

    [PSNR = 10*log_{10}(frac{255^{2}}{MSE}) ]

    其中MSE的定义为:

    [MSE = frac{1}{M*N}sum{sum(x_{ij}-y_{ij})^{2}} ]

    M,N分别为图像的宽高,xij和yij分别为两张图像的每一个像素值。 用程序来直接计算两者之间的区别:

    int yuv420_psnr(const char *path1, const char *path2, int w, int h, int num)
    {
        FILE *f1, *f2;
        f1 = fopen(path1, "r");
        f2 = fopen(path2, "r");
        if (f1 < 0 || f2 < 0) cerr << "Error when opening files";
        unsigned char *pic1 = (unsigned char *)malloc(w*h);
        unsigned char *pic2 = (unsigned char *)malloc(w*h);
    
        for (int i = 0; i < num; i++) 
        {
            if (fread(pic1, 1, w*h, f1)<0) cerr<<"Error when reading file1";
            if (fread(pic2, 1, w*h, f2)<0) cerr<<"Error when reading file2";
    
            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);
            cout << psnr << endl;
        }
        free(pic1);
        free(pic2);
        fclose(f1);
        fclose(f2);
        return 0;
    }
    

    分离RGB24像素数据中的RGB

    和分离YUV文件中的Y、U、V分量一样,不同的是RGB的排列顺序为R-G-B-R-G-B-...RGB24格式的每个像素的三个分量是连续存储的。一帧宽高分别为w、h的RGB24图像一共占用wh3 Byte的存储空间

    所以程序为:

    int rgb24_split(const char *path, int w, int h, int num) {
        FILE *fp = fopen(path, "r");
        FILE *fp1 = fopen("output_r.y", "w");
        FILE *fp2 = fopen("output_g.y", "w");
        FILE *fp3 = fopen("output_b.y", "w");
    
        unsigned char *pic = (unsigned char *)malloc(w*h * 3);
    
        for (int i = 0; i < num; i++) {
    
            fread(pic, 1, w*h * 3, fp);
    
            for (int j = 0; j < w*h * 3; j = j + 3) {
                //R
                fwrite(pic + j, 1, 1, fp1);
                //G
                fwrite(pic + j + 1, 1, 1, fp2);
                //B
                fwrite(pic + j + 2, 1, 1, fp3);
            }
        }
    
        free(pic);
        fclose(fp);
        fclose(fp1);
        fclose(fp2);
        fclose(fp3);
    
        return 0;
    }
    

    图片.png

    图片.png

    将RGB24格式像素数据封装为BMP图像

    BMP图像实际上存的就是RGB数据,所以需要程序实现对RGB像素数据的封装处理,具体实现起来就是在RGB数据前面加上文件头,同时将RGB数据中每个像素的"B"和"R"的位置互换,这是因为BMP文件是小端存储,在内存中存储的先后顺序为B-G-R。

    BMP文件结构

    BMP文件由4部分组成:

    1. 位图文件头(bitmap-file header)
    2. 位图信息头(bitmap-informationheader)
    3. 颜色表(color table)
    4. 颜色点阵数据(bits data)

    24位真彩色位图没有颜色表,所以只有1、2、4这三部分。

    位图文件头分4部分,共14字节:

    名称 占用空间 内容 实际数据
    bfType 2字节 标识,就是“BM”二字 BM
    bfSize 4字节 整个BMP文件的大小 0x000C0036(786486)【与右键查看图片属性里面的大小值一样】
    bfReserved1/2 4字节 保留字,没用 0
    bfOffBits 4字节 偏移数,即 位图文件头+位图信息头+调色板 的大小 0x36(54)

    位图信息头共40字节:

    名称 占用空间 内容 实际数据
    biSize 4字节 位图信息头的大小,为40 0x28(40)
    biWidth 4字节 位图的宽度,单位是像素 0x200(512)
    biHeight 4字节 位图的高度,单位是像素 0x200(512)
    biPlanes 2字节 固定值1 1
    biBitCount 2字节 每个像素的位数 1-黑白图,4-16色,8-256色,24-真彩色 0x18(24)
    biCompression 4字节 压缩方式,BI_RGB(0)为不压缩 0
    biSizeImage 4字节 位图全部像素占用的字节数,BI_RGB时可设为0 0x0C
    biXPelsPerMeter 4字节 水平分辨率(像素/米) 0
    biYPelsPerMeter 4字节 垂直分辨率(像素/米) 0
    biClrUsed 4字节 位图使用的颜色数 如果为0,则颜色数为2的biBitCount次方 0
    biClrImportant 4字节 重要的颜色数,0代表所有颜色都重要 0

    颜色点阵数据

    位图全部的像素,是按照自下向上,自左向右的顺序排列的。

    RGB数据也是倒着念的,原始数据是按B、G、R的顺序排列的。

    BMP文件程序结构

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

    RGB24->BMP

    而实现RGB到BMP的程序如下:

    int rgb24_to_bmp(const char* rgb24path, int width, int height, const char* bmppath)
    {
        BmpHead m_header = { 0 };
        InfoHead m_infoHeader = { 0 };
        char bfType[2] = { 'B', 'M' };
        int header_size = sizeof(bfType) + sizeof(BmpHead) + sizeof(InfoHead);
        unsigned char* rgb24_buffer;
        FILE *f_rgb, *f_bmp;
        f_rgb = fopen(rgb24path, "r");
        if (f_rgb < 0) cerr << "Cannot open RGB file";
        f_bmp = fopen(bmppath, "w");
        if (f_bmp < 0) cerr << "Cannot create BMP file";
    
        rgb24_buffer = (unsigned char *)malloc(width*height * 3);
        if (fread(rgb24_buffer, 1, width*height * 3, f_rgb) < 0)
            cerr << "Error when reading RGB";
        m_header.imageSize = header_size + width * height *3; //1像素=8bit,1byte=8bit
        m_header.startPosition = header_size;
        m_infoHeader.biSize = sizeof(InfoHead);
        m_infoHeader.width = width;
        m_infoHeader.height = -height; // BMP存储像素是从下至上的
        m_infoHeader.colorPlane = 1;
        m_infoHeader.bitColor = 24; //24位真彩
        m_infoHeader.realSize = 3 * width*height;
        cout << sizeof(BmpHead) << endl;
        cout << sizeof(InfoHead) << endl;
    
        //for (int i = 0; i < height; ++i)
        //{
        //    for (int j = 0; j < width; ++j)
        //    {
        //        char temp = rgb24_buffer[(i*width + j) * 3 + 2];
        //        rgb24_buffer[(i*width + j) * 3 + 2] = rgb24_buffer[(i*width + j) * 3];
        //        rgb24_buffer[(i*width + j) * 3] = temp;
        //    }
        //}
    
        for (int i = 0; i < height*width; ++i)
        {
            unsigned char temp = *(rgb24_buffer+i*3);
            *(rgb24_buffer + i * 3) = *(rgb24_buffer + i * 3 + 2);
            *(rgb24_buffer + i * 3 + 2) = temp;
        }
    
        fwrite(bfType, 1, sizeof(bfType), f_bmp);
        fwrite(&m_header, 1, sizeof(m_header), f_bmp);
        fwrite(&m_infoHeader, 1, sizeof(m_infoHeader), f_bmp);
        //fwrite(rgb24_buffer, 1, sizeof(rgb24_buffer), f_bmp);
        fwrite(rgb24_buffer, 3*width*height, 1, f_bmp);
    
    
        fclose(f_rgb);
        fclose(f_bmp);
        free(rgb24_buffer);
        return 0;
    }
    

    图片.png

    生成的BMP文件如下,排查了很长时间但没有发现错误(汗-_-||),出现这样子的颜色觉得可能是RGB互换的时候出现了问题,但是思路并没有出错。日后发现原因再补吧,这里留一个坑......

    将RGB24格式像素数据转换为YUV420P格式像素数据

    运用RGB和YUV的转换公式,需要注意的地方时RGB24的存储方式是Packed,YUV420P的存储方式也是Packed程序如下:

    unsigned char clip_value(unsigned char x, unsigned char min_val, unsigned char  max_val) {
        if (x>max_val) {
            return max_val;
        }
        else if (x<min_val) {
            return min_val;
        }
        else {
            return x;
        }
    }
    
    //RGB to YUV420
    bool RGB24_TO_YUV420(unsigned char *RgbBuf, int w, int h, unsigned char *yuvBuf)
    {
        unsigned char *ptrY, *ptrU, *ptrV, *ptrRGB;
        memset(yuvBuf, 0, w*h * 3 / 2);
        ptrY = yuvBuf;
        ptrU = yuvBuf + w * h;
        ptrV = ptrU + (w*h * 1 / 4);
        unsigned char y, u, v, r, g, b;
        for (int j = 0; j<h; j++) {
            ptrRGB = RgbBuf + w * j * 3; //从RgbBuf的每一行的开头开始
            for (int i = 0; i<w; i++) {
    
                r = *(ptrRGB++);
                g = *(ptrRGB++);
                b = *(ptrRGB++);
                y = (unsigned char)((66 * r + 129 * g + 25 * b + 128) >> 8) + 16;
                u = (unsigned char)((-38 * r - 74 * g + 112 * b + 128) >> 8) + 128;
                v = (unsigned char)((112 * r - 94 * g - 18 * b + 128) >> 8) + 128;
                *(ptrY++) = clip_value(y, 0, 255);
                if (j % 2 == 0 && i % 2 == 0) {
                    *(ptrU++) = clip_value(u, 0, 255);
                }
                else {
                    if (i % 2 == 0) {
                        *(ptrV++) = clip_value(v, 0, 255);
                    }
                }
            }
        }
        return true;
    }
    
    
    int rgb24_to_yuv420(const char *path1, int w, int h, int num, const char *path2) {
        FILE *fp = fopen(path1, "r");
        FILE *fp1 = fopen(path2, "w");
    
        unsigned char *pic_rgb24 = (unsigned char *)malloc(w*h * 3);
        unsigned char *pic_yuv420 = (unsigned char *)malloc(w*h * 3 / 2);
    
        for (int i = 0; i<num; i++) {
            fread(pic_rgb24, 1, w*h * 3, fp);
            RGB24_TO_YUV420(pic_rgb24, w, h, pic_yuv420);
            fwrite(pic_yuv420, 1, w*h * 3 / 2, fp1);
        }
    
        free(pic_rgb24);
        free(pic_yuv420);
        fclose(fp);
        fclose(fp1);
    
        return 0;
    }
    

    生成的YUV文件大小会缩小很多:

    图片.png

    当然只涉及了RGB24和YUV420P格式的图像文件,所以如果格式不一样,需要更改代码。

  • 相关阅读:
    同一个String在使用不同的charset编码的时候equals仍然是返回true吗
    request的生存期只限于服务器跳转
    flex 客户端缓存SharedObject
    flex NaN
    oracle 任务使用
    oracle 数据泵
    datagrid 用法
    Windows系统中path环境变量详解
    [转]eclipse导入V7包出现错误解决办法
    由多线程引起的map取值为null的分析
  • 原文地址:https://www.cnblogs.com/yunlambert/p/11234971.html
Copyright © 2011-2022 走看看