位图(BitMap)是一种非常常见的图像类型,而且因为其失真率低,所以在机器视觉中经常会使用到它,因此需要对BitMap有更深一步的认识。
BitMap总的来说分为四个结构:位图文件头结构、位图信息头结构、颜色表和位图数据。
其中,位图文件头结构存储的是对图像文件类型的声明等规定化的内容,位图信息头结构则存储了关于位图的一些相关信息,颜色表是只在灰度位图文件中存在(彩色bmp图像中不存在颜色表),其表示位图灰度值的索引以及对应值,位图数据是存放真正图像数据的内存区。
下面通过C++实现对BitMap的读写与修改,首先包含头文件并定义一些通用的变量。
#include<Windows.h>
#include<stdlib.h>
#include<iostream>
#include<string>
#include<fileapi.h>
using namespace std;
unsigned char* pBmpBuf; //读入图像数据的指针
int bmpWidth; //图像的宽
int bmpHeight; //图像的高
RGBQUAD* pColorTable; //颜色表指针
int biBitCount; //图像类型,每像素位数
实现读取8位灰度bmp图像或彩色bmp图像的函数:
bool readBmp(string bmpName)
{
//二进制只读模式打开bmp文件
FILE* fp = fopen(bmpName.c_str(), "rb");
if (0 == fp)
{
return false;
}
//跳过位图文件头结构BITMAPFILEHEADER
fseek(fp, sizeof(BITMAPFILEHEADER), 0);
//定义位图信息头结构变量,读取位图信息头进内存,存放在变量head中
BITMAPINFOHEADER head;
fread(&head, sizeof(BITMAPINFOHEADER), 1, fp);
//获取图像宽、高、每像素位数等信息
bmpWidth = head.biWidth;
bmpHeight = head.biHeight;
biBitCount = head.biBitCount;
//计算图像每行像素所占的字节数(由于windows规定每行数据的字节数必须是4的倍数,如果不是4的倍数,则最大相差为3,所以+3来将每行数据的字节数补至最大差值,再整除4去掉多余字节数,最后乘上4得到4的倍数字节数
int lineByte = (bmpWidth * biBitCount / 8 + 3) / 4 * 4;
//如果读入的bmp图像为8位灰度图像
if (biBitCount == 8)
{
//创建颜色表
pColorTable = new RGBQUAD[256];
fread(pColorTable, sizeof(RGBQUAD), 256, fp);
}
//创建位图数据空间
pBmpBuf = new unsigned char[lineByte * bmpHeight];
fread(pBmpBuf, 1, lineByte * bmpHeight, fp);
fclose(fp);
return true;
}
实现保存8位灰度bmp图像或彩色bmp图像文件的函数:
bool saveBmp(string bmpName, unsigned char* imgBuf, int width, int height, int biBitCount, RGBQUAD* pColorTable)
{
//如果位图数据指针为0,则表示无数据
if (!imgBuf)
{
return false;
}
//定义颜色表字节大小,灰度图像颜色表为1024字节(256x3),彩色图像颜色表为0(无需颜色表)
int colorTableSize = 0;
if (biBitCount == 8)
{
colorTableSize = 1024;
}
//计算存储图像数据每行字节数
int lineByte = (width * biBitCount / 8 + 3) / 4 * 4;
//二进制只写模式打开文件
FILE* fp = fopen(bmpName.c_str(), "wb");
if (0 == fp)
{
return false;
}
//定义位图文件头结构变量,填写文件头信息
BITMAPFILEHEADER fileHead;
fileHead.bfType = 0x4D42; //规定bmp类型
//整个bmp图像文件的字节大小,即四个组成部分之和
fileHead.bfSize = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER) + colorTableSize + lineByte * height;
//windows保留字,为0即可
fileHead.bfReserved1 = 0;
fileHead.bfReserved2 = 0;
//bmp图像文件从起始位置到位图数据位置之间偏移的空间大小,即前三个组成部分所需空间之和
fileHead.bfOffBits = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER) + colorTableSize;
//将文件头写入文件
fwrite(&fileHead, 1, sizeof(BITMAPFILEHEADER), fp);
//创建位图信息头结构变量,填写信息头信息
BITMAPINFOHEADER infoHead;
infoHead.biBitCount = biBitCount; //每像素的位数
infoHead.biClrImportant = 0; //重要显示的颜色数量,为0表示所有颜色都重要
infoHead.biClrUsed = 0; //位图实际用到的颜色数,为0表示实际用到颜色数为2的biBitCount次幂
infoHead.biCompression = 0; //位图压缩类型,默认为不压缩(BI_RGB==0)
infoHead.biHeight = height;
infoHead.biWidth = width;
infoHead.biPlanes = 1; //目标设备级别,必须为1
infoHead.biSize = 40; //位图信息头结构长度,为40个字节
infoHead.biSizeImage = lineByte * height; //位图数据的字节大小
infoHead.biXPelsPerMeter = 0; //指定目标设备的水平分辨率(像素/米)
infoHead.biYPelsPerMeter = 0; //指定目标设备的垂直分辨率(像素/米)
//将位图信息头写进文件
fwrite(&infoHead, sizeof(BITMAPINFOHEADER), 1, fp);
//如果是8位灰度图像,则将颜色表写入文件
if (biBitCount == 8)
{
fwrite(pColorTable, sizeof(RGBQUAD), 256, fp);
}
//将位图数据写入文件
fwrite(imgBuf, height * lineByte, 1, fp);
fclose(fp);
return true;
}
至此,实现了对8位灰度bmp图像和彩色bmp图像的读写操作,接下来我们可以在main()函数中进行调用,并且将读入的bmp图像进行像素级别的操作,从而修改bmp图像中的部分像素值,并进行保存。
void main()
{
string readPath = "C:\\Users\\iamxb\\Desktop\\Visual.CPP图像处理\\Bitmap\\1.bmp";
readBmp(readPath);
cout << "width:" << bmpWidth << "\theight:" << bmpHeight << "\tbiBitCount:" << biBitCount << endl;
//遍历图像像素,将图像左下角1/4部分置为黑色
int lineByte = (bmpWidth * biBitCount / 8 + 3) / 4 * 4;
if (biBitCount == 8)
{
for (int i = 0; i < bmpHeight / 2; i++)
{
for (int j = 0; j < bmpWidth / 2; j++)
{
*(pBmpBuf + i * lineByte + j) = 0;
}
}
}
else if (biBitCount == 24)
{
for (int i = 0; i < bmpHeight / 2; i++)
{
for (int j = 0; j < bmpWidth / 2; j++)
{
for (int k = 0; k < 3; k++)
{
*(pBmpBuf + i * lineByte + 3 * j + k) = 0;
}
}
}
}
改变灰度图像的颜色表中blue分量的值
//if (biBitCount == 8)
//{
// for (int i = 0; i < 256; i++)
// {
// pColorTable[i].rgbBlue = 255 - pColorTable[i].rgbBlue;
// }
//}
string savePath = "C:\\Users\\iamxb\\Desktop\\Visual.CPP图像处理\\Bitmap\\1(copy).bmp";
saveBmp(savePath, pBmpBuf, bmpWidth, bmpHeight, biBitCount, pColorTable);
delete []pBmpBuf;
if (biBitCount == 8)
{
delete []pColorTable;
}
}
到这里就实现了对bmp图像的一些基本操作,注意这里仅仅针对于8位灰度图像和彩色图像进行操作,如果是对于其他位深度图像的操作则需要另外对程序进行修改。
注:本篇博客中的代码借鉴了《Visual C++ 数字图像处理》中的相关内容,并进行了小部分修改以及完善注释。
PS:本人的注释比较杂,既有自己的心得体会也有网上查阅资料时摘抄下的知识内容,所以如有雷同,纯属我向前辈学习的致敬,如果有前辈觉得我的笔记内容侵犯了您的知识产权,请和我联系,我会将涉及到的博文内容删除,谢谢!