opencv6.4-imgproc圖像處理模塊之直方圖與模板


opencv6.3-imgproc圖像處理模塊之邊緣檢測

九、直方圖的相關操作

直方圖是圖像中像素強度分布的圖形表達方式;它統計了每一個強度值所具有的像素個數


圖是一個灰色圖像,通過對圖像的每個不同值進行統計個數,得到了右邊的直方圖,這是圖像操作中算是最簡單的了,因為最簡單,泛化很好,但是效果也只能呵呵了。不過簡單的如果兩幅圖的對比強烈,那么采用直方圖對比分類也算是最簡單的了。

1、均衡化

直方圖均衡化是通過拉伸像素強度分布范圍來增強圖像對比度的一種方法。說得更清楚一些, 以上面的直方圖為例, 你可以看到像素主要集中在中間的一些強度值上. 直方圖均衡化要做的就是 拉伸 這個范圍. 見下面左圖: 綠圈圈出了 少有像素分布其上的 強度值. 對其應用均衡化后, 得到了中間圖所示的直方圖. 均衡化的圖像見下面右圖:

  均衡化指的是把一個分布 (給定的直方圖) 映射 到另一個分布 (一個更寬更統一的強度值分布), 所以強度值分布會在整個范圍內展開.

 要想實現均衡化的效果, 映射函數應該是一個 累積分布函數 (cdf) (更多細節, 參考*學習OpenCV*). 對於直方圖H(i), 它的 累積分布H'(i)是:


要使用其作為映射函數, 我們必須對最大值為255 (或者用圖像的最大強度值) 的累積分布 H'(i)進行歸一化. 同上例, 累積分布函數為:


最后, 我們使用一個簡單的映射過程來獲得均衡化后像素的強度值:


 /// 轉為灰度圖
  cvtColor( src, src, CV_BGR2GRAY );

  /// 應用直方圖均衡化
  equalizeHist( src, dst );
先轉換成灰度圖然后在進行直方圖均衡化

函數原型:void equalizeHist(InputArray src, OutputArray dst);

參數列表:輸入圖像、輸出圖像

第一個參數:單通道8-bit圖像

第二個參數:與原圖像有着一樣的尺寸和類型。

內部操作過程:1、計算原圖像的直方圖;2、歸一化這個直方圖,使得直方圖條狀圖的和為255;3、計算積分,也就是上面說的累積分布函數;4、使用H'來作為一個查找表來轉換這個圖像,即

note:該函數是歸一化圖像的亮度和增加對比度。

2、計算

這里介紹如何計算上面的直方圖:

直方圖是對數據的集合 統計 ,並將統計結果分布於一系列預定義的 bins 中。

這里的 數據 不僅僅指的是灰度值 (如上面說的), 統計數據可能是任何能有效描述圖像的特征。

先看一個例子吧。 假設有一個矩陣包含一張圖像的信息 (灰度值0-255):


如果我們按照某種方式去 統計 這些數字,會發生什么情況呢? 既然已知數字的 范圍 包含 256 個值, 我們可以將這個范圍分割成子區域(稱作bins), 如:


然后再統計掉入每一個 bin 的像素數目。采用這一方法來統計上面的數字矩陣,我們可以得到下圖( x軸表示 第幾個bin, y軸表示各個bin中的像素個數)

    以上只是一個說明直方圖如何工作以及它的用處的簡單示例。直方圖可以統計的不僅僅是顏色灰度, 它可以統計任何圖像特征 (如 梯度, 方向等等)。

讓我們再來搞清楚直方圖的一些具體細節:

     dims: 需要統計的特征的數目, 在上例中, dims = 1 因為我們僅僅統計了灰度值(灰度圖像)。

     bins: 每個特征空間 子區段 的數目,在上例中, bins = 16

     range: 每個特征空間的取值范圍,在上例中, range = [0,255]

    怎樣去統計兩個特征呢? 在這種情況下, 直方圖就是3維的了,x軸和y軸分別代表一個特征, z軸是掉入 組合中的樣本數目。 同樣的方法適用於更高維的情形 (當然會變得很復雜)。OpenCV提供了一個簡單的計算數組集(通常是圖像或分割后的通道)的直方圖函數 calcHist 。 支持高達 32 維的直方圖。

