前提知识

用十六进制查看图像文件需要注意:

beyond compare/notpad++查看图像的十六进制文件,数值数据是小端模式存放的二进制和数据在内存中的表现一致,只是大于1字节的数据在内存中赋值(通过结构体赋值也是一样的)给相应的整型时,不用大小端转换,赋值后会直接得到整型的结果。

1. 什么是灰度图?

灰度图的RGB值相等
灰度图调色板的值就是ARGB 205,0,0,0到205,255,255,255的像素值,灰度图就是黑白两色在深度上面的变化256种黑白灰度颜色,不同于单纯黑白两色。灰度图的位图数据部分存放的是灰度图调色板的索引。
在非图像学术领域,灰度图的照片,灰度图的电影,也叫黑白照片和黑白电视,黑白电影。

一般使用8位的灰度图,但是医学,航拍中需要更高的精度,而采用16位灰度图像。

2.灰度图的作用?


灰度图是只含有黑白颜色,和0~255亮度等级的图片。灰度图具有 存储小,其亮度值就是256色调色板索引号, 从整幅图像的整体和局部的色彩以及亮度等级分布特征来看,灰度图描述与彩色图的描述是一致的特点。因此很多真彩色图片的分析,第一步就是转换为灰度图,然后再进行分析。


真彩色,因为是24位,2(^8) * 2(^8) * 2(^8) = 16777216种颜色,需要调色板16777216 * 4byte字节的空间也就是64MB的调色板空间,所以真彩色是不用调色板的。


例如视频目标跟踪和识别时,第一步就是要转换为灰度图。现有的成熟分析算法多是基于灰度图像的,灰度图像综合了真彩色位图的RGB各通道的信息。


3.真彩色图片转换为灰度图的常用方法?


第一种方法是根据YUV的颜色空间中,Y的分量的物理意义是点的亮度,由该值反映亮度等级,根据RGB和YUV颜色空间的变化关系可建立亮度Y与R、G、B三个颜色分量的对应:Y=0.3R+0.59G+0.11B,以这个亮度值表达图像的灰度值。

第二种方法使求出每个像素点的R、G、B三个分量的平均值,然后将这个平均值赋予给这个像素的三个分量。

具体这两种方法,根据什么应用更应该选择哪一种方法,我还不了解,知道的麻烦告诉我下,非常感激。

win32下代码实现例子:


#include <stdio.h>
#include <string>
#include <math.h>
#include <windows.h>
using namespace std;

// 灰度图公式函数指针,缺点是函数调用降低了执行效率,优点是可以灵活的选择灰化公式
typedef int (*GrayFunction)(int nRed, int nGreen, int nBlue);

// Gray = R*0.3+G*0.59+B*0.11
int RegularGray(int nRed, int nGreen, int nBlue)
{
	// 转换为整型和位运算除法,可以更有效的提高效率
	float fGray = /*float(*/0.3f * nRed + 0.59f * nGreen + 0.11f * nBlue;
	return int(fGray);
}

// Gray=(R+G+B)/3;
int AverageGray(int nRed, int nGreen, int nBlue)
{
	float fGray = float(nRed + nGreen + nBlue) / 3;
	return int(fGray);
}

