1.灰度 BMP 图像的读写
2.彩色 BMP 图像的读写:
//自定义类型名称,eg:将unsigned short int 自定义为 WORD,为了与课件中类型匹配
typedef unsigned short int WORD;
typedef unsigned int DWORD;
typedef unsigned char BYTE;
typedef long LONG;
//图像文件头(14个字节)
typedef struct readBITMAPFILEHEADER
{
//WORD bfType; // 位图文件的类型,必须为BM(0-1字节)
DWORD bfSize; // 位图文件的大小,以字节为单位(2-5字节)
WORD bfReserved1; // 位图文件保留字,必须为0(6-7字节)
WORD bfReserved2; // 位图文件保留字,必须为0(8-9字节)
DWORD bfOffBits; // 位图数据的起始位置,以相对于位图偏移量表示(10-13字节)
} BITMAPFILEHEADER;
//位图信息头(40个字节)
typedef struct readBITMAPINFOHEADER
{
DWORD biSize; // 本结构所占用字节数(14-17字节)
LONG biWidth; // 位图的宽度,以像素为单位(18-21字节)
LONG biHeight; // 位图的高度,以像素为单位(22-25字节)
WORD biPlanes; // 目标设备的级别,必须为1(26-27字节)
WORD biBitCount; // 每个像素所需的位数,必须是1(双色),(28-29字节)
// 4(16色),8(256色)或24(真彩色)之一
DWORD biCompression; // 位图压缩类型,必须是 0(不压缩),(30-33字节)
// 1(BI_RLE8压缩类型)或2(BI_RLE4压缩类型)之一
DWORD biSizeImage; // 位图的大小,以字节为单位(34-37字节)
LONG biXPelsPerMeter; // 位图水平分辨率,每米像素数(38-41字节)
LONG biYPelsPerMeter; // 位图垂直分辨率,每米像素数(42-45字节)
DWORD biClrUsed; // 位图实际使用的颜色表中的颜色数(46-49字节)
DWORD biClrImportant; // 位图显示过程中重要的颜色数(50-53字节)
}BITMAPINFOHEADER;
//调色板(颜色表)
typedef struct readRGBQUAD
{
BYTE rgbBlue; // 蓝色的亮度(值范围为0-255)
BYTE rgbGreen; // 绿色的亮度(值范围为0-255)
BYTE rgbRed; // 红色的亮度(值范围为0-255)
BYTE rgbReserved; // 保留字,必须为0
} RGBQUAD;
把WORD bfType注释掉,这是因为C语言结构体Sizeof运算规则——整体大于部分之和,如果不单独将bfType在结构体外部读取,将导致读文件错位,即打印:sizeof(readBITMAPFILEHEADER)=16≠14
你需要单独在程序里将其读进来,如下所示:(这里仅仅告知,后续程序会写出来)
//如果不先读取bfType,根据C语言结构体Sizeof运算规则——整体大于部分之和,从而导致读文件错位
unsigned short fileType;
fread(&fileType, 1, sizeof(unsigned short), f_opener);
其中展示的数据并不是文件头和信息头的全部数据,但绝对多于作业要求的数据。
//展示图像文件头函数
void showBmpFileHead(BITMAPFILEHEADER p_FileHead)
{
printf("----------------位图文件头----------------\n");
printf("BMP文件大小:%dKB\n", FileHeader.bfSize/ 1024);
printf("位图保留字:%d\n", FileHeader.bfReserved1);
printf("位图保留字:%d\n", FileHeader.bfReserved2);
printf("位图数据的起始位置: %d\n", FileHeader.bfOffBits);
}
//展示图像信息头函数
void showBmpInfoHead(BITMAPINFOHEADER p_InfoHead)
{
printf("----------------位图信息头----------------\n");
printf("位图宽度:%d\n", InfoHeader.biWidth);
printf("位图高度:%d\n", InfoHeader.biHeight);
printf("每个像素的位数:%d\n", InfoHeader.biBitCount);
printf("压缩方式:%d\n", InfoHeader.biCompression);
printf("图像的大小:%d\n", InfoHeader.biSizeImage);
printf("水平方向分辨率:%d\n", InfoHeader.biXPelsPerMeter);
printf("垂直方向分辨率:%d\n", InfoHeader.biYPelsPerMeter);
printf("使用的颜色数:%d\n", InfoHeader.biClrUsed);
}
采用C/C++语言,借助部分内置函数,读取到bmp文件信息
//读入bmp文件,可以选择灰度,也可以选择真彩色
void readBmp(const char* bmp_ptah)
{
FILE *f_opener;
f_opener = fopen(bmp_ptah, "rb+");//打开一个二进制文件,文件必须存在,只允许读
if (f_opener == NULL)
{
printf("SaveFile:open failed");
}
//如果不先读取bfType,根据C语言结构体Sizeof运算规则——整体大于部分之和,从而导致读文件错位
unsigned short fileType;
fread(&fileType, 1, sizeof(unsigned short), f_opener);
if (fileType = 0x4d42)
{
printf("该文件是bmp格式文件!");
//19778,必须是BM字符串,对应的十六进制为0x4d42,十进制为19778,否则不是bmp格式文件
printf("\n文件标识符:%d\n", fileType);
fread(&FileHeader, 1, sizeof(BITMAPFILEHEADER), f_opener);
showBmpFileHead(FileHeader);
fread(&InfoHeader, 1, sizeof(BITMAPINFOHEADER), f_opener);
showBmpInfoHead(InfoHeader);
fclose(f_opener);
}
}
int main()
{
const char *bmp_path = "lena.bmp";//读入的灰度bmp原图
readBmp(bmp_path);//调用函数,打印信息
return 0;
}
为了验证输出信息的正确性,将lena.bmp文件转为二进制数据,进行验证。我采用UltraEdit软件,进行二进制数据的查看,经过对比,证明了输出数据的准确性。
这里需要说明的是,bmp图像的位图数据:也就是一个像素所表示的数据,一张图片的第一个像素点在坐下角,然后往右一个个存取,最后一个在右上角。
而raw则是正常的像素排列方式,因此需要将读到的bmp图像作镜像处理。
因此对直接读取到的bmp数据,存储到raw前,进行了如下的变换(我定义三个逐像素处理方式,使得最终得到的raw不是镜像翻转的)
void bmp2raw(const char *bmp_path, const char *raw_path)
{
FILE *f_opener;//定义文件指针,打开bmp文件
f_opener = fopen(bmp_path, "rb+");
FILE *f_saver;//定义文件指针,存储raw文件
f_saver = fopen(raw_path, "wb+");
unsigned short fileType;
fread(&fileType, 1, sizeof(unsigned short), f_opener);
fread(&FileHeader, 1, sizeof(BITMAPFILEHEADER), f_opener);
fread(&InfoHeader, 1, sizeof(BITMAPINFOHEADER), f_opener);
int width = InfoHeader.biWidth;//获得图像的宽度
int height = InfoHeader.biHeight;//获得图像的长度
if (InfoHeader.biBitCount == 8)
{
printf("8位图像!!!\n");
}
//开辟内存空间存放bmp源图像数据
unsigned char *PixDataBmp = (unsigned char *)malloc(height*width);
//初始化PixDataBmp,里面的内容是0,大小为height*width
memset(PixDataBmp, 0, height*width);
//开辟内存空间存储raw图像处理之后数据
unsigned char *PixDataRaw = (unsigned char *)malloc(height*width);
//初始化PixDataRaw,里面的内容是0,大小为height*width
memset(PixDataRaw, 0, height*width);
//开辟内存空间存储备份raw图像处理之后数据
unsigned char *PixDataRawClone = (unsigned char *)malloc(height*width);
//初始化PixDataRawClone,里面的内容是0,大小为height*width
memset(PixDataRawClone, 0, height*width);
long nData = height * width;//得到数据的长度
//把bmp位图数据信息读到数组里,这里面存的就是像素值,PixDataBmp类似于摊平的矩阵
fread(PixDataBmp, 1, nData, f_opener);
//将bmp的像素赋值放到raw里,但此时是左右镜像
for (int i =0; i < height; i++)
{
for (int j = 0; j < width; j++)
{
int index = i * width + j;
PixDataRaw[index] = PixDataBmp[nData - index];
}
}
//将bmp的像素赋值放到备份的raw里,但此时依然是左右镜像
for (int i = 0; i < height; i++)
{
for (int j = 0; j < width; j++)
{
int index = i * width + j;
PixDataRawClone[index] = PixDataBmp[nData - index];
}
}
//将备份的raw的左右两边的像素,分别赋值给将要存储的raw
for (int i = 0; i < height; i++) {
for (int j = 0; j < width; j++) {
int index = i * width + j; //像素索引
int exchange_x = width - j - 1; //水平镜像后的x坐标
int exchange_y = i; //水平镜像后的y坐标
PixDataRaw[exchange_y * width + exchange_x] = PixDataRawClone[index ];
PixDataRaw[index] = PixDataRawClone[exchange_y * width + exchange_x];
}
}
//将得到的数据存入raw中,此时将读到的bmp镜像回来,我们看到的就是正视图
fwrite(PixDataRaw, 1, nData, f_saver);
fclose(f_opener);//关闭文件指针
fclose(f_saver);
free(PixDataRaw);//释放堆空间内存
free(PixDataBmp);
}
int main()
{
//8位bmp转raw
const char *bmp_path = "lena.bmp";//读入的灰度bmp原图
const char *raw_path = "lena.raw";//生成的灰度raw格式图
bmp2raw(bmp_path, raw_path);//8位的bmp转raw
return 0;
}
即对原始bmp进行裁剪,其核心就是对像素处理时,需要限定迭代索引i和j的位置,整个裁剪函数的实现如下:
void GrayCutBmp(const char *bmp_path, const char *cutBmp_path)
{
BITMAPFILEHEADER writebitHead;
BITMAPINFOHEADER writebitInfoHead;
int Cut_height = 256;//想要截取的长度
int Cut_width = 256;//想要截取的宽度
int x_left = 0;//想要截取的左上角坐标x值
int y_left = 0;//想要截取的左上角坐标y值
FILE *f_opener;
f_opener = fopen(bmp_path, "rb+");
FILE *f_saver;
f_saver = fopen(cutBmp_path, "wb+");
//读取位图文件头信息
unsigned short fileType;
fread(&fileType, 1, sizeof(unsigned short), f_opener);
fwrite(&fileType, 1, sizeof(unsigned short), f_saver);
if (fileType != 0x4d42)
{
printf("该文件不是bmp格式文件!");
return;
}
//读取位图文件头信息
fread(&FileHeader, 1, sizeof(BITMAPFILEHEADER), f_opener);
writebitHead = FileHeader;//由于截取图像头和源文件头相似,所以先将源文件头数据赋予截取文件头
//读取位图信息头信息
fread(&InfoHeader, 1, sizeof(BITMAPINFOHEADER), f_opener);
writebitInfoHead = InfoHeader;//同位图文件头相似
writebitInfoHead.biHeight = Cut_height;//为截取文件重写位图高度
writebitInfoHead.biWidth = Cut_width;//为截取文件重写位图宽度
int mywritewidth = writebitInfoHead.biWidth;
writebitInfoHead.biSizeImage = mywritewidth * writebitInfoHead.biHeight;//计算位图实际数据区大小
writebitHead.bfSize = 54 + writebitInfoHead.biSizeImage;//位图文件头大小为位图数据区大小加上54byte
fwrite(&writebitHead, 1, sizeof(BITMAPFILEHEADER), f_saver);//写回位图文件头信息到输出文件
fwrite(&writebitInfoHead, 1, sizeof(BITMAPINFOHEADER), f_saver);//写回位图信息头信息到输出文件
RGBQUAD wRggList[256];//8bit的有颜色表,所以要读进来,写出去
fread(&wRggList[0], sizeof(RGBQUAD), 256, f_opener);
fwrite(&wRggList[0], sizeof(RGBQUAD), 256, f_saver);
int width = InfoHeader.biWidth;
int height = InfoHeader.biHeight;
unsigned char *pColorData = (unsigned char *)malloc(height*width);//开辟内存空间存储图像数据
memset(pColorData, 0, height*width);
unsigned char *pColorDataWrite = (unsigned char *)malloc(mywritewidth*Cut_height);//开辟内存空间存储图像处理之后数据
memset(pColorDataWrite, 0, mywritewidth*Cut_height);
long nData = height * width;
long write_nData = mywritewidth * Cut_height;//截取的位图数据区长度定义
//把位图数据信息读到数组里
fread(pColorData, 1, nData, f_opener);//图像处理可通过操作这部分数据加以实现
//由于BMP图像的数据存储格式起点是图像的左下角,所以需要进行坐标换算操作
for (int i = height - y_left - Cut_height; i < height - y_left; i++)
{
for (int j = 0; j < x_left + Cut_width; j++)
{
int index = i * width + j;//像素索引
int write_index = (i - height + y_left + Cut_height)*mywritewidth + (j - x_left);
pColorDataWrite[write_index] = pColorData[index];
}
}
fwrite(pColorDataWrite, 1, write_nData, f_saver); //将处理完图像数据区写回文件
fclose(f_opener);//释放文件指针
fclose(f_saver);
free(pColorData);//回收内存
free(pColorDataWrite);
}
int main()
{
//8位bmp图像的裁剪
const char *bmp_path = "lena.bmp";//读入的灰度bmp原图
const char *cutBmp_path = "lenas.bmp";//裁剪之后的图像
GrayCutBmp(bmp_path, cutBmp_path);
return 0;
}
与读入lena.bmp文件类似,这里我将其读入文件的功能封装成了一个函数,无论是读入灰度图还是真彩色图,直接传入路径即可完成文件的读入。文件头、信息头等结构体数据与第一问定义相同,这里不再做展示。
只需要将图片名字更改一下即可
int main()
{
const char *bmp_path = "lena_C.bmp";//读入的彩色bmp原图
readBmp(bmp_path);//调用函数,打印信息
return 0;
}
由于读入的lena_C.bmp是彩色图像,涉及到通道数的增加,由原来的单通道增加至三通道,因此代码实现与灰度bmp不同。同时bmp赋值给raw时需要将三通道都赋值,由于BMP的RGB和raw的BGR的不同,通道传输需要更改,否则得到的图像偏蓝色。
下图展示了彩色bmp读取像素值的示意图,存放在一个向量里,每一个像素值的三个通道分量依次排列
//24位图像bmp转raw
void Cbmp2Craw(const char *bmp_path, const char *raw_path)
{
FILE *f_opener;
f_opener = fopen(bmp_path, "rb+");
FILE *f_saver;
f_saver = fopen(raw_path, "wb+");
unsigned short fileType;
fread(&fileType, 1, sizeof(unsigned short), f_opener);
fread(&FileHeader, 1, sizeof(BITMAPFILEHEADER), f_opener);
fread(&InfoHeader, 1, sizeof(BITMAPINFOHEADER), f_opener);
//fwrite(&fileType, sizeof(fileType), 1, f_saver);//位图类型单独读取
//fwrite(&FileHeader, sizeof(BITMAPFILEHEADER), 1, f_saver);//文件头
//fwrite(&InfoHeader, sizeof(BITMAPINFOHEADER), 1, f_saver);//信息头
printf("24位图像!!!\n");
int width = InfoHeader.biWidth;
int height = InfoHeader.biHeight;
//三个通道横着拼在一起,width就变成了512*3=1536
//int real_width = width * 3;
int real_width = WIDTHBYTES(width* InfoHeader.biBitCount);//计算位图的实际宽度并确保它为4byte的倍数
//开辟内存空间存放bmp源图像数据
unsigned char *PixDataBmp = (unsigned char *)malloc(height*real_width);
//初始化pColorData,里面的内容是0,大小为height*real_width
memset(PixDataBmp, 0, height*real_width);
//开辟内存空间存储raw图像处理之后数据
unsigned char *PixDataRaw = (unsigned char *)malloc(height*real_width);
//初始化pColorData,里面的内容是0,大小为height*real_width
memset(PixDataRaw, 0, height*real_width);
//开辟内存空间存储raw图像处理之后数据
unsigned char *PixDataRawClone = (unsigned char *)malloc(height*real_width);
//初始化pColorData,里面的内容是0,大小为height*real_width
memset(PixDataRawClone, 0, height*real_width);
long nData = height * real_width;
//把bmp位图数据信息读到数组里,这里面存的就是像素值,pColorData类似于摊平的矩阵
fread(PixDataBmp, 1, nData, f_opener);
for (int i = 0; i < height; i++)
{
for (int j = 0; j < width; j++)
{
//数组位置偏移量,对应于图像的各像素点RGB的起点 i * real_width的作用相当于另起一行
int pixel_index = i * real_width + j * 3;
PixDataRaw[pixel_index+2] = PixDataBmp[nData-pixel_index];
PixDataRaw[pixel_index + 1] = PixDataBmp[nData-pixel_index + 1];
PixDataRaw[pixel_index] = PixDataBmp[nData-pixel_index + 2];
}
}
//clone数据PixDataRaw
for (int i = 0; i < height; i++)
{
for (int j = 0; j < width; j++)
{
//数组位置偏移量,对应于图像的各像素点RGB的起点 i * real_width的作用相当于另起一行
int pixel_index = i * real_width + j * 3;
PixDataRawClone[pixel_index + 2] = PixDataBmp[nData - pixel_index];
PixDataRawClone[pixel_index + 1] = PixDataBmp[nData - pixel_index + 1];
PixDataRawClone[pixel_index] = PixDataBmp[nData - pixel_index + 2];
}
}
for (int i = 0; i < height; i++)
{
for (int j = 0; j < width; j++)
{
int index = i * real_width + j * 3;
int exchange_x = width - j - 1;
int exchange_y = i;
for (int channels = 0; channels < 3; channels++)
{
PixDataRaw[exchange_y * real_width + exchange_x * 3 + channels] = PixDataRawClone[index + channels] ;
PixDataRaw[index + channels] = PixDataRawClone[exchange_y * real_width + exchange_x * 3 + channels];
}
}
}
fwrite(PixDataRaw, 1, nData, f_saver);//将获取到的像素值写入raw文件
fclose(f_opener);
fclose(f_saver);
free(PixDataRaw);
free(PixDataBmp);
}
与灰度bmp实现相似,不同之处在彩色图是三通道,需要BGR三个通道操作,而灰度图只需要操作一个通道。
//24位bmp裁剪为1/4
void ColorCutBmp(const char *bmp_path, const char *cutBmp_path)
{
BITMAPFILEHEADER writebitHead;
BITMAPINFOHEADER writebitInfoHead;
int Cut_height = 256;
int Cut_width = 256;
int x_left = 0;
int y_left = 0;
FILE *f_opener;
f_opener = fopen(bmp_path, "rb+");
FILE *f_saver;
f_saver = fopen(cutBmp_path, "wb+");
//读取位图文件头信息
unsigned short fileType;
fread(&fileType, 1, sizeof(unsigned short), f_opener);
fwrite(&fileType, 1, sizeof(unsigned short), f_saver);
if (fileType != 0x4d42)
{
printf("该文件不是bmp格式文件!");
return;
}
//读取位图文件头信息
fread(&FileHeader, 1, sizeof(BITMAPFILEHEADER), f_opener);
writebitHead = FileHeader;//由于截取图像头和源文件头相似,所以先将源文件头数据赋予截取文件头
//读取位图信息头信息
fread(&InfoHeader, 1, sizeof(BITMAPINFOHEADER), f_opener);
writebitInfoHead = InfoHeader;//同位图文件头相似
writebitInfoHead.biHeight = Cut_height;//为截取文件重写位图高度
writebitInfoHead.biWidth = Cut_width;//为截取文件重写位图宽度
int mywritewidth = WIDTHBYTES(writebitInfoHead.biWidth*writebitInfoHead.biBitCount);//BMP图像实际位图数据区的宽度为4byte的倍数,在此计算实际数据区宽度
writebitInfoHead.biSizeImage = mywritewidth * writebitInfoHead.biHeight;//计算位图实际数据区大小
writebitHead.bfSize = 54 + writebitInfoHead.biSizeImage;//位图文件头大小为位图数据区大小加上54byte
fwrite(&writebitHead, 1, sizeof(BITMAPFILEHEADER), f_saver);//写回位图文件头信息到输出文件
fwrite(&writebitInfoHead, 1, sizeof(BITMAPINFOHEADER), f_saver);//写回位图信息头信息到输出文件
int width = InfoHeader.biWidth;
int height = InfoHeader.biHeight;
//分配内存空间把源图存入内存
int l_width = WIDTHBYTES(width*InfoHeader.biBitCount);//计算位图的实际宽度并确保它为4byte的倍数
int write_width = WIDTHBYTES(writebitInfoHead.biWidth*writebitInfoHead.biBitCount);//计算写位图的实际宽度并确保它为4byte的倍数
unsigned char *pColorData = (unsigned char *)malloc(height*l_width);//开辟内存空间存储图像数据
memset(pColorData, 0, height*l_width);
unsigned char *pColorDataWrite = (unsigned char *)malloc(mywritewidth*Cut_height);//开辟内存空间存储图像处理之后数据
memset(pColorDataWrite, 0, mywritewidth*Cut_height);
long nData = height * l_width;
long write_nData = mywritewidth * Cut_height;//截取的位图数据区长度定义
//把位图数据信息读到数组里
fread(pColorData, 1, nData, f_opener);//图像处理可通过操作这部分数据加以实现
//由于BMP图像的数据存储格式起点是图像的左下角,所以需要进行坐标换算操作
for (int i = height - y_left - Cut_height; i < height - y_left; i++)
{
for (int j = 0; j < x_left + Cut_width; j++)
{
//24位真彩图像素赋值格式
int index = i * l_width + j * 3;//数组位置偏移量,对应于图像的各像素点RGB的起点
int write_index = (i - height + y_left + Cut_height)*mywritewidth + (j - x_left) * 3;
pColorDataWrite[write_index] = pColorData[index];
pColorDataWrite[write_index + 1] = pColorData[index + 1];
pColorDataWrite[write_index + 2] = pColorData[index + 2];//不要转换通道,因为这不是转为raw
}
}
fwrite(pColorDataWrite, 1, write_nData, f_saver); //将处理完图像数据区写回文件
fclose(f_opener);
fclose(f_saver);
free(pColorData);
free(pColorDataWrite);
}
利用C语言读取BMP文件
C/C++ BMP(24位真彩色)图像处理(1)------图像の打开与数据区处理
C++实现BMP转RAW