/// 分割成3個單通道圖像 ( R, G 和 B )
 vector<Mat> rgb_planes;
 split( src, rgb_planes );

 /// 設定bin數目
 int histSize = 255;//這里是一個bin占了一個像素

 /// 設定取值范圍 ( R,G,B) )
 float range[] = { 0, 255 } ;//這是RGB模型,不是HSV等模型。
 const float* histRange = { range };//范圍數組

 bool uniform = true; bool accumulate = false;//把bin范圍設定成同樣大小(均一);以及開始統計前先清除直方圖中的痕跡

 Mat r_hist, g_hist, b_hist;//創建儲存R、G、B三個通道的直方圖矩陣

 /// 計算直方圖:
 calcHist( &rgb_planes[0], 1, 0, Mat(), r_hist, 1, &histSize, &histRange, uniform, accumulate );
 calcHist( &rgb_planes[1], 1, 0, Mat(), g_hist, 1, &histSize, &histRange, uniform, accumulate );
 calcHist( &rgb_planes[2], 1, 0, Mat(), b_hist, 1, &histSize, &histRange, uniform, accumulate );

 // 創建直方圖畫布
 int hist_w = 400; int hist_h = 400;
 int bin_w = cvRound( (double) hist_w/histSize );

 Mat histImage( hist_w, hist_h, CV_8UC3, Scalar( 0,0,0) );//顯示不同直方圖的畫布

 /// 將直方圖歸一化到范圍 [ 0, histImage.rows ],用 normalize 歸一化直方圖,這樣直方圖bin中的值就被縮放到指定范圍
//歸一化的原因在於之前的calcHist是歸一化每個bin的范圍,而這里是為了讓兩幅直方圖圖能夠在同一個畫布上顯示
 normalize(r_hist, r_hist, 0, histImage.rows, NORM_MINMAX, -1, Mat() );
 normalize(g_hist, g_hist, 0, histImage.rows, NORM_MINMAX, -1, Mat() );
 normalize(b_hist, b_hist, 0, histImage.rows, NORM_MINMAX, -1, Mat() );

 /// 在直方圖畫布上畫出直方圖
用了以下表達式:
   r_hist.at<float>(i):`i` 指示維度,假如我們要訪問2維直方圖,我們就要用到這樣的表達式:   r_hist.at<float>( i, j )
 for( int i = 1; i < histSize; i++ )   {
     line( histImage, Point( bin_w*(i-1), hist_h - cvRound(r_hist.at<float>(i-1)) ) ,//上一個末端作為當前的起始端  
                   Point( bin_w*(i), hist_h - cvRound(r_hist.at<float>(i)) ),//當前所需要的末端,作為下一個的起始端   
                   Scalar( 0, 0, 255), 2, 8, 0  );//顏色及線條的粗度等信息    
     line( histImage, Point( bin_w*(i-1), hist_h - cvRound(g_hist.at<float>(i-1)) ) ,  
                    Point( bin_w*(i), hist_h - cvRound(g_hist.at<float>(i)) ),     
                   Scalar( 0, 255, 0), 2, 8, 0  );   
     line( histImage, Point( bin_w*(i-1), hist_h - cvRound(b_hist.at<float>(i-1)) ) ,   
                   Point( bin_w*(i), hist_h - cvRound(b_hist.at<float>(i)) ),       
                  Scalar( 255, 0, 0), 2, 8, 0  );    } /// 顯示直方圖 
namedWindow("calcHist Demo", CV_WINDOW_AUTOSIZE ); 
imshow("calcHist Demo", histImage );
上面的代碼是實現進行一張RGB的圖像的直方圖計算,所以1、首先是使用split()分成不同的通道;2、使用calcHist()函數來計算直方圖;3、設定好顯示直方圖畫布的寬和高;4、歸一化每個直方圖;5、使用line()函數來畫線。

直方圖計算的函數原型: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 );

參數列表:輸入圖像、圖像的數量、通道、可選的掩碼、輸出直方圖、維數、直方圖尺寸、范圍、統一標識、累加標識;

第一個參數:輸入的圖像數組,所以才是指針類型,其中每個元素都具有相同的深度CV_8U或者CV_32F,同時它們也是相同的尺寸,不過每       個元素可以有任意的通道數;例如Mat array[] = {mat1,mat2,mat3};等等

第二個參數:輸入圖像的個數;(個人:即第一個數組中幾個mat,即幾張圖像)

