前言:
圖像特征點檢測廣泛運用於計算機視覺處理領域,包括目標識別與跟蹤、立體成像,在特征點的圖像分析中,特征點提取是非常重要的步驟,其中,角點是最常見的一類點特征。前面我們介紹了用 Harris提取角點,但是提取的角點是像素級的,精度不高,若我們進行圖像處理的目的不是提取用於識別的特征點而是進行幾何測量,這通常需要更高的精度。
那么如何提取亞像素級角點的位置呢?在 Harris 提取角點過程中,通過兩次角點篩選,剔除非角點和偽角點,利用角點響應函數執行非極大值抑制,以局部角點響應函數最大值的像素點作為初始角點,並以該初始角點為中心,以一定半徑搜索角點簇,采用最小二乘法加權角點簇與待求角點的歐幾里得距離,精化初始角點坐標,從而實現 Harris 亞像素角點准確快速定位。
一、原理解析
在亞像素級精度的角點檢測算法中,有兩種常用的方法。一種方法是從亞像素角點到周圍像素點的矢量應垂直於圖像的灰度梯度這個觀察事實得到的,通過最小化誤差函數的迭代方法來獲得亞像素級精度的坐標值。另一種方法是用二次多項式去逼近周圍3× 3領域內的角點反應函數,用線性解法求得亞像素級角點坐標。
(1) 第一種方法
角點位置特征:邊緣的交點,且角點與邊緣點的連線和邊緣點的梯度方向垂直。
如上圖所示,假設一個起始角點q在實際亞像素角點附近。p點在q點附近的鄰域中,若p點在均勻區域內部,則p點的梯度為0;若p點在邊緣上,則p點的梯度方向垂直邊緣方向。如果向量q-p方向與邊緣方向一致,那么q-p向量與p點的梯度向量點積運算結果為0。在初始角點(初始角點可能不在邊緣上)附近我們可以收集很多組點的梯度以及相關向量q-p,此時的q就是我們所要求的更精確角點位置,那么每一組的向量點積設置為0,正是基於這個思想,將點積為0的等式組合起來形成一個系統方程,該系統方程的解就是更精確的亞像素角點位置。 將新的q點作為區域的中心,可以繼續使用這個方法進行迭代,獲得很高的精度。
(2)第二種方法
一個很直接的想法就是插值。用二次多項式來逼近角點反應函數,找到的亞像素級精確位置:
用已經檢測出來的角點周圍的9個像素點可以建立含有
6個未知量的超定方程組。運用最小二乘法可以求解這個超定方程。亞像素級角點的
對應的是二次多項式的極大值點。為了求出這個坐標點,對二次多項式進行求導:
可以直接得到的亞像素級的坐標。
二、cornerSubPix()函數介紹
函數goodFeaturesToTrack()函數只能提供簡單的像素的坐標值,也就是說,有時候會需要實數坐標值而不是整數坐標值。在OpenCV中,就提供了一個cornerSubPix()函數,用於尋找亞像素角點的位置,其函數聲明如下:
其中,函數參數解釋如下:
image:輸入圖像,即源圖像;
corners:提供輸入角點的初始坐標和精確的輸出坐標。
winSize:Size類型,表示搜索窗口的半徑。若winSize=Size(5,5),那么就表示使用(5*2+1)x(5*2+1)=11*11大小的搜索窗口。
zeroZone:Size類型,表示死區的一半尺寸。而死區為不對搜索區的中央位置做求和運算的區域,用來避免自相關矩陣出現的某些可能的奇異性。值為(-1,-1)表示沒有死區。
criteria:TermCriteria類型,求角點的迭代過程的終止條件。
三、代碼演示
1 #include <opencv2/opencv.hpp>
2 #include <iostream>
3
4 using namespace cv; 5 using namespace std; 6
7 int max_corners = 20; 8 int max_count = 50; 9 Mat src_img, gray_img; 10 const string output_winName = "亞像素角點檢測"; 11 RNG random_number_generator; // 定義一個隨機數發生器
12 void SubPixel_Demo(int, void*); 13
14 int main() 15 { 16 src_img = imread("test11.png"); 17 if (src_img.empty()) 18 { 19 printf("could not load the image...\n"); 20 return -1; 21 } 22 namedWindow("原圖", CV_WINDOW_AUTOSIZE); 23 imshow("原圖", src_img); 24 cvtColor(src_img, gray_img, COLOR_BGR2GRAY); 25 namedWindow(output_winName, CV_WINDOW_AUTOSIZE); 26 createTrackbar("角點數", output_winName, &max_corners, max_count, SubPixel_Demo); //創建TrackBar
27 SubPixel_Demo(0, 0); 28
29 waitKey(0); 30 return 0; 31 } 32
33 void SubPixel_Demo(int, void*) 34 { 35 if (max_corners < 5) 36 { 37 max_corners = 5; //控制下限
38 } 39 vector<Point2f> corners; // corners是一個Vector,里面存放找到的角點坐標,即float的Point值
40 double qualityLevel = 0.01; 41 double minDistance = 10; 42 int blockSize = 3; 43 double k = 0.04; 44 cout << "************************************************************"<< endl; 45 cout << "作者;行歌" << endl; 46
47 // 調用goodFeaturesToTrack進行shi-Tomasi角點檢測
48 goodFeaturesToTrack(gray_img, corners, max_corners, qualityLevel, minDistance, Mat(), blockSize, false, k); 49 cout << "角點數: " << corners.size() << endl; 50 Mat result_img = src_img.clone(); 51 for (auto t = 0; t < corners.size();++t) 52 { 53 circle(result_img, corners[t], 2, Scalar(random_number_generator.uniform(0, 255), 54 random_number_generator.uniform(0, 255), random_number_generator.uniform(0, 255)), 2, 8, 0); // 注意corners[t]就是一個Point類型的坐標
55 } 56 imshow(output_winName, result_img); 57 // 參數設置
58 Size winSize = Size(5, 5); 59 Size zerozone = Size(-1, -1); 60 TermCriteria criteria = TermCriteria(TermCriteria::EPS + TermCriteria::MAX_ITER, 40, 0.001); 61 cornerSubPix(gray_img, corners, winSize, zerozone, criteria); // 調用cornerSubPix函數計算出亞像素角點的位置 62
63 // 輸出亞像素角點信息
64 for (auto t = 0; t < corners.size(); ++t) 65 { 66 cout << "精確角點坐標[" << t +1 << "]" << " point[x, y] = " << corners[t].x<< " , " << corners[t].y << endl; 67 } 68 }
運行程序,如下:
拖動TrackBar,查看不同角點數設置下的效果: