BMP转YUV

本文详细介绍了BMP图像格式的原理,包括文件头BITMAP_FILE_HEADER和BITMAP_INFO_HEADER的内容,以及如何计算BMP图像大小。接着,阐述了BMP到YUV转换的程序过程,特别强调了由于BMP的倒序存储特性,读取时应从最后一行开始。此外,还提到了当RGB位数为24位时的处理,并提供了实验结果。最后,针对VS2013中fopen()的安全错误问题给出了解决方案。
摘要由CSDN通过智能技术生成

一、实验原理

(1)文件头 BITMAP_FILE_HEADER,包含如下内容

typedef struct tagBITMAPFILEHEADER {
    //0x00~0x01,说明文件的类型
    WORD bfType; 
    //0x02~0x05,说明文件的大小,用字节B为单位
    DWORD bfSize;
    //0x06~0x07,保留,设置为0
    WORD bfReserved1;
    //0x08~0x09,保留,设置为0
    WORD bfReserved2;
    //0x0a~0x0d,说明从BITMAP_FILE_HEADER结构开始到实际的图像数据之间的字节偏移量
    DWORD bfOffBits;
} BITMAPFILEHEADER;

(2)信息头BITMAP_INFO_HEADER,包含如下内容

typedef struct tagBITMAPINFOHEADER {
    //0x0e~0x11,说明当前结构体所需字节数
    DWORD biSize;
    //0x12~0x15,以像素为单位说明图像的宽度
    LONG biWidth;
    //0x16~0x19,以像素为单位说明图像的高度
    LONG biHeight;
    //0x1a~0x1b,说明位面数,必须为1
    WORD biPlanes;
    //0x1c~0x1d,说明图像的位深度
    WORD biBitCount;
    //0x1e~0x21,说明图像是否压缩及压缩类型
    DWORD biCompression;
    //0x22~0x25,以字节为单位说明图像大小,必须是4的整数倍
    DWORD biSizeImage;
    //0x26~0x29,目标设备的水平分辨率,像素/米 
    LONG biXPelsPerMeter;
    //0x2a~0x2d,目标设备的垂直分辨率,像素/米
    LONG biYPelsPerMeter;
    //0x2e~0x31,说明图像实际用到的颜色数,如果为0,则颜色数为2的biBitCount次方
    DWORD biClrUsed;
    //0x32~0x35,说明对图像显示有重要影响的颜色索引的数目,如果是0,表示都重要。
    DWORD biClrImportant;
} BITMAPINFOHEADER;

(3)bmp图片基本信息

iu.bmp这里写图片描述
vs中二进制方式打开这里写图片描述
图像基本信息分辨率:1024*680 图片大小:1.99MB
0x00~0x01标记了此文件为BMP文件,内容是B和M两个字母的ASCII码
0x02~0x05以字节为单位的文件大小
0x0a~0x0d实际数据的字节偏移量。
0x22~0x25BMP图像大小biSizeImage

-

0x22~0x25,BMP图像大小biSizeImage可由下式计算

biSizeImage=cx×biBitCount+3132×4×cy+2

其中,cx,cy表示水平和垂直方向的像素数。 cx×biBitCount 表示一行图像占了多少位。BMP规定这个图像大小必须是4字节的整数倍,也就是32位的整数倍,因此需要把 cx×biBitCount 加31再除以32后下取整,就保证了计算结果是离这个数最近的而且是比它大的32的倍数,也就保证了是4字节的整数倍。乘以4和行数,得到4字节整数倍的图像大小。
另外,BMP文件的末尾两个字节是保留位,无论图像是什么这两个字节都为0,因此最后计算结果还要加上2字节。图像大小biSizeImage+字节偏移量bfOffBits=文件大小bfSize。

二、程序过程

(1)——–main_bmp2yuv.cpp——–

main()流程:

