文件格式转换(BMP to YUV)

文件格式转换(BMP to YUV)

BMP图片获取及处理

​ 选择好需要使用的图像,用系统(Windows)自带的画图软件打开所选择的文件,加上文字标识并另存为bmp文件。

  • 所选择的图像大小:720 x 480

  • 保存的格式为:24位bmp

BMP文件格式简介

BMP图像文件往往由四部分组成:

  • 位图文件头:它包含BMP图像文件的类型、显示内容等信息;
typedef  struct  tagBITMAPFILEHEADER {
    WORD    bfType;           /* 说明文件的类型 */
    DWORD   bfSize;            /* 说明文件的大小,用字节为单位*/
    WORD    bfReserved1;            /*保留,设置为0 */
    WORD    bfReserved2;           /*保留,设置为0 */
    DWORD   bfOffBits;         /* 说明从BITMAPFILEHEADER结构开始到实际的图像数据之间的字节偏移量 */
} BITMAPFILEHEADER;
  • 位图信息头:它包含有BMP图像的宽、高、压缩方法,以及定义颜色等信息
typedef struct tagBITMAPINFOHEADER {
    DWORD    biSize;       /*说明结构体所需字节数*/
    DWORD      biWidth;  /*以像素为单位说明图像的宽度*/
    DWORD      biHeight; /*以像素为单位说明图像的高速*/
    WORD     biPlanes;  /*说明位面数,必须为1 */
    WORD     biBitCount;  /*说明位数/像素,1、2、4、8、24 */
    DWORD    biCompression;  /*说明图像是否压缩及压缩类型BI_RGB,BI_RLE8,BI_RLE4,BI_BITFIELDS */
    DWORD    biSizeImage;    /*以字节为单位说明图像大小,必须是4的整数倍*/
    DWORD      biXPelsPerMeter;   /*目标设备的水平分辨率,像素/米 */
    DWORD      biYPelsPerMeter;   /*目标设备的垂直分辨率,像素/米 */
    DWORD    biClrUsed;    /* 说明图像实际用到的颜色数,如果为0则颜色数为2的biBitCount次方 */
    DWORD    biClrImportant;  /*说明对图像显示有重要影响的颜色索引的数目,如果是0,表示都重要。*/
} BITMAPINFOHEADER;
  • 调色板:这个部分是可选的,有些位图需要调色板,有些位图,比如真彩色图(24位的BMP)就不需要调色板;
typedef struct tagRGBQUAD {
    BYTE rgbBlue; /*指定蓝色分量*/
    BYTE rgbGreen; /*指定绿色分量*/
    BYTE rgbRed; /*指定红色分量*/
    BYTE rgbReserved; /*保留,指定为0*/
} RGBQUAD;
  • 位图数据:这部分的内容根据BMP位图使用的位数不同而不同,在24位图中直接使用RGB,而其他的小于24位的使用调色板中颜色索引值

