首先介紹一下直方圖
一.用帶權(quán)重的樣本統(tǒng)計(jì)直方圖
直方圖Histogram,,是一種常見的概率分布的非參數(shù)(區(qū)別于高斯分布,泊松分布等用參數(shù)表達(dá)概率密度的方法)表達(dá)方法,。直方圖可以看成概率密度分布的離散化表達(dá)方法,。它的計(jì)算很簡單,,是一種投票的方法,,就是每個樣本往對應(yīng)的小盒子(bin)里投一票,。假設(shè)N個樣本數(shù)據(jù)x量化為1~M之間的整數(shù),,那么Hist是M維數(shù)組,,對應(yīng)的直方圖計(jì)算方法如下:
//initializing for
i=1:M Hist[i] = 0; end //voting for i = 1:N Hist[x[i]]
+= 1 ; end
為了表示成概率分布,,需要Hist數(shù)組和為1:
//normalize for j =
1:M Hist[j] = Hist[j]/sum(Hist) ; //sum(Hist) = Hist[1] + Hist[2] + ... +
Hist[M]; end
在這里sum(Hist)等于樣本個數(shù)N,。以上“投票+歸一化”兩個步驟,,其實(shí)可以和成一步,,即每個樣本x[i]有一個對應(yīng)的權(quán)重w[i]=1/N,,投票的時候往小盒子里放的是權(quán)重:
//initializing for
i=1:M Hist[i] = 0; end //voting for i = 1:N Hist[x[i]]
+= w[i] ; end
這樣得到數(shù)組Hist里的每個值都是一個0-1之間的小數(shù),表示當(dāng)x取這個值(數(shù)組對應(yīng)的下標(biāo))時候的概率,。
二.樣本權(quán)重的變化影響直方圖形狀
如果想改變一個概率分布取曲線的形狀,,比如,在訓(xùn)練的時候要強(qiáng)調(diào)x[i]=3的樣本,,一種辦法是增加這種樣本的個數(shù),,使Hist[3]變大,另一種方法是增加和它對應(yīng)的樣本的權(quán)重w[i],,讓某些樣本產(chǎn)生“一個頂倆”的效果,,這兩種方式都能起到改變概率分布的作用。
對人臉檢測這類基于圖像的目標(biāo)檢測方法,需要搜集大量樣本進(jìn)行離線的訓(xùn)練,,樣本要盡可能覆蓋所有可能的情況,,從而使所提取特征的概率分布具有代表性,當(dāng)樣本數(shù)趨向于無窮大的時候,,概率分布就趨向于真實(shí)的分布情況,。但通常在實(shí)際中,樣本數(shù)總是有限的,,這時就可以通過增加某些比較重要的樣本的權(quán)重,,來達(dá)到看起來是增加了某些樣本數(shù)量的效果。
另外,,直方圖數(shù)組Hist的某個bin中的樣本權(quán)重的改變,,不應(yīng)該僅僅影響當(dāng)前的bin,也應(yīng)該對周圍相鄰的bin造成影響,。比如,,在統(tǒng)計(jì)一個地區(qū)人群的身高分布的時候,如果只有一個樣本x=1.65m,,我們可以在Hist數(shù)組中對應(yīng)1.6<=x<1.7這一個bin里投上一票,,但是1.5<=x<1.6和1.7<=x<1.8兩個bin的得票為0。其實(shí)不應(yīng)該這樣,,根據(jù)身高應(yīng)該是連續(xù)分布的這一假設(shè),,從樣本x=1.65m可以安全地推斷出世上也應(yīng)該有身高1.59和1.71的人,這樣左右兩個相鄰的bin的值就不應(yīng)為0,,應(yīng)該也受到x=1.65m這個樣本的影響,,這種影響可以假設(shè)是服從高斯分布,即越遠(yuǎn)的bin受影響越小,。這其實(shí)是另一種常見的概率密度估計(jì)的非參數(shù)方法,,kernel
density
estimation(KDE)。我們的人臉檢測應(yīng)用由于樣本足夠多,,所以沒有考慮這一方法,。也就是說,每個特征值在投票的時候,,只對一個bin起作用,,不影響相鄰的那兩個。
顏色直方圖直觀的顯示了圖像在色彩空間的分布狀況,,本文將討論在OpenCv中跟直方圖相關(guān)的一些基本操作,,包括:計(jì)算、顯示,、處理,、對比及反向投影.
直方圖的計(jì)算、顯示、處理
OpenCv中,,可以用方法cvCalcHist方法計(jì)算圖像的直方圖,。不過值得注意的是,該方法的第一個參數(shù)image是一個指向數(shù)組的IplImage*
類型指針,。這允許利用多個圖像通道,。對于多通道圖像(如HSV或者RGB),在調(diào)用函數(shù)cvCalcHist()之前,,先要調(diào)用函數(shù)cvSplit()將圖像分為單通道,,此方法與宏cvCvtPixtoPlane等同,然后再選擇需要參與直方圖計(jì)算的通道,。下面有幾段計(jì)算直方圖的代碼,,分別計(jì)算單通道(紅色)直方圖、色調(diào)和飽和度直方圖,。
如何在OpenCV中繪制一個直方圖,?下面是有關(guān)于繪制直方圖實(shí)驗(yàn)的學(xué)習(xí)總結(jié):
1.cvCreateHist(dim,&size,flag,**range,uniform):
dim為hist的維度;size為總分區(qū)間數(shù),;flag為標(biāo)記圖像是什么類型,,是CV_HIST_ARRAY,還是CV_HIST_TREE,然后就可以選擇如何保存數(shù)據(jù),如果CV_HIST_ARRAY,就用CvMatND(多維密集數(shù)組),;如果CV_HIST_TREE,就使用CvSparseMat(多維稀疏數(shù)組);range為每個維度的范圍,;uniform為歸一化標(biāo)識,,這個還不是很好理解。
2.IplImage中有一個屬性可以設(shè)置圖像的原點(diǎn)坐標(biāo),,0代表左上角,,1代表左下角。(image->origin)
3.cvCalcHist(**imge,*hist):
image為統(tǒng)計(jì)圖像,,hist為直方圖結(jié)構(gòu)體
4.獲取了直方圖信息后,,可以通過直方圖結(jié)構(gòu)體直接訪問內(nèi)部信息:
hist->type;hist->thresh[i][0]和hist->thresh[i][1]分別存儲上閥值和下閥值,。如果CV_HIST_ARRAY的話,,可以通過((CvMatND*)hist->bins)->data.fl[i]獲取統(tǒng)計(jì)值。不過OpenCV已經(jīng)提供了一個很好的方法去查詢,,cvQueryHistValue_*D(hist,d...);
5.可以通過CV_RGB(R,G,B)的宏設(shè)置RGB顏色,。
6.直方圖的繪制,使用cvQueryHistValue_*D去獲取統(tǒng)計(jì)值,,然后繪制在一個創(chuàng)建好的圖像中,。可以通過cvLine或者cvRectangle方法。
7.還可以通過設(shè)置range的范圍來獲取有需要的區(qū)域,,如果這樣的話,,最好在畫直方圖的時候,計(jì)算好每個區(qū)間的width值,。
8.cvGetMinMaxValue(hist,*min,*max,*minindex,*maxindex):
min和max必須為float類型,,minindex和maxindex為最小最大值的位置
9.cvRound方法:四舍五入
10.從HSV顏色轉(zhuǎn)換為RGB顏色:
IplImage* hsv_color = cvCreateImage(cvSize(1,1),8,3); IplImage*
rgb_color = cvCreateImage(cvSize(1,1),8,3);
cvSet2D(hsv_color,0,0,cvScalar(h*180.f/h_bins,s*255.f/s_bins,255,0)); cvCvtColor(hsv_color,rgb_color,CV_HSV2BGR); CvScalar
color = cvGet2D(rgb_color,0,0);
具體代碼如下:
View Code
1 #include "stdafx.h" 2 #include <iostream> 3 using namespace std; 4 5 6 #ifdef _CH_ 7 #pragma package <opencv> 8 #endif 9 10 #include "cv.h" 11 #include "highgui.h" 12 13 14 //色相飽和度直方圖 15 void CalcHistHs() 16 { 17 IplImage* img_source; 18 19 if (img_source = cvLoadImage("flower.jpg",1)) 20 { 21 IplImage* hsv = cvCreateImage(cvGetSize(img_source),8,3); 22 23 //rgb色彩空間轉(zhuǎn)換到hsv色彩空間 24 cvCvtColor(img_source,hsv,CV_BGR2HSV); 25 cvNamedWindow( "hsv", 1 ); 26 cvShowImage( "hsv", hsv ); 27 28 IplImage* h_plane = cvCreateImage(cvGetSize(img_source),8,1); 29 IplImage* s_plane = cvCreateImage(cvGetSize(img_source),8,1); 30 IplImage* v_plane = cvCreateImage(cvGetSize(img_source),8,1); 31 32 IplImage* planes[] ={h_plane,s_plane}; 33 //分割為單通道圖像 34 cvCvtPixToPlane(hsv,h_plane,s_plane,v_plane,0); 35 cvNamedWindow( "h_plane", 1 ); 36 cvShowImage( "h_plane", h_plane ); 37 cvNamedWindow( "s_plane", 1 ); 38 cvShowImage( "s_plane", s_plane ); 39 cvNamedWindow( "v_plane", 1 ); 40 cvShowImage( "v_plane", v_plane ); 41 42 43 //build the histogram and compute its contents 44 45 /** H 分量劃分為30個等級,S分量劃分為32個等級 */ 46 int h_bins =30, s_bins = 32; 47 48 CvHistogram* hist; 49 50 { 51 int hist_size[] = {h_bins,s_bins}; 52 float h_ranges[] = {0,180}; /* hue varies from 0 (~0°red) to 180 (~360°red again) */ 53 54 float s_ranges[] = {0,255}; /* saturation varies from 0 (black-gray-white) to 255 (pure spectrum color) */ 55 56 float* ranges[] = {h_ranges,s_ranges}; 57 58 /** 創(chuàng)建直方圖,,二維, 每個維度上均分 */ 59 hist = cvCreateHist( 60 2, // int dims 61 hist_size, // int* sizes 62 CV_HIST_ARRAY, // int type 63 ranges, // float** ranges 64 1 //uniform 65 ); 66 } 67 68 // 根據(jù)H,S兩個平面數(shù)據(jù)統(tǒng)計(jì)直方圖 69 cvCalcHist( planes, hist, 0, 0 ); 70 //歸一化處理 71 cvNormalizeHist(hist,1.0); 72 /** 獲取直方圖統(tǒng)計(jì)的最大值,,用于動態(tài)顯示直方圖 */ 73 float max_value; 74 cvGetMinMaxHistValue( hist, 0, &max_value, 0, 0 ); 75 76 int scale=10; 77 //創(chuàng)建直方圖圖像 78 /** 設(shè)置直方圖顯示圖像 */ 79 int height = 240; 80 int width = (h_bins*s_bins*6); 81 IplImage* hist_img = cvCreateImage( cvSize(width,height), 8, 3 ); 82 cvZero( hist_img ); 83 84 /** 用來進(jìn)行HSV到RGB顏色轉(zhuǎn)換的臨時單位圖像 */ 85 IplImage * hsv_color = cvCreateImage(cvSize(1,1),8,3); 86 IplImage * rgb_color = cvCreateImage(cvSize(1,1),8,3); 87 int bin_w = width / (h_bins * s_bins); 88 for(int h = 0; h < h_bins; h++) 89 { 90 for(int s = 0; s < s_bins; s++) 91 { 92 int i = h*s_bins + s; 93 /** 獲得直方圖中的統(tǒng)計(jì)次數(shù),計(jì)算顯示在圖像中的高度 */ 94 float bin_val = cvQueryHistValue_2D( hist, h, s ); 95 int intensity = cvRound(bin_val*height/max_value); 96 97 /** 獲得當(dāng)前直方圖代表的顏色,,轉(zhuǎn)換成RGB用于繪制 */ 98 cvSet2D(hsv_color,0,0,cvScalar(h*180.f / h_bins,s*255.f/s_bins,255,0)); 99 cvCvtColor(hsv_color,rgb_color,CV_HSV2BGR); 100 CvScalar color = cvGet2D(rgb_color,0,0); 101 102 cvRectangle( hist_img, cvPoint(i*bin_w,height), 103 cvPoint((i+1)*bin_w,height - intensity), 104 color, -1, 8, 0 ); 105 } 106 } 107 108 cvNamedWindow( "Source", 1 ); 109 cvShowImage( "Source", img_source ); 110 111 cvNamedWindow( "H-S Histogram", 1 ); 112 cvShowImage( "H-S Histogram", hist_img ); 113 114 115 116 117 } 118 119 } 120 121 void CalcHistRgb() 122 { 123 IplImage* img_source; 124 125 if (img_source = cvLoadImage("flower.jpg",1)) 126 { 127 IplImage* RedChannel = cvCreateImage( cvGetSize(img_source), 8, 1); 128 IplImage* GreenChannel = cvCreateImage( cvGetSize(img_source), 8, 1); 129 IplImage* BlueChannel = cvCreateImage( cvGetSize(img_source), 8, 1); 130 IplImage* alphaChannel = cvCreateImage( cvGetSize(img_source), 8, 1); 131 IplImage* gray_plane = cvCreateImage(cvGetSize(img_source),8,1); 132 133 134 //分割為單通道圖像 135 cvCvtPixToPlane(img_source,BlueChannel,GreenChannel,RedChannel,0); 136 // 顯示圖像 137 cvNamedWindow( "RedChannel", 1 ); 138 cvNamedWindow( "GreenChannel", 1 ); 139 cvNamedWindow( "BlueChannel", 1 ); 140 cvNamedWindow( "lphaChannel", 1 ); 141 142 cvShowImage( "RedChannel", RedChannel ); 143 cvShowImage( "GreenChannel", GreenChannel ); 144 cvShowImage( "BlueChannel", BlueChannel ); 145 cvShowImage( "lphaChannel", alphaChannel ); 146 147 148 cvCvtColor(img_source,gray_plane,CV_BGR2GRAY); 149 cvNamedWindow("GrayPlane",1); 150 cvShowImage("GrayPlane",gray_plane); 151 //OpenCV中不管是Windows中Load的還是攝像頭取得的都是BGR順序排列的 152 153 //然后為這四幅圖創(chuàng)建對應(yīng)的直方圖結(jié)構(gòu),。 154 int hist_size = 100; 155 156 int hist_height = 100; 157 158 float range[] = {0,255}; 159 160 float* ranges[]={range}; 161 162 CvHistogram* r_hist = cvCreateHist(1,&hist_size,CV_HIST_ARRAY,ranges,1); 163 164 CvHistogram* g_hist = cvCreateHist(1,&hist_size,CV_HIST_ARRAY,ranges,1); 165 166 CvHistogram* b_hist = cvCreateHist(1,&hist_size,CV_HIST_ARRAY,ranges,1); 167 168 CvHistogram* gray_hist = cvCreateHist(1,&hist_size,CV_HIST_ARRAY,ranges,1); 169 170 //接下來計(jì)算直方圖,創(chuàng)建用于顯示直方圖的圖像,,略去了一部分重復(fù)代碼,,以下也是 171 172 cvCalcHist(&RedChannel,r_hist,0,0); 173 cvCalcHist(&GreenChannel,g_hist,0,0); 174 cvCalcHist(&BlueChannel,b_hist,0,0); 175 cvCalcHist(&gray_plane,gray_hist,0,0); 176 cvNormalizeHist(gray_hist,1.0); 177 cvNormalizeHist(r_hist,1.0); 178 cvNormalizeHist(g_hist,1.0); 179 cvNormalizeHist(b_hist,1.0); 180 181 int scale = 2; 182 183 IplImage* hist_image = cvCreateImage(cvSize(hist_size*scale,hist_height*4),8,3); 184 185 cvZero(hist_image); 186 187 //然后開始顯示,這里對直方圖進(jìn)行了標(biāo)準(zhǔn)化處理,,不然的話無法觀察到明顯的變化,。 188 189 float r_max_value = 0; 190 float g_max_value = 0; 191 float b_max_value = 0; 192 float gray_max_value = 0; 193 cvGetMinMaxHistValue(r_hist, 0,&r_max_value,0,0); 194 cvGetMinMaxHistValue(g_hist, 0,&g_max_value,0,0); 195 cvGetMinMaxHistValue(b_hist, 0,&b_max_value,0,0); 196 cvGetMinMaxHistValue(b_hist, 0,&gray_max_value,0,0); 197 for(int i=0;i<hist_size;i++) 198 { 199 200 float r_bin_val = cvQueryHistValue_1D(r_hist,i); 201 202 int r_intensity = cvRound(r_bin_val*hist_height/r_max_value); 203 cvRectangle( 204 hist_image, 205 cvPoint(i*scale,hist_height-1), 206 cvPoint((i+1)*scale - 1, hist_height - r_intensity), 207 CV_RGB(255,0,0)); 208 209 float g_bin_val=cvQueryHistValue_1D(g_hist,i); 210 int g_intensity = cvRound(g_bin_val*hist_height/g_max_value); 211 cvRectangle( 212 hist_image, 213 cvPoint(i*scale,2*hist_height-1), 214 cvPoint((i+1)*scale - 1, 2*hist_height - g_intensity), 215 CV_RGB(0,255,0)); 216 217 float b_bin_val = cvQueryHistValue_1D(b_hist,i); 218 int b_intensity = cvRound(b_bin_val*hist_height/b_max_value); 219 cvRectangle( 220 hist_image, 221 cvPoint(i*scale,3*hist_height-1), 222 cvPoint((i+1)*scale - 1, 3*hist_height - b_intensity), 223 CV_RGB(0,0,255)); 224 225 float gray_bin_val = cvQueryHistValue_1D(gray_hist,i); 226 int gray_intensity = cvRound(gray_bin_val*hist_height/gray_max_value); 227 cvRectangle( 228 hist_image, 229 cvPoint(i*scale,4*hist_height-1), 230 cvPoint((i+1)*scale - 1, 4*hist_height - gray_intensity), 231 CV_RGB(100,100,100)); 232 233 } 234 cvNamedWindow( "Source", 1 ); 235 cvShowImage( "Source", img_source ); 236 237 cvNamedWindow( "RGB_Histogram", 1 ); 238 cvShowImage( "RGB_Histogram", hist_image ); 239 240 241 } 242 243 } 244 245 int main() 246 { 247 CalcHistRgb(); 248 CalcHistHs(); 249 cvWaitKey(0); 250 }
直方圖對比
OpenCv提供了5種對比直方圖的方式:CORREL(相關(guān))、CHISQR(卡方),、INTERSECT(相交),、BHATTACHARYYA、EMD(最小工作距離),,其中CHISQR速度最快,,EMD速度最慢且有諸多限制,但是EMD的效果最好,。世界總是充滿了矛盾,,而我們的工作就是化解矛盾,把每種方式都測試一下,,找到正好能解決問題并且速度最優(yōu)的方法,。 需要注意的是:EMD方式要求先將直方圖轉(zhuǎn)換成矩陣: EMD方法會占用很很很大量的內(nèi)存,在使用前請注意直方圖的維數(shù)及區(qū)間數(shù)目,,不然會出現(xiàn)內(nèi)存不足的異常,。關(guān)于這點(diǎn),請參看我的另一篇文章《關(guān)于使用cvCalcEMD2計(jì)算兩個直方圖間最小工作距離的限制(Why
cvCalcEMD2 Throw Insufficient Memory
Exception)》,。還有一點(diǎn)值得注意的是,,不同的對比方式對待結(jié)果的方式很不一樣,結(jié)果越大不一定說明匹配度更高,,具體請參看《學(xué)習(xí)OpenCv》這本書的相關(guān)章節(jié),。
對于直方圖的相關(guān)和相交對比,,結(jié)果值越大(即亮度較高)的地方表示匹配程度越高; 對于直方圖的卡方,、Bhattacharyya,、EMD對比,結(jié)果值越?。丛胶诎担┑牡胤奖硎酒ヅ涑潭仍礁?。
陸地移動距離EMD
光線能引起圖像顏色值的漂移,盡管這些漂移沒有改變顏色直方圖的形狀,,但是這些漂移引起了顏色值位置的變化,,從而導(dǎo)致前述匹配策略失效。如果利用顏色直方圖的距離測量來代替直方圖的匹配策略,,那么我們?nèi)匀豢梢韵裰狈綀D對比一樣對比兩個直方圖的距離,,即使第二個直方圖發(fā)生漂移,也能找到最小的距離度量,。
陸地移動距離時一種度量準(zhǔn)則,,它實(shí)際上度量的是怎樣將一個直方圖的形狀轉(zhuǎn)變?yōu)榱硪粋€直方圖的形狀,包括移動直方圖的部分或者全部到一個新的位置,,可以在任何維的直方圖上進(jìn)行這種度量,。
0表示最精確匹配,半匹配是成功將直方圖的一半轉(zhuǎn)換,,將左邊直方圖的一半轉(zhuǎn)換到下一個直方圖,。最終移動整個直方圖到右邊需要整個單位的距離(即將模板直方圖轉(zhuǎn)換為完全不匹配直方圖)。1表示完全不匹配,。
具體代碼如下:
View Code
1 int main() 2 { 3 /*CalcHistHs("flower.jpg"); 4 CvHistogram* hist1 = CalcHistRgb("flower.jpg"); 5 CvHistogram* hist2 = CalcHistRgb("flower3.jpg"); 6 7 double result = cvCompareHist(hist1,hist2,CV_COMP_CORREL); 8 cout<< result; 9 10 cvWaitKey(0);*/ 11 12 IplImage *src = cvLoadImage("flower.jpg"); 13 IplImage *hsv = cvCreateImage(cvGetSize(src),8,3); 14 cvCvtColor(src,hsv,CV_BGR2HSV);// change RGB of src to HSV of hsv 15 IplImage *h_plane = cvCreateImage(cvGetSize(src),8,1); 16 IplImage *s_plane = cvCreateImage(cvGetSize(src),8,1); 17 IplImage *v_plane = cvCreateImage(cvGetSize(src),8,1); 18 IplImage *planes[]={h_plane,s_plane}; 19 cvSplit(hsv,h_plane,s_plane,v_plane,0);//split HSV to 3 channal 20 21 //求得直方圖 22 int h_bins=30,s_bins=32; 23 CvHistogram *hist1,*hist2; 24 int size[]={h_bins,s_bins}; 25 float h_ranges[]={0,180}; 26 float s_ranges[]={0,255}; 27 float *ranges[]={h_ranges,s_ranges}; 28 29 hist1=cvCreateHist(2,size,CV_HIST_ARRAY,ranges,1); 30 cvCalcHist(planes,hist1,0,0);//只能一個通道一個通道的寫入直方圖,,所以上面分成H、S,、V 31 cvNormalizeHist(hist1,1.0);//歸一化直方圖,使所有塊的值加起來為1 32 hist2=cvCreateHist(2,size,CV_HIST_ARRAY,ranges,1); 33 cvCalcHist(planes,hist2,0,0); 34 cvNormalizeHist(hist2,1.0); 35 36 //求得signature用于比較直方圖 37 CvMat *sig1,*sig2; 38 int numrows=h_bins*s_bins; 39 sig1=cvCreateMat(numrows,3,CV_32FC1);//由于是2維圖像直方圖,,所以只需保存每個點(diǎn)的(值,,橫坐標(biāo),縱坐標(biāo))三個數(shù),,一共numrows個點(diǎn) 40 sig2=cvCreateMat(numrows,3,CV_32FC1); 41 for(int h=0;h<h_bins;h++) 42 { 43 for(int s=0;s<s_bins;s++) 44 { 45 float bin_val=cvQueryHistValue_2D(hist1,h,s); 46 cvSet2D(sig1,h*s_bins+s,0,cvScalar(bin_val)); 47 cvSet2D(sig1,h*s_bins+s,1,cvScalar(h)); 48 cvSet2D(sig1,h*s_bins+s,2,cvScalar(s)); 49 bin_val=cvQueryHistValue_2D(hist2,h,s); 50 cvSet2D(sig2,h*s_bins+s,0,cvScalar(bin_val)); 51 cvSet2D(sig2,h*s_bins+s,1,cvScalar(h)); 52 cvSet2D(sig2,h*s_bins+s,2,cvScalar(s)); 53 } 54 } 55 float emd=cvCalcEMD2(sig1,sig2,CV_DIST_L2); 56 cout<<emd;//可以清楚的看出因?yàn)槲覍Ρ韧粋€圖像直方圖,,所以得數(shù)0是對的,完全一樣 57 cvWaitKey(0); 58 return 0; 59 60 61 }
直方圖的反向投影
我對于反向投影的理解是通過顏色直方圖,,檢測圖片中的某個像素點(diǎn)的顏色是否位于直方圖中,,若位于則將其加亮(閥值化?),,通過對圖像的檢測,,得出結(jié)果圖像,。如果照著我這個理解的話,那么對同一物體的顏色的取樣點(diǎn)越多,,那么越能更好的找出目標(biāo)圖形(當(dāng)然,,這很可能也會帶來一些不需要的點(diǎn),那么這個方法應(yīng)該對于對比度較大的圖像比較有效……)
換個說法就是在反向投影中,,直方圖的作用在于提供一個比較標(biāo)準(zhǔn),,即對于要檢測的圖像來說,需要給它提供一個用于識別出它需要被識別出的部分的直方圖,。 1.反向投影的作用是什么,?
反向投影用于在輸入圖像(通常較大)中查找特定圖像(通常較小或者僅1個像素,以下將其稱為模板圖像)最匹配的點(diǎn)或者區(qū)域,,也就是定位模板圖像出現(xiàn)在輸入圖像的位置,。 2.反向投影如何查找(工作)?
查找的方式就是不斷的在輸入圖像中切割跟模板圖像大小一致的圖像塊,,并用直方圖對比的方式與模板圖像進(jìn)行比較,。
假設(shè)我們有一張100x100的輸入圖像,有一張10x10的模板圖像,,查找的過程是這樣的: (1)從輸入圖像的左上角(0,0)開始,,切割一塊(0,0)至(10,10)的臨時圖像; (2)生成臨時圖像的直方圖,; (3)用臨時圖像的直方圖和模板圖像的直方圖對比,,對比結(jié)果記為c; (4)直方圖對比結(jié)果c,,就是結(jié)果圖像(0,0)處的像素值,; (5)切割輸入圖像從(0,1)至(10,11)的臨時圖像,對比直方圖,,并記錄到結(jié)果圖像,; (6)重復(fù)(1)~(5)步直到輸入圖像的右下角。
3.反向投影的結(jié)果是什么,?
反向投影的結(jié)果包含了:以每個輸入圖像像素點(diǎn)為起點(diǎn)的直方圖對比結(jié)果,。可以把它看成是一個二維的浮點(diǎn)型數(shù)組,,二維矩陣,,或者單通道的浮點(diǎn)型圖像。 4.特殊情況怎么樣,?
如果輸入圖像和模板圖像一樣大,,那么反向投影相當(dāng)于直方圖對比。如果輸入圖像比模板圖像還小,,直接罷工~~,。 5.使用時有什么要注意的地方,?
需要注意的地方比較多,我們對照反向投影函數(shù)來說: void cvCalcBackProjectPatch(
IplImage** image, /*輸入圖像:是一個單通道圖像數(shù)組,,而非實(shí)際圖像*/ CvArr* dst,
/*輸出結(jié)果:是一個單通道32位浮點(diǎn)圖像,,它的寬度為W-w+1,高度為H-h+1,,這里的W和H是輸入圖像的寬度和高度,,w和h是模板圖像的寬度和高度*/
CvSize patch_size, /*模板圖像的大小:寬度和高度*/ CvHistogram* hist,
/*模板圖像的直方圖:直方圖的維數(shù)和輸入圖像的個數(shù)相同,,并且次序要一致,;例如:輸入圖像包含色調(diào)和飽和度,那么直方圖的第0維是色調(diào),,第1維是飽和度*/
int method,
/*對比方式:跟直方圖對比中的方式類似,,可以是:CORREL(相關(guān))、CHISQR(卡方),、INTERSECT(相交),、BHATTACHARYYA*/
float factor
/*歸一化因子,一般都設(shè)置成1,,否則很可能會出錯,;中文、英文以及各路轉(zhuǎn)載的文檔都錯了,,這個參數(shù)的實(shí)際類型是double,,而非float,我看了源代碼才搞定這個地方*/
);
還有最需要注意的地方:這個函數(shù)的執(zhí)行效率非常的低,,在使用之前尤其需要注意圖像的大小,,直方圖的維數(shù),對比方式,。如果說對比單個直方圖對現(xiàn)在的電腦來說是清風(fēng)拂面,,那么反向投影是狂風(fēng)海嘯。對于1010x1010的RGB輸入圖像,,10x10的模板圖像,,需要生成1百萬次3維直方圖,對比1百萬次3維直方圖,。
下面為具體代碼:
View Code
1 #include <cv.h> 2 #include <highgui.h> 3 4 5 int main() 6 { 7 IplImage* src = cvLoadImage("./sample1.jpg"); 8 if (src != NULL) 9 { 10 IplImage* hsv = cvCreateImage(cvGetSize(src), IPL_DEPTH_8U, 3); 11 //將bgr圖像轉(zhuǎn)化為hsv圖像 12 cvCvtColor(src, hsv, CV_BGR2HSV); 13 14 15 //創(chuàng)建3個單通道的圖像,用于將hsv圖像分離為3個通道 16 IplImage* h = cvCreateImage(cvGetSize(src), IPL_DEPTH_8U, 1); 17 IplImage* s = cvCreateImage(cvGetSize(src), IPL_DEPTH_8U, 1); 18 IplImage* v = cvCreateImage(cvGetSize(src), IPL_DEPTH_8U, 1); 19 IplImage* planes[] = {h, s}; 20 //將hsv圖像分離 21 cvCvtPixToPlane(hsv, h, s, v, 0); 22 23 24 //在h通道上設(shè)定30個劃分度,,在s通道上設(shè)定32個劃分度 25 int h_bins(30), s_bins(32); 26 CvHistogram* hist; 27 { 28 int hist_size[] = {h_bins, s_bins}; 29 float h_ranges[] = {0, 180}; 30 float s_ranges[] = {0, 255}; 31 //將h通道上的范圍,,即[0,180],以及s通道上的范圍,,即[0,255]組織成函數(shù)cvCreateHist所需 32 //的形式 33 float* ranges[] = {h_ranges, s_ranges}; 34 //創(chuàng)建一個2維的,、h維上劃分度為30,、s維上劃分度為32的均分直方圖 35 hist = cvCreateHist(2, hist_size, CV_HIST_ARRAY, ranges, 1); 36 } 37 //通過源圖像上的h與s通道上圖像數(shù)據(jù)統(tǒng)計(jì)出直方圖 38 cvCalcHist(planes, hist, 0, 0); 39 //獲取得到的直方圖的所有數(shù)據(jù)的和 40 CvScalar sc = cvSum(hist->bins); 41 float sum = sc.val[0] + sc.val[1] + sc.val[2] + sc.val[3]; 42 //歸一化直方圖 43 cvNormalizeHist(hist, 1.0); 44 //scale:每一個劃分在用于顯示的圖像上所占的像素點(diǎn)數(shù) 45 int scale = 10; 46 IplImage* img = cvCreateImage(cvSize(h_bins * scale, s_bins * scale), IPL_DEPTH_8U, 3); 47 cvZero(img); 48 49 50 //為什么要求最大值呢?由于歸一化之后的圖像中的像素點(diǎn)的值都不會大于1,,那么在人眼看來都是黑 51 //色的一片,,沒法通過圖像來直觀的感受直方圖了,所以在下邊會通過將直方圖的值和直方圖中的最大 52 //值做除法,,再乘以255,,使人眼能看出直方圖中數(shù)值的變化……如下代碼對應(yīng)直方圖2 53 float max_value = 0; 54 cvGetMinMaxHistValue(hist, NULL, &max_value, 0, 0); 55 for (int h = 0; h < h_bins; ++h) 56 { 57 for (int s = 0; s < s_bins; ++s) 58 { 59 //訪問直方圖在關(guān)于h與s所確定的顏色的數(shù)值 60 float bin_val = cvQueryHistValue_2D(hist, h, s); 61 int intensity = cvRound(bin_val * 255 / max_value); 62 cvRectangle(img, 63 cvPoint(h * scale, s * scale), 64 cvPoint((h + 1) * scale - 1, (s + 1) * scale - 1), 65 CV_RGB(intensity, intensity, intensity), 66 CV_FILLED); 67 } 68 } 69 70 71 //如下代碼對應(yīng)直方圖1 72 int height = 240; 73 int width = (h_bins*s_bins*6); 74 IplImage* hist_img = cvCreateImage( cvSize(width,height), 8, 3 ); 75 cvZero( hist_img ); 76 77 IplImage * hsv_color = cvCreateImage(cvSize(1,1),8,3); 78 IplImage * rgb_color = cvCreateImage(cvSize(1,1),8,3); 79 int bin_w = width / (h_bins * s_bins); 80 for(int h = 0; h < h_bins; h++) 81 { 82 for(int s = 0; s < s_bins; s++) 83 { 84 int i = h*s_bins + s; 85 86 float bin_val = cvQueryHistValue_2D( hist, h, s ); 87 int intensity = cvRound(bin_val*height/max_value); 88 89 cvSet2D(hsv_color,0,0,cvScalar(h*180.f / h_bins,s*255.f/s_bins,255,0)); 90 //hsv顏色轉(zhuǎn)化為bgr顏色 91 cvCvtColor(hsv_color,rgb_color,CV_HSV2BGR); 92 CvScalar color = cvGet2D(rgb_color,0,0); 93 //IplImage類型指定的圖像原點(diǎn)在左上角,所以要實(shí)現(xiàn)圖中的效果,,矩形的兩個頂角應(yīng)像下邊 94 //代碼中設(shè)置 95 cvRectangle( hist_img, cvPoint(i*bin_w,height), 96 cvPoint((i+1)*bin_w,height - intensity), 97 color, -1, 8, 0 ); 98 } 99 } 100 cvNamedWindow( "H-S Histogram", 1 ); 101 cvShowImage( "H-S Histogram", hist_img ); 102 cvNamedWindow("src"); 103 cvShowImage("src", src); 104 cvNamedWindow("img"); 105 cvShowImage("img", img); 106 107 108 IplImage* img2 = cvLoadImage("./sample2.jpg"); 109 IplImage* hsv2 = cvCreateImage(cvGetSize(img2), IPL_DEPTH_8U, 3); 110 cvCvtColor(img2, hsv2, CV_BGR2HSV); 111 IplImage* h2 = cvCreateImage(cvGetSize(img2), IPL_DEPTH_8U, 1); 112 IplImage* s2 = cvCreateImage(cvGetSize(img2), IPL_DEPTH_8U, 1); 113 IplImage* v2 = cvCreateImage(cvGetSize(img2), IPL_DEPTH_8U, 1); 114 IplImage* planes2[] = {h2, s2}; 115 cvCvtPixToPlane(hsv2, h2, s2, v2, 0); 116 //cvCvtPixToPlane(img2, h2, s2, v2, 0); 117 IplImage* backProject = cvCreateImage(cvGetSize(img2), IPL_DEPTH_8U, 1); 118 cvSetZero(backProject); 119 cvNormalizeHist(hist, sum); 120 cvCalcBackProject(planes2, backProject, hist); 121 122 123 cvNamedWindow("img2"); 124 cvShowImage("img2", img2); 125 cvNamedWindow("back project"); 126 cvShowImage("back project", backProject); 127 128 129 //釋放資源 130 cvWaitKey(0); 131 cvDestroyAllWindows(); 132 cvReleaseImage(&src); 133 cvReleaseImage(&img); 134 cvReleaseImage(&backProject); 135 cvReleaseImage(&img2); 136 cvReleaseHist(&hist); 137 } 138 139 140 return 0; 141 }
模板匹配的工作方式
模板匹配的工作方式跟直方圖的反向投影基本一樣,,大致過程是這樣的:通過在輸入圖像上滑動圖像塊對實(shí)際的圖像塊和輸入圖像進(jìn)行匹配。
假設(shè)我們有一張100x100的輸入圖像,,有一張10x10的模板圖像,,查找的過程是這樣的:
(1)從輸入圖像的左上角(0,0)開始,切割一塊(0,0)至(10,10)的臨時圖像,;(改者注:其實(shí)每次匹配都是在模板的中心點(diǎn)對應(yīng)的位置來給像素賦值,,即第一次比較應(yīng)該是將模板的(temp.width/2,temp.height/2)中心點(diǎn)開始的1/4面積同輸入圖像進(jìn)行比較,匹配得到的結(jié)果c保存到模板中心點(diǎn)所在像素值中,,具體參照《Learning
OpenCV》,,所以最終用來保留匹配結(jié)果的圖像大小應(yīng)該是size = (images->width - patch_size.x +
1,images->height - patch_size.y + 1))
(2)用臨時圖像和模板圖像進(jìn)行對比,對比結(jié)果記為c,; (3)對比結(jié)果c,,就是結(jié)果圖像(0,0)處的像素值;
(4)切割輸入圖像從(0,1)至(10,11)的臨時圖像,,對比,,并記錄到結(jié)果圖像; (5)重復(fù)(1)~(4)步直到輸入圖像的右下角,。
大家可以看到,,直方圖反向投影對比的是直方圖,而模板匹配對比的是圖像的像素值,;模板匹配比直方圖反向投影速度要快一些,,但是我個人認(rèn)為直方圖反向投影的魯棒性會更好。
模板匹配的匹配方式 在OpenCv和EmguCv中支持以下6種對比方式:
CV_TM_SQDIFF 平方差匹配法:該方法采用平方差來進(jìn)行匹配,;最好的匹配值為0,;匹配越差,匹配值越大,。
CV_TM_CCORR 相關(guān)匹配法:該方法采用乘法操作,;數(shù)值越大表明匹配程度越好。
CV_TM_CCOEFF 相關(guān)系數(shù)匹配法:1表示完美的匹配,;-1表示最差的匹配,。
CV_TM_SQDIFF_NORMED 歸一化平方差匹配法 CV_TM_CCORR_NORMED 歸一化相關(guān)匹配法
CV_TM_CCOEFF_NORMED 歸一化相關(guān)系數(shù)匹配法
根據(jù)我的測試結(jié)果來看,,上述幾種匹配方式需要的計(jì)算時間比較接近(跟《學(xué)習(xí)OpenCv》書上說的不同),我們可以選擇一個能適應(yīng)場景的匹配方式,。
具體代碼:
View Code
1 int main( int argc, char** argv ) { 2 3 IplImage *src, *templ,*ftmp[6]; //ftmp is what to display on 4 int i; 5 6 //Read in the template to be used for matching: 7 if((templ=cvLoadImage("flower1.jpg", 1))== 0) { 8 printf("Error on reading template /n"); 9 return(-1); 10 } 11 12 //Read in the source image to be searched: 13 if((src=cvLoadImage("flower.jpg", 1))== 0) { 14 printf("Error on reading src image /n"); 15 return(-1); 16 } 17 18 int patchx = templ->width; 19 int patchy = templ->height; 20 int iwidth = src->width - patchx + 1; 21 int iheight = src->height - patchy + 1; 22 for(i=0; i<6; ++i){ 23 ftmp[i] = cvCreateImage( cvSize(iwidth,iheight),32,1); 24 } 25 26 //DO THE MATCHING OF THE TEMPLATE WITH THE IMAGE 27 for(i=0; i<6; ++i){ 28 cvMatchTemplate( src, templ, ftmp[i], i); 29 // double min,max; 30 // cvMinMaxLoc(ftmp,&min,&max); 31 cvNormalize(ftmp[i],ftmp[i],1,0,CV_MINMAX); 32 } 33 //DISPLAY 34 cvNamedWindow( "Template", 0 ); 35 cvShowImage( "Template", templ ); 36 cvNamedWindow( "Image", 0 ); 37 cvShowImage( "Image", src ); 38 39 cvNamedWindow( "SQDIFF", 0 ); 40 cvShowImage( "SQDIFF", ftmp[0] ); 41 42 cvNamedWindow( "SQDIFF_NORMED", 0 ); 43 cvShowImage( "SQDIFF_NORMED", ftmp[1] ); 44 45 cvNamedWindow( "CCORR", 0 ); 46 cvShowImage( "CCORR", ftmp[2] ); 47 48 cvNamedWindow( "CCORR_NORMED", 0 ); 49 cvShowImage( "CCORR_NORMED", ftmp[3] ); 50 51 cvNamedWindow( "CCOEFF", 0 ); 52 cvShowImage( "CCOEFF", ftmp[4] ); 53 54 cvNamedWindow( "CCOEFF_NORMED", 0 ); 55 cvShowImage( "CCOEFF_NORMED", ftmp[5] ); 56 57 58 //LET USER VIEW RESULTS: 59 cvWaitKey(0); 60 61 return 0; 62 }
感謝您耐心看完本文,,希望對您有所幫助。
|