【学习图像处理】之实验一——处理BMP图片

处理BMP图片

    • BMP格式
    • 实验内容
    • 头文件分析
    • 第一部分:图像处理
      • 1、反白图像
      • 2、改变调色板的数值
      • 3、彩图变灰图
    • 第二部分:bmp2txt
      • 1、输出txt函数
      • 2、Excel作图
    • 结语

BMP格式

bmp是一种常见的未压缩图像格式,也是大多数图像处理入门课会用到的一种引路格式。具体的BMP图像格式解析请见:BMP图像格式详解

实验内容

  1. 反白图像
  2. 改变调色板的颜色值,看对图像的影响
  3. 将彩色图像变为灰度图像
  4. 将一灰度图像数据变为文本格式存入bmp.txt中,txt文件的一行对应图像文件的一行。将bmp.txt导入到excel中,并用至少三种可视化工具将其图形化显示。

老师已经给出了用于处理BMP格式的类的头文件以及部分函数实现代码(C/C++)。由于函数实现为老师的成果,所以我就不贴出代码了,不过我们可以一起分析一下头文件,看一看这个类有什么数据成员,能完成什么功能。

头文件分析

class BMPFILE {
     
	BYTE *Imagedata;					//位图数据域
public:
	int imagew, imageh;					//图片的宽度和高度
	int iYRGBnum;						//1:灰度,3:彩色
	RGBQUAD palette[256];				//调色板
	BYTE *pDataAt(int h, int Y0R0G1B2 = 0);	
	//指向图像第h行的位置,Y0R0G1B2表示:灰度(Y)=0,R=0,G=1,B=2
	BOOL AllocateMem();					//为图像分配内存
	BOOL LoadBMPFILE(const char *fname);//从硬盘加载图像
	BOOL SaveBMPFILE(const char *fname);//将图像保存至硬盘
	BMPFILE();							//构造函数,初始化
	~BMPFILE();							//析构函数
};

这里唯一需要解释的可能是pDataAt这个函数,尤其是形参中Y0R0G1B2这个让人摸不着头脑的命名。
首先说功能,这个函数的功能是获取第w列列首像素的位置,也就是求偏移量h=imageh*w,然后再用Imagedata+h去定位。
但是这里要补充一个点,那就是BMP数据在内存中的存储方式。我们知道,对24bit的彩图而言,在硬盘中每位像素会以BGR的格式排列,但这不意味着在内存中也是同样连续存储BGR的。相反,在内存中的真实状态是R、G、B被分别存储于3个矩阵中,因此我们要找到第h行的行首像素时,要分别找到其R、G、B的位置。因此实际偏移量应为:

h=imageh*w+Y0R0G1B2*imagew*imageh
// 当然也可以写作 int w = imageh * h + Y0R0G1B2 * imagew * imageh,则定位到h行,行首像素

第一部分:图像处理

我们把实验分为两部分,因为bmp2txt并用excel作图那部分卡了我很久,而前面那三个却很简单,我们就先说这简单的部分吧。

1、反白图像

这里所谓的反白其实就是反色,只是对于灰度图而言我们看到的效果是黑白颠倒。我们选用的颜色量级为0-255,因此反色就是用255减去当前的颜色。下面贴出部分代码(只给了大家灰度图的反白,如何做彩图的反色呢?留给读者自行解决)

void Reverse(BMPFILE &src,BMPFILE &des)	//反白函数
{
     
	for (int i = 0; i < src.imageh; i++)
		for (int j = 0; j < src.imagew; j++)
		{
     
			//反白图	
			des.pDataAt(i)[j] = 255 - src.pDataAt(i)[j];
		}
}

效果如下

原图:【学习图像处理】之实验一——处理BMP图片_第1张图片
反色效果:
【学习图像处理】之实验一——处理BMP图片_第2张图片

2、改变调色板的数值

在没有要求的情况下,随意修改即可,内容没什么可讲的那我们就来说说调色板吧。

typedef struct tagRGBQUAD {
      
  BYTE rgbBlue;
  BYTE rgbGreen;
  BYTE rgbRed;
  BYTE rgbReserved;
} RGBQUAD;

上面是RGBQUAD,也就是调色板所用的结构体的定义。可以看到每一个RGBQUAD中定义了蓝、绿、红和一个保留位。如果调色板中R=G=B,则显示的是灰度图;相反,如果RGB值各不相同,那么一共可以有256x256x256种颜色,不过注意,由于调色板是一个有256个RGBQUAD的数组,所以最终的图像最多也就256种颜色。对于rgbReserved这一项,stackoverflow上的这份问答大家可以读一下,相信你会有比较深刻的理解。What is rgbReserved?

3、彩图变灰图

彩图变灰图其实也没什么可说的,因为经过历代程序员的尝试我们已经有了非常完美的转换公式,Y=0.299R + 0.587G + 0.114B。我们的要求只是显示效果的彩转灰,如果真的是要变成灰度图,那么除了让像素颜色按照公式转换,我们还应该对信息头、文件头以及调色板作出修改,这里就不做了。

上个效果吧
原图:
【学习图像处理】之实验一——处理BMP图片_第3张图片
转灰效果图:
【学习图像处理】之实验一——处理BMP图片_第4张图片

第二部分:bmp2txt

1、输出txt函数

说实话这个把bmp数据存入txt的题目要求让我疑惑了很久,很长一段时间大概一天我都在修改类似下面的代码:

