图像直方图

非归一化直方图:

h(rk)=nk,其中rk为图像像素灰度值,nk为图像中灰度值rk对应的像素个数h(r_k) = n_k,其中r_k为图像像素灰度值,n_k为图像中灰度值r_k对应的像素个数

归一化直方图:

p(rk)=nkMN,其中MN为图像行数和列数。p(r_k) = \frac {n_k} {MN},其中M和N为图像行数和列数。

计算直方图与一些函数相关:

1
2
3
4
5
6
7
8
9
void calcHist( const Mat* images,
int nimages,
const int* channels,
InputArray mask,
OutputArray hist,
int dims,
const int* histSize,
const float** ranges,
bool uniform = true, bool accumulate = false )

参数如下:

  • images:源数组指针,它们都应该具有相同的深度,CV_8U, CV_16U或CV_32F,以及相同的大小。它们中的每一个都可以有任意数量的通道。

  • nimages:源图像的数量。

  • channels:需要统计直方图的第几通道。通道的数量必须与直方图的维度相匹配。第一个数组通道的编号从0到images[0].channels()-1,第二个数组通道的编号从images[0].channels()到images[0].channels() + images[1].channels()-1,以此类推。

  • mask:掩码。选择感兴趣区域,选定后只能对该区域进行操作。

  • hist:直方图计算的输出值。

  • dims:输出直方图的维度(由channels指定)。

  • histSize:直方图中每个dims维度需要分成多少个区间(直方图竖条的个数)。

  • ranges:统计像素值的区间。

  • uniform=true:是否对得到的直方图进行归一化处理。

  • accumulate=false:在多个图像时是否累计计算像素值的个数。

获取图像的直方图代码:

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
#include <opencv2/opencv.hpp>
#include <iostream>

int main()
{
cv::Mat image = cv::imread("src/test.jpg");
cv::Mat hist, image_gray;
cv::cvtColor(image, image_gray, cv::COLOR_BGR2GRAY);

cv::namedWindow("img", cv::WINDOW_NORMAL);
cv::imshow("img", image_gray);

int hsize = 256; // 直方图区间数
float ranges[] = { 0, 256 }; // 统计像素值的区间
const float *hRanges = { ranges };
// 计算直方图的输出值
cv::calcHist(&image_gray, 1, 0, cv::Mat(), hist, 1, &hsize, &hRanges, true, false);

int hist_h = 300, hist_w = 512; // 直方图图像高和宽
int bin_w = hist_w / hsize; // 区间

// 直方图图像
cv::Mat histImage(hist_h, hist_w, CV_8UC3, cv::Scalar(255, 255, 255));
// 直方图输出值归一化到0~255
cv::normalize(hist, hist, 0, hist_h, cv::NORM_MINMAX, -1, cv::Mat());

for (int i = 1; i < hsize; i ++)
cv::line(histImage, cv::Point((i - 1) * bin_w, hist_h - cvRound(hist.at<float>(i - 1))), cv::Point((i) *bin_w, hist_h - cvRound(hist.at<float>(i))), cv::Scalar(100, 100, 100), 2);

cv::imshow("pic", histImage);
cv::waitKey(0);
return 0;
}

效果展示:

效果展示

直方图均衡化

通过均衡化处理可以使得图像的直方图分布变得较广较平均。

经过公式:

sk=(L1)j=0kPr(rj)k=0,1,L1s_k = (L - 1)\sum_{j=0}^kP_r(r_j),k = 0,1…,L-1

其中,sks_k是变换后的灰度级,Pr(rj)P_r(r_j)是灰度级为rjr_j的直方图值。

涉及一个函数:

1
void equalizeHist( InputArray src, OutputArray dst )

该函数参数十分简单,只有输入图像矩阵和输出图像矩阵,即可进行图像直方图的均衡化。

代码:

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
#include <opencv2/opencv.hpp>
#include <iostream>

int main()
{
cv::Mat image = cv::imread("src/test.jpg");
cv::Mat hist, hist1, image_gray, image_;
cv::cvtColor(image, image_gray, cv::COLOR_BGR2GRAY);

cv::namedWindow("img_before", cv::WINDOW_NORMAL);
cv::namedWindow("img_after", cv::WINDOW_NORMAL);
cv::imshow("img_before", image_gray);

// 直方图均衡化
cv::equalizeHist(image_gray, image_);
cv::imshow("img_after", image_);

int hsize = 256; // 直方图区间数
float ranges[] = { 0, 256 }; // 统计像素值的区间
const float *hRanges = { ranges };
// 计算直方图的输出值
cv::calcHist(&image_gray, 1, 0, cv::Mat(), hist, 1, &hsize, &hRanges, true, false);
cv::calcHist(&image_, 1, 0, cv::Mat(), hist1, 1, &hsize, &hRanges, true, false);

int hist_h = 300, hist_w = 512; // 直方图图像高和宽
int bin_w = hist_w / hsize; // 区间

// 直方图图像
cv::Mat histImage(hist_h, hist_w, CV_8UC3, cv::Scalar(255, 255, 255));
cv::Mat histImage1(hist_h, hist_w, CV_8UC3, cv::Scalar(255, 255, 255));

// 直方图输出值归一化到0~255
cv::normalize(hist, hist, 0, hist_h, cv::NORM_MINMAX, -1, cv::Mat());
cv::normalize(hist1, hist1, 0, hist_h, cv::NORM_MINMAX, -1, cv::Mat());

for (int i = 1; i < hsize; i ++)
{
cv::line(histImage, cv::Point((i - 1) * bin_w, hist_h - cvRound(hist.at<float>(i - 1))), cv::Point((i) *bin_w, hist_h - cvRound(hist.at<float>(i))), cv::Scalar(100, 100, 100), 2);
cv::line(histImage1, cv::Point((i - 1) * bin_w, hist_h - cvRound(hist1.at<float>(i - 1))), cv::Point((i) *bin_w, hist_h - cvRound(hist1.at<float>(i))), cv::Scalar(100, 100, 100), 2);
}

cv::imshow("before", histImage);
cv::imshow("after", histImage1);
cv::waitKey(0);
return 0;
}

