1 摘要
在分析圖像、物體和視頻信息的時候,我們通常使用直方圖來表示我們關(guān)注的信息。直方圖可以描述很多不同的信息,如物體的顏色分布,物體的邊緣梯度模板,假設(shè)的物體位置的概率分布。下圖簡單演示了如何使用直方圖進行快速的手勢識別。

在該示例中模型手的邊緣梯度分布被分為5個不同的組,分別表示上、下、左、右和OK手勢。通過網(wǎng)絡(luò)攝像頭分析用戶的手勢可以實現(xiàn)用戶通過手勢控制視頻的播放。在獲取到每一幀圖像后,首先分析感興趣的顏色區(qū)域,這里是用戶的手,然計算其邊緣的梯度直方圖,再將其和已知的5類手勢直方圖模型進行匹配,找到匹配程度最高的模型,從而達到手勢識別的目的。
圖中的豎直細長矩形條表示當前幀的興趣區(qū)域邊緣直方圖和已有手勢模型直方圖的匹配程度,水平灰色線條表示匹配程度的閾值,即只有當匹配程度高于此值時這種匹配關(guān)系才是有效的。
在很多計算機視覺程序中都使用到了直方圖,如視頻數(shù)據(jù)中每幀畫面的邊緣和顏色統(tǒng)計直方圖發(fā)生顯著變化表示場景的切換。你也可以通過興趣點附近特征的直方圖為其添加標簽,從而識別興趣點。邊緣、顏色和角點等直方圖形成了目標的通用特征,這些特征可以被分類器使用從而識別目標對象。顏色和邊緣直方圖的序列也可以被用于鑒定視頻是否復(fù)制于網(wǎng)絡(luò)。類似的應(yīng)用實例還有很多,總之直方圖是計算機視覺的經(jīng)典工具。
直方圖只是簡單的將原始數(shù)據(jù)分到預(yù)先定義好的多個組中,并計算每個組內(nèi)的樣本數(shù)量。也可以先計算出原始數(shù)據(jù)中的梯度幅度、方向和顏色等特征,然后再對這些特征計數(shù)。不管是哪一種情況,直方圖獲得的都是原始數(shù)據(jù)分布的統(tǒng)計圖像。通常直方圖的維度都比原始數(shù)據(jù)更低,如下圖中左上圖展示了一個二維分布的樣本集合,通過右上圖中定義的組統(tǒng)計每個組內(nèi)樣本的數(shù)量,則得到右下圖的一維統(tǒng)計直方圖。

由于直方圖可以處理任意含義的原始數(shù)據(jù),因此它是一個用于表示圖像信息的便利工具。
在生成直方圖時可能會遇到下圖演示的問題。左側(cè)的兩幅圖像的分組過寬,則得到的結(jié)果會過于粗糙,并且丟失了數(shù)據(jù)的分布細節(jié)信息。右側(cè)的兩幅圖像的分組過細,每個組中就沒有足夠的數(shù)據(jù)點來準確的表示分布信息,并且會出現(xiàn)一些細小的突刺單元。

