【從零學習OpenCV 4】直方圖匹配
- 2019 年 12 月 24 日
- 筆記
經過幾個月的努力,小白終於完成了市面上第一本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 <opencv2opencv.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程式中給影像的直方圖