效果展示:

效果展示

直方图匹配

将需要处理的图像匹配另一幅直方图形状。

由于直方图均衡化映射函数T为单调递增,即变换后的直方图可以经过逆变换回到原直方图。

得到直方图匹配的步骤:

  1. 计算输入图像的直方图 P(r)P(r) ,并进行直方图均衡化,得到均衡化后的灰度 sks_k

  2. 根据公式计算 G(zq)G(z_q) 并存储。

G(zq)=(L1)i=0qPz(zj)G(z_q) = (L - 1)\sum_{i = 0}^qP_z(z_j)

  1. sks_k 的每个值,都找到 zqz_q 对应的值,使得 G(zq)G(z_q) 最接近 sks_k ,并存储从s到z的映射。

  2. 从步骤3中找到映射,将 sks_k 的值映射到直方图指定图像中值为 zqz_q 的对应像素,形成直方图。

涉及映射Look up Table(LUT)函数:

1
void LUT(InputArray src, InputArray lut, OutputArray dst)

参数如下:

  • src:表示输入图像。

  • lut:表示查找表。

  • dst:表示输出图像。

代码:white.jpg为纯白图像

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
#include <opencv2/opencv.hpp>
#include <iostream>

int main()
{
cv::Mat image1 = cv::imread("src/test.jpg"), image2 = cv::imread("src/white.jpg");
cv::Mat image1_gray, image2_gray, hist1, hist2, image_;
cv::cvtColor(image1, image1_gray, cv::COLOR_BGR2GRAY);
cv::cvtColor(image2, image2_gray, cv::COLOR_BGR2GRAY);
cv::namedWindow("img1_gray", cv::WINDOW_NORMAL);
cv::namedWindow("img2_gray", cv::WINDOW_NORMAL);
cv::imshow("img1_gray", image1_gray);
cv::imshow("img2_gray", image2_gray);

// 图像1和图像2进行均衡化
cv::equalizeHist(image1_gray, image1_gray);
cv::equalizeHist(image2_gray, image2_gray);

// 求图像1和图像2均衡化后的直方图
int hsize = 256;
float ranges[] = { 0, 256 };
const float *hranges = { ranges };
cv::calcHist(&image1_gray, 1, 0, cv::Mat(), hist1, 1, &hsize, &hranges, true, false);
cv::calcHist(&image2_gray, 1, 0, cv::Mat(), hist2, 1, &hsize, &hranges, true, false);

// 计算两个均衡化图像直方图的累积概率
float hist1_rate[256] = { hist1.at<float>(0) };
float hist2_rate[256] = { hist2.at<float>(0) };
for (int i = 1; i < 256; i ++)
{
hist1_rate[i] = (hist1_rate[i - 1] + hist1.at<float>(i));
hist2_rate[i] = (hist2_rate[i - 1] + hist2.at<float>(i));
}

for (int i = 0; i < 256; i ++)
{
hist1_rate[i] /= (image1_gray.rows * image1_gray.cols);
hist2_rate[i] /= (image2_gray.rows * image2_gray.cols);
}

// 两个累计概率之间的差值,用于找到最接近的点
float diff[256][256];
for (int i = 0; i < 256; i ++)
for (int j = 0; j < 256; j ++)
diff[i][j] = fabs(hist1_rate[i] - hist2_rate[j]);

cv::Mat lut(1, 256, CV_8U);
for (int i = 0; i < 256; i ++)
{
// 查找源灰度级为i的映射灰度和i的累积概率差最小(灰度接近)的规定化灰度
float min = diff[i][0];
int idx = 0;
for (int j = 0; j < 256; j ++)
{
if(min > diff[i][j])
{
min = diff[i][j];
idx = j;
}
}
lut.at<uchar>(i) = idx;
}
cv::LUT(image1_gray, lut, image_);
cv::namedWindow("image_", cv::WINDOW_NORMAL);
cv::imshow("image_", image_);

cv::waitKey(0);
return 0;
}

效果展示:

效果展示