calcHist函數在Opencv中是極難理解的一個函數,一方面是參數說明晦澀難懂,另一方面,說明書給出的實例也不足以令人完全搞清楚該函數的使用方式。最難理解的是第6,7,8個參數dims、histSize和ranges。以前一直都是想當然認為,該函數可以一次統計多張圖片每個通道的灰度值數據,實際上calcHist函數一次只能統計一個通道上的直方圖。我估計許多同學都犯過和我類似的錯誤,認為第5個參數hist,可以根據dims設定維度,比如dims=3,則輸出的hist的維度就是3,並且會想當然的認為這個三維矩陣會保存三個通道上的直方圖統計,悲哀的是,錯了。它實際上是在三維坐標上統計的直方圖,比如以第0個通道的灰度值有效統計范圍(比如0~255)作為縱坐標,類似於笛卡爾坐標的y軸;以第1個通道灰度值統計有效范圍作為橫坐標,類似於笛卡爾坐標的x軸;以第2個通道的灰度值有效統計范圍作為z軸。這三個軸坐標作為統計的依據。比如坐標(6,7,8),表示統計滿足“0通道上灰度值為1;1通道上灰度為7;2通道上灰度為8的像素”,在整個圖像上的像素個數。
當然,當dims=1時,就好理解了,就是對某一個單一通道上的灰度值直方圖統計。
還有很重要一點,那就是,calcHist函數有bug。當圖像尺度小於9*12,直方圖均勻分布,並且直方條個數histSize為4時,會有統計錯誤,在后面的實例中我們會看到。
calcHist函數的聲明如下:
OpenCV提供了兩個重載的calcHist函數,它可以計算一系列陣列的直方圖,這些系列通常是圖像或像平面。它最多可以同時處理32個圖像。
C++: void calcHist(const Mat* images, int nimages, const int* channels, InputArray mask, OutputArray hist, int dims, const int* histSize, const float** ranges, bool uniform=true, bool accumulate = false )
C++: void calcHist(const Mat* images, int nimages, const int* channels, InputArray mask, SparseMat& hist, int dims, const int* histSize, const float** ranges, bool uniform = true, bool accumulate= false )
參數說明:
images – 源圖像數組,它們有同樣的位深CV_8U或 CV_32F ,同樣的尺寸;圖像陣列中的每一個圖像都可以有任意多個通道;
nimages – 源圖像的數目。
channels – 維度通道序列,第一幅圖像的通道標號從0~image[0].channels( )-1。Image[0]表示圖像數組中的第一幅圖像,channels()表示該圖像的通道數量。同理,圖像陣列中的第二幅圖像,通道標號從image[0].channerls( )開始,到image[1].channels( )-1為止;第三、四幅圖像的通道標號順序依此類推;也就是說圖像陣列中的所有圖像的通道根據圖像排列順序,排成一個通道隊列。
mask – 可選擇的mask。如果該矩陣不空的話,它必須是一個8-bit的矩陣,與images[i]同尺寸。在圖像中,只有被mask覆蓋的區域的像素才參與直方圖統計。如果這個參數想用默認值,輸入Mat()就可以了。
hist – 輸出直方圖, 它是一個稠密或稀疏矩陣,具有dims個維度;
dims – 直方圖的維度,一定是正值, CV_MAX_DIMS(當前OpenCV版本是32個);
histSize – 數組,即histSize[i]表示第i個維度上bin的個數;這里的維度可以理解為通道。
ranges – 當uniform=true時,ranges是多個二元數組組成的數組;當uniform=false時,ranges是多元數組組成的數組。當在每個維度(或通道)上每個直方條等寬時,即uniform=true時,灰度值的有效統計范圍的下界用L0表示,上界用UhistSize[i]-1表示,角標中的i表示第i個維度(或通道),上下界值可以表示為hrange[i]={ L0, UhistSize[i]-1}, 在統計時, L0和UhistSize[i]-1不在統計范圍內。而ranges={ hrange[0], hrange[1], …… , hrange[dims]}。ranges的元素個數由參數dims決定。
其中,L0表示在該通道上第0個直方條(bin)的下邊界,UhistSize[i]-1表示最后一個直方條histSize[i]-1的上邊界。在該維度上直方條的個數為histSize[i],如hrange[0]={ L0, UhistSize[0]},hrange[1]={ L1, UhistSize[1]}, hrange[2]={ L2, UhistSize[2]}, …… , hrange[dims]={ L0, UhistSize[0]}。
當uniform=false時,ranges中的每個元素ranges[i]都是一個多元數組,元素個數為histSize[i]+1,它們分別是:L0 , U0=L1, U1= L2, …… ,UhistSize[i]-2 , LhistSize[i]-1 , UhistSize[i]-1 。所以,ranges[i]={ L0 , L1, L2, …… , LhistSize[i]-1 ,UhistSize[i]-1}
uniform – 標識,用於說明直方條bin是否是均勻等寬的。
accumulate – 累積標識。如果該項設置,當直方圖重新分配時,直方圖在開始清零。這個特征可以使你通過幾幅圖像,累積計算一個簡單的直方圖,或者及時更新直方圖。
函數calcHist可以計算一幅或多幅圖像的直方圖。在元組中增量一個直方圖的時候,就是從輸入圖像組中的原位置提取一幅圖像,並計算出它的直方圖,並添加到元組中。
當參數dims>1時,輸出矩陣Hist是二維矩陣。
calcHist示例:單通道灰度圖像
int main() { Mat hist; //這是一個9行12列的單通道圖像 Mat img=(Mat_<uchar>(9,12) <<1,2,3,4,5,6,7,8,9,10,11,12, 1,2,3,4,5,6,7,8,9,10,11,12, 1,2,3,4,5,6,7,8,9,10,11,12, 1,2,3,4,5,6,7,8,9,10,11,12, 1,2,3,4,5,6,7,8,9,10,11,12, 1,2,3,4,5,6,7,8,9,10,11,12, 1,2,3,4,5,6,7,8,9,10,11,12, 1,2,3,4,5,6,7,8,9,10,11,12, 1,2,3,4,5,6,7,8,9,10,11,12);
//灰度值有效范圍是1~12,所以設定為hr1[]={1,13};將灰度范圍均分為3部分,三個直方條取值范圍分別為:第1部分1~4,第2部分5~8,第3部分9~12。
//因此令histSize[1]={3},這里的3意味着整個圖像灰度范圍1~12被均分為3部分,分別統計像素個數。
int histSize[1]={3}; int channels[1]={0}; float hr1[]={1,13};//矩陣b的灰度值有效范圍是1<=V<13 const float* ranges[]={hr1}; calcHist(&img,1,channels,Mat(),hist,1,histSize,ranges,true); cout<<"img="<<endl<<img<<endl; cout<<"hist="<<endl<< hist<<endl; return 0; }
輸出結果如下:

本來一切都很完美,不過可惜,該函數有bug。
calcHist函數的bug
依然以上面的示例為例,當histSize[1]={3};或{6};或{12}都不會有問題。可是當histSize[1]={2};或{4}時,雖然uniform=true,但是在整個圖像上灰度范圍上,並沒有被平均分割為2個或4個灰度直方條進行像素統計。輸出結果如下所示:
經過測試,猜測,大概直方條的寬度含有3的整數倍時,均勻直方統計就會失效。如將上例中的圖像改成9*15,灰度取值范圍1~15,直方條個數為5,即直方條的寬度為3,則該函數的均勻直方統計就會失效,如下左圖。然而,當直方條寬度為5時,則有正確輸出結果,如下右圖:

不過也有解決的辦法,就是將灰度值統計范圍的左值,也就是L0,減1,即L0=L0-1。還是以上面的例子為例,灰度值有效范圍:1~15。令bin=3,但是hr1={0,16},經驗證可以得到正確的統計結果,如下圖所示:

其他數據大家可以自行驗證,都是正確的。