2 直方圖
新版本的OpenCV使用矩陣cv::Mat和稀疏矩陣cv::SparseMat對象來表示一維或者多維的直方圖,同時支持在每個維度上組的分布都可以是均勻的或者非均勻的,另外還提供了一些有用的函數(shù)來執(zhí)行常見的直方圖操作。
盡管直方圖對象使用了和圖像數(shù)據(jù)同類型的對象,盡管他們底層的數(shù)據(jù)結(jié)構(gòu)是相同的,但是它們內(nèi)部的數(shù)據(jù)含義卻不相同。對于n行矩陣表示的直方圖(N×1的矩陣除外),每個數(shù)據(jù)表示的都是在某個維度(以行索引標識)的某組數(shù)據(jù)(以列索引標識)的統(tǒng)計結(jié)果。數(shù)據(jù)組的索引和其實際含義是分離的,在使用直方圖時需要在它們之間轉(zhuǎn)換。例如,在表示人體重的直方圖中,數(shù)據(jù)組可能被分為20到40、40到60、60到80和80到100四組,它們的索引是0、1、2、3,幸運的是OpenCV中的很多函數(shù)都會在內(nèi)部執(zhí)行這種轉(zhuǎn)換邏輯。
如果需要使用高維的直方圖時,通常情況下大部分元素的值都是0,此時可以選擇更合適的數(shù)據(jù)類型cv::SparseMat。實際上該類型的設(shè)計原因就是為了更好的處理直方圖數(shù)據(jù)。稠密矩陣的大部分函數(shù)都適用于稀疏矩陣,當然我們也會介紹一些值得關(guān)注的特例。
2.1 創(chuàng)建直方圖
創(chuàng)建直方圖的函數(shù)原型如下,需要注意直方圖的維度和輸入矩陣數(shù)組的維度無關(guān),僅和其數(shù)量及矩陣的數(shù)量相關(guān),直方圖的每個維度反應(yīng)的是某個輸入矩陣的某個通道上的數(shù)據(jù)統(tǒng)計結(jié)果。當然你也可以指定應(yīng)當統(tǒng)計哪些輸入矩陣的哪些通道。
// images:C語言風格的待處理的矩陣數(shù)組,基本數(shù)據(jù)類型為8U或者32F
// nimages:輸入矩陣的個數(shù)
// channels:C語言風格的待處理通道列表,下文介紹
// mask:蒙版矩陣,只統(tǒng)計其中非0值對應(yīng)的原始數(shù)據(jù)中的點
// hist:統(tǒng)計結(jié)果直方圖
// dims:直方圖的維度,必須小于cv::MAX_DIMS(32)
// histSize:C語言風格數(shù)組,直方圖在每個維度應(yīng)當分配的數(shù)據(jù)組數(shù)
// ranges:C語言風格的數(shù)組,每個元素都是一個數(shù)組,表示在對應(yīng)維度上的分組策略,
// 具體細節(jié)和uniform相關(guān),下文介紹
// uniform:直方圖分組策略是均勻分組還是非均勻分組,下文介紹
// accumulate:在生成直方圖時是累加(false)還是清空(true)hist內(nèi)部的數(shù)據(jù)
void cv::calcHist(const cv::Mat* images, int nimages, const int* channels,
cv::InputArray mask, cv::OutputArray hist, int dims,
const int* histSize, const float** ranges, bool uniform = true,
bool accumulate = false);
// 和前一個函數(shù)類似,但是輸出數(shù)據(jù)類型為稀疏矩陣
void cv::calcHist(const cv::Mat* images, int nimages, const int* channels,
cv::InputArray mask, cv::SparseMat& hist, int dims,
const int* histSize, const float** ranges, bool uniform = true,
bool accumulate =false);
函數(shù)cv::calcHist()有三種形式,除了上文列出的形式使用C語言風格的數(shù)組作為輸入矩陣外,第三種使用STL向量模板容器作為參數(shù)類型。
函數(shù)的第一個參數(shù)images包含了構(gòu)建直方圖需要的nimages個數(shù)據(jù)矩陣。所有的矩陣尺寸必須相同,但是通道數(shù)可以不同,基本事件類型可以是8位或者32位,但是它們應(yīng)該是相同的。channels指定了輸入數(shù)組中的哪些通道應(yīng)當被處理的,通道的索引是按順序編號的,即images[0]中的第一個通道的索引是0,然后遞增,images[1]的通道索引在此基礎(chǔ)上繼續(xù)遞增??梢悦黠@看出channels的元素個數(shù)和最后得到的直方圖的維度相同。
參數(shù)ranges表示直方圖各個維度上的分組策略,數(shù)組的具體含義和參數(shù)uniform的取值相關(guān)。如果參數(shù)uniform設(shè)置為true,則每個組的區(qū)間是相同的,此時需要在參數(shù)ranges中指定每個組的下邊界和上邊界(不包含),例如ranges[i] = [0, 100.0)。如果參數(shù)uniform設(shè)置為false,如果在第i維度存在N個分組,則ranges[i]應(yīng)該包含N+1個元素,其中的第j個元素表示第j-1分組的上界,以及低j分組的下界。如果使用的參數(shù)類型為STL的vector <float>,則和c語言數(shù)組的區(qū)別在于向量類型的參數(shù)中數(shù)據(jù)都是一維的。Ranges內(nèi)數(shù)據(jù)的含義如下圖。

