Exercise2--直方图处理


前言

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

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

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

直方图统计

灰度直方图是灰度级函数,他表现为图像当中某种灰度的像素个数,反应了图像中每种灰度的出现频率

其中S为第r级灰度的灰度频率

算法

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

1
2
3
4
5
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文件(注意跳字节),提取出自己有用的部分,然后再进行统计

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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文件的读取

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
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当中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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.height;
return grayhistogram;
}

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

1
2
3
4
5
6
7
8
9
10
11
12
13
//归一化
void GrayHistogram::normalize()
{
if(isNormalize)
return;

for (float& i : gray)
{
i = i / pixelCount;
}

isNormalize = true;
}

输出直方图

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

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

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
//由于可以使用原图本身的信息,因此可以省略一些填写
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();
}

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

直方图均衡化

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

在直方图均衡化当中,均衡化函数满足以下特点

且T(r)在区间内为单值且单调递增

累积函数分布方法(DCF)

累积函数分布方法是一种比较经典的直方图均衡化方法,他的公式表现为

也就是对于每一处的灰度分布概率都进行积分,由于概率总和为1,因此,T(r)自然也是在0与1之间了,而且也必然是单调递增且单值的。

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

当然,由于我们的图片的灰度级别是离散的,因此,这个函数应该也是离散的,所以他的离散形式为

均衡化之后,每一个点的灰度级别都是自己的灰度级数的概率的积分。

算法实现

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
//均衡化			
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

  • 本文作者: ShinyGX
  • 本文链接: https://ShinyGX.github.io/posts/75ff8234/
  • 版权声明: 本博客所有文章除特别声明外,均采用 https://creativecommons.org/licenses/by-nc-sa/3.0/ 许可协议。转载请注明出处!
0%