第三個參數:用於計算直方圖的維數通道列表,也是指針類型。第一個數組通道是從0 到images[0].channels()-1,第二個數組通道是從         images[0].channels()到images[0].channels()+images[1].channels()-1以此類推。(個人:可以看出與第一個參數對比,這里說的是每個圖像的通道數的排序,如果輸入是2副圖像,第一副圖像有0,1,2共三個channel,第二幅圖像只有0一個channel,那么輸入就一共有4個channes,如果int channels[3] = {3, 2, 0},那么就表示是使用第二副圖像的第一個通道和第一副圖像的第2和第0個通道來計算直方圖

第四個參數:可選的掩碼,如果矩陣不是空的,那么必須是一個與images[i]有着同樣的尺寸,而且是8-bit的數組。非0掩碼元素在直方圖中會與數組元素一起計算(一般是1,所以如果顯示這個矩陣,會發現和純黑一樣,可以通過閾值的方式放大不同的部分,而且這里是InputArray類型,雖不能用它來聲明一個變量,但是通過對他傳遞Mat,std::vector<>,Matx<>,Vec<>,或者Scalar都是可以的,它是“代理”類,所以這里可以是Vector<Mat>類型傳遞給它)。

第五個參數:輸出直方圖,可以是密集或者是稀疏 dims-維度的數組。需要注意這里可以使用Mat類型,然后如果第六個參數是大於2的,那么這里就能發現這個矩陣也是dim維度的。

第六個參數:直方圖的維度必須是正的而且不大於CV_MAX_DIMS(在3.0和之前的版本中,這個值都是32).(這里指輸出的直方圖上bin的維數,下面的附帶代碼中維度是2,說明同時考慮h和s,即統計的時候是按照(bin_h1,bin_s1)這個塊中多少個像素點落在這個上面這時候Mat的行和列表是bin的不同維度軸上的標識,里面的元素表是落在這個上面的個數,這樣的以此類推)。

第七個參數:在每個維度上直方圖尺寸的數組;即每個維度的bin個數。

第八個參數:在每個維度上直方圖bin邊界的dims數組的數組(每個維度的取值范圍)。當直方圖歸一化了(uniform = true),那么對於第 i 個維度指明第0 個直方圖bin的下限(包括)L_0和最后一個直方圖bin_histSIze[i]-1的上限(不包含)U_histSize[i]-1。也就是說在,ranges[i]的每個歸一化直方圖中都是一個2元素的數組(如下面附帶代碼的hranges和sranges中的2個元素,歸一化會直接讓每個bin的大小相同,也就是均分)。當直方圖沒有歸一化(uniform= false)ranges[i]的每一個直方圖都包含histSize[i]+1個元素: 。(例如第2維上設置了5個bin,那么需要設定6個值,假設自己設定為{1,3,5,7,9,11},那么bin的大小就為【1,3】,【3,5】,【5,7】,【7,9】,【9,11】完全自己指定)其中不在L_0和U_histSize[i]-1之間的元素都不會計算在直方圖中。

第九個參數:標識,用來指示直方圖是否需要歸一化;即bin大小相同。是歸一化,那么就第八個參數每個維度上只需要設定上限和下限就行,會自動通過第七個參數每個維度上bin的個數來均分的。

第十個參數:累加標識。如果為true的話,直方圖不清除在開始時候的分配。這個功能能夠讓你從幾個數組集合中計算一個單一的直方圖,或者及時的更新直方圖。

這個函數calcHist計算一個或多個數組的直方圖。一個元組的元素用來增加在同一個位置上對應於輸入數組得到的直方圖bin。

為了加深理解,下面是另一個對該函數的使用代碼:

cvtColor(src, hsv, COLOR_BGR2HSV);


int hbins = 30, sbins = 32;// Quantize the hue to 30 levels and the saturation to 32 levels
int histSize[] = {hbins, sbins};
float hranges[] = { 0, 180 };// hue varies from 0 to 179, see cvtColor
float sranges[] = { 0, 256 };
const float* ranges[] = { hranges, sranges };
MatND hist;//typedef Mat MatND;同一個東西罷了
int channels[] = {0, 1};
// we compute the histogram from the 0-th and 1-st channels
calcHist( &hsv, 1, channels, Mat(), // do not use mask
                 hist, 2, histSize, ranges,
                 true, // the histogram is uniform
                 false );
double maxVal=0;
minMaxLoc(hist, 0, &maxVal, 0, 0);//留待以后介紹????
int scale = 10;
Mat histImg = Mat::zeros(sbins*scale, hbins*10, CV_8UC3);
for( int h = 0; h < hbins; h++ )
    for( int s = 0; s < sbins; s++ )
{
            float binVal = hist.at<float>(h, s);
                int intensity = cvRound(binVal*255/maxVal);
        rectangle( histImg, Point(h*scale, s*scale),
                            Point( (h+1)*scale - 1, (s+1)*scale - 1),
                            Scalar::all(intensity),
                            CV_FILLED );
}
namedWindow( "Source", 1 );
imshow( "Source", src );
namedWindow( "H-S Histogram", 1 );
imshow( "H-S Histogram", histImg );

歸一化的函數原型:void normalize(InputArray src, InputOutputArray dst, double alpha=1, double beta=0, int norm_type  = NORM_L2, int dtype=-1, InputArray mask=noArray() );

參數列表:輸入數組、輸出數組、alpha值、beta值、歸一化類型、目標圖像深度的類型、可選操作掩碼;

第一個參數:輸入數組;

第二個參數:與輸入數組有同樣尺寸的輸出數組;

第三個參數:用於歸一化的范數或者是歸一化范圍內的下限值;

第四個參數:歸一化范圍內的上限值;不用來作為范數歸一化;

第五個參數:當為負的時候,輸出數組有着與原數組一樣的size;不然它有着與輸入數組一樣的通道數而且depth = CV_MAT_DEPTH(dtype);

第六個參數:可選的操作掩碼。

這個函數歸一化 縮放並且平移輸入數組的元素,使得當normType = NORM_INF,L1,NORM_L2,的時候, p = Inf,1,或者2


;或者當normTpye=NORM_MINMXA(只對密集數組有用) 


。可選的掩碼指定一個子數組進行歸一化。也就是說norm或者min-n-max可以基於這個子數組計算得到,然后這個子數組來被歸一化。如果只想計算這個norm或者min-max 但是卻想修改整個數組,你可以使用norm()和MAT::convertTo()。

在稀疏矩陣中,只有非0值被分析和轉換。而且稀疏矩陣可以平移0 level,所以稀疏矩陣的范圍轉換不被允許。

3、對比

要比較兩個直方圖( H1 和 H2 ), 首先必須要選擇一個衡量直方圖相似度的 對比標准 () 。

OpenCV 函數 compareHist 執行了具體的直方圖對比的任務。該函數提供了好幾種對比標准來計算相似度,不同版本的個數不同,在3.0beta中提供了6種,在2.49版本中提供了5種:

a、Correlation ( CV_COMP_CORREL ):


其中:


 N是直方圖中bin的數目。

b、Chi-Square ( CV_COMP_CHISQR )


c、Alternative Chi-Square (method=CV_COMP_CHISQR_ALT)

 

     這個 alternative 公式 通常用來作為紋理對比 

c、Intersection ( CV_COMP_INTERSECT )


d、Bhattacharyya 距離( CV_COMP_BHATTACHARYYA或者CV_COMP_HELLINGER ),在opencvn中計算的hellinger距離是與             bhattacharyya系數相關的


e、Kullback-Leibler divergence (method=CV_COMP_KL_DIV).



Mat src_base, hsv_base;//聲明原圖像和轉換到hsv的圖像
Mat hsv_half_down;//提取轉換后hsv下半部分的圖像
cvtColor( src_base, hsv_base, CV_BGR2HSV );//rgb轉換到hsv空間
hsv_half_down = hsv_base( Range( hsv_base.rows/2, hsv_base.rows - 1 ), Range( 0, hsv_base.cols - 1 ) );//提取下半部分
/// 對hue通道使用30個bin,對saturatoin通道使用32個bin
  int h_bins = 50; int s_bins = 60;
  int histSize[] = { h_bins, s_bins };

  // hue的取值范圍從0到256, saturation取值范圍從0到180
  float h_ranges[] = { 0, 256 };
  float s_ranges[] = { 0, 180 };

  const float* ranges[] = { h_ranges, s_ranges };

  // 使用第0和第1通道
  int channels[] = { 0, 1 };
/// 直方圖 其實MatND就等於Mat
  MatND hist_base;
  MatND hist_half_down;
/// 計算HSV圖像的直方圖
  calcHist( &hsv_base, 1, channels, Mat(), hist_base, 2, histSize, ranges, true, false );
  normalize( hist_base, hist_base, 0, 1, NORM_MINMAX, -1, Mat() );

  calcHist( &hsv_half_down, 1, channels, Mat(), hist_half_down, 2, histSize, ranges, true, false );
  normalize( hist_half_down, hist_half_down, 0, 1, NORM_MINMAX, -1, Mat() );

  ///應用不同的直方圖對比方法
  for( int i = 0; i < 4; i++ )
     { int compare_method = i;
       double base_base = compareHist( hist_base, hist_base, compare_method );
       double base_half = compareHist( hist_base, hist_half_down, compare_method );

     }
上面的代碼就是個計算直方圖相關性的例子,其中 會預料到當將基准圖像直方圖及其自身進行對比時會產生完美的匹配,與來源於同一樣的背景環境的半身圖對比時應該會有比較高的相似度, 當與來自不同亮度光照條件的其它圖像(這里未列出)對比時匹配度應該不是很好:
(最后兩列不再代碼里面,這個是為了說明四種標准的評判趨勢的,)
對於 Correlation 和 Intersection 標准, 值越大相似度越大。*基准 - 基准* 的對比結果值是最大的, 而 基准 - 半身 的匹配則是第二好(跟我們預測的一致);而另外兩種對比標准,則是結果越小相似度越大。
range函數原型

class CV_EXPORTS Range
{
public:
Range();
Range(int _start, int _end);
Range(const CvSlice& slice);
int size() const;
bool empty() const;
static Range all();
operator CvSlice() const;
int start, end;
};
這個類是用來指定在矩陣(Mat)行或者列的范圍的,Range(a,b)就是在Matlab中的a:b或者是Python中的 a..b。在Python中這是類似【a,b)這樣的范圍的。