Created with Raphaël 2.1.0 读取文件 获得bmp文件自包含信息 创建buf缓冲区 分别调用BMP2RGB(),RGB2YUV()函数进行空间转换 将YUV缓冲区内容依次写入YUV文件 释放缓冲区
int main(int argc, char** argv)
{

    //设置命令行参数
    char* bmpFileName = argv[1];
    char* yuvFileName = argv[2];

    //打开文件
    FILE* bmpFile = fopen(bmpFileName, "rb");
    if (bmpFile == NULL)
    {
        printf("Cannot open the BMP file.\n");
        exit(1);
    }
    else
    {
        printf("The BMP file is %s\n", bmpFileName);
    }
    FILE* yuvFile = fopen(yuvFileName, "wb");
    if (yuvFile == NULL)
    {
        printf("Cannot open the YUV file.\n");
        exit(1);
    }
    else
    {
        printf("The YUV file is %s\n", yuvFileName);
    }

    //读取BMP文件头,信息头,读取错误时的处理代码
    BITMAPFILEHEADER file_header;
    BITMAPINFOHEADER info_header;
    if (fread(&file_header, sizeof(BITMAPFILEHEADER), 1, bmpFile) != 1)
        if (file_header.bfType != 0x4D42)
        {
            printf("Not BMP file.\n");
            exit(1);
        }
    if (fread(&info_header, sizeof(BITMAPINFOHEADER), 1, bmpFile) != 1)
    {
        printf("read info header error!");
        exit(0);
    }//结束读取BMP文件头

    //读取图像尺寸
    int width = info_header.biWidth;
    int height = info_header.biHeight;

    //开辟缓冲区 buf
    u_int8_t* yBuf = (u_int8_t*)malloc(height*width);
    u_int8_t* uBuf = (u_int8_t*)malloc(height*width / 4);
    u_int8_t* vBuf = (u_int8_t*)malloc(height*width / 4);
    u_int8_t* rgbBuf = (u_int8_t*)malloc(height*width * 3);
    if (yBuf == NULL || uBuf == NULL || vBuf == NULL || rgbBuf == NULL)
    {
        printf("Not enough memory\n");
        exit(1);
    }

    //BMP与RGB的转换,得到RGB数据
    if (BMP2RGB(file_header, info_header, bmpFile, rgbBuf))
        {
            printf("BMP2RGB error\n");
            exit(1);
        }

    //RGB与YUV的转换,得到YUV数据
    int flip = 0;
    /*读取到的图像数据是倒序存放的,flip=0保证了RGB2YUV可以正确地对其转换*/
    if (RGB2YUV(width, height, rgbBuf, yBuf, uBuf, vBuf, flip))
        {
            printf("RGB2YUV error\n");
            exit(1);
        }

    //将yuv按顺序写入yuvfile文件
    fwrite(yBuf, 1, width * height, yuvFile);
    fwrite(uBuf, 1, (width * height) / 4, yuvFile);
    fwrite(vBuf, 1, (width * height) / 4, yuvFile);

    //打印宽高,方便yuv观看程序打开
    printf("width is %i", width);
    printf("\n");
    printf("heightis %i", height); 
    printf("\n");
    //清理内存
    free(rgbBuf);
    free(yBuf); free(uBuf); free(vBuf);
    fclose(bmpFile);
    fclose(yuvFile);

    return 0;
}

特别:
因为bmp文件是倒序存储的。所以在读文件是应该从最后一行开始读,开始设置(!flip)=1,则执行以下条件

if (!flip) {
        for (j = 0; j < y_dim; j ++)
        {
            y = y_buffer + (y_dim - j - 1) * x_dim;
            u = u_buffer + (y_dim - j - 1) * x_dim;
            v = v_buffer + (y_dim - j - 1) * x_dim;

            for (i = 0; i < x_dim; i ++) {
                g = b + 1;
                r = b + 2;
                *y = (unsigned char)(  RGBYUV02990[*r] + RGBYUV05870[*g] + RGBYUV01140[*b]);
                *u = (unsigned char)(- RGBYUV01684[*r] - RGBYUV03316[*g] + (*b)/2          + 128);
                *v = (unsigned char)(  (*r)/2          - RGBYUV04187[*g] - RGBYUV00813[*b] + 128);
                b += 3;
                y ++;
                u ++;
                v ++;
            }
        }
    } 

重点解释:假设图片是5*3的像素矩阵最后一行的地址为y_buffer+(3-1)*5
抽象成宽高分别x_dim、y_dim的循环则为:

ybuffer+(ydimj1)xdim

y = y_buffer + (y_dim - j - 1) * x_dim;
u = u_buffer + (y_dim - j - 1) * x_dim;
v = v_buffer + (y_dim - j - 1) * x_dim;

(2)BMP2RGB()函数

1.rgb的位数为24(8,8,8)

BMP2RGB()流程:

Created with Raphaël 2.1.0 确定像素的实际点阵数 开辟实际字节数量的缓冲区 依次写入rgb 释放缓冲区
//确定像素的实际点阵数
    w = (info_h.biWidth*info_h.biBitCount + 31) / 32 * 4;//w为实际一行的字节数
    h = info_h.biHeight;//h为列数

    //开辟实际字节数量的缓冲区,读数据,一次读取一个字节
    u_int8_t* dataBuf = (u_int8_t*)malloc(w*h);
    /*使用文件头的字节偏移属性bfOffBits
    直接把文件指针定位到像素值数据的起始 */
    fseek(pFile, file_h.bfOffBits, 0);
    fread(dataBuf, 1, w*h, pFile);
    unsigned char* data = dataBuf;
    unsigned char* rgb = rgbBuf;

    //开始写入rgb
    int i, j;
    for (j = 0; j < h; j++)//j控制行循环
    {
        for (i = 0; i < w; i += 3)//i控制列循环
        {
            *rgb = data[i + w*j];//B
            *(rgb + 1) = data[i + w*j + 1];//G
            *(rgb + 2) = data[i + w*j + 2];//R
            rgb += 3;
        }
    }
    //释放内存
    free(dataBuf);
    return 0;
}

实验结果:rgb的位数为24

这里写图片描述

  • 补充说明
  • fopen()vs2013提示安全错误解决办法:打开文件的属性页,单击“预处理器”将“_CRT_SECURE_NO_WARNINGS”复制在“预处理器定义中’,然后确定再单击应用即可。
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值