bool bmp2txt(const char *cFilename)
{
     
	FILE *fin,*fout;
	fin=fopen(cFilename, "r+b");
	fout = fopen("bmp.txt", "w+");
	int rc;
	unsigned char buf[1024];
	fseek(fin, sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER), SEEK_SET);
	while ((rc = fread(buf, sizeof(unsigned char), 1, fin)) != 0)
	{
     
		fwrite(buf, sizeof(unsigned char), rc, fout);
	}
	fclose(fin);
	fclose(fout);
}

为什么卡那么久,主要是这个代码如果把w+改成w+b,再把fseek删掉,写出的二进制文件改成bmp图像是可以正常显示的。这样就让我一度认为方法是没问题的,细节上出错了。包括改为fprintf(fout,"%4d",buf)试图转换为10进制输出也没有解决问题(输出的是重复值,原因未明)。
终于,我决定重新读题,然后我发现了这句话“txt 文件的一行对应图像的一行,按图像显示的顺序存储。
突然间我就悟了,下面这段正确代码很快就写出来了:

bool bmp2txt(const char *cFilename)	//将bmp数据转存入txt,路径为形参
{
     
	//将像素数据存入TXT文件。
	BMPFILE bmpfile;
	if (!bmpfile.LoadBMPFILE(cFilename))
		return false;
	FILE *outfile;
	if ((outfile = fopen("bmp.txt", "w+")) == NULL)
	{
     
		printf("ERROR\n");
		return false;
	}
	for (int i = 0; i < bmpfile.imageh; i++)	//按行写入
	{
     
		for (int j = 0; j < bmpfile.imagew; j++)
		{
     
			fprintf(outfile, "%4d", bmpfile.pDataAt(i)[j]);
		}
		fprintf(outfile, "\n");
	}
	fclose(outfile);
	return true;
}

//下面贴一下loadbmpfile的函数实现,毕竟挺重要的
//其实就是按照文件头、信息头、数据域一一读入
BOOL BMPFILE::LoadBMPFILE(const char *cFilename)
{
     
	FILE *f;
	if (strlen(cFilename) < 1)	//文件不存在
		return FALSE;
	f = fopen(cFilename, "r+b");	//以二进制只读打开
	if (f == NULL)
		return FALSE;
	BITMAPFILEHEADER fh;	//文件头
	BITMAPINFOHEADER ih;	//信息头
	fread(&fh, sizeof(BITMAPFILEHEADER), 1, f);		//从文件中读取文件头
	if (fh.bfType != 0x4d42)	//如果不是"BM"文件,直接退出
	{
     
		fclose(f);
		return FALSE;
	}
	fread(&ih, sizeof(BITMAPINFOHEADER), 1, f);		//从文件中读取信息头
	if ((ih.biBitCount != 8) && (ih.biBitCount != 24))	//如果不是8位灰度图,或24位彩图,则退出
	{
     
		fclose(f);
		return FALSE;
	}
	iYRGBnum = ih.biBitCount / 8;	//1为灰度图,3为彩图
	imagew = ih.biWidth;
	imageh = ih.biHeight;
	if (!AllocateMem())	//如果分配内存失败,退出
	{
     
		fclose(f);
		return FALSE;
	}
	if (iYRGBnum == 1)	//灰度图需要调色板
		fread(palette, sizeof(RGBQUAD), 256, f);
	fseek(f, fh.bfOffBits, SEEK_SET);	//指向像素数据包
	int w4b = (imagew * iYRGBnum + 3) / 4 * 4, i, j;	//w4b为像素数据包大小
	BYTE *ptr;
	ptr = new BYTE[w4b];
	if (ptr == NULL)
	{
     
		fclose(f);
		return FALSE;
	}
	if (iYRGBnum == 1)
	{
     
		for (i = imageh - 1; i >= 0; i--)	//从最下面开始往上拷贝字节
		{
     
			fread(ptr, w4b, 1, f);
			memmove(pDataAt(i), ptr, imagew);	//memmove(des,src,count)
		}
	}
	if (iYRGBnum == 3)
	{
     
		for (i = imageh - 1; i >= 0; i--)
		{
     
			fread(ptr, w4b, 1, f);
			for (j = 0; j < imagew; j++)	//分别读取R、G、B(注意磁盘里反储)
			{
     
				*(pDataAt(i, 0) + j) = *(ptr + j * 3 + 2);
				*(pDataAt(i, 1) + j) = *(ptr + j * 3 + 1);
				*(pDataAt(i, 2) + j) = *(ptr + j * 3 + 0);
			}
		}
	}
	delete[] ptr;
	fclose(f);
	return TRUE;
}

这告诉我们还是要认真读题啊。。。

2、Excel作图

这里只给出我认为有意义的一种可视化,因为老师说他只是让我们看看数据在不同表现形式下的效果,理解“有数就有图”。但实际上直方图、折线图都完全没有意义,下面介绍下色阶表示。
我们首先将bmp.txt导入到excel中穷人只能用wps了呜呜呜,如图:
【学习图像处理】之实验一——处理BMP图片_第5张图片
之后全选(ctrl+a),然后在开始——条件格式——色阶,随意选一个之后缩放到最小,就能看到神奇的画面:

当然这个灰度图的显示效果是自定义的,你只需要在其他规则中把最低值改为黑色,最高值改为白色即可。这个原理其实还是个调色板,按照数值的大小去显示介于最小值和最大值之间的一种颜色罢了。不过做出来的那一刻还是很有成就感的。

结语

这学期的第一次图像处理实验,说是三周内上交实际上已经给了快五周时间了,如果对已经给出的代码掌握熟练的话,其实一两天完成不是问题。完整项目工程可见我的gitee。

你可能感兴趣的:(学习图像处理,c++,c语言,图形学,可视化,计算机视觉)