1.直方圖的繪制
圖像直方圖就是統(tǒng)計(jì)圖像中每個(gè)灰度值的個(gè)數(shù),之后將灰度值作為橫軸,以灰度值個(gè)數(shù)或者灰度值所占比率作為縱軸的統(tǒng)計(jì)圖。通過直方圖,可以看出圖像中哪些灰度值數(shù)目較多,哪些較少,可以通過一定的方法將灰度值較為集中的區(qū)域映射到較為稀疏的區(qū)域,從而使圖像在像素灰度值上的分布更加符合期望狀態(tài)。在通常情況下,像素灰度值代表亮暗程度,因此通過直方圖,可以分析圖像亮暗對(duì)比度,并調(diào)整圖像的亮暗程度。
在OpenCV中,只提供了圖像直方圖的統(tǒng)計(jì)函數(shù)calcHist(),該函數(shù)能夠統(tǒng)計(jì)出圖像中每個(gè)灰度值的個(gè)數(shù),但是,對(duì)于直方圖的繪制,需要自行進(jìn)行:
void calcHist(
const Mat* images, // 待統(tǒng)計(jì)直方圖的圖像數(shù)組,數(shù)據(jù)類型只能是CV_8U、CV_16U、CV_32F,不同圖像的通道數(shù)可以不同
int nimages, // 輸入圖像數(shù)量
const int* channels, // 需要統(tǒng)計(jì)的通道索引數(shù)組,第一個(gè)圖像的通道索引號(hào)從0到images[0].channels()-1,第二個(gè)圖像的通道索引從image[0].channels()到image[0].channels()+image[1].channels()-1,以此類推
InputArray mask, // 可選的操作掩碼,如果是空矩陣表示圖像中所有位置的像素都計(jì)入直方圖中
OutputArray hist, // 輸出的統(tǒng)計(jì)直方圖結(jié)果,時(shí)dims維度的數(shù)組
int dims, // 需要計(jì)算直方圖的維度,必須是整數(shù),不能大于CV_MAX_DIMS
const int* histSize, // 存放每個(gè)維度直方圖的數(shù)組的尺寸
const float** ranges, // 每個(gè)圖相同道中灰度值的取值范圍
bool uniform = true, // 直方圖是否均勻的標(biāo)志
bool accumulate = false // 是否累積統(tǒng)計(jì)直方圖的標(biāo)志,如果累積,那么在統(tǒng)計(jì)新圖像時(shí),之前的統(tǒng)計(jì)結(jié)果不會(huì)被清除,該參數(shù)主要用于統(tǒng)計(jì)多個(gè)圖像整體的直方圖。
);
由于該函數(shù)具有較多參數(shù),并且每個(gè)參數(shù)都較為復(fù)雜,因此在使用該函數(shù)時(shí)只統(tǒng)計(jì)單通道圖像的灰度值分布,對(duì)于多個(gè)通道圖像,可以將圖像每個(gè)通道分離后再進(jìn)行統(tǒng)計(jì)。
#include <opencv2/core/utils/logger.hpp>
#include <opencv2/opencv.hpp>
using namespace cv;
using namespace std;
int main()
{
cout << "OpenCV Version: " << CV_VERSION << endl;
utils::logging::setLogLevel(utils::logging::LOG_LEVEL_SILENT);
Mat img = imread("apple.jpg");
if (img.empty())
{
cout << "請(qǐng)確認(rèn)圖像文件名稱是否正確" << endl;
return -1;
}
Mat gray;
cvtColor(img, gray, COLOR_BGR2GRAY);
//設(shè)置提取直方圖的相關(guān)變量
Mat hist; //用于存放直方圖計(jì)算結(jié)果
const int channels[1] = { 0 }; //通道索引
float inRanges[2] = { 0,255 };
const float* ranges[1] = { inRanges }; //像素灰度值范圍
const int bins[1] = { 256 }; //直方圖的維度,其實(shí)就是像素灰度值的最大值
calcHist(&gray, 1, channels, Mat(), hist, 1, bins, ranges); //計(jì)算圖像直方圖
cout << "hist.channels(): " << hist.channels() << endl; // 1
double maxval = 0.0;
minMaxLoc(hist, 0, &maxval);
cout << "hist maxval: " << maxval << endl; // 最多的一個(gè)灰度值數(shù)量有3896
//準(zhǔn)備繪制直方圖
int hist_w = 512; // 256個(gè)灰度值,每個(gè)灰度值畫成矩形,矩形寬度為2
int hist_h = 400;
int width = 2;
Mat histImage = Mat::zeros(hist_h, hist_w, CV_8UC3);
for (int i = 1; i <= hist.rows; i++)
{
rectangle(histImage, Point(width * (i - 1), hist_h - 1),
// cvRound四舍五入取整,因?yàn)榛叶戎禂?shù)量太多,所以除以了15
Point(width * i - 1, hist_h - cvRound(hist.at<float>(i - 1) / 15)),
Scalar(255, 255, 255), -1);
}
namedWindow("histImage", WINDOW_AUTOSIZE);
imshow("histImage", histImage);
imshow("gray", gray);
int k = waitKey(0); // Wait for a keystroke in the window
return 0;
}
由于圖像中灰度值像素?cái)?shù)目較多,因此將每個(gè)灰度值數(shù)目縮小為原來的 1 15 \frac{1}{15} 151? 后再進(jìn)行繪制。
2.直方圖歸一化
前面完成了對(duì)一幅圖像像素灰度值的統(tǒng)計(jì),并成功繪制了圖像的直方圖。直方圖可以用來表示圖像的明亮程度,從理論上講,通過對(duì)同一個(gè)圖像縮放后得到的兩幅尺寸不一樣的圖像將具有大致相似的直方圖分布特性,因此用灰度值的數(shù)目作為統(tǒng)計(jì)結(jié)果具有一定的局限性。
OpenCV提供了normalize函數(shù)實(shí)現(xiàn)多種形式的歸一化功能:
void normalize(
InputArray src,
InputOutputArray dst,
double alpha = 1, // 在范圍歸一化的情況下,歸一化到下限邊界的標(biāo)準(zhǔn)值
double beta = 0, // 在范圍歸一化時(shí)的上限范圍,他不用于標(biāo)準(zhǔn)規(guī)范化
int norm_type = NORM_L2, // 歸一化過程中數(shù)據(jù)范數(shù)種類標(biāo)志,常用的可選參數(shù)如下所示
int dtype = -1, // 輸出數(shù)據(jù)類型,默認(rèn)即可
InputArray mask = noArray() // 掩碼矩陣
);
/*
該函數(shù)通過alpha設(shè)置將數(shù)據(jù)縮放到最大范圍,通過norm_type參數(shù)選擇計(jì)算范數(shù)的種類,之后將輸入矩陣中的每個(gè)數(shù)據(jù)分別除以求取得范數(shù)數(shù)值,最后得到縮放的結(jié)果。
norm_type:計(jì)算不同的范數(shù),最后的結(jié)果也不相同。
NORM_INF: x/max
NORM_L1: x/sum(ary)
NORM_L2: x/sqrt( sum(pow(i,2)) )
NORM_MINMAX: (x-min)/(max-min)
*/
下面通過不同方式歸一化數(shù)組 [2.0, 8.0, 10.0]
,并且分別用灰度值所占比例除以數(shù)據(jù)最大值的方式對(duì)圖像進(jìn)行歸一化操作。為了更加直觀展示直方圖歸一化后的結(jié)果,將每個(gè)灰度值所占比例放大了30倍,并將直方圖的圖像高度作為1進(jìn)行繪制:
#include <opencv2/core/utils/logger.hpp>
#include <opencv2/opencv.hpp>
using namespace cv;
using namespace std;
int main()
{
cout << "OpenCV Version: " << CV_VERSION << endl;
utils::logging::setLogLevel(utils::logging::LOG_LEVEL_SILENT);
//system("color F0"); //更改輸出界面顏色
vector<double> positiveData = { 2.0, 8.0, 10.0 };
vector<double> normalized_L1, normalized_L2, normalized_Inf, normalized_L2SQR;
//測(cè)試不同歸一化方法
normalize(positiveData, normalized_L1, 1.0, 0.0, NORM_L1); //絕對(duì)值求和歸一化
cout << "normalized_L1=[" << normalized_L1[0] << ", "
<< normalized_L1[1] << ", " << normalized_L1[2] << "]" << endl;
normalize(positiveData, normalized_L2, 1.0, 0.0, NORM_L2); //模長(zhǎng)歸一化
cout << "normalized_L2=[" << normalized_L2[0] << ", "
<< normalized_L2[1] << ", " << normalized_L2[2] << "]" << endl;
normalize(positiveData, normalized_Inf, 1.0, 0.0, NORM_INF); //最大值歸一化
cout << "normalized_Inf=[" << normalized_Inf[0] << ", "
<< normalized_Inf[1] << ", " << normalized_Inf[2] << "]" << endl;
normalize(positiveData, normalized_L2SQR, 1.0, 0.0, NORM_MINMAX); //偏移歸一化
cout << "normalized_MINMAX=[" << normalized_L2SQR[0] << ", "
<< normalized_L2SQR[1] << ", " << normalized_L2SQR[2] << "]" << endl;
//將圖像直方圖歸一化
Mat img = imread("apple.jpg");
if (img.empty())
{
cout << "請(qǐng)確認(rèn)圖像文件名稱是否正確" << endl;
return -1;
}
Mat gray, hist;
cvtColor(img, gray, COLOR_BGR2GRAY);
const int channels[1] = { 0 };
float inRanges[2] = { 0,255 };
const float* ranges[1] = { inRanges };
const int bins[1] = { 256 };
calcHist(&gray, 1, channels, Mat(), hist, 1, bins, ranges);
int hist_w = 512;
int hist_h = 400;
int width = 2;
Mat histImage_L1 = Mat::zeros(hist_h, hist_w, CV_8UC3);
Mat histImage_Inf = Mat::zeros(hist_h, hist_w, CV_8UC3);
Mat hist_L1, hist_Inf;
normalize(hist, hist_L1, 1, 0, NORM_L1, -1, Mat());
for (int i = 1; i <= hist_L1.rows; i++)
{
rectangle(histImage_L1, Point(width * (i - 1), hist_h - 1),
Point(width * i - 1, hist_h - cvRound(30 * hist_h * hist_L1.at<float>(i - 1)) - 1),
Scalar(255, 255, 255), -1);
}
normalize(hist, hist_Inf, 1, 0, NORM_INF, -1, Mat());
for (int i = 1; i <= hist_Inf.rows; i++)
{
rectangle(histImage_Inf, Point(width * (i - 1), hist_h - 1),
Point(width * i - 1, hist_h - cvRound(hist_h * hist_Inf.at<float>(i - 1)) - 1),
Scalar(255, 255, 255), -1);
}
imshow("histImage_L1", histImage_L1);
imshow("histImage_Inf", histImage_Inf);
int k = waitKey(0); // Wait for a keystroke in the window
return 0;
}
normalized_L1=[0.1, 0.4, 0.5]
normalized_L2=[0.154303, 0.617213, 0.771517]
normalized_Inf=[0.2, 0.8, 1]
normalized_MINMAX=[0, 0.75, 1]
結(jié)果顯示,無論是否進(jìn)行歸一化,或者采用那種歸一化方法,直方圖的分布特性都不會(huì)改變。
3.直方圖比較
通過兩幅圖像的直方圖特性比較兩幅圖像的相似程度。從一定程度上來講,雖然兩幅圖像的直方圖分布相似不代表兩幅圖像相似,但是兩幅圖像相似則兩幅圖像的直方圖分布一定相似。例如,在通過差值對(duì)圖像進(jìn)行縮放后,雖然圖形的直方圖不會(huì)與之前完全一致,但是兩者之間一定具有很高的相似性,因而可以通過比較兩幅圖像的直方圖分布相似性對(duì)圖像進(jìn)行初步的篩選與識(shí)別。
OpenCV中提供了compareHist函數(shù)用于比較兩個(gè)圖像直方圖相似性:
double compareHist(
InputArray H1,
InputArray H2,
int method
);
// method
enum HistCompMethods {
HISTCMP_CORREL = 0, // 相關(guān)法
HISTCMP_CHISQR = 1, // 卡方法
HISTCMP_INTERSECT = 2, // 直方圖相交法
HISTCMP_BHATTACHARYYA = 3, // 巴氏距離法
HISTCMP_HELLINGER = HISTCMP_BHATTACHARYYA, // 3
HISTCMP_CHISQR_ALT = 4, // 替代卡方法
HISTCMP_KL_DIV = 5 // 相對(duì)熵法
};
示例程序:
#include <opencv2/core/utils/logger.hpp>
#include <opencv2/opencv.hpp>
using namespace cv;
using namespace std;
void drawHist(Mat& hist, int type, string name) //歸一化并繪制直方圖函數(shù)
{
int hist_w = 512;
int hist_h = 400;
int width = 2;
Mat histImage = Mat::zeros(hist_h, hist_w, CV_8UC3);
normalize(hist, hist, 1, 0, type, -1, Mat());
for (int i = 1; i <= hist.rows; i++)
{
rectangle(histImage, Point(width * (i - 1), hist_h - 1),
Point(width * i - 1, hist_h - cvRound(hist_h * hist.at<float>(i - 1)) - 1),
Scalar(255, 255, 255), -1);
}
imshow(name, histImage);
}
int main()
{
cout << "OpenCV Version: " << CV_VERSION << endl;
utils::logging::setLogLevel(utils::logging::LOG_LEVEL_SILENT);
Mat img = imread("apple.jpg");
if (img.empty())
{
cout << "請(qǐng)確認(rèn)圖像文件名稱是否正確" << endl;
return -1;
}
Mat gray, hist, gray2, hist2, gray3, hist3;
cvtColor(img, gray, COLOR_BGR2GRAY);
resize(gray, gray2, Size(), 0.5, 0.5);
gray3 = imread("lena.png", IMREAD_GRAYSCALE);
const int channels[1] = { 0 };
float inRanges[2] = { 0,255 };
const float* ranges[1] = { inRanges };
const int bins[1] = { 256 };
// gray:蘋果 gray2:蘋果縮小 gray3:lena
calcHist(&gray, 1, channels, Mat(), hist, 1, bins, ranges);
calcHist(&gray2, 1, channels, Mat(), hist2, 1, bins, ranges);
calcHist(&gray3, 1, channels, Mat(), hist3, 1, bins, ranges);
drawHist(hist, NORM_INF, "hist");
drawHist(hist2, NORM_INF, "hist2");
drawHist(hist3, NORM_INF, "hist3");
//原圖直方圖與原圖直方圖的相關(guān)系數(shù)
double hist_hist = compareHist(hist, hist, HISTCMP_CORREL);
cout << "apple_apple=" << hist_hist << endl;
//原圖直方圖與縮小原圖直方圖的相關(guān)系數(shù)
double hist_hist2 = compareHist(hist, hist2, HISTCMP_CORREL);
cout << "apple_apple256=" << hist_hist2 << endl;
//兩張不同圖像直方圖相關(guān)系數(shù)
double hist_hist3 = compareHist(hist, hist3, HISTCMP_CORREL);
cout << "apple_lena=" << hist_hist3 << endl;
/*
apple_apple=1
apple_apple256=0.998067
apple_lena=0.285314
*/
int k = waitKey(0); // Wait for a keystroke in the window
return 0;
}
4.直方圖均衡化
如果一個(gè)圖像的直方圖都集中在一個(gè)區(qū)域那么整體圖像的對(duì)比度比較小,不便于圖像中紋理識(shí)別。如果通過映射關(guān)系,將圖像中灰度值的范圍擴(kuò)大,增加原來兩個(gè)灰度值之間的差值,就可以提高圖像的對(duì)比度,進(jìn)而將圖像中的紋理凸顯出來,這個(gè)過程稱為圖像直方圖均衡化。
OpenCV中提供了equalizeHist函數(shù)用于將圖像的直方圖均衡化:
void equalizeHist(
InputArray src, // CV_8UC1
OutputArray dst
);
該函數(shù)只能對(duì)單通道的灰度圖進(jìn)行直方圖均衡化。
#include <opencv2/core/utils/logger.hpp>
#include <opencv2/opencv.hpp>
using namespace cv;
using namespace std;
void drawHist(Mat& hist, int type, string name) //歸一化并繪制直方圖函數(shù)
{
int hist_w = 512;
int hist_h = 400;
int width = 2;
Mat histImage = Mat::zeros(hist_h, hist_w, CV_8UC3);
normalize(hist, hist, 1, 0, type, -1, Mat());
for (int i = 1; i <= hist.rows; i++)
{
rectangle(histImage, Point(width * (i - 1), hist_h - 1),
Point(width * i - 1, hist_h - cvRound(hist_h * hist.at<float>(i - 1)) - 1),
Scalar(255, 255, 255), -1);
}
imshow(name, histImage);
}
int main()
{
cout << "OpenCV Version: " << CV_VERSION << endl;
utils::logging::setLogLevel(utils::logging::LOG_LEVEL_SILENT);
Mat img = imread("gearwheel.jpg");
if (img.empty())
{
cout << "請(qǐng)確認(rèn)圖像文件名稱是否正確" << endl;
return -1;
}
Mat gray, hist, hist2;
cvtColor(img, gray, COLOR_BGR2GRAY);
Mat equalImg;
equalizeHist(gray, equalImg); //將圖像直方圖均衡化
const int channels[1] = { 0 };
float inRanges[2] = { 0,255 };
const float* ranges[1] = { inRanges };
const int bins[1] = { 256 };
calcHist(&gray, 1, channels, Mat(), hist, 1, bins, ranges);
calcHist(&equalImg, 1, channels, Mat(), hist2, 1, bins, ranges);
drawHist(hist, NORM_INF, "hist");
drawHist(hist2, NORM_INF, "hist2");
imshow("原圖", gray);
imshow("均衡化后的圖像", equalImg);
int k = waitKey(0); // Wait for a keystroke in the window
return 0;
}
5.直方圖規(guī)定化(直方圖匹配)
直方圖均衡化函數(shù)可以自動(dòng)地改變圖像直方圖的分布形式,這種方式極大地簡(jiǎn)化了直方圖均衡化過程中需要的操作步驟,但是該函數(shù)不能指定均衡化后的直方圖分布形式。在某些特定條件下,需要將直方圖映射成指定的分布形式,這種將直方圖映射成指定分布形式的算法成為直方圖規(guī)定化或直方圖匹配。
直方圖規(guī)定化與直方圖均衡化相似,都是對(duì)圖像的直方圖分布形式進(jìn)行改變,只是直方圖均衡化后的圖像是均勻分布的,而直方圖規(guī)定化后的直方圖可以任意指定,即在執(zhí)行直方圖規(guī)定化操作時(shí),首先要知道變換后的灰度直方圖分布形式,進(jìn)而確定變換函數(shù),直方圖規(guī)定化有目的地增強(qiáng)某個(gè)灰度區(qū)間。
不同圖像間像素?cái)?shù)目不同,為了使兩個(gè)圖像直方圖能夠匹配,需要使用概率形式表示每個(gè)灰度值在圖像像素中所占的比例。在理想狀態(tài)下,經(jīng)過圖像直方圖匹配操作后,圖像直方圖分布形式應(yīng)于目標(biāo)分布一致,因此兩者間的累積概率(小于等于某一灰度值的像素?cái)?shù)目占所有像素的比例)分布也一致。
用
V
s
V_s
Vs? 表示原圖像直方圖的各個(gè)灰度級(jí)的累積概率,用
V
z
V_z
Vz? 表示匹配后直方圖的各個(gè)灰度級(jí)累積概率,由原圖像中灰度值
n
n
n 映射成
r
r
r 的條件:
n
,
r
=
a
r
g
min
?
n
,
r
∣
V
s
(
n
)
?
V
z
(
n
)
∣
n,r = arg \min_{n,r} |V_s(n) - V_z(n)|
n,r=argn,rmin?∣Vs?(n)?Vz?(n)∣
下面說明直方圖匹配過程:
運(yùn)算 | 步驟和結(jié)果 | |||||||
---|---|---|---|---|---|---|---|---|
原圖像灰度級(jí) | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
原直方圖概率 | 0.19 | 0.24 | 0.2 | 0.17 | 0.09 | 0.05 | 0.03 | 0.02 |
原直方圖累積概率 | 0.19 | 0.43 | 0.63 | 0.8 | 0.89 | 0.94 | 0.97 | 0.99 |
目標(biāo)直方圖概率 | 0 | 0 | 0 | 0.16 | 0.19 | 0.29 | 0.2 | 0.16 |
目標(biāo)直方圖累積概率 | 0 | 0 | 0 | 0.16 | 0.35 | 0.64 | 0.84 | 1 |
匹配的灰度值 | 3 | 4 | 5 | 6 | 6 | 7 | 7 | 7 |
映射關(guān)系 | 0->3 | 1->4 | 2->5 | 3->6 | 4->6 | 5->7 | 6->7 | 7->7 |
1.計(jì)算原直方圖概率
2.計(jì)算原直方圖累積概率
3.計(jì)算目標(biāo)直方圖概率
4.計(jì)算目標(biāo)直方圖累積概率
5.確定映射:
原直方圖灰度值0,累積概率 V s V_s Vs? 為0.19,目標(biāo)直方圖累積概率 V z V_z Vz? 可以為0.16,0.35…,但 V s V_s Vs? 為0.19(灰度級(jí)為0)距離 V z V_z Vz? 為0.16(灰度級(jí)為3)最小,因此有映射關(guān)系:0->3
這個(gè)尋找灰度值匹配的過程是直方圖規(guī)定化的關(guān)鍵,在代碼實(shí)現(xiàn)中可以通過構(gòu)建元直方圖累積概率與目標(biāo)直方圖累積概率之間的差值表,尋找原直方圖中灰度值 n n n 的累積概率與目標(biāo)直方圖中所有灰度值累積概率差值的最小值,這個(gè)最小值對(duì)應(yīng)的灰度值 r r r 就是 n n n 匹配后的灰度值。
在OpenCV中并沒有提供直方圖匹配的函數(shù),需要自己根據(jù)算法實(shí)現(xiàn)圖像直方圖匹配。下面代碼給出了實(shí)現(xiàn)直方圖匹配的實(shí)例,該程序中待匹配的原圖是一個(gè)整體偏暗的圖像,目標(biāo)直方圖分配形式來自于一幅較為明亮的圖像,經(jīng)過圖像直方圖匹配操作之后,提高了圖像的整體亮度,圖像直方圖的分布更加均勻:
#include <opencv2/core/utils/logger.hpp>
#include <opencv2/opencv.hpp>
using namespace cv;
using namespace std;
void drawHist(Mat& hist, int type, string name) //歸一化并繪制直方圖函數(shù)
{
int hist_w = 512;
int hist_h = 400;
int width = 2;
Mat histImage = Mat::zeros(hist_h, hist_w, CV_8UC3);
normalize(hist, hist, 1, 0, type, -1, Mat());
for (int i = 1; i <= hist.rows; i++)
{
rectangle(histImage, Point(width * (i - 1), hist_h - 1),
Point(width * i - 1, hist_h - cvRound(hist_h * hist.at<float>(i - 1)) - 1),
Scalar(255, 255, 255), -1);
}
imshow(name, histImage);
}
int main()
{
cout << "OpenCV Version: " << CV_VERSION << endl;
utils::logging::setLogLevel(utils::logging::LOG_LEVEL_SILENT);
Mat img1 = imread("histMatch.png");
Mat img2 = imread("equalLena.png");
if (img1.empty() || img2.empty())
{
cout << "請(qǐng)確認(rèn)圖像文件名稱是否正確" << endl;
return -1;
}
Mat hist1, hist2;
//計(jì)算兩張圖像直方圖
const int channels[1] = { 0 };
float inRanges[2] = { 0,255 };
const float* ranges[1] = { inRanges };
const int bins[1] = { 256 };
calcHist(&img1, 1, channels, Mat(), hist1, 1, bins, ranges);
calcHist(&img2, 1, channels, Mat(), hist2, 1, bins, ranges);
//歸一化兩張圖像的直方圖
drawHist(hist1, NORM_L1, "hist1");
drawHist(hist2, NORM_L1, "hist2");
//計(jì)算兩張圖像直方圖的累積概率
float hist1_cdf[256] = { hist1.at<float>(0) };
float hist2_cdf[256] = { hist2.at<float>(0) };
for (int i = 1; i < 256; i++)
{
hist1_cdf[i] = hist1_cdf[i - 1] + hist1.at<float>(i);
hist2_cdf[i] = hist2_cdf[i - 1] + hist2.at<float>(i);
}
//構(gòu)建累積概率誤差矩陣
float diff_cdf[256][256];
for (int i = 0; i < 256; i++)
{
for (int j = 0; j < 256; j++)
{
diff_cdf[i][j] = fabs(hist1_cdf[i] - hist2_cdf[j]);
}
}
//生成LUT映射表
Mat lut(1, 256, CV_8U);
for (int i = 0; i < 256; i++)
{
// 查找源灰度級(jí)為i的映射灰度
// 和i的累積概率差值最小的規(guī)定化灰度
float min = diff_cdf[i][0];
int index = 0;
//尋找累積概率誤差矩陣中每一行中的最小值
for (int j = 1; j < 256; j++)
{
if (min > diff_cdf[i][j])
{
min = diff_cdf[i][j];
index = j;
}
}
lut.at<uchar>(i) = (uchar)index;
}
Mat result, hist3;
LUT(img1, lut, result);
imshow("待匹配圖像", img1);
imshow("匹配的模板圖像", img2);
imshow("直方圖匹配結(jié)果", result);
calcHist(&result, 1, channels, Mat(), hist3, 1, bins, ranges);
drawHist(hist3, NORM_L1, "hist3"); //繪制匹配后的圖像直方圖
int k = waitKey(0); // Wait for a keystroke in the window
return 0;
}
6.直方圖反向投影
如果一幅圖像的某個(gè)區(qū)域中顯示的是一種結(jié)構(gòu)紋理或者一個(gè)獨(dú)特的形狀,那么這個(gè)區(qū)域的直方圖就可以看作是這個(gè)結(jié)構(gòu)或者形狀的概率函數(shù),在圖像中尋找這種概率分布就是在圖像中尋找該結(jié)構(gòu)紋理或者獨(dú)特形狀。
反向投影(back projection)就是記錄給定圖像中的像素點(diǎn)如何適應(yīng)直方圖模型像素分布方式的一種方法。反射投影就是首先計(jì)算某一特征的直方圖模型,然后使用該模型去尋找是否存在該特征。
void calcBackProject(
const Mat* images,
int nimages,
const int* channels, // 前三個(gè)參數(shù)同calcHist
InputArray hist, // 模版圖像直方圖
OutputArray backProject, // 輸出結(jié)果
const float** ranges,// 同calcHist
double scale = 1, // 輸出反向投影矩陣的比例因子
bool uniform = true // 同calcHist
);
/*
該函數(shù)用于在輸入圖像中尋找與指定圖像最匹配的點(diǎn)或區(qū)域,即對(duì)圖像直方圖反向投影,輸入?yún)?shù)和calcHist相似。
關(guān)注輸入圖像和模版圖像的直方圖,返回一幅圖像。
*/
下面代碼中,首先加載待反向投影圖像和模版圖像,模版圖像從待反向投影圖像中截取。之后將兩幅圖像由RGB顏色空間轉(zhuǎn)成HSV空間,統(tǒng)計(jì)H-S通道的直方圖,將直方圖歸一化后繪制H-S通道的二維直方圖。最后將待反向投影圖像和模版圖像輸入calcBackProject函數(shù),得到對(duì)圖像直方圖反向投影結(jié)果。文章來源:http://www.zghlxwxcb.cn/news/detail-793718.html
#include <opencv2/core/utils/logger.hpp>
#include <opencv2/opencv.hpp>
using namespace cv;
using namespace std;
void drawHist(Mat& hist, int type, string name) //歸一化并繪制直方圖函數(shù)
{
int hist_w = 512;
int hist_h = 400;
int width = 2;
Mat histImage = Mat::zeros(hist_h, hist_w, CV_8UC3);
normalize(hist, hist, 1, 0, type, -1, Mat());
for (int i = 1; i <= hist.rows; i++)
{
rectangle(histImage, Point(width * (i - 1), hist_h - 1),
Point(width * i - 1, hist_h - cvRound(hist_h * hist.at<float>(i - 1)) - 1),
Scalar(255, 255, 255), -1);
}
imshow(name, histImage);
}
int main()
{
cout << "OpenCV Version: " << CV_VERSION << endl;
utils::logging::setLogLevel(utils::logging::LOG_LEVEL_SILENT);
Mat img = imread("apple.jpg");
Mat sub_img = imread("sub_apple.jpg");
Mat img_HSV, sub_HSV, hist, hist2;
if (img.empty() || sub_img.empty())
{
cout << "請(qǐng)確認(rèn)圖像文件名稱是否正確" << endl;
return -1;
}
imshow("img", img);
imshow("sub_img", sub_img);
//轉(zhuǎn)成HSV空間,提取S、V兩個(gè)通道
cvtColor(img, img_HSV, COLOR_BGR2HSV);
cvtColor(sub_img, sub_HSV, COLOR_BGR2HSV);
int h_bins = 32; int s_bins = 32;
int histSize[] = { h_bins, s_bins };
//H通道值的范圍由0到179
float h_ranges[] = { 0, 180 };
//S通道值的范圍由0到255
float s_ranges[] = { 0, 256 };
const float* ranges[] = { h_ranges, s_ranges }; //每個(gè)通道的范圍
int channels[] = { 0, 1 }; //統(tǒng)計(jì)的通道索引
//繪制H-S二維直方圖
calcHist(&sub_HSV, 1, channels, Mat(), hist, 2, histSize, ranges, true, false);
drawHist(hist, NORM_INF, "hist"); //直方圖歸一化并繪制直方圖
Mat backproj;
calcBackProject(&img_HSV, 1, channels, hist, backproj, ranges, 1.0); //直方圖反向投影
imshow("反向投影后結(jié)果", backproj);
cout << "backproj.size(): " << backproj.size() << endl;
cv::Mat imageWithMask(img.size(), img.type());
img.copyTo(imageWithMask, backproj);
imshow("使用反向投影掩碼后結(jié)果", imageWithMask);
int k = waitKey(0); // Wait for a keystroke in the window
return 0;
}
反向投影圖像給出了輸入圖像中每個(gè)像素與目標(biāo)模型的相似程度。具體來說,反向投影圖像的每個(gè)像素值表示該像素在目標(biāo)模型中的可能性或置信度。像素值越高,表示該像素更可能屬于目標(biāo)模型。上面backproj圖形是0-1二值圖,直接顯示看到的將是黑色,當(dāng)做掩膜可以看到效果。文章來源地址http://www.zghlxwxcb.cn/news/detail-793718.html
到了這里,關(guān)于OpenCV10-圖像直方圖:直方圖繪制、直方圖歸一化、直方圖比較、直方圖均衡化、直方圖規(guī)定化、直方圖反射投影的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!