直方图处理

前言

图片增强主要是处理目标图片,从而达到处理结果的图片比原图片更适合特定的应用。

而在各种各样的图片处理当中,就有一些是专门处理灰度图的。

我们可以利用图片的像素亮度(灰度级别)看成是一个随机变量,其分布的情况就反应了图片的特征。我们可以使用Probability Density Function(PDF)来刻画与描述,表现为灰度直方图

直方图统计

灰度直方图是灰度级函数,他表现为图像当中某种灰度的像素个数,反应了图像中每种灰度的出现频率
S r = P ( r ) S_r = P(r) Sr=P(r)
其中S为第r级灰度的灰度频率

算法

假定图像有L级灰度,大小为P = M x N,各像素的灰度为f(x,y),pBuffer[k]为各像素的灰度的量

step1(初始化) : pBuffer[k] = 0 (k = 0,1,2....,M×N)

step2(统计) : pBuffer[f(x,y)]++
    
step3(归一化) : pBuffer[k] /= MxN(k = 0,1,2,....,MxN)

第三步归一化操作是可选的。

实现

实现这个直方图统计并不复杂,操作也一样是读取BITMAP文件(注意跳字节),提取出自己有用的部分,然后再进行统计

在这里我先定义了两个数据结构以便于之后的操作

typedef struct ImageData
{
    BITMAPFILEHEADER fileHeader;
	BITMAPINFOHEADER infoHeader;
	RGBQUAD rgbquad[256];
	BYTE * img;
	int length;
	int width, height;
}IMGDATA;

typedef struct GrayHistogram
{
	float gray[256] = {0};
	int pixelCount = 0;
	void normalize();
	void draw();

private :
	bool isNormalize = false;
}GRAYHISTOGRAM;

然后就是对BITMAP文件的读取

IMGDATA loadImage(const std::string& path)
{
	std::ifstream ifstream;
	ifstream.open(path, std::ios::binary);
	if (!ifstream.is_open())
		return {};

	BITMAPFILEHEADER fileHeader;
	BITMAPINFOHEADER infoHeader;
	RGBQUAD rgbquad[256];

	ifstream.read(reinterpret_cast<char*>(&fileHeader), sizeof(BITMAPFILEHEADER));
	ifstream.read(reinterpret_cast<char *>(&infoHeader), sizeof(BITMAPINFOHEADER));
	
	ifstream.read(reinterpret_cast<char *>(&rgbquad), sizeof(RGBQUAD) * infoHeader.biClrUsed);

	BYTE *img = new BYTE[infoHeader.biSizeImage];
	ifstream.read(reinterpret_cast<char*>(img), infoHeader.biSizeImage);

	IMGDATA imgdate;
	imgdate.infoHeader = infoHeader;
	imgdate.fileHeader = fileHeader;
	for(int i = 0;i < 256;i++)
	{
		imgdate.rgbquad[i] = rgbquad[i];
	}
	imgdate.pImg = img;
	imgdate.length = infoHeader.biSizeImage;
	imgdate.width = infoHeader.biWidth;
	imgdate.height = infoHeader.biHeight;

	ifstream.close();
	return imgdate;
}

解析读取到的信息,并进行分析,最后保存到GrayHistogram当中

GRAYHISTOGRAM getHistogram(const IMGDATA data)
{
	GRAYHISTOGRAM grayhistogram;
	int point = 0;
	for (int i = 0; i < data.height; i++)
	{
		for(int j = 0;j < data.width;j++)
		{
			grayhistogram.gray[data.img[point++]]++;
		}

		while (point % 4 != 0)
			point++;
	}

	grayhistogram.pixelCount = data.width * data.width;
	return grayhistogram;
}

最后一步就是归一化以及画出来了,由于归一化是可选的,因此这个也可以不进行归一化,当然,无论有没有归一化都是可以表现图片的特征的

//归一化
void GrayHistogram::normalize()
{
	if(isNormalize)
		return;
		
	for (float& i : gray)
	{
		i = i / pixelCount;
	}

	isNormalize = true;
}

输出直方图

之后,我们就需要输出直方图了。由于直方图是一个在图像处理当中比较重要的环节,因此,我们需要输出一个比较好看的直方图,因此,我选择将直方图输出到图片当中。

这个图片的大小为256 x 256,因此图片的u轴就是相当于横坐标,v轴相当于纵坐标,然后,由白色的像素组成直方图,其余部分用黑色像素显示,从而达到了输出一张好看的直方图的目的。

因此,我们需要利用数据源的图片构建一个直方图信息以及一张新的图片。

构建一张新的图片并不难,只要将信息按照BITMAP的格式输入就可以了,而直方图信息的建立刚刚也说了,因此,我们现在就可以建立一个输出直方图的图片了。

