练习目的
BMP格式图片灰度化
位图知识点
刚拿到任务时觉得图像处理不算难,以前在学校用matlab做过简单的数字图像处理作业,如果用c++写的话调用OpenCV的库函数应该也不难,但后来发现要用纯c++开发,就有点懵逼了。matlab和OpenCV里的封装函数确实用起来很方便很快,但函数的算法是如何实现的,到底是对图片的什么信息做了什么更改,一无所知。所以需要好好补一下这部分知识,这样有利于从根本上理解图像处理的本质方法。
上网搜了很多资料,发现对图片进行处理大家都提到了BMP格式,后来再查发现bmp格式的文件里存储了图片的很多信息,包括:信息头,文件头,调色板,位图数据(实际的像素值)。刚开始的想法就是我能拿到图片的像素值就好办了,后来发现原来BMP格式就存储着像素信息,那就继续了解下BMP格式的存储结构,还要知道怎么拿到这些数据。
BMP图像存储结构
块名称 | Windows结构体定义 | 大小(BYTE) |
---|---|---|
文件信息头 | BITMAPFILEHEADER | 14 |
位图信息头 | BITMAPINFOHEADER | 40 |
调色板 | RGBQUAD | 单色、16色、256色图像文件特有,相对应的调色板大小是2,16,256。 |
RGB像素值阵列 | BYTE* | 由图像长宽尺寸决定,每一行字节数必须是4的整数倍 |
位图文件头
提供文件的格式,大小等信息
typedef struct tagBITMAPFILEHEADER
{
UINT16 bfType; //2BYTES,必须为BM,即0x424D,文件格式,windows位图文件
DWORD bfSize; //4BYTES,整个BMP文件的大小
UINT16 bfReverved1; //2BYTES,保留,值为0
UINT16 bfReverved2; //2BYTES,保留,值为0
DWORD bfOffBits; //4BYTES,文件起始位置到图像像素数据的字节偏移量
}BITMAPFILEHEADER;
位图信息头
定义如下:
typedef struct _tagBMP_INFOHEADER
{
DWORD biSize; //4BYTES,INFOHEADER结构体大小
LONG biWidth; //4BYTES,图像宽度(以像素为单位)
LONG biHeight; //4BYTES,图像高度。值为正,图像存储顺序从下到上,从左到右;值为负,正好相反。
WORD biPlanes; //2BYTES,图像数据平面,BMP存储RGB数据,因此总为1
WORD biBitCount; //2BYTES,图像像素位数
DWORD biCompression; //4BYTES,0:不压缩;1:RLE8;2:RLE4
DWORD biSizeImage; //4BYTES,4字节对齐的图像数据大小
LONG biXPelsPerMeter; //4BYTES,用像素/米表示的水平分辨率
LONG biYPelsPerMeter; //4BYTES,用像素/米表示的垂直分
DWORD biClrUsed; //4BYTES,实际使用的调色板索引数,0:使用所有的调色板索引
DWORD biClrImportant; //4BYTES,重要的调色板索引数,0:所有的索引都重要
}BMP_INFOHEADER;
调色板
结构体定义如下:
typedef struct _tagRGBQUAD
{
BYTE rgbBlue; //指定蓝色强度
BYTE rgbGreen; //指定绿色强度
BYTE rgbRed; //指定红色强度
BYTE rgbReserved; //保留,设0
}RGBQUAD;
1,4,8位图像才会使用调色板数据,16,24,32位图像不需要调色板数据,即调色板最多只需256项(索引0-255)。
颜色表的大小根据所使用的颜色模式而定:2色图像为8字节;16色图像位64字节;256色图像为1024字节。其中,每4字节表示一种颜色,并以B(蓝色)、G(绿色)、R(红色)、alpha(32位位图的透明度值,一般不需要)。即首先4字节表示颜色号1的颜色,接下来表示颜色号2的颜色,依此类推。
颜色表中RGBQUAD结构数据的个数有biBitCount来确定,当biBitCount=1,4,8时,分别有2,16,256个表项。
当biBitCount=1时,为2色图像,BMP位图中有2个数据结构RGBQUAD,一个调色板占用4字节数据,所以2色图像的调色板长度为2*4为8字节。
当biBitCount=4时,为16色图像,BMP位图中有16个数据结构RGBQUAD,一个调色板占用4字节数据,所以16像的调色板长度为16*4为64字节。
当biBitCount=8时,为256色图像,BMP位图中有256个数据结构RGBQUAD,一个调色板占用4字节数据,所以256色图像的调色板长度为256*4为1024字节。
当biBitCount=16,24或32时,没有颜色表。
位图数据
位图数据记录了位图的每一个像素值,记录顺序是在扫描行内是从左到右,扫描行之间是从下到上。位图的一个像素值所占的字节数:
当biBitCount=1时,8个像素占1个字节;
当biBitCount=4时,2个像素占1个字节;
当biBitCount=8时,1个像素占1个字节;
当biBitCount=24时,1个像素占3个字节;
Windows规定一个扫描行所占的字节数必须是4的倍数(即以long为单位),不足的以0填充,
一个扫描行所占的字节数计算方法:
DataSizePerLine= (biWidth* biBitCount+31)/8;
// 一个扫描行所占的字节数
DataSizePerLine= DataSizePerLine/4*4; // 字节数必须是4的倍数
位图数据的大小(不压缩情况下):
DataSize= DataSizePerLine* biHeight;
转灰度图重点
这里参考了文章:http://blog.sina.com.cn/s/blog_677a04530101244t.html
把24位真彩BMP图像转变成256阶灰度图的具体步骤如下:
(1) 修改信息头
信息头共有11部分,灰度化时需要修改两部分
bi2.biBitCount=8;
bi2.biSizeImage=( (bi.biWidth+3)/4 ) * 4bi.biHeight;
(2)修改文件头
文件头共有5部分,灰度化时需要修改两部分
bf2.bfOffBits = sizeof(bf2)+sizeof(BITMAPINFOHEADER)+256sizeof(RGBQUAD);
bf2.bfSize = bf2.bfOffBits + bi2.biSizeImage;
(3)创建调色板
RGBQUAD ipRGB2 = (RGBQUAD )malloc(256sizeof(RGBQUAD));
for ( i = 0; i < 256; i++ )
ipRGB2[i].rgbRed = ipRGB2[i].rgbGreen = ipRGB2[i].rgbBlue = i;
(4)修改位图数据部分
这部分主要是由原真彩图的rgbRed、rgbGreen、rgbBlue分量值得到灰度图像的灰度值Y,可以用下面公式得到:
Y=0.299rgbRed+0.587* rgbGreen+0.114*rgbBlue;
代码例程
代码参考了CSDN小伙伴 keep_moving_cqu的代码,对代码做了注释,并在最后释放了内存。试了下可用
https://blog.csdn.net/mydreamremindme/article/details/9909141
#include <iostream>