其中的靜態方法Range::all()返回一個特殊的值,用來表示“整個序列”或者“整個范圍”,就像在matlab中的“:”或者Python中的“...”。在opencv中所有的方法和函數使用Range的話也支持Range::all()。不過在用戶自定義的處理方法中,需要顯式的檢查和處理

void my_function(..., const Range& r, ....)
{
   if(r == Range::all()) {
         // process all the data
     }
    else {
        // process [r.start, r.end)
     }
}
直方圖對比的函數原型:double compareHist(InputArray H1, InputArray H2, int method)
                      double compareHist(const SparseMat& H1, const SparseMat& H2, int method)

參數列表:第一個要對比的直方圖、第二個要對比的直方圖、方法;

第一個參數:略

第二個參數:與第一個參數需要有相同的size

第三個參數:方法:

      – CV_COMP_CORREL Correlation
      – CV_COMP_CHISQR Chi-Square
      – CV_COMP_CHISQR_ALT Alternative Chi-Square (來自3.0beta)
      – CV_COMP_INTERSECT Intersection
      – CV_COMP_BHATTACHARYYA Bhattacharyya distance
      – CV_COMP_HELLINGER Synonym for CV_COMP_BHATTACHARYYA
      – CV_COMP_KL_DIV Kullback-Leibler divergence; (來自3.0beta)