//由于可以使用原图本身的信息,因此可以省略一些填写
void outputHistogram(const IMGDATA data,const std::string& path)
{
	IMGDATA newData = data;
	GRAYHISTOGRAM histogram = getHistogram(data);

	// newData.fileHeader.bfType = 0x4d42;
	// newData.fileHeader.bfReserved1 = 0;
	// newData.fileHeader.bfReserved2 = 0;
	newData.fileHeader.bfSize = sizeof(BITMAPINFOHEADER) + sizeof(BITMAPINFOHEADER) + sizeof(RGBQUAD) * 2 + 256 * 256;
    newData.fileHeader.bfOffBits = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER) + sizeof(RGBQUAD) * 2;
 //
	// newData.infoHeader.biSize = sizeof(BITMAPINFOHEADER);
	// newData.infoHeader.biPlanes = 1;
	 newData.infoHeader.biBitCount = 8;
	 newData.infoHeader.biClrUsed = 2;
	//newData.infoHeader.biCompression = BI_RGB;
	newData.infoHeader.biSizeImage = 256 * 256;
	newData.infoHeader.biHeight = 256;
	newData.infoHeader.biWidth = 256;
	// newData.infoHeader.biClrImportant = data.infoHeader.biClrImportant;
	// newData.infoHeader.biXPelsPerMeter = data.infoHeader.biXPelsPerMeter;
	// newData.infoHeader.biYPelsPerMeter = data.infoHeader.biYPelsPerMeter;

	newData.pImg = new BYTE[256 * 256]{ 0 };

	histogram.normalize();

	 RGBQUAD white;
	 white.rgbReserved = 0;
	 white.rgbRed = 255;
	 white.rgbBlue = 255;
	 white.rgbGreen = 255;
 
	 RGBQUAD black;
	 black.rgbReserved = 0;
	 black.rgbRed = 0;
	 black.rgbBlue = 0;
	 black.rgbGreen = 0;
 
	 newData.rgbquad[0] = black;
	 newData.rgbquad[1] = white;

	for(int i = 0 ;i < 256;i++)
	{		
		int length = histogram.gray[i] * 255 * 25;
		if (length > 255)
			length = 255;
		for(int j = 0;j < length;j++)
		{
			newData.pImg[j * 256 + i] = 1;
		}
	}

	newData.length = 256 * 256;
	newData.width = 256;
	newData.height = 256;

	output(newData, 2, path);
}

void output(IMGDATA data,int clrUsed,const std::string& path)
{
	std::ofstream out;
	out.open(path, std::ios::out | std::ios::trunc | std::ios::binary);
	if (!out.is_open())
		return;

	std::cout << "output " << path << "...." << std::endl;
	out.write(reinterpret_cast<char *>(&data.fileHeader), sizeof(BITMAPFILEHEADER));
	out.write(reinterpret_cast<char *>(&data.infoHeader), sizeof(BITMAPINFOHEADER));
	out.write(reinterpret_cast<char *>(&data.rgbquad), clrUsed * sizeof(RGBQUAD));
	out.write(reinterpret_cast<char *>(data.pImg), data.length);

	out.close();
}

此时,直方图的信息统计就完成了。就可以进行下一步的操作了。

直方图均衡化

直方图均衡化,指的是,对图片的直方图进行均衡化处理,也就是对图片的灰度级数进行均衡,直观的从图片上来说,就是图片的对比度更加的明显。因为,在这种情况下,原图片的灰度级数摊到了其他的级数上去了。

在直方图均衡化当中,均衡化函数满足以下特点
s = T ( r ) 0 ≤ r ≤ 1 , 0 ≤ T ( r ) ≤ 1 s = T(r)\qquad 0\leq r\leq1,0\leq T(r)\leq 1 s=T(r)0r10T(r)1
且T( r )在区间内为单值且单调递增

累积函数分布方法(DCF)

累积函数分布方法是一种比较经典的直方图均衡化方法,他的公式表现为
s = T ( r ) = ∫ 0 r p r ( w ) d w s = T(r) = \int_0^rp_r(w)dw s=T(r)=0rpr(w)dw
也就是对于每一处的灰度分布概率都进行积分,由于概率总和为1,因此,T( r )自然也是在0与1之间了,而且也必然是单调递增且单值的。

利用这个函数,可以将直方图当中的灰度信息进行均衡,灰度集中的地方自然会更加显眼,而本身没什么刻画的地方也会更加的白,因此对比度自然提上来了。

当然,由于我们的图片的灰度级别是离散的,因此,这个函数应该也是离散的,所以他的离散形式为
S k = T ( r k ) = ∑ j = 0 k p r ( r j ) = ∑ j = 0 k n j n S_k = T(r_k)=\sum_{j=0}^kp_r(r_j)=\sum_{j=0}^k\frac{n_j}{n} Sk=T(rk)=j=0kpr(rj)=j=0knnj
均衡化之后,每一个点的灰度级别都是自己的灰度级数的概率的积分。

算法实现

算法的实现比较简单,按照之前写出的公式即可

//均衡化			
IMGDATA balance(const GRAYHISTOGRAM histogram,const IMGDATA data)
{
	const IMGDATA newData = data;
	for(int i = 0;i < data.length;i++)
	{
		newData.pImg[i] = calculate(data.pImg[i], histogram) * 255;
	}
	return newData;
}

//对灰度级别积分
float calculate(int index, const GRAYHISTOGRAM histogram)
{
	float result = 0;
	for(int i = 0;i < index + 1;i++)
	{
		result += histogram.gray[i];
	}
	if (result > 1)
		result = 1;
	return result;
}

此时,我们的直方图处理也总算是完成了。

源码

https://github.com/DearSummer/DigitalImageProcessing

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值