2.2 基礎(chǔ)直方圖操作
盡管直方圖使用的數(shù)據(jù)類型是圖像數(shù)據(jù)使用的cv::Mat,但是在OpenCV中對于直方圖含義的矩陣支持一些新的操作,在本小節(jié)將會介紹這些操作,另外也會介紹如何使用矩陣本身已經(jīng)支持的函數(shù)來完成一些重要的直方圖操作。
2.2.1 直方圖標準化
通常我們得到直方圖數(shù)據(jù)后需要將其標準化,使直方圖的每個維度上的數(shù)值都表示的是占整個直方圖的比值,其實現(xiàn)方式如下。
// 使用運算符
cv::Mat normalized = my_hist / sum(my_hist)[0];
// 使用函數(shù)
cv::normalize(my_hist, my_hist, 1, 0, NORM_L1);
2.2.2 直方圖閾值處理
另外一個很常見的操作是你希望閾值化處理一個直方圖并丟棄其中所以值低于閾值的元素,此時你可以使用處理標準矩陣的閾值函數(shù),示例代碼如下。
cv::threshold(my_hist, my_thresholded_hist, threshold, 0, cv::THRESH_TOZERO);
2.2.3 尋找最大最小值
通常在處理概率分布直方圖時,你可能并不想做閾值處理,而是像找到其中的最大或者最小值,OpenCV提供一種函數(shù)來實現(xiàn)這個功能,其中處理二維矩陣函數(shù)void cv::minMaxLoc()的原型如下。
// 處理二維矩陣的函數(shù)
// src:待查詢的矩陣
// minVal:最小值,設(shè)置為NULL時不會計算
// maxVal:最大值,設(shè)置為NULL時不會計算
// minLoc:最小值的位置,設(shè)置為NULL時不會計算
// maxLoc:最大值的位置,設(shè)置為NULL時不會計算
// mask:蒙板矩陣,數(shù)值為0的點對應(yīng)的原始矩陣在統(tǒng)計時將被忽略
void cv::minMaxLoc(cv::InputArray src, double* minVal, double* maxVal = 0,
cv::Point* minLoc = 0, cv::Point* maxLoc = 0,
cv::InputArray mask = cv::noArray());
// 處理任意維度稀疏矩陣的函數(shù)
void cv::minMaxLoc(const cv::SparseMat& src, double* minVal, double* maxVal = 0,
int* minLoc = 0, int* maxLoc = 0,
cv::InputArray mask = cv::noArray());
該函數(shù)的使用實例如下。提示:對于vector<>數(shù)組,可以通過cv::Mat(vec).reshape(1)將其轉(zhuǎn)換為通道數(shù)為1的矩陣。
// 尋找二維矩陣表示的直方圖中的最大值及其位置
double max_val;
cv::Point max_pt;
cv::minMaxLoc(my_hist, NULL, &max_val, NULL, &max_pt);
// 尋找任意維度稀疏矩陣表示的直方圖中的最大值及其位置
double maxval;
int max_pt[CV_MAX_DIM];
cv::minMaxLoc(my_hist, NULL, &max_val, NULL, max_pt);
函數(shù)cv::minMaxIdx()用于尋找任意維度矩陣中的最大最小值以及它們的位置,其函數(shù)原型如下。
void cv::minMaxIdx(cv::InputArray src, double* minVal, double* maxVal = 0,
int* minLoc = 0, int* maxLoc = 0,
cv::InputArray mask = cv::noArray());
需要注意的是如果使用了一維矩陣作為輸入函數(shù),參數(shù)minLoc和maxLoc分配的矩陣仍需要包含兩個元素,因為該函數(shù)內(nèi)部會將一維矩陣轉(zhuǎn)換為二維矩陣。如果尋找到的元素索引為k,當輸入矩陣尺寸為N??1,返回值為(k, 0),當輸入矩陣尺寸為1??N,返回值為(0, k)。
2.2.4 直方圖比較
在某種指定的標準下比較直方圖的相似性是一個必不可缺的圖像處理工具,該方法由Swain和Ballard發(fā)明,并由Schiele和Crowley加以推廣。該方法可以有很多應(yīng)用場景,可以通過該函數(shù)比較兩幅圖像的直方圖的相似性完成圖像匹配任務(wù),也可以通過直方圖比較來搜索模型。后者的做法是比較圖像不同子區(qū)域和目標模型的直方圖,通過匹配程度判斷該子區(qū)域是否包含模型。其函數(shù)原型如下。
// H1:待比較的直方圖1
// H2:待比較的直方圖2,尺寸需要和H1相同
// method:比較方法,下文介紹
double cv::compareHist(cv::InputArray H1, cv::InputArray H2,
int method);
double cv::compareHist(const cv::SparseMat& H1, const cv::SparseMat& H2,
int method);
參數(shù)method指定了直方圖相似的判斷標準,可以選擇的方法有四種。
相關(guān)性方法
定義相關(guān)性方法的值為cv::COMP_CORREL,該方法基于皮爾遜(Pearson)相關(guān)性系數(shù)實現(xiàn),當H1和H2表示概率分布時,使用該方法非常合適。距離計算的公式如下。

其中

N表示直方圖的分組數(shù)量,直方圖的匹配程度越高,使用該方法的匹配函數(shù)返回值越大。1表示完全匹配,-1表示完全不匹配,而0表示兩個分布無關(guān)系,也就是隨機組合。
Chi-square方法
定義Chi-square方法的值為cv::COMP_CHISQR_ALT,該方法基于chi-squared測試統(tǒng)計方法實現(xiàn),它同樣可以檢測兩個分布的相關(guān)性,其函數(shù)原型如下。

