【从零学习OpenCV 4】直方图匹配
重磅干货,第一时间送达
经过几个月的努力,小白终于完成了市面上第一本OpenCV 4入门书籍《从零学习OpenCV 4》。为了更让小伙伴更早的了解最新版的OpenCV 4,小白与出版社沟通,提前在公众号上连载部分内容,请持续关注小白。
直方图均衡化函数可以自动的改变图像直方图的分布形式,这种方式极大的简化了直方图均衡化过程中需要的操作步骤,但是该函数不能指定均衡化后的直方图分布形式。在某些特定的条件下需要将直方图映射成指定的分布形式,这种将直方图映射成指定分布形式的算法称为直方图匹配或者直方图规定化。直方图匹配与直方图均衡化相似,都是对图像的直方图分布形式进行改变,只是直方图均衡化后的图像直方图是均匀分布的,而直方图匹配后的直方图可以随意指定,即在执行直方图匹配操作时,首先要知道变换后的灰度直方图分布形式,进而确定变换函数。直方图匹配操作能够有目的的增强某个灰度区间,相比于直方图均衡化操作,该算法虽然多了一个输入,但是其变换后的结果也更灵活。
由于不同图像间像素数目可能不同,为了使两个图像直方图能够匹配,需要使用概率形式去表示每个灰度值在图像像素中所占的比例。理想状态下,经过图像直方图匹配操作后图像直方图分布形式应与目标分布一致,因此两者之间的累积概率分布也一致。累积概率为小于等于某一灰度值的像素数目占所有像素中的比例。我们用Vs表示原图像直方图的各个灰度级的累积概率,用Vz表示匹配后直方图的各个灰度级累积概率。那么确定由原图像中灰度值n映射成r的条件如式(6.8)所示。
(6.8)
为了更清楚的说明直方图匹配过程,在图4-7中给出了一个直方图匹配示例。示例中目标直方图灰度值2以下的概率都为0,灰度值3的累积概率为0.16,灰度值4的累积概率为0.35,原图像直方图灰度值为0时累积概率为0.19。0.19距离0.16的距离小于距离0.35的距离,因此需要将原图像中灰度值0匹配成灰度值3。同样,原图像灰度值1的累积概率为0.43,其距离目标直方图灰度值4的累积概率0.35的距离为0.08,而距离目标直方图灰度值5的累积概率0.64的距离为0.21,因此需要将原图像中灰度值1匹配成灰度值4。
图4-7 直方图匹配示例
这个寻找灰度值匹配的过程是直方图匹配算法的关键,在代码实现中我们可以通过构建原直方图累积概率与目标直方图累积概率之间的差值表,寻找原直方图中灰度值n的累积概率与目标直方图中所有灰度值累积概率差值的最小值,这个最小值对应的灰度值r就是n匹配后的灰度值。
在OpenCV 4中并没有提供直方图匹配的函数,需要自己根据算法实现图像直方图匹配。在代码清单4-9中给出了实现直方图匹配的示例程序。程序中待匹配的原图是一个图像整体偏暗的图像,目标直方图分配形式来自于一张较为明亮的图像,经过图像直方图匹配操作之后,提高了图像的整体亮度,图像直方图分布也更加均匀,程序中所有的结果在图4-8、图4-9给出。
代码清单4-9 myHistMatch.cpp图像直方图匹配
1. #include <opencv2\opencv.hpp>
2. #include <iostream>
3.
4. using namespace cv;
5. using namespace std;
6.
7. void drawHist(Mat &hist, int type, string name) //归一化并绘制直方图函数
8. {
9. int hist_w = 512;
10. int hist_h = 400;
11. int width = 2;
12. Mat histImage = Mat::zeros(hist_h, hist_w, CV_8UC3);
13. normalize(hist, hist, 1, 0, type, -1, Mat());
14. for (int i = 1; i <= hist.rows; i++)
15. {
16. rectangle(histImage, Point(width*(i - 1), hist_h - 1),
17. Point(width*i - 1,hist_h - cvRound(20 * hist_h*hist.at<float>(i-1)) - 1),
18. Scalar(255, 255, 255), -1);
19. }
20. imshow(name, histImage);
21. }
22. //主函数
23. int main()
24. {
25. Mat img1 = imread("histMatch.png");
26. Mat img2 = imread("equalLena.png");
27. if (img1.empty()||img2.empty())
28. {
29. cout << "请确认图像文件名称是否正确" << endl;
30. return -1;
31. }
32. Mat hist1, hist2;
33. //计算两张图像直方图
34. const int channels[1] = { 0 };
35. float inRanges[2] = { 0,255 };
36. const float* ranges[1] = { inRanges };
37. const int bins[1] = { 256 };
38. calcHist(&img1, 1, channels, Mat(), hist1, 1, bins, ranges);
39. calcHist(&img2, 1, channels, Mat(), hist2, 1, bins, ranges);
40. //归一化两张图像的直方图
41. drawHist(hist1, NORM_L1, "hist1");
42. drawHist(hist2, NORM_L1, "hist2");
43. //计算两张图像直方图的累积概率
44. float hist1_cdf[256] = { hist1.at<float>(0) };
45. float hist2_cdf[256] = { hist2.at<float>(0) };
46. for (int i = 1; i < 256; i++)
47. {
48. hist1_cdf[i] = hist1_cdf[i - 1] + hist1.at<float>(i);
49. hist2_cdf[i] = hist2_cdf[i - 1] + hist2.at<float>(i);
50.
51. }
52. //构建累积概率误差矩阵
53. float diff_cdf[256][256];
54. for (int i = 0; i < 256; i++)
55. {
56. for (int j = 0; j < 256; j++)
57. {
58. diff_cdf[i][j] = fabs(hist1_cdf[i] - hist2_cdf[j]);
59. }
60. }
61.
62. //生成LUT映射表
63. Mat lut(1, 256, CV_8U);
64. for (int i = 0; i < 256; i++)
65. {
66. // 查找源灰度级为i的映射灰度
67. // 和i的累积概率差值最小的规定化灰度
68. float min = diff_cdf[i][0];
69. int index = 0;
70. //寻找累积概率误差矩阵中每一行中的最小值
71. for (int j = 1; j < 256; j++)
72. {
73. if (min > diff_cdf[i][j])
74. {
75. min = diff_cdf[i][j];
76. index = j;
77. }
78. }
79. lut.at<uchar>(i) = (uchar)index;
80. }
81. Mat result, hist3;
82. LUT(img1, lut, result);
83. imshow("待匹配图像", img1);
84. imshow("匹配的模板图像", img2);
85. imshow("直方图匹配结果", result);
86. calcHist(&result, 1, channels, Mat(), hist3, 1, bins, ranges);
87. drawHist(hist3, NORM_L1, "hist3"); //绘制匹配后的图像直方图
88. waitKey(0);
89. return 0;
90. }
图4-8 myHistMatch.cpp程序中匹配图像原图、模板以及匹配后图像
图4-9 myHistMatch.cpp程序中给图像的直方图