直方圖不僅能夠表示圖像像素的統(tǒng)計特性,,應用統(tǒng)計的直方圖結(jié)果也可以增強圖像的對比度,在圖像中尋找相似區(qū)域等,。本節(jié)中將重點介紹如果通過調(diào)整直方圖分布提高圖像的對比度,、利用直方圖反向投影尋找相同區(qū)域以及將圖像的對比度調(diào)整為指定的形式。 直方圖均衡化如果一個圖像的直方圖都集中在一個區(qū)域,,則整體圖像的對比度比較小,,不便于圖像中紋理的識別。例如相鄰的兩個像素灰度值如果分別是120和121,,僅憑肉眼是如法區(qū)別出來的,。同時,如果圖像中所有的像素灰度值都集中在100到150之間,,則整個圖像想會給人一種模糊的感覺,,看不清圖中的內(nèi)容,。如果通過映射關(guān)系,將圖像中灰度值的范圍擴大,,增加原來兩個灰度值之間的差值,,就可以提高圖像的對比度,進而將圖像中的紋理突出顯現(xiàn)出來,,這個過程稱為圖像直方圖均衡化,。 在OpenCV 4中提供了equalizeHist()函數(shù)用于將圖像的直方圖均衡化,該函數(shù)的函數(shù)原型在代碼清單4-7中給出,。 代碼清單4-7 equalizeHist()函數(shù)原型
1.void cv::equalizeHist(InputArray src,
2. OutputArray dst
3. ) 該函數(shù)形式比較簡單,,但是需要注意該函數(shù)只能對單通道的灰度圖進行直方圖均衡化。對圖像的均衡化示例程序在代碼清單4-8中給出,,程序中我們將一張圖像灰度值偏暗的圖像進行均衡化,,通過結(jié)果可以發(fā)現(xiàn)經(jīng)過均衡化后的圖像對比度明顯增加,可以看清楚原來看不清的紋理,。通過繪制原圖和均衡化后的圖像的直方圖可以發(fā)現(xiàn),,經(jīng)過均衡化后的圖像直方圖分布更加均勻。 代碼清單4-8 myEqualizeHist.cpp直方圖均衡化實現(xiàn)
4.#include <opencv2\opencv.hpp>
5.#include <iostream>
6.
7.using namespace cv;
8.using namespace std;
9.
10.void drawHist(Mat &hist, int type, string name) //歸一化并繪制直方圖函數(shù)
11.{
12.int hist_w = 512;
13.int hist_h = 400;
14.int width = 2;
15.Mat histImage = Mat::zeros(hist_h, hist_w, CV_8UC3);
16.normalize(hist, hist, 1, 0, type, -1, Mat());
17.for (int i = 1; i <= hist.rows; i++)
18.{
22.}
23.imshow(name, histImage);
24.}
25.//主函數(shù)
26.int main()
27.{
28.Mat img = imread("gearwheel.jpg");
29.if (img.empty())
30.{
31.cout << "請確認圖像文件名稱是否正確" << endl;
32.return -1;
33.}
34.Mat gray, hist, hist2;
35.cvtColor(img, gray, COLOR_BGR2GRAY);
36.Mat equalImg;
37.equalizeHist(gray, equalImg); //將圖像直方圖均衡化
38.const int channels[1] = { 0 };
39.float inRanges[2] = { 0,255 };
40.const float* ranges[1] = { inRanges };
41.const int bins[1] = { 256 };
42.calcHist(&gray, 1, channels, Mat(), hist, 1, bins, ranges);
43.calcHist(&equalImg, 1, channels, Mat(), hist2, 1, bins, ranges);
44.drawHist(hist, NORM_INF, "hist");
45.drawHist(hist2, NORM_INF, "hist2");
46.imshow("原圖", gray);
47.imshow("均衡化后的圖像", equalImg);
48.waitKey(0);
49.return 0;
50.}
圖4-6 myEqualizeHist.cpp程序運行結(jié)果
直方圖匹配直方圖均衡化函數(shù)可以自動的改變圖像直方圖的分布形式,,這種方式極大的簡化了直方圖均衡化過程中需要的操作步驟,,但是該函數(shù)不能指定均衡化后的直方圖分布形式。在某些特定的條件下需要將直方圖映射成指定的分布形式,,這種將直方圖映射成指定分布形式的算法稱為直方圖匹配或者直方圖規(guī)定化,。直方圖匹配與直方圖均衡化相似,都是對圖像的直方圖分布形式進行改變,,只是直方圖均衡化后的圖像直方圖是均勻分布的,,而直方圖匹配后的直方圖可以隨意指定,即在執(zhí)行直方圖匹配操作時,,首先要知道變換后的灰度直方圖分布形式,,進而確定變換函數(shù)。直方圖匹配操作能夠有目的的增強某個灰度區(qū)間,,相比于直方圖均衡化操作,,該算法雖然多了一個輸入,但是其變換后的結(jié)果也更靈活,。 由于不同圖像間像素數(shù)目可能不同,,為了使兩個圖像直方圖能夠匹配,,需要使用概率形式去表示每個灰度值在圖像像素中所占的比例,。理想狀態(tài)下,,經(jīng)過圖像直方圖匹配操作后圖像直方圖分布形式應與目標分布一致,因此兩者之間的累積概率分布也一致,。累積概率為小于等于某一灰度值的像素數(shù)目占所有像素中的比例,。我們用
V
s
{V_s}
Vs表示原圖像直方圖的各個灰度級的累積概率,用
V
z
{V_z}
Vz表示匹配后直方圖的各個灰度級累積概率,。那么確定由原圖像中灰度值n映射成r的條件如式(6.8)所示,。
n
,
r
=
arg
?
min
?
n
,
r
∣
V
s
(
n
)
?
V
z
(
r
)
∣
(6.8)
n,r = \arg \mathop {\min }\limits_{n,r} \left| {{V_s}(n) - {V_z}(r)} \right| \tag{6.8}
n,r=argn,rmin∣Vs(n)?Vz(r)∣(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 直方圖匹配示例
這個尋找灰度值匹配的過程是直方圖匹配算法的關(guān)鍵,,在代碼實現(xiàn)中我們可以通過構(gòu)建原直方圖累積概率與目標直方圖累積概率之間的差值表,,尋找原直方圖中灰度值n的累積概率與目標直方圖中所有灰度值累積概率差值的最小值,這個最小值對應的灰度值r就是n匹配后的灰度值,。 在OpenCV 4中并沒有提供直方圖匹配的函數(shù),,需要自己根據(jù)算法實現(xiàn)圖像直方圖匹配。在代碼清單4-9中給出了實現(xiàn)直方圖匹配的示例程序,。程序中待匹配的原圖是一個圖像整體偏暗的圖像,,目標直方圖分配形式來自于一張較為明亮的圖像,經(jīng)過圖像直方圖匹配操作之后,,提高了圖像的整體亮度,,圖像直方圖分布也更加均勻,程序中所有的結(jié)果在圖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) //歸一化并繪制直方圖函數(shù)
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.//主函數(shù)
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.//構(gòu)建累積概率誤差矩陣
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的累積概率差值最小的規(guī)定化灰度
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("直方圖匹配結(jié)果", 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程序中給圖像的直方圖
直方圖反向投影如果一張圖像的某個區(qū)域中顯示的是一種結(jié)構(gòu)紋理或者一個獨特的形狀,,那么這個區(qū)域的直方圖就可以看作是這個結(jié)構(gòu)或者形狀的概率函數(shù),,在圖像中尋找這種概率分布就是在圖像中尋找該結(jié)構(gòu)紋理或者獨特形狀。反向投影(back projection)就是一種記錄給定圖像中的像素點如何適應直方圖模型像素分布方式的一種方法。簡單的講,,所謂反向投影就是首先計算某一特征的直方圖模型,,然后使用模型去尋找圖像中是否存在該特征的方法。 OpenCV 4提供了calcBackProject()函數(shù)用于對圖像直方圖反向投影,,該函數(shù)的函數(shù)原型在代碼清單4-10中給出,。 代碼清單4-10 calcBackProject()函數(shù)原型
1.void cv::calcBackProject(const Mat * images,
2. int nimages,
3. const int * channels,
4. InputArray hist,
5. OutputArray backProject,
6. const float ** ranges,
7. double scale = 1,
8. bool uniform = true
9. ) images:待統(tǒng)計直方圖的圖像數(shù)組,數(shù)組中所有的圖像應具有相同的尺寸和數(shù)據(jù)類型,,并且數(shù)據(jù)類型只能是CV_8U,、CV_16U和CV_32F三種中的一種,但是不同圖像的通道數(shù)可以不同,。 nimages:輸入圖像數(shù)量 channels:需要統(tǒng)計的通道索引數(shù)組,,第一個圖像的通道索引從0到images[0].channels()-1,第二個圖像通道索引從images[0].channels()到images[0].channels()+ images[1].channels()-1,,以此類推,。 hist:輸入直方圖 backProject:目標反投影圖像,與images[0]具有相同尺寸和數(shù)據(jù)類型的單通道圖像,。 ranges:每個圖像通道中灰度值的取值范圍 scale:輸出反投影矩陣的比例因子,。 uniform:直方圖是否均勻的標志符,默認狀態(tài)下為均勻(true),。
該函數(shù)用于在輸入圖像中尋找與特定圖像最匹配的點或者區(qū)域,,即對圖像進行反向投影。該函數(shù)輸入?yún)?shù)與計算圖像直方圖函數(shù)calcHist()大致相似,,都需要輸入圖像和需要進行反向投影的通道索引數(shù)目,。區(qū)別之處在于該函數(shù)需要輸入模板圖像的直方圖統(tǒng)計結(jié)果,并返回的是一張圖像,,而不是直方圖統(tǒng)計結(jié)果,。根據(jù)該函數(shù)所需要的參數(shù)可知,該函數(shù)在使用時主要分為四個步驟: Step1:加載模板圖像和待反向投影圖像,; Step2:轉(zhuǎn)換圖像顏色空間,,常用的顏色空間為灰度圖像和HSV空間; Step3:計算模板圖像的直方圖,,灰度圖像為一維直方圖,,HSV圖像為H-S通道的二維直方圖; tep4:將待反向投影的圖像和模板圖像的直方圖賦值給反向投影函數(shù)calcBackProject(),,最終得到反向投影結(jié)果,。
為了更加熟悉該函數(shù)的使用方式,了解圖像反向投影的作用,,在代碼清單4-11中給出了對圖像進行反向投影的示例程序,。程序中首先加載待反向投影圖像和模板圖像,,模板圖像從待反向投影的圖像中截取,之后將兩張圖像由RGB顏色空間轉(zhuǎn)成HSV空間中,,統(tǒng)計H-S通道的直方圖,,將直方圖歸一化后繪制H-S通道的二維直方圖。最后將待反向投影和模板圖像的直方圖輸入給函數(shù)calcBackProject(),,得到圖像反向投影結(jié)果。 代碼清單4-11 myCalcBackProject.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) //歸一化并繪制直方圖函數(shù)
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, 255, 0, type, -1, Mat());
14.namedWindow(name, WINDOW_NORMAL);
15.imshow(name, hist);
16.}
17.//主函數(shù)
18.int main()
19.{
20.Mat img = imread("apple.jpg");
21.Mat sub_img = imread("sub_apple.jpg");
22.Mat img_HSV, sub_HSV, hist, hist2;
23.if (img.empty() || sub_img.empty())
24.{
25.cout << "請確認圖像文件名稱是否正確" << endl;
26.return -1;
27.}
28.
29.imshow("img", img);
30.imshow("sub_img", sub_img);
31.//轉(zhuǎn)成HSV空間,,提取S,、V兩個通道
32.cvtColor(img, img_HSV, COLOR_BGR2HSV);
33.cvtColor(sub_img, sub_HSV, COLOR_BGR2HSV);
34.int h_bins = 32; int s_bins = 32;
35.int histSize[] = { h_bins, s_bins };
36.//H通道值的范圍由0到179
37.float h_ranges[] = { 0, 180 };
38.//S通道值的范圍由0到255
39.float s_ranges[] = { 0, 256 };
40.const float* ranges[] = { h_ranges, s_ranges }; //每個通道的范圍
41.int channels[] = { 0, 1 }; //統(tǒng)計的通道索引
42.//繪制H-S二維直方圖
43.calcHist(&sub_HSV, 1, channels, Mat(), hist, 2, histSize, ranges, true, false);
44.drawHist(hist, NORM_INF, "hist"); //直方圖歸一化并繪制直方圖
45.Mat backproj;
//直方圖反向投影
47.imshow("反向投影后結(jié)果", backproj);
48.waitKey(0);
49.return 0;
50.}
圖4-10 myCalcBackProject.cpp程序運行結(jié)果
|