直方圖的匹配程度越高,該方法的到的值越低,完全匹配的結(jié)果為0,而完全不匹配的值取決于直方圖的大小。
交集法
定義交集法的值為cv::COMP_INTERSECT,該方法基于兩個直方圖的簡單交集實現(xiàn),其計算公式如下。

直方圖的匹配程度越高,該方法計算得到的值越高,如果H1和H2是兩個經(jīng)過標準化處理的直方圖,則完全匹配的結(jié)果為1,完全不匹配的結(jié)果為0。
巴氏距離
定義巴氏距離(Bhattacharyya)的值為cv::COMP_BHATTACHARYYA,也是基于兩個分布的重疊部分實現(xiàn)的一種距離計算方法,其公式如下。

直方圖的匹配程度越高,該方法計算得到的值越低,完全匹配的結(jié)果為0,完全不匹配的結(jié)果為1。盡管該方法內(nèi)部會有一個因子來對直方圖進行標準化處理,但是通常情況像你應(yīng)當自行對輸入?yún)?shù)進行標準化處理,因為類似計算直方圖交集的概念對于未標準化的直方圖是完全沒有意義的。
考慮簡單的只包含兩個分組的一維標準化直方圖,模型左側(cè)分組的值為1.0,右側(cè)分組的值為0,則使用上述4種方法計算出的匹配結(jié)果如下圖所示。仔細觀察下圖會發(fā)現(xiàn)當直方圖匹配模型只是簡單反轉(zhuǎn)時,即下圖的第2和第4行圖像,前4種方法計算得到的都是完全不匹配的結(jié)果,即使在某種程度上這兩種分布仍然有一定相似性。

EMD也是一種距離算法,在處理半匹配時該方法能夠得到更準確的結(jié)果,下文還會詳細的介紹到EMD(Earth Mover‘s Distance)算法,這里暫時先不再討論。根據(jù)該書原作者的經(jīng)驗,使用交集法處理快速粗糙的直方圖匹配效果更好,而chi-square方法和巴氏方法精度更高,但是計算成本更高,EMD算法給出的結(jié)果是最符合視覺感受的,但是它的計算速度更慢。
2.2.5 直方圖創(chuàng)建示例
示例Computation讀取了一副圖像,將其轉(zhuǎn)換為HSV顏色空間,然后統(tǒng)計器色度分量H和飽和度分量S的分布直方圖,其核心代碼如下。
int main(int argc, const char * argv[]) {
// 讀取原圖
cv::Mat src = cv::imread(argv[1], cv::IMREAD_COLOR);
// 構(gòu)建HSV顏色空間數(shù)據(jù)
cv::Mat hsv;
cv::cvtColor(src, hsv, cv::COLOR_BGR2HSV);
// 設(shè)定二維直方圖中的分組策略
// HSV標準定義色調(diào)H取值區(qū)域為[0, 360),S和V取值區(qū)域為[0, 1],而在OpenCV中為了能夠用8位
// 統(tǒng)一表示,H減半處理及,OpenCV中HSV的H值僅為實際值的一半,因此其取值范圍為[0, 180),
// 而S和V的取值為[0, 255]
float h_ranges[] = {0, 180};
float s_ranges[] = {0, 256};
const float * ranges[] = {h_ranges, s_ranges};
int histSize[] = {30, 32};
int ch[] = {0, 1};
// 計算二維直方圖
cv::Mat hist;
cv::calcHist(&hsv, 1, ch, cv::noArray(), hist, 2, histSize, ranges, true);
// 標準化處理直方圖
cv::normalize(hist, hist, 0, 255, cv::NORM_MINMAX);
// 定義單個數(shù)據(jù)點表示的方塊直徑
int scale = 10;
// 創(chuàng)建顯示二維直方圖的圖像
cv::Mat hist_img(histSize[0] * scale, histSize[1] * scale, CV_8UC3);
// 在圖像hist_img中繪制scale??scale像素的方塊表示二維直方圖hist中的每個數(shù)據(jù)點
for (int h = 0; h < histSize[0]; h++) {
for (int s = 0; s < histSize[1]; s++) {
float hval = hist.at<float>(h, s);
cv::rectangle(hist_img,
cv::Rect(h * scale, s * scale, scale, scale),
cv::Scalar::all(hval), -1);
}
}
return 0;
}
程序的運行結(jié)果如下圖。其中左圖為原始圖像,右圖為其色度和飽和度分布直方圖,不用為圖中的大部分黑色區(qū)域感到好奇,這是因為色度和飽和度的分布過于集中引起的。

在很多實際的應(yīng)用中膚色的顏色直方圖很有用,下圖包含了不同光照環(huán)境下的手掌照片及整張圖片的顏色直方圖,最左側(cè)一列是手掌的照片,中間列是BGR顏色空間的直方圖,而右側(cè)一列是HSV顏色空間的直方圖。從圖中可以看出隨著光照環(huán)境的改變,手掌的膚色也會發(fā)生一些變化。

