這篇往后,會暫時先更ORB、SITF、SURF三篇特征算子,在代碼部分,會在本篇介紹下OPENCV特征匹配的特征點KeyPoint、特征描述子和匹配算子Match等的構成。
1ORB簡介
ORB算法是一種特征匹配算法,可用於目標追蹤、圖像匹配等多個方面,在實時圖像處理上,有較好的效果。目前比較流行的特征匹配算子有SIFT、SURF、ORB等,三者有不同的優缺點,SIFT是90年代提出的一種特征匹配算子,在機器學習流行前,十分火熱,SURF是SIFT的改進,相對於SIFT,運行速度更快。ORB是一種為滿足實時特征匹配提出的算子,運行速度較前兩者有很大提升。
2ORB特征匹配原理
2.1概述
ORB特征匹配步驟可分為3步:
(1).特征點檢測,(2)計算特征點描述子,(3)進行圖像的特征點的匹配。(特征點的匹配部分由OPENCV提供的API實現,不再介紹)
2.2特征點檢測:
在特征點檢測部分,ORB采用FAST算法,在介紹ORB算法內FAST的實現前,先介紹下FAST算法的原理,詳細如下:
如上圖,任選圖像中的一點P,以該點為圓形,r為半徑確定一個圓,在圓上均勻取m個像素點,設定一個閾值t,如果m個像素點中,有連續N個像素點的大小均大於或小於t,則這個點就是角點。(其中r、m、t和N參數的設置 可設為 3 、16 、未知、9會有更好的效果,t可根據自己需要進行設置)。
如果對於像分辨率為1440*1080尺寸比較大的圖形,對每個像素點進行上述步驟處理,會需要很大的計算量,不滿足ORB實時的要求,故有一種通過排除非角點的算法,提高了FAST特征點檢測的效率:
先進性提取像素點周圍1,5,9,13四個位置的像素點大小,后通過1,9,5,13的順序進行比較,若其中至少三個大於或小於t,則該點可能為角點,進一步按照上面不走進行判斷;否則,該點不符合角點條件。
上面是FAST算法原理,下面介紹FAST在ORB內的實現。
ORB算法作者使用FAST-9算法進行特征點提取(FAST-9算法有更好的效果,其中9代表m值為9即周圍均勻的9個點)。在進行FAST進行角點檢測時,邊緣位置的部分易混淆,如對一個圓形物體,圓上的每一點按照FAST算法,均可視為角點。為最小化這種影響,ORB算法作何通過Harris角點檢測器把N個關鍵點進行等級排序,使用者可提取前n個自己需要的點(排序方法根據Harris角點響應的大小,響應大小計算可看Harris角點檢測一節中的介紹)。在進行特征點匹配時,檢測出的角點需要滿足尺度不變形和旋轉不變性。ORB作者通過增加圖像金字塔和計算角度的方法達到效果。詳細如下面介紹。
對於尺度不變形,ORB算法通過對初始圖像的按1/2的比例不斷下采樣(即按1/2的比例不斷縮放),得到一系列圖像,形成圖像金字塔。對每層圖像,進行FAST角點檢測,得到一系列角點,具備尺度不變性。(尺度不變性:在一幅圖像提取角點后,對圖像進行縮放,該角點仍能被檢測出,且與第一幅檢測角點進行匹配。)
對於旋轉不變形,ORB采用灰度質心法進行計算每個特征點的主方向。灰度質心法的定義如下:
其中x,y分別表示像素點周圍圓上所選取點的橫坐標和縱坐標,I(x,y)表示灰度值大小,p和q表示指數。角度計算的方法如下:
θ=atan2(m01,m10)。
2.3 特征描述子計算
2.3.1 描述子概念
描述子用於在提取角點后,兩幅圖之間的匹配,相當於角點的特征描述。同時,描述子應具備尺度、角度和光照等條件的不變形。
ORB內描述子的計算采用BRIEF描述子計算算法實現,下面先介紹BRIEF算法原理。
2.3.2 BRIEF算法原理
BRIEF算法在2010年由一篇《BRIEF:Binary Robust Independent Elementary Features》論文提出,是一種二進制編碼的描述子,因沒有采用直方圖描述點的方式,加快特征提取速度,降低匹配時間。
BRIEF算法可分為兩步:1特征點大小的對比。2.選取多對特征點對比。下面具體介紹。
第一步中,以特征點為中心,取SXS的鄰域窗口,在窗口上通過某種規律(下面會有介紹)選擇兩個點p(x)和p(y),比較兩個點像素值的大小,進行如下賦值:
第二步中,在窗口中通過某種規律選取N對像素點,重復第一步進行像素值大小的比較,最后形成一個二進制編碼(編碼為比較的結果,如01010101.. N值大小一般為256)。
上文中提到的某種規律,BRIEF作者嘗試了5中方法,具體如下文,其中法(2)效果較好:
2.3.3ORB內的BRIEF算法
OBR算法對BRIEF有兩種改變,分別為steer BRIEF和rBRIEF。下面做介紹。
相對於BRIEF,steer BRIEF具備旋轉不變形的特征。通過2.2可計算出角點的角度大小θ,將該點周圍的點旋轉θ度,得到新的點對,公式如下:(R.表示旋轉矩陣)
旋轉后,在新的位置上比較像素值的大小,得到描述子。
相對於BRIEF,rBRIEF的提出,是為了彌補在通過steer BRIEF進行旋轉后的缺陷。下圖為三者的一個對比:
其中橫坐標表示均值(即二進制形成的描述子的均值)距離0.5的距離,縱坐標表示數量。
通過上圖可發現,BRIEF描述子主要分布在0附近,表明通過BRIEF計算出的描述子內0和1的數目大致相等,而通過steer BRIEF計算出的描述子在0.3附近較多,表明通過steer BRIEF計算出的描述子內出現0多於1、或1多於0的情況,不易於兩個特征點的描述子的區分,增大了描述符之間的相關性。
rBRIEF算法通過改變描述子的計算方法,進一步減弱同一圖像中特征點的描述子的相關性,具體如下:
對每個角點,考慮其31X31的鄰域,使用領域中每個點周圍的5X5的鄰域的像素值平均值作為該點的像素值,進而比較點對的大小。上面計算可得到(31-5+1)*(31-5+1)=729個子窗口,提取點對的方法有729*728=265356種,通過在這265356中方法中選取256種取法,形成描述子。下面是具體選擇步驟:
(1) 在300k特征點的每個31X31鄰域內,M=265356種方法取點對,比較點對大小,形成一個300kXM的矩陣Q。矩陣種的每一列代表一種提取方法得到的二進制數。
(2) 對Q矩陣的每一列求取平均值,按照平均值到0.5的距離大小,重新對Q進行列向量的排序形成矩陣T。
(3) 將T的第一列向量放到R中。
(4) 將T的下一列向量和R中的所有列向量計算相關性,如果相關系數小於設定的閾值,則將T中的該列向量移入R中。
(5) 重復步驟(4)的操作,直至R中列向量數目為256.
上面即為rBRIEF的算法原理。
3ORB在OPENCV內的API和一個demo:
//一點個人教訓:在使用orb、sift和surf時,發現第一次進行特征點檢測、描述子計算耗費時間遠大於通過已用的接口進行第二次特征點
檢測和描述子計算。(至於原因,博主暫時也未搞懂)舉個簡單的例子:
cv::Ptr<cv::ORB> orb = cv::ORB::create(50); //創建orb對象,后為得到的特征點數目 cv::Mat imageL1,imageR2,imageL2,imageR2,despL1,despR1,despL2,despR2; //只是舉例,建設圖像已傳入 std::vector<cv::KeyPoint> KeyPointL1,KeyPointR1,KeyPointL2,KeyPointR2; orb->detectAndCompute(imageL1,cv::Mat(),KeyPointL1,despL1); //第一次特征點檢測和描述子計算 orb->detectAndCompute(imageR1,cv::Mat(),KeyPointR1,despR1); orb->detectAndCompute(imageL2,cv::Mat(),KeyPointL2,despL2); //第二次特征點檢測和描述子計算 orb->detectAndCompute(imageR2,cv::Mat(),KeyPointR2,despR2);
//博主在使用中,發現第二次用時遠少於第一次用時。同樣情況出現在SIFT和SURF中。ps:使用opencv和opencv-contrib版本為4.0+(這是個大坑,記得博主使用時,因為這個坑,自己都寫出起算法進行特征匹配,還好最后脫坑)
分為三部分:
cv::Ptr<cv::ORB> orb = cv::ORB::create(50); //創建orb對象,后為得到的特征點數目 std::vector<cv::KeyPoint> keyPointL, keyPointR; //創建關鍵點對象 orb->detect(imageR, keyPointR); //關鍵點檢測 orb->detect(imageL, keyPointL); //關鍵點檢測 cv::Mat despL,despR;//創建描述子 orb->compute(imageR, cv::Mat(),keyPointR,despL);//cv::Mat()表示無roi區域 計算描述子 orb->detect(imageL, cv::Mat(), keyPointL,despR); //cv::Mat()表示無roi區域 計算描述子 orb->detectAndCompute(imageL,cv::Mat(),KeyPointL,despL); //進行檢測和計算,相當於上面detect和compute兩步 orb->detectAndCompute(imageR,cv::Mat(),KeyPointR,despR); std::vector<cv::DMatch> matches; cv::Ptr<cv::DescriptorMatcher> matcher=cv::DescriptorMatcher::create("FlannBased"); //進行匹配 matcher->match(despL, despR, matches); std::vector point_l,std::vector point_r; for(int i=0;i<matches.size();i++) //得到匹配點 { point_l.push_back(matches[i].queryIdx); point_r.push_back(matches[i].trainIdx); }
Demo:
#include <opencv2/opencv.hpp> #include <opencv2/xfeatures2d.hpp> int main() { cv::Mat imageL = cv::imread(".I/like/Study.png"); //圖片位置 cv::Mat imageR = cv::imread("./Study/make/me/happy.png");//圖片2位置 cv::cvtColor(imageL,imageL,cv::COLOR_BGR2GRAY); cv::cvtColor(imageR,imageR,cv::COLOR_BGR2GRAY); cv::resize(imageL,imageL,cv::Size(120,90)); cv::resize(imageR,imageR,cv::Size(120,90)); // ORB cv::Ptr<cv::ORB> orb = cv::ORB::create(50); //特征點 std::vector<cv::KeyPoint> keyPointL, keyPointR; //單獨提取特征點 orb->detect(imageL, keyPointL); orb->detect(imageR, keyPointR); //畫特征點 cv::Mat keyPointImageL; cv::Mat keyPointImageR; drawKeypoints(imageL, keyPointL, keyPointImageL, cv::Scalar::all(-1), cv::DrawMatchesFlags::DRAW_RICH_KEYPOINTS); drawKeypoints(imageR, keyPointR, keyPointImageR, cv::Scalar::all(-1), cv::DrawMatchesFlags::DRAW_RICH_KEYPOINTS); //顯示窗口 cv::namedWindow("KeyPoints of imageL"); cv::namedWindow("KeyPoints of imageR"); //顯示特征點 cv::imshow("KeyPoints of imageL", keyPointImageL); cv::imshow("KeyPoints of imageR", keyPointImageR); //特征點匹配 cv::Mat despL, despR; //提取特征點並計算特征描述子 orb->detectAndCompute(imageL, cv::Mat(), keyPointL, despL); orb->detectAndCompute(imageR, cv::Mat(), keyPointR, despR); std::vector<cv::DMatch> matches; //如果采用flannBased方法 那么 desp通過orb的到的類型不同需要先轉換類型 if (despL.type() != CV_32F || despR.type() != CV_32F) { despL.convertTo(despL, CV_32F); despR.convertTo(despR, CV_32F); } cv::Ptr<cv::DescriptorMatcher> matcher = cv::DescriptorMatcher::create("FlannBased"); matcher->match(despL, despR, matches); // //計算特征點距離的最大值 double maxDist = 0; for (int i = 0; i < despL.rows; i++) { double dist = matches[i].distance; if (dist > maxDist) maxDist = dist; } //挑選好的匹配點 std::vector< cv::DMatch > good_matches; int size = matches.size(); for (int i = 0; i < despL.rows; i++) { int low = 0; for(int n=0;n<despL.rows;n++) { if(matches[i].distance<matches[n].distance) { low+=1; } } if(low>=size-15) { good_matches.push_back(matches[i]); } } cv::Mat imageOutput; cv::drawMatches(imageL, keyPointL, imageR, keyPointR, good_matches, imageOutput); cv::namedWindow("picture of matching",cv::WINDOW_NORMAL); cv::imshow("picture of matching", imageOutput); cv::waitKey(0); return 0; }
三、代碼部分:
首先介紹匹配部分DescriptorMatcher:
OPENCV特征匹配部分通過DescriptorMatcher實現,DescriptorMatcher是匹配器的抽象基類,其具體類有: class FlannBasedMatcher class BFMatcher 源代碼暫時未找到,不再介紹其內部如何構造,在此僅介紹其使用方法,匹配器的創建有兩種方式(建議用1,方便,接口多): 一: static Ptr<DescriptorMatcher> create(const string& descriptorMatcherType) 如: cv::Ptr<cv::DescriptorMatcher> matcher = cv::DescriptorMatcher::create("FlannBased"); 二: DescriptorMatcher *pMatcher = newBFMatcher; DescriptorMatcher的接口函數有以下幾種(對於一般的使用,僅crete()和match()兩個函數足可): create():創建匹配器,支持的匹配器類型有:BruteForce 、BruteForce、BruteForce-Hamming、BruteForce-Hamming(2)和FlannBased (博主目前使用FlannBased,效果還可,其它暫未嘗試) match(desL,desR,match) :通過描述子進行匹配,desL和desR為兩個Mat類型的描述子,match為DMatch類型的vector,下面會介紹DMatch類型 knnMatch();找到最好的k個匹配 add();添加匹配器,用來連接匹配器集合,若匹配器集合不是空的,新加的匹配器會加到集合的后面。 getTrainDescriptors();獲得匹配器集合 isMaskSupported();匹配器是否支持掩模,使得話就返回ture radiusMatch();找到距離不超過規定的匹配
介紹DMatch類型:
簡化版(針對於不想看下面源代碼的人):DMatch包含四個有用信息,queryIdx(要匹配的描述子的索引(序列)),trainIdx(被匹配的描述子索引(序列)),imageIdx(匹配圖像的索引)distance表示兩個描述子之間的距離,越小匹配效果越好。queryIdx
為match()函數第一個參數描述子對應圖像的關鍵點的索引,trainIdx為第二個參數描述子對應圖像的關鍵點的索引。imageIdx僅在多張圖像間進行匹配時,用於表示匹
圖像的序列。請注意,queryIdx為要匹配,trainIdx為被匹配。下面有實戰展示如何使用。
/* * DMatch主要用來儲存匹配信息的結構體,query是要匹配的描述子,train是被匹配的描述子,在Opencv中進行匹配時 * void DescriptorMatcher::match( const Mat& queryDescriptors, const Mat& trainDescriptors, vector<DMatch>& matches, const Mat& mask ) const * match函數的參數中位置在前面的為query descriptor,后面的是 train descriptor * 例如:query descriptor的數目為20,train descriptor數目為30,則DescriptorMatcher::match后的vector<DMatch>的size為20 * 若反過來,則vector<DMatch>的size為30 */ struct CV_EXPORTS_W_SIMPLE DMatch { //默認構造函數,FLT_MAX是無窮大 //#define FLT_MAX 3.402823466e+38F /* max value */ CV_WRAP DMatch() : queryIdx(-1), trainIdx(-1), imgIdx(-1), distance(FLT_MAX) {} //DMatch構造函數 CV_WRAP DMatch( int _queryIdx, int _trainIdx, float _distance ) : queryIdx(_queryIdx), trainIdx(_trainIdx), imgIdx(-1), distance(_distance) {} //DMatch構造函數 CV_WRAP DMatch( int _queryIdx, int _trainIdx, int _imgIdx, float _distance ) : queryIdx(_queryIdx), trainIdx(_trainIdx), imgIdx(_imgIdx), distance(_distance) {} //queryIdx為query描述子的索引,match函數中前面的那個描述子 CV_PROP_RW int queryIdx; // query descriptor index //trainIdx為train描述子的索引,match函數中后面的那個描述子 CV_PROP_RW int trainIdx; // train descriptor index //imgIdx為進行匹配圖像的索引 //例如已知一幅圖像的sift描述子,與其他十幅圖像的描述子進行匹配,找最相似的圖像,則imgIdx此時就有用了。 CV_PROP_RW int imgIdx; // train image index //distance為兩個描述子之間的距離 CV_PROP_RW float distance; //DMatch比較運算符重載,比較的是DMatch中的distance,小於為true,否則為false // less is better bool operator<( const DMatch &m ) const { return distance < m.distance; } };
介紹KeyPoint類型(未找到文本樣式,盜幾張圖):
簡化版(懶人通道):KeyPoint主要使用的類型有pt(二維坐標點),size(該關鍵點領域直徑大小),angle(關鍵點的方向:0~360°),response(關鍵點的相應強度,可用於排序),octve(關鍵點的金字塔層數),class_id(當要對圖片進行分類時,用class_id對每個關鍵點進行區分,默認為-1。暫時不使用,使用時你也就已明白它的含義。),
KeyPointsFilter類型(屬於對KeyPoint處理的封裝好的類,已在算法內實現。一般僅在自己想寫特征匹配算法時,才會用到這些):
KeyPointsFilter包含5個功能函數:分別為runByImageBorder()、runByKeyPointSzie()、runByPixelsMask()、removeDuplicated()、retainBest()。其源代碼結構如下:
ORB API:
//一點個人教訓:在使用orb、sift和surf時,發現第一次進行特征點檢測、描述子計算耗費時間遠大於通過已用的接口進行第二次特征點
檢測和描述子計算。(至於原因,博主暫時也未搞懂)舉個簡單的例子:
cv::Ptr<cv::ORB> orb = cv::ORB::create(50); //創建orb對象,后為得到的特征點數目
cv::Mat imageL1,imageR2,imageL2,imageR2,despL1,despR1,despL2,despR2; //只是舉例,建設圖像已傳入
std::vector<cv::KeyPoint> KeyPointL1,KeyPointR1,KeyPointL2,KeyPointR2;
orb->detectAndCompute(imageL1,cv::Mat(),KeyPointL1,despL1); //第一次特征點檢測和描述子計算
orb->detectAndCompute(imageR1,cv::Mat(),KeyPointR1,despR1);
orb->detectAndCompute(imageL2,cv::Mat(),KeyPointL2,despL2); //第二次特征點檢測和描述子計算
orb->detectAndCompute(imageR2,cv::Mat(),KeyPointR2,despR2);
//博主在使用中,發現第二次用時遠少於第一次用時。同樣情況出現在SIFT和SURF中。ps:使用opencv和opencv-contrib版本為4.0+(這是個大坑,記得博主使用時,因為這個坑,自己都寫出起算法進行特征匹配,還好最后脫坑)
分為三部分: cv::Ptr<cv::ORB> orb = cv::ORB::create(50); //創建orb對象,后為得到的特征點數目 std::vector<cv::KeyPoint> keyPointL, keyPointR; //創建關鍵點對象 orb->detect(imageR, keyPointR); //關鍵點檢測 orb->detect(imageL, keyPointL); //關鍵點檢測 cv::Mat despL,despR;//創建描述子 orb->compute(imageR, cv::Mat(),keyPointR,despL);//cv::Mat()表示無roi區域 計算描述子 orb->detect(imageL, cv::Mat(), keyPointL,despR); //cv::Mat()表示無roi區域 計算描述子
orb->detectAndCompute(imageL,cv::Mat(),KeyPointL,despL); //進行檢測和計算,相當於上面detect和compute兩步
orb->detectAndCompute(imageR,cv::Mat(),KeyPointR,despR); std::vector<cv::DMatch> matches; cv::Ptr<cv::DescriptorMatcher> matcher=cv::DescriptorMatcher::create("FlannBased"); //進行匹配 matcher->match(despL, despR, matches); std::vector point_l,std::vector point_r; for(int i=0;i<matches.size();i++) //得到匹配點 { point_l.push_back(matches[i].queryIdx); point_r.push_back(matches[i].trainIdx); }
實戰展示:
#include <opencv2/opencv.hpp>
#include <opencv2/xfeatures2d.hpp>
int main()
{
cv::Mat imageL = cv::imread(".I/like/Study.png"); //圖片位置
cv::Mat imageR = cv::imread("./Study/make/me/happy.png");//圖片2位置
cv::cvtColor(imageL,imageL,cv::COLOR_BGR2GRAY);
cv::cvtColor(imageR,imageR,cv::COLOR_BGR2GRAY);
cv::resize(imageL,imageL,cv::Size(120,90));
cv::resize(imageR,imageR,cv::Size(120,90));
// ORB
cv::Ptr<cv::ORB> orb = cv::ORB::create(50);
//特征點
std::vector<cv::KeyPoint> keyPointL, keyPointR;
//單獨提取特征點
orb->detect(imageL, keyPointL);
orb->detect(imageR, keyPointR);
//畫特征點
cv::Mat keyPointImageL;
cv::Mat keyPointImageR;
drawKeypoints(imageL, keyPointL, keyPointImageL, cv::Scalar::all(-1), cv::DrawMatchesFlags::DRAW_RICH_KEYPOINTS);
drawKeypoints(imageR, keyPointR, keyPointImageR, cv::Scalar::all(-1), cv::DrawMatchesFlags::DRAW_RICH_KEYPOINTS);
//顯示窗口
cv::namedWindow("KeyPoints of imageL");
cv::namedWindow("KeyPoints of imageR");
//顯示特征點
cv::imshow("KeyPoints of imageL", keyPointImageL);
cv::imshow("KeyPoints of imageR", keyPointImageR);
//特征點匹配
cv::Mat despL, despR;
//提取特征點並計算特征描述子
orb->detectAndCompute(imageL, cv::Mat(), keyPointL, despL);
orb->detectAndCompute(imageR, cv::Mat(), keyPointR, despR);
std::vector<cv::DMatch> matches;
//如果采用flannBased方法 那么 desp通過orb的到的類型不同需要先轉換類型
if (despL.type() != CV_32F || despR.type() != CV_32F)
{
despL.convertTo(despL, CV_32F);
despR.convertTo(despR, CV_32F);
}
cv::Ptr<cv::DescriptorMatcher> matcher = cv::DescriptorMatcher::create("FlannBased");
matcher->match(despL, despR, matches);
// //計算特征點距離的最大值
double maxDist = 0;
for (int i = 0; i < despL.rows; i++)
{
double dist = matches[i].distance;
if (dist > maxDist)
maxDist = dist;
}
//挑選好的匹配點
std::vector< cv::DMatch > good_matches;
int size = matches.size();
for (int i = 0; i < despL.rows; i++)
{
int low = 0;
for(int n=0;n<despL.rows;n++)
{
if(matches[i].distance<matches[n].distance)
{
low+=1;
}
}
if(low>=size-15)
{
good_matches.push_back(matches[i]);
}
}
cv::Mat imageOutput;
cv::drawMatches(imageL, keyPointL, imageR, keyPointR, good_matches, imageOutput);
cv::namedWindow("picture of matching",cv::WINDOW_NORMAL);
cv::imshow("picture of matching", imageOutput);
cv::waitKey(0);
return 0;
}
ORB源代碼:
CV_EXPORTS_W ORB : Feature2D { : { kBytes = 32, HARRIS_SCORE=0, FAST_SCORE=1 }; CV_WRAP ORB( nfeatures = 500, scaleFactor = 1.2f, nlevels = 8, edgeThreshold = 31, firstLevel = 0, WTA_K=2, scoreType=ORB::HARRIS_SCORE, patchSize=31 ); descriptorSize() ; descriptorType() ; operator()(InputArray image, InputArray mask, vector<KeyPoint>& keypoints) ; operator()( InputArray image, InputArray mask, vector<KeyPoint>& keypoints, OutputArray descriptors, useProvidedKeypoints= ) ; AlgorithmInfo* info() ; : computeImpl( Mat& image, vector<KeyPoint>& keypoints, Mat& descriptors ) ; detectImpl( Mat& image, vector<KeyPoint>& keypoints, Mat& mask=Mat() ) ; CV_PROP_RW nfeatures; CV_PROP_RW scaleFactor; CV_PROP_RW nlevels; CV_PROP_RW edgeThreshold; CV_PROP_RW firstLevel; CV_PROP_RW WTA_K; CV_PROP_RW scoreType; CV_PROP_RW patchSize; }; /** Compute the ORB features and descriptors on an image * @param keypoints the resulting keypoints * @param do_keypoints if true, the keypoints are computed, otherwise used as an input */ ORB::operator()( InputArray _image, InputArray _mask, vector<KeyPoint>& _keypoints, OutputArray _descriptors, useProvidedKeypoints) { CV_Assert(patchSize >= 2); do_keypoints = !useProvidedKeypoints; do_descriptors = _descriptors.needed(); ( (!do_keypoints && !do_descriptors) || _image.empty() ) ; HARRIS_BLOCK_SIZE = 9; halfPatchSize = patchSize / 2;. border = std::max(edgeThreshold, std::max(halfPatchSize, HARRIS_BLOCK_SIZE/2))+1; Mat image = _image.getMat(), mask = _mask.getMat(); ( image.type() != CV_8UC1 ) cvtColor(_image, image, CV_BGR2GRAY); levelsNum = ->nlevels; ( !do_keypoints ) { levelsNum = 0; ( i = 0; i < _keypoints.size(); i++ ) levelsNum = std::max(levelsNum, std::max(_keypoints[i].octave, 0)); levelsNum++; } vector<Mat> imagePyramid(levelsNum), maskPyramid(levelsNum); ( level = 0; level < levelsNum; ++level) { scale = 1/getScale(level, firstLevel, scaleFactor); static inline float getScale(int level, int firstLevel, double scaleFactor) return (float)std::pow(scaleFactor, (double)(level - firstLevel)); */ Size sz(cvRound(image.cols*scale), cvRound(image.rows*scale)); Size wholeSize(sz.width + border*2, sz.height + border*2); Mat temp(wholeSize, image.type()), masktemp; imagePyramid[level] = temp(Rect(border, border, sz.width, sz.height)); ( !mask.empty() ) { masktemp = Mat(wholeSize, mask.type()); maskPyramid[level] = masktemp(Rect(border, border, sz.width, sz.height)); } ( level != firstLevel ) { ( level < firstLevel ) { resize(image, imagePyramid[level], sz, 0, 0, INTER_LINEAR); (!mask.empty()) resize(mask, maskPyramid[level], sz, 0, 0, INTER_LINEAR); } { resize(imagePyramid[level-1], imagePyramid[level], sz, 0, 0, INTER_LINEAR); (!mask.empty()) { resize(maskPyramid[level-1], maskPyramid[level], sz, 0, 0, INTER_LINEAR); threshold(maskPyramid[level], maskPyramid[level], 254, 0, THRESH_TOZERO); } } copyMakeBorder(imagePyramid[level], temp, border, border, border, border, BORDER_REFLECT_101+BORDER_ISOLATED); (!mask.empty()) copyMakeBorder(maskPyramid[level], masktemp, border, border, border, border, BORDER_CONSTANT+BORDER_ISOLATED); } { copyMakeBorder(image, temp, border, border, border, border, BORDER_REFLECT_101); ( !mask.empty() ) copyMakeBorder(mask, masktemp, border, border, border, border, BORDER_CONSTANT+BORDER_ISOLATED); } } vector < vector<KeyPoint> > allKeypoints; ( do_keypoints ) { computeKeyPoints(imagePyramid, maskPyramid, allKeypoints, nfeatures, firstLevel, scaleFactor, edgeThreshold, patchSize, scoreType); for (int level = 0; level < n_levels; ++level) vector<KeyPoint>& keypoints = all_keypoints[level]; keypoints.clear(); keypoint_end = temp.end(); keypoint != keypoint_end; ++keypoint) } { KeyPointsFilter::runByImageBorder(_keypoints, image.size(), edgeThreshold); allKeypoints.resize(levelsNum); (vector<KeyPoint>::iterator keypoint = _keypoints.begin(), keypointEnd = _keypoints.end(); keypoint != keypointEnd; ++keypoint) allKeypoints[keypoint->octave].push_back(*keypoint); ( level = 0; level < levelsNum; ++level) { (level == firstLevel) ; vector<KeyPoint> & keypoints = allKeypoints[level]; scale = 1/getScale(level, firstLevel, scaleFactor); (vector<KeyPoint>::iterator keypoint = keypoints.begin(), keypointEnd = keypoints.end(); keypoint != keypointEnd; ++keypoint) keypoint->pt *= scale; } } Mat descriptors; vector<Point> pattern; ( do_descriptors ) { nkeypoints = 0; ( level = 0; level < levelsNum; ++level) nkeypoints += ()allKeypoints[level].size(); ( nkeypoints == 0 ) _descriptors.release(); { _descriptors.create(nkeypoints, descriptorSize(), CV_8U); descriptors = _descriptors.getMat(); } npoints = 512; Point patternbuf[npoints]; Point* pattern0 = ( Point*)bit_pattern_31_; ( patchSize != 31 ) { pattern0 = patternbuf; makeRandomPattern(patchSize, patternbuf, npoints); } CV_Assert( WTA_K == 2 || WTA_K == 3 || WTA_K == 4 ); ( WTA_K == 2 ) std::copy(pattern0, pattern0 + npoints, std::back_inserter(pattern)); { ntuples = descriptorSize()*4; initializeOrbPattern(pattern0, pattern, ntuples, WTA_K, npoints); } } _keypoints.clear(); offset = 0; ( level = 0; level < levelsNum; ++level) { vector<KeyPoint>& keypoints = allKeypoints[level]; nkeypoints = ()keypoints.size(); (do_descriptors) { Mat desc; (!descriptors.empty()) { desc = descriptors.rowRange(offset, offset + nkeypoints); } offset += nkeypoints; Mat& workingMat = imagePyramid[level]; GaussianBlur(workingMat, workingMat, Size(7, 7), 2, 2, BORDER_REFLECT_101); computeDescriptors(workingMat, keypoints, desc, pattern, descriptorSize(), WTA_K); } (level != firstLevel) { scale = getScale(level, firstLevel, scaleFactor); (vector<KeyPoint>::iterator keypoint = keypoints.begin(), keypointEnd = keypoints.end(); keypoint != keypointEnd; ++keypoint) keypoint->pt *= scale; } _keypoints.insert(_keypoints.end(), keypoints.begin(), keypoints.end()); } } /** Compute the ORB keypoints on an image * @param keypoints the resulting keypoints, clustered per level computeKeyPoints( vector<Mat>& imagePyramid, vector<Mat>& maskPyramid, vector<vector<KeyPoint> >& allKeypoints, nfeatures, firstLevel, scaleFactor, edgeThreshold, patchSize, scoreType ) { nlevels = ()imagePyramid.size(); vector<> nfeaturesPerLevel(nlevels); factor = ()(1.0 / scaleFactor); ndesiredFeaturesPerScale = nfeatures*(1 - factor)/(1 - ()pow(()factor, ()nlevels)); sumFeatures = 0; ( level = 0; level < nlevels-1; level++ ) { nfeaturesPerLevel[level] = cvRound(ndesiredFeaturesPerScale); sumFeatures += nfeaturesPerLevel[level]; ndesiredFeaturesPerScale *= factor; } nfeaturesPerLevel[nlevels-1] = std::max(nfeatures - sumFeatures, 0); halfPatchSize = patchSize / 2; vector<> umax(halfPatchSize + 2); v, v0, vmax = cvFloor(halfPatchSize * sqrt(2.f) / 2 + 1); vmin = cvCeil(halfPatchSize * sqrt(2.f) / 2); (v = 0; v <= vmax; ++v) umax[v] = cvRound(sqrt(()halfPatchSize * halfPatchSize - v * v)); (v = halfPatchSize, v0 = 0; v >= vmin; --v) { (umax[v0] == umax[v0 + 1]) ++v0; umax[v] = v0; ++v0; } allKeypoints.resize(nlevels); ( level = 0; level < nlevels; ++level) { featuresNum = nfeaturesPerLevel[level]; allKeypoints[level].reserve(featuresNum*2); vector<KeyPoint> & keypoints = allKeypoints[level]; FastFeatureDetector fd(20, ); fd.detect(imagePyramid[level], keypoints, maskPyramid[level]); KeyPointsFilter::runByImageBorder(keypoints, imagePyramid[level].size(), edgeThreshold); ( scoreType == ORB::HARRIS_SCORE ) { KeyPointsFilter::retainBest(keypoints, 2 * featuresNum); HarrisResponses(imagePyramid[level], keypoints, 7, HARRIS_K); } KeyPointsFilter::retainBest(keypoints, featuresNum); sf = getScale(level, firstLevel, scaleFactor); (vector<KeyPoint>::iterator keypoint = keypoints.begin(), keypointEnd = keypoints.end(); keypoint != keypointEnd; ++keypoint) { keypoint->octave = level; keypoint->size = patchSize*sf; } computeOrientation(imagePyramid[level], keypoints, halfPatchSize, umax); } } static computeOrientation( Mat& image, vector<KeyPoint>& keypoints, halfPatchSize, vector<>& umax) { (vector<KeyPoint>::iterator keypoint = keypoints.begin(), keypointEnd = keypoints.end(); keypoint != keypointEnd; ++keypoint) { keypoint->angle = IC_Angle(image, halfPatchSize, keypoint->pt, umax); } } static IC_Angle( Mat& image, half_k, Point2f pt, vector<> & u_max) { m_01 = 0, m_10 = 0; uchar* center = &image.at<uchar> (cvRound(pt.y), cvRound(pt.x)); ( u = -half_k; u <= half_k; ++u) m_10 += u * center[u]; step = ()image.step1(); ( v = 1; v <= half_k; ++v) { v_sum = 0; d = u_max[v]; ( u = -d; u <= d; ++u) { val_plus = center[u + v*step], val_minus = center[u - v*step]; v_sum += (val_plus - val_minus); m_10 += u * (val_plus + val_minus); } m_01 += v * v_sum; } fastAtan2(()m_01, ()m_10); } static computeDescriptors( Mat& image, vector<KeyPoint>& keypoints, Mat& descriptors, vector<Point>& pattern, dsize, WTA_K) { CV_Assert(image.type() == CV_8UC1); descriptors = Mat::zeros(()keypoints.size(), dsize, CV_8UC1); ( i = 0; i < keypoints.size(); i++) computeOrbDescriptor(keypoints[i], image, &pattern[0], descriptors.ptr(()i), dsize, WTA_K); } static computeOrbDescriptor( KeyPoint& kpt, Mat& img, Point* pattern, uchar* desc, dsize, WTA_K) { angle = kpt.angle; angle *= ()(CV_PI/180.f); a = ()cos(angle), b = ()sin(angle); uchar* center = &img.at<uchar>(cvRound(kpt.pt.y), cvRound(kpt.pt.x)); step = ()img.step; center[cvRound(pattern[idx].x*b + pattern[idx].y*a)*step + \ cvRound(pattern[idx].x*a - pattern[idx].y*b)] x, y; ix, iy; (x = pattern[idx].x*a - pattern[idx].y*b, \ y = pattern[idx].x*b + pattern[idx].y*a, \ ix = cvFloor(x), iy = cvFloor(y), \ x -= ix, y -= iy, \ cvRound(center[iy*step + ix]*(1-x)*(1-y) + center[(iy+1)*step + ix]*(1-x)*y + \ center[iy*step + ix+1]*x*(1-y) + center[(iy+1)*step + ix+1]*x*y)) ( WTA_K == 2 ) { ( i = 0; i < dsize; ++i, pattern += 16) { t0, t1, val; t0 = GET_VALUE(0); t1 = GET_VALUE(1); val = t0 < t1; t0 = GET_VALUE(2); t1 = GET_VALUE(3); val |= (t0 < t1) << 1; t0 = GET_VALUE(4); t1 = GET_VALUE(5); val |= (t0 < t1) << 2; t0 = GET_VALUE(6); t1 = GET_VALUE(7); val |= (t0 < t1) << 3; t0 = GET_VALUE(8); t1 = GET_VALUE(9); val |= (t0 < t1) << 4; t0 = GET_VALUE(10); t1 = GET_VALUE(11); val |= (t0 < t1) << 5; t0 = GET_VALUE(12); t1 = GET_VALUE(13); val |= (t0 < t1) << 6; t0 = GET_VALUE(14); t1 = GET_VALUE(15); val |= (t0 < t1) << 7; desc[i] = (uchar)val; } } ( WTA_K == 3 ) { ( i = 0; i < dsize; ++i, pattern += 12) { t0, t1, t2, val; t0 = GET_VALUE(0); t1 = GET_VALUE(1); t2 = GET_VALUE(2); val = t2 > t1 ? (t2 > t0 ? 2 : 0) : (t1 > t0); t0 = GET_VALUE(3); t1 = GET_VALUE(4); t2 = GET_VALUE(5); val |= (t2 > t1 ? (t2 > t0 ? 2 : 0) : (t1 > t0)) << 2; t0 = GET_VALUE(6); t1 = GET_VALUE(7); t2 = GET_VALUE(8); val |= (t2 > t1 ? (t2 > t0 ? 2 : 0) : (t1 > t0)) << 4; t0 = GET_VALUE(9); t1 = GET_VALUE(10); t2 = GET_VALUE(11); val |= (t2 > t1 ? (t2 > t0 ? 2 : 0) : (t1 > t0)) << 6; desc[i] = (uchar)val; } } ( WTA_K == 4 ) { ( i = 0; i < dsize; ++i, pattern += 16) { t0, t1, t2, t3, u, v, k, val; t0 = GET_VALUE(0); t1 = GET_VALUE(1); t2 = GET_VALUE(2); t3 = GET_VALUE(3); u = 0, v = 2; ( t1 > t0 ) t0 = t1, u = 1; ( t3 > t2 ) t2 = t3, v = 3; k = t0 > t2 ? u : v; val = k; t0 = GET_VALUE(4); t1 = GET_VALUE(5); t2 = GET_VALUE(6); t3 = GET_VALUE(7); u = 0, v = 2; ( t1 > t0 ) t0 = t1, u = 1; ( t3 > t2 ) t2 = t3, v = 3; k = t0 > t2 ? u : v; val |= k << 2; t0 = GET_VALUE(8); t1 = GET_VALUE(9); t2 = GET_VALUE(10); t3 = GET_VALUE(11); u = 0, v = 2; ( t1 > t0 ) t0 = t1, u = 1; ( t3 > t2 ) t2 = t3, v = 3; k = t0 > t2 ? u : v; val |= k << 4; t0 = GET_VALUE(12); t1 = GET_VALUE(13); t2 = GET_VALUE(14); t3 = GET_VALUE(15); u = 0, v = 2; ( t1 > t0 ) t0 = t1, u = 1; ( t3 > t2 ) t2 = t3, v = 3; k = t0 > t2 ? u : v; val |= k << 6; desc[i] = (uchar)val; } } CV_Error( CV_StsBadSize, }
如有錯誤,歡迎指正和批評。有部分參考,因忘記記錄,未標注下面,抱歉。
參考:
https://blog.csdn.net/Small_Munich/article/details/80866162
https://www.cnblogs.com/TransTown/p/7396996.html
http://docs.opencv.org/3.3.0/d2/d29/classcv_1_1KeyPoint.html
https://blog.csdn.net/Darlingqiang/article/details/79404869
https://blog.csdn.net/frozenspring/article/details/78146076
https://blog.csdn.net/zcg1942/article/details/83824367
https://www.cnblogs.com/wyuzl/p/7856863.html