該函數包含兩個原型,對應密集矩陣和稀疏矩陣。

當與1,2,3維度的密度直方圖的時候,這個函數的效果還是很不錯的,但是不適合在維度特別的大的時候。因為這樣的直方圖的aliasing和sampling的問題(也就是別名和采樣的問題,別名可參考搜索“__restrict__”,通過指定唯一訪問數據的方法就是該指針,從而讓編譯器能夠毫無顧及的將所有該數據出現的地方的公共子表達部分進行優化,減少指令的數量)非零直方圖bins的坐標系有可能會有輕微的平移。為了對比這樣的直方圖或者更通用的帶有權值點的稀疏結構,可以考慮使用EMD()函數。

4、反向投影

反向投影是一種記錄給定圖像中的像素點如何適應直方圖模型像素分布的方式。簡單的講, 所謂反向投影就是首先計算某一特征的直方圖模型,然后使用模型去尋找圖像中存在的該特征例如, 你有一個膚色直方圖 ( Hue-Saturation 直方圖 ),你可以用它來尋找圖像中的膚色區域:

這里通過使用膚色直方圖為例來解釋反向投影的工作原理:

a、假設你已經通過下圖得到一個膚色直方圖(Hue-Saturation), 旁邊的直方圖就是 模型直方圖 ( 代表手掌的皮膚色調).你可以通過掩碼操作來抓取手掌所在區域的直方圖:


b、下圖是另一張手掌圖(測試圖像) 以及對應的整張圖像的直方圖:


c、我們要做的就是使用 模型直方圖 (代表手掌的皮膚色調) 來檢測測試圖像中的皮膚區域。以下是檢測的步驟:

      i)對測試圖像中的每個像素 p(i,j),獲取色調數據並找到該色調(h_ij,s_ij)在直方圖中的bin的位置。

      ii)查詢 模型直方圖 中對應的bin -(h_ij,s_ij) - 並讀取該bin的數值。

      iii)將此數值儲存在新的圖像中(BackProjection)。 你也可以先歸一化 模型直方圖 ,這樣測試圖像的輸出就可以在屏幕顯示了。

     iiii)通過對測試圖像中的每個像素采用以上步驟, 我們得到了下面的 BackProjection 結果圖:

     iiiii)使用統計學的語言, BackProjection 中儲存的數值代表了測試圖像中該像素屬於皮膚區域的 概率 。比如以上圖為例, 亮起的區域是皮膚區域的概率更大(事實確實如此),而更暗的區域則表示更低的概率(注意手掌內部和邊緣的陰影影響了檢測的精度)。

。這里的一個保證是之前提取的模型是對的,那么假設其顏色區域的直方圖范圍為200-230之間的直方圖的bin,然后讀取的當前像素位置的值在去查找模型的值,模型中那個對應的bin是20,也就是有20個像素點落在這里,然后在測試圖像剛剛那個像素點位置上填上20,所以結果就是不再模型中的直方圖。例如灰度或者顏色的部分就被忽略了。乍一看是很好,不過覺得這個魯棒性肯定很差。理解這個的突破點在於最后得到的那個backprojection是個統計矩陣。相比較背景來說,顏色不同亮度不同所以可以被忽略。