為了測試直方圖比較的結(jié)果,從某個圖片中選擇部分區(qū)域(如室內(nèi)圖片的上部分),計算其顏色直方圖并將其和室內(nèi)環(huán)境的下半部分圖片及另外兩個光照環(huán)境下的整幅手掌圖片的顏色直方圖比較。這里選擇使用HSV顏色空間的直方圖,它可以使用色度H和飽和度S的直方圖進行膚色匹配,盡管還有變量亮度V的值未使用,但是這已經(jīng)足夠我們完成即使是跨種族的膚色匹配任務(wù)。
一個實現(xiàn)了上述膚色匹配任務(wù)的比較結(jié)果如下表。其中,室內(nèi)圖像的上半幅圖片和下半幅圖片對顏色直方圖匹配程度很高,和另外兩種光照環(huán)境下的顏色直方圖匹配程度較低。
| 比較目標 | 相關(guān)性法 | Chi-Square方法 | 交集法 | 巴氏距離 |
|---|---|---|---|---|
| 完全匹配參考目標 | (1.0) | (0.0) | (1.0) | (0.0) |
| 下半幅室內(nèi)圖片 | 0.96 | 0.14 | 0.82 | 0.2 |
| 室外陰影圖片 | 0.09 | 1.57 | 0.13 | 0.8 |
| 室外明亮圖片 | 0.0 | 1.98 | 0.01 | 0.99 |
2.3 復(fù)雜直方圖方法
在介紹了一些基礎(chǔ)的直方圖操作后,接下來將會介紹一些高級的直方圖方法,這些方法包括比較直方圖,計算或者可視化圖像的哪部分對指定部分的直方圖有貢獻。
2.3.1 EMD距離
在前面的文章中我們已經(jīng)看見光照條件的改變會導(dǎo)致明顯的顏色位移,如在前文的手掌照片及直方圖展示中就能觀察到這個現(xiàn)象。盡管這些位移不會改變顏色直方圖的形狀,但是會改變它的位置,從而使得直方圖匹配度較低。這也是直方圖匹配度一個難點,我們經(jīng)常想要找到一個距離度量標準使得形狀相同但是發(fā)生位移的直方圖分布能夠得到一個匹配的比較結(jié)果,EMD距離(Earth Mover‘s Distance)就是這樣的距離度量標準。
該方法的基本思想是通過移動部分或者整個直方圖到新的位置將其“搬到”另外一個直方圖中,并度量該操作耗費的成本。如在上文演示函數(shù)cv::compareHist()使用的簡單匹配模型結(jié)果中,該方法的完美匹配直方圖計算得到的距離為0,而對于半匹配的模型距離為0.5,而對于其中完全不匹配的情況需要將整個直方圖向由移動一步,因此得到的距離為1。
實際上EMD是一個相同通用的算法,它允許用戶自定義距離度量衡以及移動成本矩陣。你可以通過記錄數(shù)據(jù)從一個直方圖的何處移動到了另一個直方圖中的何處,并且基于數(shù)據(jù)的先驗信息(Prior Information)的到一個非線性的距離。OpenCV提供的EMD函數(shù)原型如下。
// 參數(shù)詳細含義下文介紹
// signature1:待比較的直方圖轉(zhuǎn)換的簽名格式,尺寸為sz1??dms+1
// signature2:待比較的直方圖轉(zhuǎn)換的簽名格式,尺寸為sz2??dms+1
// distType:距離類型,如cv::DIST_L1
// cost:移動成本矩陣,尺寸為sz1??sz2,當參數(shù)選擇cv::DIST_USER需要設(shè)置此參數(shù)
// lowerBound:兩個直方圖重心距離下界,可以同時作為輸入和輸出
// flow:尺寸為sz1??sz2,表示直方圖1中第I個元素流向直方圖2中第j個元素的質(zhì)量
float cv::EMD(cv::InputArray signature1, cv::InputArray signature2,
int distType, cv::InputArray cost = noArray(),
float* lowerBound = 0, cv::OutputArray flow = noArray());
在使用該函數(shù)比較直方圖距離的時候,必須將直方圖轉(zhuǎn)換為一種稱為簽名的格式,該格式需要傳入一個sz1??dms+1的矩陣,每一行數(shù)據(jù)都包含直方圖在某個坐標上的統(tǒng)計結(jié)果以及對應(yīng)的坐標。如在三維直方圖中在點(7, 43, 11)的分組的值為537,則簽名矩陣對應(yīng)的某行數(shù)據(jù)為[537, 7, 43, 11]。
參數(shù)distType表示EMD距離的度量衡,其取值在前文很多地方都已經(jīng)有過介紹,可以是曼哈頓街區(qū)距離(Manhattan Distance),取值為cv::DIST_L1,歐式距離(Euclidean Distance),取值為cv::DIST_L2,也可以是棋盤距離(Checkerboard Distance),取值為cv::DIST_C,或者是用戶自定義的距離衡,取值為cv::DIST_USER。當選擇用戶自定義距離時,需要通過參數(shù)cost指定移動成本,即自定義的移動距離。其尺寸為sz1??sz2,該矩陣的每個元素(坐標為ij)表示從直方圖1的第i個元素表示的位置到直方圖2第j個元素表示的位置之間的距離。
lowerBound可以同時作為輸入和輸出參數(shù)。作為輸出參數(shù),它表示兩個直方圖的重心(Center of Mass)距離的下界。為了計算這個下屆,必須使用標準的距離度量衡,即參數(shù)distType不能設(shè)置為cv::DIST_USER,并且兩個直方圖的總權(quán)重必須相同(經(jīng)過標準化處理的兩個直方圖總權(quán)重就是相同的,都為1)。做為輸入?yún)?shù),它必須被賦予有意義的值,即如果兩個直方圖的重心距離低于等于該值才會計算EMD距離,這種機制很有用,因為計算重心距離會比計算EMD距離快很多,如果重心距離都已經(jīng)大于指定的值了,則我們認為兩個直方圖已經(jīng)不匹配了,就沒有必要再計算其準確的EMD距離。如果想跳過此邏輯,只需將參數(shù)lowerBound的值設(shè)置為0即可。
參數(shù)flow是一個可選的sz1??sz2矩陣,其中的元素E(i, j)表示直方圖1中第i個元素流向直方圖2中第j個元素的質(zhì)量,其實該參數(shù)就是表示數(shù)據(jù)流動的過程。
示例程序EMD使用已經(jīng)介紹過的5種距離比較了前文室內(nèi)手掌圖片上半幅圖與下半幅圖,以及與其他光照條件下的整幅手掌圖,以及一副完全無關(guān)圖像的顏色直方圖的相似程度。程序運行后得到的原始圖像及其顏色直方圖表示如下。

