BMP文件读写以及RGB与YUV转换

来源:互联网 发布:清理恶意软件 编辑:程序博客网 时间:2024/05/16 15:33


最近学习图像信息处理,没想到会在BMP文件的读写以及RGB与YUV转换花费这么多时间。写下这篇文章,总结一下自己的经验教训。

一、BMP文件读写

1.1 BMP文件格式

位图文件(Bitmap-File,BMP)格式是Windows采用的图像文件存储格式,在Windows环境下运行的所有图像处理软件都支持这种格式。BMP位图文件默认的文件扩展名是bmp或者dib。BMP文件大体上分为四个部分:位图文件头、位图信息头、调色板

紧跟在调色板之后的是图像数据字节阵列。对于用到调色板的位图,图像数据就是该像素颜色在调色板中的索引值(逻辑色)。对于真彩色图,图像数据就是实际的R、G、B值。图像的每一扫描行由表示图像像素的连续的字节组成,每一行的字节数取决于图像的颜色数目和用像素表示的图像宽度。规定每一扫描行的字节数必需是4的整倍数,也就是DWORD对齐的。扫描行是由底向上存储的,这就是说,阵列中的第一个字节表示位图左下角的像素,而最后一个字节表示位图右上角的像素。

如果是没有调色板的24位真彩色BMP,其格式可用下列结构定义(共54字节):

typedef struct bmp{    //14字节    unsigned short bfType; //文件标识 2字节 必须为BM    unsigned int bfSize; //文件大小 4字节    unsigned short bfReserved1; //保留,每字节以"00"填写 2字节    unsigned short bfReserved2; //同上 2字节    unsigned int bfOffBits; //记录图像数据区的起始位置(图象数据相对于文件头字节的偏移量)。 4字节    //40字节    unsigned int biSize; //表示本结构的大小 4字节    int biWidth; //位图的宽度 4字节    int biHeight; //位图的高度 4字节    unsigned short biPlanes; //永远为1 , 2字节    unsigned short biBitCount; //位图的位数 分为1 4 8 16 24 32 ,2字节    unsigned int biCompression; //压缩说明 4字节    unsigned int biSizeImage; //表示位图数据区域的大小以字节为单位 4字节    int biXPelsPerMeter; //用象素/米表示的水平分辨率 4字节    int biYPelsPerMeter; //用象素/米表示的垂直分辨率 4字节    unsigned int biClrUsed; //位图使用的颜色索引数,如果为0,则颜色数为2的biBitCount次方 4字节    unsigned int biClrImportant; //对图象显示有重要影响的颜色索引的数目,如果是0,表示都重要 4字节} BMP;
1.2 BMP读写

可以参考这位大神的博客http://blog.csdn.net/leixiaohua1020/article/details/13506099 对1-8位与16位的BMP进行读写。

我为了方便操作,直接选用了24位BMP图片。这个比较简单,直接按照B/G/R的顺序写入缓冲区即可。

有一个坑点是图片前54个字节一定要写对 不然图片是无法正常显示的!!!

下面是我写的检测并读取BMP图片前54位字节的函数:

int ReadBmpHead(FILE * outfp,FILE* fp,char* buf_head,int* picwidth,int* picheight,long int biSizeImage){    short ind;    char bm[2];        //读取文件头与图像头信息 判断是否为24位bmp图片    fseek(fp,0L,0);    fread(bm,2,1,fp);    fseek(fp,0L,0);    fread(buf_head, 54, 1, fp);    fwrite(buf_head, 1, 54, outfp);        fseek(fp,28L,0);    fread(&ind,2,1,fp);    if(bm[0]!='B'||bm[1]!='M'||ind!=24){        printf("\n非24位BMP图片\n");        return 1;    }    fseek(fp, 18L, 0);    fread(picwidth, 4, 1, fp);    fread(picheight, 4, 1, fp);        fseek(fp,34L,0);//图像数据区的大小    fread(&biSizeImage,4,1,fp);    fseek(fp, 54L, 0);    printf("图片尺寸%d*%d ",*picwidth,*picheight);   // fread(buf_total, 1, 54+biSizeImage, fp);    return 0;};
之后,结合fread、fseek、fwrite函数进行文件流的读写即可,下面给出一个RGB直接转灰度图[方法比较粗暴]的例子:
fseek(fp, 54L, 0);        for (i=0;i<picheight;i++)        for (j = 0; j < picwidth; j++){            fread(&b, 1, 1, fp);            fread(&g, 1, 1, fp);            fread(&r, 1, 1, fp);            //为防止rgb溢出,将其值赋给int型的r1,b1,g1进行之后的公式运算            r1 = r;            b1 = b;            g1 = g;            //注意:这之后的步骤可根据需要自行修改                       fwrite(&b1, 1, 1, outfp2);            fwrite(&b1, 1, 1, outfp2);            fwrite(&b1, 1, 1, outfp2);                                }

二、RGB转YUV 再转回RGB

YUV数据格式可以参考这篇文章:http://blog.csdn.net/beyond_cn/article/details/12998247

我的目标是RGB转YUV 再转回RGB,这个步骤的重点是解决转换过程中的溢出问题。为了简单,我直接用公式法;我选用的公式是

            y= 0.2990*r1+0.5869*g1+0.1140*b1;

            u= -0.1687*r1-0.3313*g1+0.5000*b1 +128;

            v= 0.5000*r1-0.4187*g1-0.0813*b1 + 128;

中间再处理一下数据不在【0,255】之间的情况即可。

这个是含偏移量的公式。经验证可以正确转换出我想看到的效果。其他的公式总是会出bug

当然,这种方法没什么优化,都是浮点数运算。优化的话,可以参考这个:http://blog.csdn.net/chen825919148/article/details/7921475


附一张我用的测试图:【获得24位BMP图片的方式也很简单,直接用windows自带的画图工具,另存一下即可】



另外,我还学到了一点 #Pragma Pack(n)与内存分配 的知识。


写完之后才发现,这个过程并没有那么复杂……但我还是花费了大量时间,两点原因:1.自己还是不喜欢动脑,没先搞清楚BMP的格式,就急着写代码;2.网上的资料良莠不齐,浪费了不少时间。都是泪……
对了,最后还有一点值得提一下:BMP是微软家的格式,window系统里的window.h头文件里有BMP文件结构的定义,可以直接用。我因为用的是Mac系统所以不得不重新定义

0 0
原创粉丝点击