下面的例子是簡單的示例,如果想更復雜的有使用 H-S 直方圖和 floodFill 來定義皮膚區域的掩碼);或者查看camshiftdemo例子。

/// 全局變量
Mat src; Mat hsv; Mat hue;
int bins = 25;
 /// 轉換到 HSV 空間
  cvtColor( src, hsv, CV_BGR2HSV );
  /// 分離 Hue 通道
  hue.create( hsv.size(), hsv.depth() );//創建個空值矩陣
  int ch[] = { 0, 0 };
  mixChannels( &hsv, 1, &hue, 1, ch, 1 );//抽取 HSV圖像的0通道(Hue)
  MatND hist;
  int histSize = MAX( bins, 2 );//#  define MAX(a,b)  ((a) < (b) ? (b) : (a))
  float hue_range[] = { 0, 180 };
  const float* ranges = { hue_range };

  /// 計算直方圖並歸一化
  calcHist( &hue, 1, 0, Mat(), hist, 1, &histSize, &ranges, true, false );
  normalize( hist, hist, 0, 255, NORM_MINMAX, -1, Mat() );

  /// 計算反向投影
  MatND backproj;
  calcBackProject( &hue, 1, 0, hist, backproj, &ranges, 1, true );

  /// 顯示反向投影
  imshow( "BackProj", backproj );

  /// 顯示直方圖
  int w = 400; int h = 400;
  int bin_w = cvRound( (double) w / histSize );
  Mat histImg = Mat::zeros( w, h, CV_8UC3 );

  for( int i = 0; i < bins; i ++ )
     { rectangle( histImg, 
                 Point( i*bin_w, h ), 
                 Point( (i+1)*bin_w, h - cvRound( hist.at<float>(i)*h/255.0 ) ), 
                 Scalar( 0, 0, 255 ), -1 ); }

  imshow( "Histogram", histImg );
}
抽取通道函數的原型:void mixChannels(const Mat* src, size_t nsrcs, Mat* dst, size_t ndsts, const int* fromTo, size_t npairs);
                                        void mixChannels(InputArrayOfArrays src, InputOutputArrayOfArrays dst, const int* fromTo, size_t npairs);
                                        void mixChannels(InputArrayOfArrays src, InputOutputArrayOfArrays dst, const std::vector<int>&fromTo);

參數列表:src:輸入的數組或者是矩陣向量;其中所有的矩陣必須有着相同的size和depth;

nsrcs:在src中的矩陣的個數;

dst:輸出的數組或者是矩陣向量;所有的矩陣必須已經被預分配了空間;他們的size和depth必須和src【0】中的一致

ndst:在dst中的矩陣的個數;

fromTo:索引對的數組,用來指定哪個通道被復制和復制到哪里;fromTo[k*2]在src中是一個基於0(以0作為開始)的輸入通道的索引;fromTo[k*2+1]在dst中是作為輸出通道的索引;可以使用連續的通道數字;第一個輸入圖像的通道由0到src【0】.channels()-1,第二個輸入圖像的通道由src【0】.channels()到src【0】.channels()+src【1】.channels()-1,以此類推。同樣的方案可以用來指定輸出圖像的通道;作為一個特別的例子,當fromTo[k*2]是負的時候,對應的輸出通道可以用0來填充。

npairs:在fromTo中索引對的個數。

該函數提供了一個打亂圖像通道的高級機制,而split()和merge()以及cvtColot()的部分格式是對於mixChannels()的特殊情況。下面的代碼是將一個4通道的RGBA划分成一個3通道的BGR(這里R和B通道替換了)和一個獨立的alpha通道圖像:

Mat rgba( 100, 100, CV_8UC4, Scalar(1,2,3,4) );
Mat bgr( rgba.rows, rgba.cols, CV_8UC3 );
Mat alpha( rgba.rows, rgba.cols, CV_8UC1 );

    // forming an array of matrices is a quite efficient operation,
    // because the matrix data is not copied, only the headers
Mat out[] = { bgr, alpha };
    // rgba[0] -> bgr[2], rgba[1] -> bgr[1],
    // rgba[2] -> bgr[0], rgba[3] -> alpha[0]
int from_to[] = { 0,2, 1,1, 2,0, 3,3 };
mixChannels( &rgba, 1, out, 2, from_to, 4 );;
反向投影的函數原型:void calcBackProject(const Mat* images, int nimages, const int* channels, InputArray hist, OutputArray backProject, const float** ranges, double scale=1, bool uniform= true );
                                        void calcBackProject(const Mat* images, int nimages, const int* channels, const SparseMat& hist, OutputArray backProject, const float** ranges, double scale=1, bool uniform= true );