距離比較結(jié)果如下表。這里在進行前四種方法比較直方圖時標準化的區(qū)域為0到255,使用EMD比較直方圖距離時的標準化方式是直方圖的所有元素數(shù)據(jù)權(quán)重和為1。這里和前文原書中給出的比較結(jié)果不同,推測應(yīng)當是標準化策略以及圖像選取的范圍有差異。
| 比較目標 | 相關(guān)性法 | Chi-Square方法 | 交集法 | 巴氏距離 | EMD距離 |
|---|---|---|---|---|---|
| 下半幅室內(nèi)圖片 | 0.501425 | 1648.11 | 301.039 | 0.55207 | 1.52593 |
| 室外陰影圖片 | 0.459135 | 244318 | 633.146 | 0.449522 | 2.73921 |
| 室外明亮圖片 | 0.676008 | 35634.4 | 756.639 | 0.408152 | 1.80114 |
| 完全無關(guān)的水果圖片 | 0.0938923 | 3380830 | 389.745 | 0.689889 | 2.8382 |
2.3.2 反向投影
反向投影(Back Projection)可以判斷像素集合與指定的直方圖表示的顏色分布的匹配程度。例如假定我們有膚色的顏色直方圖,可以通過該技術(shù)尋找圖像中和膚色匹配的區(qū)域。從統(tǒng)計學(xué)的角度看,如果將已知的直方圖分布看作是在特定目標上的顏色分布的先驗概率分布(Prior Probability Distribution),則反向投影就是計算圖像中的任意區(qū)域符合該先驗概率分布的概率,也就是說屬于目標物體的概率。實際上反向投影就是計算每個像素在直方圖分布對應(yīng)分組中的計數(shù)值。
OpenCV提供了兩個函數(shù)分別用于處理密集矩陣和稀疏矩陣,其函數(shù)原型如下。直方圖中只包含了維度、維度分組及每個分組的統(tǒng)計結(jié)果,但是不包含分組的區(qū)間,所以需要額外的參數(shù)ranges確定。
// images:反向投影的目標查詢數(shù)組矩陣,單通道8U或者三通道32F,所以矩陣大小類型必須相同,
// 通道數(shù)可以不同
// nimages:images中包含矩陣的個數(shù)
// channels:需要比較的通道索引,索引的編號規(guī)則和函數(shù)cv::calcHist()相同,該數(shù)組的格式和
// 參數(shù)hist表示的直方圖的維度相同
// hist:比較的直方圖矩陣
// backProject:反向投影的結(jié)果,和參數(shù)images中的矩陣大小和數(shù)據(jù)類型相同,單通道
// ranges:直方圖每個維度的分組策略,和函數(shù)cv::calcHist()相同
// scale:輸出結(jié)果的縮放系數(shù),通常反向投影得到的數(shù)據(jù)值都較低,有時適當放大結(jié)果可視化效果更好
// uniform:分組策略是否為均勻分布,和函數(shù)cv::calcHist()相同
void cv::calcBackProject(const cv::Mat* images, int nimages, const int* channels,
cv::InputArray hist, cv::OutputArray backProject,
const float** ranges,
double scale = 1, bool uniform = true);
void cv::calcBackProject(const cv::Mat* images, int nimages, const int* channels,
const cv::SparseMat& hist, cv::OutputArray backProject,
const float** ranges,
double scale = 1, bool uniform = true);
void cv::calcBackProject(cv::InputArrayOfArrays images,
const vector<int>& channels,
cv::InputArray hist, cv::OutputArray backProject,
const vector<float>& ranges,
double scale = 1, bool uniform = true);
如果調(diào)用該函數(shù)使用的直方圖是經(jīng)過標準化處理的,則反向投影的結(jié)果就是某個像素是直方圖表示的分布中的一部分的概率。即對于前文講到的膚色示例而言,如果C是某個像素的顏色值,而F表示該像素屬于皮膚的概率。則可以通過p(C|F)表示在皮膚中像素C出現(xiàn)的概率,這和p(F|C)表示的含義不同,后者表示顏色C屬于皮膚的概率,也是反向投影在某種程度上表示的含義。但是這兩個概率可以通過如下貝葉斯公式計算,其中p(F)表示場景中指定目標的累計概率,p(C)表示場景中顏色C的累計概率,當然對于直方圖而言這是顏色區(qū)間的累計概率。