程序流程及主要代码

  1. 文件读入

    //重建读入文件名,并用fopen函数打开文件
    strcpy_s(name_in, argv[1]);
    _itoa_s(i, str, 20, 10);
    strcat_s(name_in, 20, str);
    strcat_s(name_in, 20, ".bmp");
    fopen_s(&bmpFile, name_in, "rb");
    
  2. 获取位图文件头、位图信息头

    //读取位图文件头
    fread(&File_header, sizeof(BITMAPFILEHEADER), 1, bmpFile);
    //读取位图信息头
    fread(&Info_header, sizeof(BITMAPINFOHEADER), 1, bmpFile);
    
  3. 从bmp文件中读出rgb信息

    • bmp文件存储形式为从左往右、从下往上存储,这里进行正序读取,倒序输出;也可以直接进行倒序读取,正序输出
    • rgb的存储顺序为b->g->r
    void READrgb(FILE* pFile, const BITMAPFILEHEADER& file_h, const BITMAPINFOHEADER& info_h, unsigned char* rgbDataOut)
    {
        unsigned long  width, height;
        unsigned char  * raw_Data, * Data;
    
        //对于24位bmp文件,有效的数据大小为:长*宽*3字节(24bit)
        width = info_h.biWidth * 3;
        height = info_h.biHeight;
    
         //倒序前数据缓存区
        raw_Data = (unsigned char*)malloc(height * width);//buffer大小应该与bmp中有效数据大小相同
        //倒序后数据缓存区,用于存放bmp中的有效数
        Data = (unsigned char*)malloc(height * width);//buffer大小应该与bmp中有效数据大小相同
    
        //令文件指针指向实际有效数据处:需要使用到文件头中的有效数据偏移量
        fseek(pFile, file_h.bfOffBits, 0);
    
        if (fread(raw_Data, height * width, 1, pFile) != 1)
        {
            printf("read file error!");
            exit(0);
        }
    
        //倒序存放
        for (int i = 0; i < height; i++) 
        {
            for (int j = 0; j < width; j++)
            {
                Data[i * width + j] = raw_Data[(height - i - 1) * width + j];
            }
        }
    
        //将缓存区中倒序后的数据复制到输出缓存区(24bit位图可直接写)
        memcpy(rgbDataOut, Data, height * width);
    
        if (raw_Data)
            free(raw_Data);
        if (Data)
            free(Data);
    
    }
    
  4. 将rgb信息转换成yuv信息

    • RGB转换成YUV公式:
      Y = 0.30 R + 0.59 G + 0.11 B Y=0.30R+0.59G+0.11B Y=0.30R+0.59G+0.11B

      U = 0.493 ( B - Y ) U=0.493(B-Y) U=0.493(BY)

      V = 0.877 ( R - Y ) V=0.877(R-Y) V=0.877(RY)

    • 4:2:0 yuv格式采样公式:
      四 个 Y 公 用 的 U = 四 个 Y 对 应 的 U 之 和 / 4 四个Y公用的 U = 四个Y对应的U之和/4 YU=YU/4

      四 个 Y 公 用 的 V = 四 个 Y 对 应 的 V 之 和 / 4 四个Y公用的 V = 四个Y对应的V之和/4 YV=YV/4

    • 在转换后yuv会出现非正常取值(噪点),需要对此进行处理

    void rgb2yuv(unsigned long w, unsigned long h, unsigned char* rgbData, unsigned char* y, unsigned char* u, unsigned char* v)
    {
        unsigned char* ytemp = NULL;
        unsigned char* utemp = NULL;
        unsigned char* vtemp = NULL;
        utemp = (unsigned char*)malloc(w * h);
        vtemp = (unsigned char*)malloc(w * h);
    
        unsigned long i, nr, ng, nb, nSize;
    
        //进行 rgb到yuv的转换
        for (i = 0, nSize = 0; nSize < w * h * 3; nSize += 3)
        {
            //分别提取每个像素的r、g、b值
            nb = rgbData[nSize];
            ng = rgbData[nSize + 1];
            nr = rgbData[nSize + 2];
            //根据转换公式进行yuv转换
            y[i] = (unsigned char)(0.2990 * nr + 0.5870 * ng + 0.1140 * nb);
            utemp[i] = (unsigned char)(-0.168 * nr - 0.332 * ng + 0.5 * nb + 128);
            vtemp[i] = (unsigned char)(0.5 * nr - 0.419 * ng - 0.081 * nb + 128);
            i++;
        }
    
        //根据4:2:0格式对u、v进行采样
        int k = 0;
        for (i = 0; i < h; i += 2) 
        {
            for (unsigned long j = 0; j < w; j += 2)
            {
                u[k] = (utemp[i * w + j] + utemp[(i + 1) * w + j] + utemp[i * w + j + 1] + utemp[(i + 1) * w + j + 1]) / 4;
                v[k] = (vtemp[i * w + j] + vtemp[(i + 1) * w + j] + vtemp[i * w + j + 1] + vtemp[(i + 1) * w + j + 1]) / 4;
                k++;
            }
        }
        
        //处理后会出现噪点值,进行降噪
        for (i = 0; i < w * h; i++)
        {
            if (y[i] < 16)
                y[i] = 16;
            if (y[i] > 235)
                y[i] = 235;
        }
        for (i = 0; i < h * w / 4; i++)
        {
            if (u[i] < 16)
                u[i] = 16;
            if (v[i] < 16)
                v[i] = 16;
            if (u[i] > 240)
                u[i] = 240;
            if (v[i] > 240)
                v[i] = 240;
        }
    
        if (utemp)
            free(utemp);
        if (vtemp)
            free(vtemp);
    }
    
  5. 文件写入

    //构造写出文件名并创建文件
    strcpy_s(name_out, argv[1]);
    strcat_s(name_out, 20, ".yuv");
    fopen_s(&yuvFile, name_out, "wb");
    
    
    //根据命令行参数设定的帧数写入文件
    for (int m = 0; m < atoi(argv[2+i]); m++) {
        //将转好的yuv写到文件中
        flag = WriteYUV(yBuffer, uBuffer, vBuffer, width * height, yuvFile);
    }
    
    bool WriteYUV(unsigned char* Y, unsigned char* U, unsigned char* V, unsigned long size, FILE* outFile)
    {
        if (fwrite(Y, 1, size, outFile) != size)
            return false;
        if (fwrite(U, 1, size / 4, outFile) != size / 4)
            return false;
        if (fwrite(V, 1, size / 4, outFile) != size / 4)
            return false;
        return true;
    }
    