參數列表:images:原數組,它們都具有相同的depth,CV_8U或者是CV_32F,並且具有相同的size,每張圖像可以有任意的通道數;

                    nimages:原圖像的個數;

                    channels:通道列表,用來計算反向投影的。通道數必須匹配直方圖的維度數。第一個數組通道由0到images[0].channels()-1,第二個數組通道         由images[0].channels()到images[0].channels()+images[1].channels()-1,以此類推;

                    hist:輸入的直方圖,可以是密集或者稀疏的;

                   backProject:目標反向投影數組,必須是和images[0]一樣size和depth的單通道數組;

                   ranges:每個維度上直方圖bin邊界數組的數組,可見calcHist();

                   scale:可選的為了輸出反向投影的縮放因子;

                  uniform:標識,用來指示直方圖是否需要歸一化。

函數calcBackProject是計算直方圖的反向投影。與calcHist相似的,在每個位置上(x,y),該函數計算來自輸入圖像被選取通道的值並查找對應的直方圖的bin。不過不是對bin的值進行增加,而是讀取bin的值,並通過scale進行縮放,和存儲在backProject(x,y)。統計學觀點來說,這個函數是通過對表示的直方圖的經驗性概率分布來計算每個元素值的概率。例如,你可以在一個場景中追蹤一個具有亮顏色的對象:

    a、在追蹤之前,將對象進行拍照並且使得對象幾乎能夠占滿整個圖像幀。計算hue直方圖。這個直方圖會有很強的最大值,對應着對象中的領域顏色;

    b、在追蹤的時候,使用預計算過的直方圖來計算每個輸入視頻幀的hue平面的反向投影。使用閾值反向索引來壓縮弱顏色。對於沒有足夠的顏色飽和度的壓縮像素和太黑或者太亮的像素來說都是很有意義的。

    c、查找相連的成分生成圖像,並選定最大的成分。

這是CamShift()顏色對象追蹤的一個相似的算法。

十、模板匹配

模板匹配是一項在一幅圖像中尋找與另一幅模板圖像最匹配(相似)部分的技術.;

我們需要2幅圖像:

           原圖像 (I): 在這幅圖像里,我們希望找到一塊和模板匹配的區域

           模板 (T): 將和原圖像比照的圖像塊

我們的目標是檢測最匹配的區域:


為了確定匹配區域, 我們不得不滑動模板圖像和原圖像進行 比較 :

    通過 滑動, 我們的意思是圖像塊一次移動一個像素 (從左往右,從上往下). 在每一個位置, 都進行一次度量計算來表明它是 “好” 或 “壞” 地與那個位置匹配 (或者說塊圖像和原圖像的特定區域有多么相似).

   對於 T 覆蓋在 I 上的每個位置,你把度量值 保存 到 結果圖像矩陣 (R) 中. 在 R 中的每個位置(x,y)都包含匹配度量值:

    上圖就是 TM_CCORR_NORMED 方法處理后的結果圖像 R . 最白的位置代表最高的匹配. 正如您所見, 紅色橢圓框住的位置很可能是結果圖像矩陣中的最大數值, 所以這個區域 (以這個點為頂點,長寬和模板圖像一樣大小的矩陣) 被認為是匹配的.

    實際上, 我們使用函數 minMaxLoc 來定位在矩陣 R 中的最大值點 (或者最小值, 根據函數輸入的匹配參數) .

OpenCV通過函數 matchTemplate 實現了模板匹配算法. 可用的方法有6個:

 下面的符號:I表示image,T表示template,R表示result。得到的結果為x' = 0,...,w-1;y' = 0,...,h-1。

a、平方差匹配 method=CV_TM_SQDIFF:這類方法利用平方差來進行匹配,最好匹配為0.匹配越差,匹配值越大.


b、標准平方差匹配 method=CV_TM_SQDIFF_NORMED

c、相關匹配 method=CV_TM_CCORR:這類方法采用模板和圖像間的乘法操作,所以較大的數表示匹配程度較高,0標識最壞的匹配效果.


d、標准相關匹配 method=CV_TM_CCORR_NORMED


e、相關匹配 method=CV_TM_CCOEFF:這類方法將模版對其均值的相對值與圖像對其均值的相關值進行匹配,1表示完美匹配,-1表示糟糕的匹配,0表示沒有任何相關性(隨機序列).


這里:


f、標准相關匹配 method=CV_TM_CCOEFF_NORMED


    通常,隨着從簡單的測量(平方差)到更復雜的測量(相關系數),我們可獲得越來越准確的匹配(同時也意味着越來越大的計算代價). 最好的辦法是對所有這些設置多做一些測試實驗,以便為自己的應用選擇同時兼顧速度和精度的最佳方案.

       在該函數完成對比之后,最好的匹配可以使用函數minMaxLoc()查找全局最小值(CV_TM_SQDIFF)或者最大值(CV_TM_CCORR、CV_TM_CCOEFF)。在一個顏色圖像中,基於所有的通道計算分子中的模板總和和分母中每個和,不過對於每個通道使用的是分離的均值。也就是說,這個函數可以采用一個顏色模板和一個彩色圖像。這個結果仍然是一個單通道圖像,這也更容易理解。

/// 載入原圖像和模板塊
  img = imread( );
  templ = imread( );
/// 將被顯示的原圖像
  Mat img_display;
  img.copyTo( img_display );

  /// 創建輸出結果的矩陣,用來輸出匹配結果,這里很像cnn中的卷積層中的特征子圖
  int result_cols =  img.cols - templ.cols + 1;
  int result_rows = img.rows - templ.rows + 1;

  result.create( result_cols, result_rows, CV_32FC1 );

  /// 進行匹配和標准化
  matchTemplate( img, templ, result, match_method );
  normalize( result, result, 0, 1, NORM_MINMAX, -1, Mat() );

  /// 通過函數 minMaxLoc 定位最匹配的位置
  double minVal; double maxVal; Point minLoc; Point maxLoc;
  Point matchLoc;

  minMaxLoc( result, &minVal, &maxVal, &minLoc, &maxLoc, Mat() );

  /// 對於方法 SQDIFF 和 SQDIFF_NORMED, 越小的數值代表更高的匹配結果. 而對於其他方法, 數值越大匹配越好
  if( match_method  == CV_TM_SQDIFF || match_method == CV_TM_SQDIFF_NORMED )
    { matchLoc = minLoc; }
  else
    { matchLoc = maxLoc; }

  /// 讓我看看您的最終結果
  rectangle( img_display, matchLoc, Point( matchLoc.x + templ.cols , matchLoc.y + templ.rows ), Scalar::all(0), 2, 8, 0 );
  rectangle( result, matchLoc, Point( matchLoc.x + templ.cols , matchLoc.y + templ.rows ), Scalar::all(0), 2, 8, 0 );

  imshow( image_window, img_display );
  imshow( result_window, result );
上圖就是使用圖像匹配的例子代碼,其中在<tpyes_c.h>中method的定義如下:enum
{
    CV_TM_SQDIFF        =0,
    CV_TM_SQDIFF_NORMED =1,
    CV_TM_CCORR         =2,
    CV_TM_CCORR_NORMED  =3,
    CV_TM_CCOEFF        =4,
    CV_TM_CCOEFF_NORMED =5
};

匹配模板的函數原型:void matchTemplate(InputArray image, InputArray templ, OutputArray result, int method);

參數列表;輸入圖像、輸入模板、輸出結果、方法選項

第一個參數:所需要搜尋的圖像,必須是8-bit或者32-bit的浮點(floating-point);

第二個參數:搜尋的模板。必須是不大於原圖像,而且需要有同樣的數據類型;

第三個參數:對比的結果映射圖。必須是單通道32-bit的浮點類。如果image是W×H,而templ是w×h,那么result就是(W-w+1)×(H-h+1);

第四個參數:指定具體的方法。

尋找最大值的函數原型:void minMaxLoc(InputArray src, double* minVal, double* maxVal=0, Point* minLoc=0, Point* maxLoc=0, InputArray mask= noArray() );
                                           void minMaxLoc(const SparseMat& a, double* minVal, double* maxVal, int* minIdx=0, int* maxIdx=0 );

參數列表:src:輸入的單通道數組;

                   minVal:指向返回的最小值的指針;當不需要的時候賦值NULL即可;

                  maxVal:指向返回的最大值的指針;當不需要的時候賦值NULL即可;

                  minLoc:指向返回的最小值的位置(在2D情況中);當不需要的時候賦值NULL即可;

                  maxLoc:指向返回的最大值的位置(在2D情況中);當不需要的時候賦值NULL即可;

                 mask:可選的用來選擇一個子數組。

該函數用來查找最小和最大的元素值,並給出它們的位置。極端的情況就是如果mask是一個空矩陣,或者非空,但是在特定的數組區域中,那么就需要搜索整個數組。該函數不能與多通道數組一起工作。如果想要在所有的通道中找最小和最大值,那么使用Mat::reshape()先將涉及到的數組轉換成單通道;或者你使用extractImageCOI(),或者mixChannels(),或者split()來先提取特定的通道。






免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM