影象特徵描述子之ORB
ORB(Oriented FAST and Rotated BRIEF)演算法是對FAST特徵點檢測和BRIEF特徵描述子的一種結合,在原有的基礎上做了改進與優化,使得ORB特徵具備多種區域性不變性,併為實時計算提供了可能。
特徵點檢測
ORB首先利用FAST演算法檢測特徵點,然後計算每個特徵點的Harris角點響應值,從中篩選出
相關內容已在博文FAST角點檢測和Harris角點檢測分別做了詳細的介紹。
FAST檢測特徵點不具備尺度不變性,可以像SIFT特徵一樣,藉助尺度空間理論構建影象高斯金字塔,然後在每一層金字塔影象上檢測角點,以實現尺度不變性。對於旋轉不變性,原論文中提出了一種利用影象矩(幾何矩),在半徑為r的鄰域內求取灰度質心的方法,從特徵點到灰度質心的向量,定義為該特徵點的主方向。影象矩定義如下:
特徵點與質心形成的向量與
特徵點描述
ORB採用BRIEF作為特徵描述方法,BRIEF雖然速度優勢明顯,但也存在一些缺陷,例如不具備尺度不變性和旋轉不變性,對噪聲敏感。尺度不變性的問題在利用FAST檢測特徵點時,通過構建高斯金字塔得以解決。BRIEF中採用
至於旋轉不變性問題,可利用FAST特徵點檢測時求取的主方向,旋轉特徵點鄰域,但旋轉整個Patch再提取BRIEF特徵描述子的計算代價較大,因此,ORB採用了一種更高效的方式,在每個特徵點鄰域Patch內,先選取256對隨機點,將其進行旋轉,然後做判決編碼為二進位制串。n個點對構成矩陣
旋轉矩陣
旋轉後的座標矩陣為
描述子的區分性
通過上述方法得到的特徵描述子具有旋轉不變性,稱為steered BRIEF(sBRIEF),但匹配效果卻不如原始BRIEF演算法,因為可區分性減弱了。特徵描述子的一個要求就是要儘可能地表達特徵點的獨特性,便於區分不同的特徵點。如下圖所示,為幾種特徵描述子的均值分佈,橫軸為均值與0.5之間的距離,縱軸為相應均值下特徵點的統計數量。可以看出,BRIEF描述子所有位元位的均值接近於0.5,且方差很大;方差越大表明可區分性越好。不同特徵點的描述子表現出較大的差異性,不易造成無匹配。但steered BRIEF進行了座標旋轉,損失了這個特性,導致可區分性減弱,相關性變強,不利於匹配。
為了解決steered BRIEF可區分性降低的問題,ORB使用了一種基於學習的方法來選擇一定數量的隨機點對。首先建立一個大約300k特徵點的資料集(特徵點來源於PASCAL2006中的影象),對每個特徵點,考慮其
- 對矩陣
- 將
- 取出
- 迴圈執行上一步,直到
這樣,最後得到的就是相關性最小的256對隨機點,該方法稱為rBRIEF。
Experiment & Result
OpenCV實現ORB特徵檢測與描述
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/features2d/features2d.hpp>
using namespace cv;
int main(int argc, char** argv)
{
Mat img_1 = imread("box.png");
Mat img_2 = imread("box_in_scene.png");
// -- Step 1: Detect the keypoints using STAR Detector
std::vector<KeyPoint> keypoints_1,keypoints_2;
ORB orb;
orb.detect(img_1, keypoints_1);
orb.detect(img_2, keypoints_2);
// -- Stpe 2: Calculate descriptors (feature vectors)
Mat descriptors_1, descriptors_2;
orb.compute(img_1, keypoints_1, descriptors_1);
orb.compute(img_2, keypoints_2, descriptors_2);
//-- Step 3: Matching descriptor vectors with a brute force matcher
BFMatcher matcher(NORM_HAMMING);
std::vector<DMatch> mathces;
matcher.match(descriptors_1, descriptors_2, mathces);
// -- dwaw matches
Mat img_mathes;
drawMatches(img_1, keypoints_1, img_2, keypoints_2, mathces, img_mathes);
// -- show
imshow("Mathces", img_mathes);
waitKey(0);
return 0;
}
OpenCV中ORB演算法的部分原始碼實現
//計算Harris角點響應
static void HarrisResponses(const Mat& img, vector<KeyPoint>& pts, int blockSize, float harris_k)
{
CV_Assert( img.type() == CV_8UC1 && blockSize*blockSize <= 2048 );
size_t ptidx, ptsize = pts.size();
const uchar* ptr00 = img.ptr<uchar>();
int step = (int)(img.step/img.elemSize1());
int r = blockSize/2;
float scale = (1 << 2) * blockSize * 255.0f;
scale = 1.0f / scale;
float scale_sq_sq = scale * scale * scale * scale;
AutoBuffer<int> ofsbuf(blockSize*blockSize);
int* ofs = ofsbuf;
for( int i = 0; i < blockSize; i++ )
for( int j = 0; j < blockSize; j++ )
ofs[i*blockSize + j] = (int)(i*step + j);
for( ptidx = 0; ptidx < ptsize; ptidx++ )
{
int x0 = cvRound(pts[ptidx].pt.x - r);
int y0 = cvRound(pts[ptidx].pt.y - r);
const uchar* ptr0 = ptr00 + y0*step + x0;
int a = 0, b = 0, c = 0;
for( int k = 0; k < blockSize*blockSize; k++ )
{
const uchar* ptr = ptr0 + ofs[k];
int Ix = (ptr[1] - ptr[-1])*2 + (ptr[-step+1] - ptr[-step-1]) + (ptr[step+1] - ptr[step-1]);
int Iy = (ptr[step] - ptr[-step])*2 + (ptr[step-1] - ptr[-step-1]) + (ptr[step+1] - ptr[-step+1