主程序完整代码

int main(int argc, char** argv)
{
    BITMAPFILEHEADER File_header;
    BITMAPINFOHEADER Info_header;
    FILE* bmpFile;
    FILE* yuvFile;

    unsigned char* rgbBuffer, * yBuffer, * uBuffer, * vBuffer;
    char name_out[20];

    //构造写出文件名并创建文件
    strcpy_s(name_out, argv[1]);
    strcat_s(name_out, 20, ".yuv");
    fopen_s(&yuvFile, name_out, "wb");
    if (!yuvFile)
    {
        printf("yuvfile open error!\n");
        exit(0);
    }

    //根据命令行参数打开文件并进行转换操作
    for (int i = 1; i <= atoi(argv[2]); i++) { //atoi():将字符串转换成整数
        char str[20];
        char name_in[20];

        //重建读入文件名,并用fopen函数打开文件
        strcpy_s(name_in, argv[1]);
        _itoa_s(i, str, 20, 10);
        strcat_s(name_in, 20, str);
        strcat_s(name_in, 20, ".bmp");
        fopen_s(&bmpFile, name_in, "rb");

        //保证bmp文件能够打开
        if (bmpFile)
        {
            //读取位图文件头
            fread(&File_header, sizeof(BITMAPFILEHEADER), 1, bmpFile);
            //读取位图信息头
            fread(&Info_header, sizeof(BITMAPINFOHEADER), 1, bmpFile);

            //判断文件类型
            if (File_header.bfType != 0x4D42)
            {
                printf("Not bmp file!\n");
                //exit(0);
                break;
            }

            unsigned long width, height;
            width = Info_header.biWidth;
            height = Info_header.biHeight;

            //开辟缓冲区
            rgbBuffer = new unsigned char[height * width * 3];
            yBuffer = new unsigned char[height * width];
            uBuffer = new unsigned char[height * width / 4];
            vBuffer = new unsigned char[height * width / 4];

            //读取rgb信息
            READrgb(bmpFile, File_header, Info_header, rgbBuffer);

            //将rgb数据转为yuv
            rgb2yuv(width, height, rgbBuffer, yBuffer, uBuffer, vBuffer);

            bool flag;
            //根据命令行参数设定的帧数写入文件
            for (int m = 0; m < atoi(argv[2+i]); m++) {
                //将转好的yuv写到文件中
                flag = WriteYUV(yBuffer, uBuffer, vBuffer, width * height, yuvFile);
            }

            //判断文件成功写入
            if (flag)
            {
                printf("YUVfile written!\n");
            }
            else
            {
                break;
            }

            if (rgbBuffer)
                free(rgbBuffer);
            if (yBuffer)
                free(yBuffer);
            if (uBuffer)
                free(uBuffer);
            if (vBuffer)
                free(vBuffer);
            fclose(bmpFile);

        }
        else {
            printf("bmpfile open error!\n");
            break; 
        }

    }
    fclose(yuvFile);
    return 0;
}

命令行参数及bmp存储文件名说明

argv[1]:bmp文件名前缀

argv[2]:bmp文件数

argv[3]~argv[n]:每帧图像重复帧数

在这里插入图片描述

实验结果

  • bmp文件准备:统一命名为 文件名前缀n.bmp(这里直接用的另一门课上生成的图进行操作了),存储在工程同一目录下

在这里插入图片描述
r

  • 生成的yuv文件

在这里插入图片描述

其他注意事项

​ 在构造文件头、信息头结构体时成员变量的长度并不相同,会有以下问题:

  • sizeof(BITMAPFILEHEADER)sizeof(BITMAPINFOHEADER) 得到的结果与定义的结构体总长不同
  • 字节对齐问题,在使用 fread() 函数进行读取时出现有字节被跳过的现象而导致实验失败

​ 这部分内容具体讲解参考:https://www.cnblogs.com/wuyudong/p/memory-alignment.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值