OpenCV之imgproc 模块. 图像处理(4)直方图均衡化 直方图计算 直方图对比 反向投影 模板匹配
直方圖均衡化
目標
在這個教程中你將學到:
- 什么是圖像的直方圖和為什么圖像的直方圖很有用
- 用OpenCV函數?equalizeHist?對圖像進行直方圖均衡化
原理
圖像的直方圖是什么?
- 直方圖是圖像中像素強度分布的圖形表達方式.
- 它統計了每一個強度值所具有的像素個數.
直方圖均衡化是什么?
- 直方圖均衡化是通過拉伸像素強度分布范圍來增強圖像對比度的一種方法.
- 說得更清楚一些, 以上面的直方圖為例, 你可以看到像素主要集中在中間的一些強度值上. 直方圖均衡化要做的就是?拉伸?這個范圍. 見下面左圖: 綠圈圈出了?少有像素分布其上的?強度值. 對其應用均衡化后, 得到了中間圖所示的直方圖. 均衡化的圖像見下面右圖.
直方圖均衡化是怎樣做到的?
-
均衡化指的是把一個分布 (給定的直方圖)?映射?到另一個分布 (一個更寬更統一的強度值分布), 所以強度值分布會在整個范圍內展開.
-
要想實現均衡化的效果, 映射函數應該是一個?累積分布函數 (cdf)?(更多細節, 參考*學習OpenCV*). 對于直方圖?, 它的累積分布??是:
要使用其作為映射函數, 我們必須對最大值為255 (或者用圖像的最大強度值) 的累積分布??進行歸一化. 同上例, 累積分布函數為:
-
最后, 我們使用一個簡單的映射過程來獲得均衡化后像素的強度值:
例程
-
咋個例程是用來干嘛的?
- 加載源圖像
- 把源圖像轉為灰度圖
- 使用OpenCV函數?EqualizeHist?對直方圖均衡化
- 在窗體中顯示源圖像和均衡化后圖像.
-
下載例程: 點擊?這里
-
例程一瞥:
說明
聲明原圖和目標圖以及窗體名稱:
Mat src, dst;char* source_window = "Source image"; char* equalized_window = "Equalized Image";加載源圖像:
src = imread( argv[1], 1 );if( !src.data ){ cout<<"Usage: ./Histogram_Demo <path_to_image>"<<endl;return -1;}轉為灰度圖:
cvtColor( src, src, CV_BGR2GRAY );利用函數?equalizeHist?對上面灰度圖做直方圖均衡化:
equalizeHist( src, dst );可以看到, 這個操作的參數只有源圖像和目標 (均衡化后) 圖像.
顯示這兩個圖像 (源圖像和均衡化后圖像) :
namedWindow( source_window, CV_WINDOW_AUTOSIZE ); namedWindow( equalized_window, CV_WINDOW_AUTOSIZE );imshow( source_window, src ); imshow( equalized_window, dst );等待用戶案件退出程序
waitKey(0); return 0;結果
為了更好地觀察直方圖均衡化的效果, 我們使用一張對比度不強的圖片作為源圖像輸入, 如下圖:
它的直方圖為:
注意到像素大多集中在直方圖中間的強度上.
使用例程進行均衡化后, 我們得到下面的結果:
這幅圖片顯然對比度更強. 再驗證一下均衡化后圖片的直方圖:
注意到現在像素在整個強度范圍內均衡分布.
Note
?你們想知道上面的直方圖是怎樣繪制出來的嗎? 請關注接下來的教程!
直方圖計算
目標
本文檔嘗試解答如下問題:
- 如何使用OpenCV函數?split?將圖像分割成單通道數組。
- 如何使用OpenCV函數?calcHist?計算圖像陣列的直方圖。
- 如何使用OpenCV函數?normalize?歸一化數組。
Note
?在上一篇中 (直方圖均衡化) 我們介紹了一種特殊直方圖叫做?圖像直方圖?。現在我們從更加廣義的角度來考慮直方圖的概念,繼續往下讀!
什么是直方圖?
-
直方圖是對數據的集合?統計?,并將統計結果分布于一系列預定義的?bins?中。
-
這里的?數據?不僅僅指的是灰度值 (如上一篇您所看到的), 統計數據可能是任何能有效描述圖像的特征。
-
先看一個例子吧。 假設有一個矩陣包含一張圖像的信息 (灰度值?):
-
如果我們按照某種方式去?統計?這些數字,會發生什么情況呢? 既然已知數字的?范圍?包含 256 個值, 我們可以將這個范圍分割成子區域(稱作?bins), 如:
然后再統計掉入每一個??的像素數目。采用這一方法來統計上面的數字矩陣,我們可以得到下圖( x軸表示 bin, y軸表示各個bin中的像素個數)。
-
以上只是一個說明直方圖如何工作以及它的用處的簡單示例。直方圖可以統計的不僅僅是顏色灰度, 它可以統計任何圖像特征 (如 梯度, 方向等等)。
-
讓我們再來搞清楚直方圖的一些具體細節:
- dims: 需要統計的特征的數目, 在上例中,?dims = 1?因為我們僅僅統計了灰度值(灰度圖像)。
- bins: 每個特征空間?子區段?的數目,在上例中,?bins = 16
- range: 每個特征空間的取值范圍,在上例中,?range = [0,255]
-
怎樣去統計兩個特征呢? 在這種情況下, 直方圖就是3維的了,x軸和y軸分別代表一個特征, z軸是掉入??組合中的樣本數目。 同樣的方法適用于更高維的情形 (當然會變得很復雜)。
OpenCV的直方圖計算
OpenCV提供了一個簡單的計算數組集(通常是圖像或分割后的通道)的直方圖函數?calcHist?。 支持高達 32 維的直方圖。下面的代碼演示了如何使用該函數計算直方圖!
源碼
-
本程序做什么?
- 裝載一張圖像
- 使用函數?split?將載入的圖像分割成 R, G, B 單通道圖像
- 調用函數?calcHist?計算各單通道圖像的直方圖
- 在一個窗口疊加顯示3張直方圖
-
下載代碼: 點擊?這里
-
代碼一瞥:
解釋
創建一些矩陣:
Mat src, dst;裝載原圖像
src = imread( argv[1], 1 );if( !src.data ){ return -1; }使用OpenCV函數?split?將圖像分割成3個單通道圖像:
vector<Mat> rgb_planes; split( src, rgb_planes );輸入的是要被分割的圖像 (這里包含3個通道), 輸出的則是Mat類型的的向量。
現在對每個通道配置?直方圖?設置, 既然我們用到了 R, G 和 B 通道, 我們知道像素值的范圍是?
設定bins數目 (5, 10...):
int histSize = 255;設定像素值范圍 (前面已經提到,在 0 到 255之間 )
/// 設定取值范圍 ( R,G,B) ) float range[] = { 0, 255 } ; const float* histRange = { range };我們要把bin范圍設定成同樣大小(均一)以及開始統計前先清除直方圖中的痕跡:
bool uniform = true; bool accumulate = false;最后創建儲存直方圖的矩陣:
Mat r_hist, g_hist, b_hist;下面使用OpenCV函數?calcHist?計算直方圖:
/// 計算直方圖: 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 );參數說明如下:
- &rgb_planes[0]:?輸入數組(或數組集)
- 1: 輸入數組的個數 (這里我們使用了一個單通道圖像,我們也可以輸入數組集 )
- 0: 需要統計的通道 (dim)索引 ,這里我們只是統計了灰度 (且每個數組都是單通道)所以只要寫 0 就行了。
- Mat(): 掩碼( 0 表示忽略該像素), 如果未定義,則不使用掩碼
- r_hist: 儲存直方圖的矩陣
- 1: 直方圖維數
- histSize:?每個維度的bin數目
- histRange:?每個維度的取值范圍
- uniform?和?accumulate: bin大小相同,清楚直方圖痕跡
創建顯示直方圖的畫布:
// 創建直方圖畫布 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) );在畫直方圖之前,先使用?normalize?歸一化直方圖,這樣直方圖bin中的值就被縮放到指定范圍:
/// 將直方圖歸一化到范圍 [ 0, histImage.rows ] 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:?輸入數組
- r_hist:?歸一化后的輸出數組(支持原地計算)
- 0?及?histImage.rows: 這里,它們是歸一化?r_hist?之后的取值極限
- NORM_MINMAX:?歸一化方法 (例中指定的方法將數值縮放到以上指定范圍)
- -1:?指示歸一化后的輸出數組與輸入數組同類型
- Mat():?可選的掩碼
請注意這里如何讀取直方圖bin中的數據 (此處是一個1維直方圖):
/// 在直方圖畫布上畫出直方圖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 );}使用了以下表達式:.. code-block:: cppr_hist.at<float>(i):math:`i` 指示維度,假如我們要訪問2維直方圖,我們就要用到這樣的表達式:.. code-block:: cppr_hist.at<float>( i, j )最后顯示直方圖并等待用戶退出程序:
namedWindow("calcHist Demo", CV_WINDOW_AUTOSIZE ); imshow("calcHist Demo", histImage );waitKey(0);return 0;結果
使用下圖作為輸入圖像:
產生以下直方圖:
直方圖對比
目標
本文檔嘗試解答如下問題:
- 如何使用OpenCV函數?compareHist?產生一個表達兩個直方圖的相似度的數值。
- 如何使用不同的對比標準來對直方圖進行比較。
原理
-
要比較兩個直方圖(??and??), 首先必須要選擇一個衡量直方圖相似度的?對比標準?() 。
-
OpenCV 函數?compareHist?執行了具體的直方圖對比的任務。該函數提供了4種對比標準來計算相似度:
-
Correlation ( CV_COMP_CORREL )
其中
?是直方圖中bin的數目。
-
Chi-Square ( CV_COMP_CHISQR )
-
Intersection ( CV_COMP_INTERSECT )
-
Bhattacharyya 距離( CV_COMP_BHATTACHARYYA )
源碼
-
本程序做什么?
- 裝載一張?基準圖像?和 兩張?測試圖像?進行對比。
- 產生一張取自?基準圖像?下半部的圖像。
- 將圖像轉換到HSV格式。
- 計算所有圖像的H-S直方圖,并歸一化以便對比。
- 將?基準圖像?直方圖與 兩張測試圖像直方圖,基準圖像半身像直方圖,以及基準圖像本身的直方圖分別作對比。
- 顯示計算所得的直方圖相似度數值。
-
下載代碼: 點擊?這里
-
代碼一瞥:
解釋
聲明儲存基準圖像和另外兩張對比圖像的矩陣( RGB 和 HSV )
Mat src_base, hsv_base; Mat src_test1, hsv_test1; Mat src_test2, hsv_test2; Mat hsv_half_down;裝載基準圖像(src_base) 和兩張測試圖像:
if( argc < 4 ){ printf("** Error. Usage: ./compareHist_Demo <image_settings0> <image_setting1> <image_settings2>\n");return -1;}src_base = imread( argv[1], 1 ); src_test1 = imread( argv[2], 1 ); src_test2 = imread( argv[3], 1 );將圖像轉化到HSV格式:
cvtColor( src_base, hsv_base, CV_BGR2HSV ); cvtColor( src_test1, hsv_test1, CV_BGR2HSV ); cvtColor( src_test2, hsv_test2, CV_BGR2HSV );同時創建包含基準圖像下半部的半身圖像(HSV格式):
hsv_half_down = hsv_base( Range( hsv_base.rows/2, hsv_base.rows - 1 ), Range( 0, hsv_base.cols - 1 ) );初始化計算直方圖需要的實參(bins, 范圍,通道 H 和 S ).
int h_bins = 50; int s_bins = 32; int histSize[] = { h_bins, s_bins };float h_ranges[] = { 0, 256 }; float s_ranges[] = { 0, 180 };const float* ranges[] = { h_ranges, s_ranges };int channels[] = { 0, 1 };創建儲存直方圖的 MatND 實例:
MatND hist_base; MatND hist_half_down; MatND hist_test1; MatND hist_test2;計算基準圖像,兩張測試圖像,半身基準圖像的直方圖:
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() );calcHist( &hsv_test1, 1, channels, Mat(), hist_test1, 2, histSize, ranges, true, false ); normalize( hist_test1, hist_test1, 0, 1, NORM_MINMAX, -1, Mat() );calcHist( &hsv_test2, 1, channels, Mat(), hist_test2, 2, histSize, ranges, true, false ); normalize( hist_test2, hist_test2, 0, 1, NORM_MINMAX, -1, Mat() );按順序使用4種對比標準將基準圖像(hist_base)的直方圖與其余各直方圖進行對比:
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 );double base_test1 = compareHist( hist_base, hist_test1, compare_method );double base_test2 = compareHist( hist_base, hist_test2, compare_method );printf( " Method [%d] Perfect, Base-Half, Base-Test(1), Base-Test(2) : %f, %f, %f, %f \n", i, base_base, base_half , base_test1, base_test2 );}結果
使用下列輸入圖像:
|
|
|
|
第一張為基準圖像,其余兩張為測試圖像。同時我們會將基準圖像與它自身及其半身圖像進行對比。
我們應該會預料到當將基準圖像直方圖及其自身進行對比時會產生完美的匹配, 當與來源于同一樣的背景環境的半身圖對比時應該會有比較高的相似度, 當與來自不同亮度光照條件的其余兩張測試圖像對比時匹配度應該不是很好:
下面顯示的是結果數值:
| Correlation | 1.000000 | 0.930766 | 0.182073 | 0.120447 |
| Chi-square | 0.000000 | 4.940466 | 21.184536 | 49.273437 |
| Intersection | 24.391548 | 14.959809 | 3.889029 | 5.775088 |
| Bhattacharyya | 0.000000 | 0.222609 | 0.646576 | 0.801869 |
對于?Correlation?和?Intersection?標準, 值越大相似度越大。因此可以看到對于采用這兩個方法的對比,*基準 - 基準* 的對比結果值是最大的, 而?基準 - 半身?的匹配則是第二好(跟我們預測的一致)。而另外兩種對比標準,則是結果越小相似度越大。 我們可以觀察到基準圖像直方圖與兩張測試圖像直方圖的匹配是最差的,這再一次印證了我們的預測。
反向投影
目標
本文檔嘗試解答如下問題:
- 什么是反向投影,它可以實現什么功能?
- 如何使用OpenCV函數?calcBackProject?計算反向投影?
- 如何使用OpenCV函數?mixChannels?組合圖像的不同通道?
原理
什么是反向投影?
- 反向投影是一種記錄給定圖像中的像素點如何適應直方圖模型像素分布的方式。
- 簡單的講, 所謂反向投影就是首先計算某一特征的直方圖模型,然后使用模型去尋找圖像中存在的該特征。
- 例如, 你有一個膚色直方圖 ( Hue-Saturation 直方圖 ),你可以用它來尋找圖像中的膚色區域:
反向投影的工作原理?
-
我們使用膚色直方圖為例來解釋反向投影的工作原理:
-
假設你已經通過下圖得到一個膚色直方圖(Hue-Saturation), 旁邊的直方圖就是?模型直方圖?( 代表手掌的皮膚色調).你可以通過掩碼操作來抓取手掌所在區域的直方圖:
-
下圖是另一張手掌圖(測試圖像) 以及對應的整張圖像的直方圖:
-
我們要做的就是使用?模型直方圖?(代表手掌的皮膚色調) 來檢測測試圖像中的皮膚區域。以下是檢測的步驟
-
對測試圖像中的每個像素 (??),獲取色調數據并找到該色調(??)在直方圖中的bin的位置。
-
查詢?模型直方圖?中對應的bin -??- 并讀取該bin的數值。
-
將此數值儲存在新的圖像中(BackProjection)。 你也可以先歸一化?模型直方圖?,這樣測試圖像的輸出就可以在屏幕顯示了。
-
通過對測試圖像中的每個像素采用以上步驟, 我們得到了下面的 BackProjection 結果圖:
-
使用統計學的語言,?BackProjection?中儲存的數值代表了測試圖像中該像素屬于皮膚區域的?概率?。比如以上圖為例, 亮起的區域是皮膚區域的概率更大(事實確實如此),而更暗的區域則表示更低的概率(注意手掌內部和邊緣的陰影影響了檢測的精度)。
源碼
-
本程序做什么?
-
裝載圖像
-
轉換原圖像到 HSV 格式,再分離出?Hue?通道來建立直方圖 (使用 OpenCV 函數?mixChannels)
- 讓用戶輸入建立直方圖所需的bin的數目。
- 計算同一圖像的直方圖 (如果bin的數目改變則更新直方圖) 和反向投影圖。
-
顯示反向投影圖和直方圖。
-
-
下載源碼:
- 點擊?這里?獲取簡單版的源碼 (本教程使用簡單版)。
- 要嘗試更炫的代碼 (使用 H-S 直方圖和 floodFill 來定義皮膚區域的掩碼)你可以點擊?增強版演示
- 當然你也可以從實例庫里下載經典的?camshiftdemo?示例。
-
代碼一瞥:
解釋
申明圖像矩陣,初始化bin數目:
Mat src; Mat hsv; Mat hue; int bins = 25;讀取輸入圖像并轉換到HSV 格式:
src = imread( argv[1], 1 ); cvtColor( src, hsv, CV_BGR2HSV );本教程僅僅使用Hue通道來創建1維直方圖 (你可以從上面的鏈接下載增強版本,增強版本使用了更常見的H-S直方圖,以獲取更好的結果):
hue.create( hsv.size(), hsv.depth() ); int ch[] = { 0, 0 }; mixChannels( &hsv, 1, &hue, 1, ch, 1 );你可以看到這里我們使用?mixChannels?來抽取 HSV圖像的0通道(Hue)。 該函數接受了以下的實參:
- &hsv:?一系列輸入圖像的數組, 被拷貝的通道的來源
- 1:?輸入數組中圖像的數目
- &hue:?一系列目的圖像的數組, 儲存拷貝的通道
- 1:?目的數組中圖像的數目
- ch[] = {0,0}:?通道索引對的數組,指示如何將輸入圖像的某一通道拷貝到目的圖像的某一通道。在這里,&hsv圖像的Hue(0) 通道被拷貝到&hue圖像(單通道)的0 通道。
- 1:?通道索引對德數目
創建Trackbar方便用戶輸入bin數目。 Trackbar的任何變動將會調用函數?Hist_and_Backproj?。
char* window_image = "Source image"; namedWindow( window_image, CV_WINDOW_AUTOSIZE ); createTrackbar("* Hue bins: ", window_image, &bins, 180, Hist_and_Backproj ); Hist_and_Backproj(0, 0);顯示并等待用戶突出程序:
imshow( window_image, src );waitKey(0); return 0;Hist_and_Backproj 函數:?初始化函數?calcHist?需要的實參, bin數目來自于 Trackbar:
void Hist_and_Backproj(int, void* ) {MatND hist;int histSize = MAX( bins, 2 );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() );調用函數?calcBackProject?計算同一張圖像的反向投影
MatND backproj; calcBackProject( &hue, 1, 0, hist, backproj, &ranges, 1, true );所有的實參都已經知道了(與計算直方圖的實參一樣), 僅僅增加了 backproj 矩陣,用來儲存原圖像(&hue)的反向投影。
顯示 backproj:
imshow( "BackProj", backproj );顯示1維 Hue 直方圖:
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 );結果
下面是對一張樣本圖像(猜猜是什么?又是一掌)進行的測試結果。 你可以改變bin的數目來觀察它是如何影響結果圖像的:
|
|
|
|
模板匹配
目標
在這節教程中您將學到:
- 使用OpenCV函數?matchTemplate?在模板塊和輸入圖像之間尋找匹配,獲得匹配結果圖像
- 使用OpenCV函數?minMaxLoc?在給定的矩陣中尋找最大和最小值(包括它們的位置).
原理
什么是模板匹配?
模板匹配是一項在一幅圖像中尋找與另一幅模板圖像最匹配(相似)部分的技術.它是怎么實現的?
-
我們需要2幅圖像:
- 原圖像 (I):?在這幅圖像里,我們希望找到一塊和模板匹配的區域
- 模板 (T):?將和原圖像比照的圖像塊
-
為了確定匹配區域, 我們不得不滑動模板圖像和原圖像進行?比較?:
-
通過?滑動, 我們的意思是圖像塊一次移動一個像素 (從左往右,從上往下). 在每一個位置, 都進行一次度量計算來表明它是 “好” 或 “壞” 地與那個位置匹配 (或者說塊圖像和原圖像的特定區域有多么相似).
-
對于?T?覆蓋在?I?上的每個位置,你把度量值?保存?到?結果圖像矩陣?(R)?中. 在?R?中的每個位置??都包含匹配度量值:
上圖就是?TM_CCORR_NORMED?方法處理后的結果圖像?R?. 最白的位置代表最高的匹配. 正如您所見, 紅色橢圓框住的位置很可能是結果圖像矩陣中的最大數值, 所以這個區域 (以這個點為頂點,長寬和模板圖像一樣大小的矩陣) 被認為是匹配的.
-
實際上, 我們使用函數?minMaxLoc?來定位在矩陣?R?中的最大值點 (或者最小值, 根據函數輸入的匹配參數) .
我們的目標是檢測最匹配的區域:
OpenCV中支持哪些匹配算法?
問得好. OpenCV通過函數?matchTemplate?實現了模板匹配算法. 可用的方法有6個:
這類方法利用平方差來進行匹配,最好匹配為0.匹配越差,匹配值越大.
標準平方差匹配 method=CV_TM_SQDIFF_NORMED
相關匹配 method=CV_TM_CCORR
這類方法采用模板和圖像間的乘法操作,所以較大的數表示匹配程度較高,0標識最壞的匹配效果.
標準相關匹配 method=CV_TM_CCORR_NORMED
相關匹配 method=CV_TM_CCOEFF
這類方法將模版對其均值的相對值與圖像對其均值的相關值進行匹配,1表示完美匹配,-1表示糟糕的匹配,0表示沒有任何相關性(隨機序列).
在這里
標準相關匹配 method=CV_TM_CCOEFF_NORMED
通常,隨著從簡單的測量(平方差)到更復雜的測量(相關系數),我們可獲得越來越準確的匹配(同時也意味著越來越大的計算代價). 最好的辦法是對所有這些設置多做一些測試實驗,以便為自己的應用選擇同時兼顧速度和精度的最佳方案.
代碼
-
在這程序實現了什么?
- 載入一幅輸入圖像和一幅模板圖像塊 (template)
- 通過使用函數?matchTemplate?實現之前所述的6種匹配方法的任一個. 用戶可以通過滑動條選取任何一種方法.
- 歸一化匹配后的輸出結果
- 定位最匹配的區域
- 用矩形標注最匹配的區域
-
下載代碼: 單擊?這里
-
看一下代碼:
代碼說明
定義一些全局變量, 例如原圖像(img), 模板圖像(templ) 和結果圖像(result) , 還有匹配方法以及窗口名稱:
Mat img; Mat templ; Mat result; char* image_window = "Source Image"; char* result_window = "Result window";int match_method; int max_Trackbar = 5;載入原圖像和匹配塊:
img = imread( argv[1], 1 ); templ = imread( argv[2], 1 );創建窗口,顯示原圖像和結果圖像:
namedWindow( image_window, CV_WINDOW_AUTOSIZE ); namedWindow( result_window, CV_WINDOW_AUTOSIZE );創建滑動條并輸入將被使用的匹配方法. 一旦滑動條發生改變,回調函數?MatchingMethod?就會被調用.
char* trackbar_label = "Method: \n 0: SQDIFF \n 1: SQDIFF NORMED \n 2: TM CCORR \n 3: TM CCORR NORMED \n 4: TM COEFF \n 5: TM COEFF NORMED"; createTrackbar( trackbar_label, image_window, &match_method, max_Trackbar, MatchingMethod );一直等待,直到用戶退出這個程序.
waitKey(0); return 0;讓我們先看看回調函數. 首先, 它對原圖像進行了一份復制:
Mat img_display; img.copyTo( img_display );然后, 它創建了一幅用來存放匹配結果的輸出圖像矩陣. 仔細看看輸出矩陣的大小(它包含了所有可能的匹配位置)
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 );很自然地,參數是輸入圖像?I, 模板圖像?T, 結果圖像?R?還有匹配方法 (通過滑動條給出)
我們對結果進行歸一化:
normalize( result, result, 0, 1, NORM_MINMAX, -1, Mat() );通過使用函數?minMaxLoc?,我們確定結果矩陣?R?的最大值和最小值的位置.
double minVal; double maxVal; Point minLoc; Point maxLoc; Point matchLoc;minMaxLoc( result, &minVal, &maxVal, &minLoc, &maxLoc, Mat() );函數中的參數有:
- result:?匹配結果矩陣
- &minVal?和?&maxVal:?在矩陣?result?中存儲的最小值和最大值
- &minLoc?和?&maxLoc:?在結果矩陣中最小值和最大值的坐標.
- Mat():?可選的掩模
對于前二種方法 ( CV_SQDIFF 和 CV_SQDIFF_NORMED ) 最低的數值標識最好的匹配. 對于其他的, 越大的數值代表越好的匹配. 所以, 我們在?matchLoc?中存放相符的變量值:
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 );結果
開始測試我們的程序,一幅輸入圖像:
還有一幅模版圖像:
產生了一下結果圖像矩陣 (第一行是標準的方法 SQDIFF, CCORR 和 CCOEFF, 第二行是相同的方法在進行標準化后的圖像). 在第1列, 最黑的部分代表最好的匹配, 對于其它2列, 越白的區域代表越好的匹配.
|
|
|
|
正確的匹配在下面顯示 (右側被矩形標注的人臉). 需要注意的是方法 CCORR 和 CCOEFF 給出了錯誤的匹配結果, 但是它們的歸一化版本給出了正確的結果, 這或許是由于我們實際上僅僅考慮 “最匹配” 而沒考慮其他可能的高匹配位置.
from: http://www.opencv.org.cn/opencvdoc/2.3.2/html/doc/tutorials/imgproc/table_of_content_imgproc/table_of_content_imgproc.html#table-of-content-imgproc
總結
以上是生活随笔為你收集整理的OpenCV之imgproc 模块. 图像处理(4)直方图均衡化 直方图计算 直方图对比 反向投影 模板匹配的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: OpenCV之imgproc 模块. 图
- 下一篇: OpenCV之imgproc 模块. 图