下圖使用膚色直方圖計算了一副圖像中像素顏色屬于皮膚的概率,直方圖計算的是HSV顏色空間中的色度H和飽和度S。其中左上角圖片為皮膚模型的顏色直方圖,可以用于計算上述公式中的p(C|F),右上側(cè)圖像為測試圖像,左下圖為測試圖像的顏色直方圖,右下角圖像是根據(jù)該顏色直方圖進行反向投影得到的結(jié)果,也就是上述公式中的p(F|C)。需要注意這里進行反向投影時傳入的直方圖為皮膚模型的顏色直方圖。

通常情況下你可以通過如下三步來尋找感興趣的區(qū)域。首先創(chuàng)建需要尋找的目標或者區(qū)域的直方圖。然后使用該直方圖計算待查詢圖像的后向投影,其中較高的值表示和感興趣區(qū)域匹配程度更高。最后根據(jù)后向投影中的高值取原圖的部分區(qū)域,再計算其直方圖和目標區(qū)域的直方圖進行比較。
需要注意的是如果后向投影圖的基本數(shù)據(jù)類型為cv::U8,不要對直方圖進行標準化,對于已經(jīng)標準化的直方圖要進行放大處理,因為標準后后的直方圖中的最大值為1,在cv::U8數(shù)據(jù)類型中除了1都會被轉(zhuǎn)換為0。
3 模版匹配
模版匹配不依賴于直方圖,相反其實現(xiàn)方式是通過一個圖像塊在輸入圖像上滑動,匹配的方法下文介紹,一個模版匹配的示例如下圖。其中左上角圖片表示了待匹配目標HSV顏色模型下的HS直方圖。而右側(cè)圖像是需要查詢的圖片,其中白色方塊表示滑動圖像塊的大小,這里需要查找的是咖啡杯。左下圖為待查詢圖像的HS直方圖,而右下圖為測試圖像應(yīng)用模版匹配的結(jié)果,可以明顯看出咖啡杯已經(jīng)被挑選出來。

另外一個模版匹配的場景如下圖,這里用的是一副包含人臉的圖像滑塊,通過在輸入圖像滑動該滑塊可以在整個圖像中尋找到最到最好的匹配效果來確定人臉的存在。

OpenCV提供的模版匹配函數(shù)如下。
// image:待查詢的圖片,8U或者32F的灰度或者彩色圖像,大小為W??H
// templ:圖像滑塊,大小為w??h
// result:模版匹配的結(jié)果,單通道,數(shù)據(jù)類型為32F,大小為W-w+1??H-h+1
// method:模版匹配使用的方法,下文介紹
void cv::matchTemplate(cv::InputArray image, cv::InputArray templ,
cv::OutputArray result, int method);
參數(shù)method指定了模版匹配的方法,可選值如下,在下面的公式中將使用I表示輸入圖像,T表示圖像滑塊,R表示模版匹配的結(jié)果。每一種方法都有一個標準化版本,因為不同光照環(huán)境下的數(shù)據(jù)標準化后系數(shù)相同,可以排除光照條件帶來的干擾。
3.1 方差匹配方法
該方法取值為cv::TM_SQDIFF,比較的是兩個矩陣的方差,得到的值越小表示越相似,其公式如下。

3.2 歸一化方差匹配
歸一化方差匹配的取值為cv::TM_SQDIFF_NORMED,完全匹配情況下計算得到的值為0,其公式如下。

3.3 相關(guān)性匹配
相關(guān)性匹配方法的取值為cv::TM_CCORR,該方法以乘法的方式匹配模版,越匹配的矩陣計算結(jié)果越大,完全不匹配的矩陣計算結(jié)果為0,其公式如下。

3.4 歸一化相關(guān)性匹配
歸一化相關(guān)性匹配的取值為cv::TM_CCORR_NORMED,極度不匹配的矩陣計算結(jié)果趨于0,其公式如下。

3.5 相關(guān)性系數(shù)匹配
相關(guān)性系數(shù)匹配的取值為cv::TM_CCOEFF,它比較的是兩個矩陣中元素和矩陣本身均值差值的相關(guān)性,完美匹配矩陣的計算結(jié)果為1,完全不匹配矩陣的計算結(jié)果為-1,0表示無相關(guān)性,其公式如下。

3.6 歸一化相關(guān)系數(shù)匹配
歸一化相關(guān)系數(shù)匹配的取值為cv::TM_CCOEFF_NORMED,較好的匹配矩陣計算結(jié)果是較大正值,而匹配程度很差的矩陣計算結(jié)果是較大的負值,其公式如下,其中T’和I’計算方式通相關(guān)性系數(shù)匹配方法。

使用相對復(fù)雜的匹配方法(相關(guān)性系數(shù)匹配)替換相對簡單的匹配方法(如方差匹配)將會得到更準確的匹配結(jié)果,但是會花費更多的計算成本。最后嘗試所有的方法,并在最終的應(yīng)用程序中權(quán)衡計算成本和結(jié)果精度選擇最合適的方法。需要注意的是除了方差匹配和歸一化方差匹配對于越好的匹配得到的計算結(jié)果越小,其他方法都是相反的。
當模版匹配完成后,可以通過函數(shù)cv::minMaxLoc()或者cv::minMaxIdx()尋找最佳匹配結(jié)果的位置。一個好的匹配模式應(yīng)當是一個局部區(qū)域的像素點都取得較好的匹配結(jié)果,因為模版在像素點臨域內(nèi)滑動時,模版覆蓋區(qū)域內(nèi)像素點輕微改變通常不會使得匹配結(jié)果相差太多。同時為了避免圖像噪聲引起的異常高匹配,可以輕微的平滑模版匹配得到的結(jié)果再尋找極值。在這種場景下,前文介紹的圖像形態(tài)學(xué)的方法能夠發(fā)揮很好的作用。
示例程序Template實現(xiàn)了不同方法的模版匹配,其核心代碼如下。
int main(int argc, const char * argv[]) {
// 讀取匹配模版圖像
cv::Mat templ = cv::imread(argv[1], 1);
// 讀取待查找圖像
cv::Mat src = cv::imread(argv[2], 1);
// 使用6種不同的方法執(zhí)行模版匹配操作
cv::Mat ftmp[6];
for (int i = 0; i < 6; ++i) {
cv::matchTemplate( src, templ, ftmp[i], i);
cv::normalize(ftmp[i],ftmp[i],1,0,cv::NORM_MINMAX);
}
return 0;
}
示例程序使用的模版圖像和待查找的圖像如下圖,其中左側(cè)圖像是使用的模版滑塊,右側(cè)圖像是待查找的圖像。

6種不同的模版匹配方法得到的效果圖如下,需要注意方差法和歸一化方差法的最佳匹配計算結(jié)果為0,其他方法則剛好相反。因此在下圖中第一列的兩幅圖片中的黑色區(qū)域為匹配好的區(qū)域,而右側(cè)兩列圖片中亮色區(qū)域為匹配程度較高的區(qū)域。

4 小結(jié)
本章介紹了在OpenCV中如何使用矩陣和稀疏矩陣對象表示直方圖,在實際應(yīng)用中直方圖通常表示為概率密度函數(shù),即其內(nèi)部的每個元素表示其對應(yīng)分組在整個隨機變量分布中的概率。此外還介紹了如何使用直方圖識別物體和興趣區(qū)域。另外本章也介紹了直方圖的基本操作,當直方圖被看作是概率密度函數(shù)時這些操作通常能發(fā)揮較大的作用。例如直方圖的標準化,以及比較直方圖。在本章的最后,我們討論了模版匹配,該技術(shù)能很好的處理高度結(jié)構(gòu)化的圖片。