計算機視覺 OpenCV Android | 特徵檢測與匹配之角點檢測——Harris角點檢測與Shi-Tomasi角點檢測
本文要點總結(倆演算法的聯絡與區別)
Harris角點檢測與Shi-Tomasi角點檢測都是經典的角點特徵提取演算法,
但兩者在API的使用上有出入(詳見文中程式碼或GitHub專案);
-
Harris角點檢測的API,
返回/輸出
的是一個與輸入影象大小一致的Mat物件
,這個
Mat物件
的每一個座標(i,j)
都是對應輸入影象對應座標(i,j)
的畫素
的響應值R
,要先將這個
Mat物件
歸一化,再迴圈每一個
Mat資料元素
,一 一 跟自己設定的閾值
進行比較,合格的再
認為是角點
並提取
出來,進行繪製和儲存;
-
與
Harris角點
輸出不同,shi-tomasi
簡單多了,直接輸出一個
包含若干個(具體個數通過API形參設定)角點座標的角點陣列
,(其資料型別是MatOfPoint
)省略了很多步驟;
遍歷
這個角點陣列,
繪製出
每個角點
即可。
引子
-
前面兩章筆記(
影象操作、基本特徵檢測
)主要講述了OpenCV中影象處理模組的主要知識與API使用;
-
本章的筆記記錄OpenCV中另外一個重要模組——
feature2d模組
,該模組的 主要功能 是
檢測影象的特徵
,並
根據特徵進行物件匹配
; -
首先,關於
影象的特徵
,簡單地說,特徵就是
邊緣、角點、紋理
等。 -
本章會筆記
特徵提取、檢測與匹配
相關的知識與API,包括
角點特徵檢測、特徵點檢測、特徵描述子提取
,以及
根據特徵描述子去匹配、尋找特徵物件
。 -
本章知識會涉及較多的數學知識與公式,
我們可以閱讀一些與
特徵提取相關的數學知識
,比如導
數與微分、多項式與高斯公式曲線擬合,三角函式,矩陣的特徵值與特徵向量的簡單計算
等基礎數學知識,以更好地掌握本章知識以及各個API引數意義與用法。
0 角點的定義與作用
基本特徵檢測
一章中,學習了關於 邊緣檢測
的知識,
在 影象邊緣
中,有一些 特殊的畫素點值
得我們特別關注,
那就是 影象邊緣的角點
,
這些角點更能反映出 影象中物件的整體特徵
,
本文首先筆記如何提取影象的角點特徵。
1 Harris角點檢測
關於角點特徵提取 最經典
的演算法之一就是 Harris角點檢測
。
Harris角點檢測
的 基本原理
是對影象 求導
,對每個畫素點生成 二階梯度影象
,
只是在 卷積
核使用的時候需要使用 高斯核
,
得到 影象X與Y方向的二階矩
,
基於它們就可以得到如下 Hessian矩陣
:

求得 最大兩個特徵值 λ1 與 λ2
,可以得到如下 角點響應值R
:

其中,係數K常見的取值範圍為0.02~0.04。
-
每個畫素點
有自己的一個響應值R
,
也即有自己的一對特徵值 λ1 與 λ2; -
全域性畫素則有多個R值;
根據 M計算
可以得到 特徵值 λ1、λ2
,它們的值與角點的關係如下圖:

Harris角點檢測
的API:
-
cornerHarris(Mat src, Mat dst, int blockSize, int ksize, double k)
src
:單通道的8位或者浮點數影象,用灰度影象;
dst
:輸出的每個畫素點的響應值
,是CV_32F
型別,大小與輸入影象一致。blockSize
:根據特徵值與特徵向量計算矩陣M的大小,常見取值為2
。ksize Sobel
:運算元梯度計算,常見取值為3
。k
:係數大小,取值範圍為0.02~0.04
。
使用Harris角點檢測函式計算得到影象角點的演示程式碼如下:
private void harrisCornerDemo(Mat src, Mat dst) { // 定義閾值T//初始化各種Mat物件 int threshold = 100; Mat gray = new Mat(); Mat response = new Mat(); Mat response_norm = new Mat(); // 角點檢測 Imgproc.cvtColor(src, gray, Imgproc.COLOR_BGR2GRAY);//轉灰度!!!!!! Imgproc.cornerHarris(gray, response, 2, 3, 0.04); Core.normalize(response, response_norm, 0, 255, Core.NORM_MINMAX, CvType.CV_32F);//歸一化 // 繪製角點 dst.create(src.size(), src.type()); src.copyTo(dst); float[] data = new float[1];//!!!!!!!!!! for(int j=0; j<response_norm.rows(); j++ ) { for(int i=0; i<response_norm.cols(); i++ ) { response_norm.get(j, i, data); if((int)data[0] > 100) { Imgproc.circle(dst, new Point(i, j), 5, new Scalar(0, 0, 255), 2, 8, 0); Log.i("Harris Corner", "find corner point……"); } } } gray.release(); response.release(); }
-
response_norm
歸一化後的響應值Mat物件 - data[0]是某個響應值;
>100
認為其是一個較大的響應值,
響應值大於指定閾值T(這裡是100),則對應的畫素點被認為是角點; -
float[] data = new float[1] //在這裡,可能有人有疑問, 陣列長度只有 1
get()
方法,第三個引數要求是陣列,
get多個畫素時,傳入一個多元素空陣列,常規理解操作;
但當只要get一個畫素,則需建立一個只有一個元素的陣列
!而非變數
!
這種介面設計思想
,
一個方法(如get()
)介面即可實現包含一到多個數據元素的形式引數的傳入;
而沒必要去準備/過載
兩個方法——
一個用來接收包含單個數據元素的變數型形參
,
另一個用來接收包含多個數據元素的陣列型形參
;沒必要這樣了;
即無論是負責接收資料的形參是包含 單個數據元素 還是 多個數據元素 ,一律按 陣列型形參 處理,把資料形參位定義成陣列型別,介面統一設計,一套程式碼,一個介面即可,省時省力!
-
上述程式首先把彩色RGB影象轉換為單通道灰度影象,
然後使用Harris角點檢測函式完成各個畫素點上角點響應值的計算,
最後使用閾值過濾繪製那些響應值R比較大的畫素點(角點)。
注意,閾值T與繪製檢測得到的角點數目相關,
T值越大,被過濾的響應畫素點越多,留下來的就越可能是角點
,反之亦然。
本章完整程式碼在文末GitHub裡邊的 Feature2dMainActivity.java檔案中
,後續對此不再說明。
2 Shi-Tomasi角點檢測
還有一種經常使用的角點檢測方法稱為 Shi-Tomasi角點檢測
,
其與Harris角點檢測類似,這種方法同樣是 基於梯度影象
發展而來的,
它是1994年由兩位作者Jianbo Shi與Carlo Tomasi一起提出來的,
他們當時所發表的論文名為<<Good Feature to Track>>,
這也是為什麼在OpenCV中使用同名函式來表示Shi-Tomasi角點檢測的原因。
Shi-Tomasi角點檢測與Harris角點檢測 唯一(指的是方法邏輯,不包括API,API的輸出還不同) 不同的地方
在於 計算角點響應R值
時使用的是如下方法:

如果R大於指定閾值T,則對應的畫素點被認為是角點;
假設 λ1、λ2
為座標,
則對 角點
的描述就是 當λ1、λ2都大於閾值T=λmin的右上角時
,
角點響應值滿足要求的區域,
如下圖:

相關的API如下:
-
goodFeaturesToTrack(Mat image, MatOfPoint corners, int maxCorners, double qualityLevel, double minDistance, Mat mask, int blockSize, boolean useHarrisDetector, double k)
image
:表示輸入影象、型別為單通道的8位或浮點數,用灰度影象
;corners
:輸出得到角點陣列,注意資料型別
;maxCorners
:表示獲取前N個
最強響應R值的角點。qualityLevel
:其取值範圍為0~1,這裡取它與最大R值
相乘,得到的值作為閾值T
,低於它的都要被丟棄,假設Rmax=1500,qualityLevel=0.01,則閾值T=15,小於15的角點都會被丟棄。
每個畫素點有自己的一個響應值R,去全域性畫素最大的R為Rmax;
minDistance
: 最終返回的角點
之間的最小距離,小於這個距離則 的角點
被丟棄。
mask
:預設全部為零。
blockSize
:計算矩陣M時需要的,常取值為3。
useHarrisDetector
:是否使用Harris角點檢測,true表示使用,若為false則使用Shi-Tomasi角點檢測。
k
:當使用Harris角點檢測的時候才使用。
實現shi-tomasi角點檢測的demo:
private void shiTomasicornerDemo(Mat src, Mat dst) { // 變數定義 double k = 0.04; int blockSize = 3; double qualityLevel= 0.01; boolean useHarrisCorner = false; // 角點檢測 Mat gray = new Mat(); Imgproc.cvtColor(src, gray, Imgproc.COLOR_BGR2GRAY); MatOfPoint corners = new MatOfPoint(); Imgproc.goodFeaturesToTrack(gray, corners, 100, qualityLevel, 10, new Mat(), blockSize, useHarrisCorner, k); // 繪製角點 dst.create(src.size(), src.type()); src.copyTo(dst); Point[] points = corners.toArray(); for(int i=0; i<points.length; i++) { Imgproc.circle(dst, points[i], 5, new Scalar(0, 0, 255), 2, 8, 0); } gray.release(); }
完整的程式碼可參考GitHub專案。
參考材料
- 《OpenCV Android 開發實戰》(賈志剛 著)
- 關於《OpenCV Android 開發實戰》作者的GitHub專案
- 筆者基於作者GitHub維護的APP