//将位图转换为256色灰度图
void ToGray(const string& srcFile,const string& desFile, GrayFunction func)
{
	BITMAPFILEHEADER bmfHeader;
	BITMAPINFOHEADER bmiHeader;

	FILE *pFile;
	if ((pFile = fopen(srcFile.c_str(),"rb")) == NULL)
	{
		printf("open bmp file error.");
		exit(-1);
	}
	//读取文件和Bitmap头信息
	fseek(pFile,0,SEEK_SET);
	fread(&bmfHeader,sizeof(BITMAPFILEHEADER),1,pFile);
	fread(&bmiHeader,sizeof(BITMAPINFOHEADER),1,pFile);
	//先不支持16位位图
	int bitCount = bmiHeader.biBitCount;
	if (bitCount == 16)
	{
		exit(-1);
	}
	double byteCount = (double)bitCount / 8;
	int nClr = 0;
	if (bitCount < 16)
	{        
		nClr = bmiHeader.biClrUsed ? bmiHeader.biClrUsed : 1 << bitCount;
		if (nClr > 256)
			nClr = 0;        
	}
	//读取调色板
	RGBQUAD *quad = NULL;
	if (nClr > 0)
	{
		quad = new RGBQUAD[nClr];
		fread(quad,sizeof(RGBQUAD) * nClr,1,pFile);
	}

	int srcW = bmiHeader.biWidth;
	int srcH = bmiHeader.biHeight;
	//原图像每一行去除偏移量的字节数
	int lineSize = bitCount * srcW >> 3;
	//偏移量,windows系统要求每个扫描行按四字节对齐
	// 数n加上一个数r-1,又与上非r-1,其实是求得数n加上足够的偏移后[n, n+r-1]内的关于r的唯一倍数k。
	// 数k是数n不经过填充或者经过最小填充后的是r的倍数。
	// alignBytes是不用填充或者填充后的,相对于原来的数,填充的字节数。
	int alignBytes = (((bmiHeader.biWidth * bitCount + 31) & ~31) >> 3)
		- ((bmiHeader.biWidth * bitCount) >> 3);
	//原图像缓存    
	int srcBufSize = lineSize * srcH;
	BYTE* srcBuf = new BYTE[srcBufSize];
	int i,j;
	//读取文件中数据
	for (i = 0; i < srcH; i++)
	{        
		// 按照BYTE读取进来,也就是BGRA形式读取进来到内存里面了。
		fread(&srcBuf[lineSize * i],lineSize,1,pFile);
		fseek(pFile,alignBytes,SEEK_CUR);
	}
	//256色位图调色板
	RGBQUAD testData,*pTestData = new RGBQUAD;// RGBQUAD结构体默认构造函数是给每个通道赋值204,new时候是给每个分量205.
	RGBQUAD *quadDes = NULL;
	quadDes = new RGBQUAD[256];
	for (i = 0; i < 256; i++)
	{
		//灰度图的RGB值相等
		// 灰度图调色板的值就是ARGB 205,0,0,0到205,255,255,255的像素值,灰度图就是黑白两色在深度上面的变化256种,不同于单纯黑白两色。
		// 在非图像学术领域,灰度图的照片,灰度图的电影,也叫黑白照片和黑白电视,黑白电影。
		quadDes[i].rgbBlue = quadDes[i].rgbGreen = quadDes[i].rgbRed = i; 
		testData = quadDes[i];
		//printf("testData: %d: %d: %d: %d\n",i,i,i,quadDes[i].rgbReserved);
	}
	delete pTestData;
	//灰度图每个像素采用8位表示,每行对齐的字节数(包括对齐填充字节),window需要按照4字节对齐。
	int nLineByteCountIncludeAlign = (((srcW * 8 + 31) & ~31) >> 3);

	// 高度也是一个像素一个字节,所以desBufSize是总的图片位图数据字节数
	int desBufSize = nLineByteCountIncludeAlign * srcH;
	BYTE *desBuf = new BYTE[desBufSize];
	//每个扫描行占用字节数
	int desLineSize = nLineByteCountIncludeAlign/*((srcW * 8 + 31) >> 5) * 4*/;

	for (i = 0; i < srcH; i++)
	{
		for (j = 0; j < srcW; j++)
		{
			//从调色板中读取RGB值
			if (nClr > 0)
			{
				// 获得位图数据表示的调色板索引值
				unsigned int pos = srcBuf[i * lineSize + int(j * byteCount)];
				
				// 根据调色板索引到调色板取位图像素
				desBuf[i * desLineSize + j] = func( quad[pos].rgbRed, quad[pos].rgbGreen, quad[pos].rgbBlue );
			}
			else
			{
				// 直接从真彩色的位图数据中取得像素转换为灰度图索引
				//srcBuf是BGRA方式将位图数据读取到内存里面去了
				desBuf[i * desLineSize + j] = func( srcBuf[i * lineSize + int(j * byteCount) + 2] , \
					srcBuf[i * lineSize + int(j * byteCount) + 1], \
					srcBuf[i * lineSize + int(j * byteCount)] );
				//printf("PixelIndexData: %d\n",desBuf[i * desLineSize + j]);
			}
		}
	}

	//创建目标文件
	HFILE hfile = _lcreat(desFile.c_str(),0);    
	//文件头信息
	BITMAPFILEHEADER nbmfHeader;    
	nbmfHeader.bfType = 0x4D42;
	nbmfHeader.bfSize = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER)
		+ 256 * sizeof(RGBQUAD) + srcW * srcH;
	nbmfHeader.bfReserved1 = 0;
	nbmfHeader.bfReserved2 = 0;
	nbmfHeader.bfOffBits = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER) + 256 * sizeof(RGBQUAD);
	//Bitmap头信息
	BITMAPINFOHEADER   bmi; 
	bmi.biSize=sizeof(BITMAPINFOHEADER); 
	bmi.biWidth=srcW; 
	bmi.biHeight=srcH; 
	bmi.biPlanes=1; 
	bmi.biBitCount=8; 
	bmi.biCompression=BI_RGB; 
	bmi.biSizeImage=0; 
	bmi.biXPelsPerMeter=0; 
	bmi.biYPelsPerMeter=0; 
	bmi.biClrUsed= 256; 
	bmi.biClrImportant=0; 

	//写入文件头信息
	_lwrite(hfile,(LPCSTR)&nbmfHeader,sizeof(BITMAPFILEHEADER));
	//写入Bitmap头信息
	_lwrite(hfile,(LPCSTR)&bmi,sizeof(BITMAPINFOHEADER));
	if (quadDes)
	{
		_lwrite(hfile,(LPCSTR)quadDes,sizeof(RGBQUAD) * 256);
	}
	//写入图像数据
	_lwrite(hfile,(LPCSTR)desBuf,desBufSize);
	_lclose(hfile);
	if (quad)
	{
		delete[] quad;
		quad = NULL;
	}
	if (quadDes)
	{
		delete[] quadDes;
		quadDes = NULL;
	}
}

int main(int argc, char* argv[])
{
	string srcFile("f://data//apple.bmp");
	string desFile("f://data//applegray2.bmp");
	ToGray(srcFile,desFile, AverageGray/*RegularGray*/);
	system("pause");
	return 0;
}