OpenCV開發筆記(六十五):紅胖子8分鐘帶你深入瞭解ORB特徵點(圖文並茂+淺顯易懂+程式原始碼)

紅胖子(紅模仿)發表於2020-06-23
 

前言

  紅胖子,來也!
  識別除了傳統的模板匹配之外就是體徵點了,前面介紹了Suft特徵點,還有一個傳統的就會ORB特徵點了。
  其實識別的特徵點多種多樣,既可以自己寫也可以使用opencv為我們提供的,一般來說根據特徵點的特性和效率,選擇適合我們場景的特徵就可以了。
  本篇,介紹ORB特徵提取。

 

Demo

  在這裡插入圖片描述
  在這裡插入圖片描述
  在這裡插入圖片描述
  在這裡插入圖片描述

 

ORB特徵點

概述

  ORB是ORiented Brief的簡稱,是briedf演算法的改進版,於2011年在《ORB:an fficient alternative to SIFT or SURF》中提出。
ORB演算法分為兩部分,分別是特徵點提取和特徵點描述:

  • 特徵提取:由FAST(Features from Accelerated Segment Test)演算法發展來的;
  • 特徵點描述:根據BRIEF(Binary Robust IndependentElementary Features)特徵描述演算法改進的。

  ORB特徵是將FAST特徵點的檢測方法與BRIEF特徵描述子結合起來,並在它們原來的基礎上做了改進與優化。據說,ORB演算法的速度是sift的100倍,是surf的10倍。

Brief描述子

  該特徵描述子是在特徵點附近隨機選取若干點對,將這些點對的灰度值的大小,組合成一個二進位制串,組合成一個二進位制傳,並將這個二進位制串作為該特徵點的特徵描述子。
  Brief的速度快,但是使用灰度值作為描述字計算的源頭,毫無疑問會有一些顯而易見的問題:

  • 旋轉後灰度變了導致無法識別,因其不具備旋轉不變形;
  • 由於是計算灰度,噪聲灰度化則無法去噪,所以對噪聲敏感;
  • 尺度不同影響灰度計算,所以也不具備尺度不變形;
    ORB是試圖使其具備旋轉不變性和降低噪聲敏感度而提出的。

特徵檢測步驟

步驟一:使用brief運算元的方式初步提取。

  該步能夠提取大量的特徵點,但是有很大一部分的特徵點的質量不高。從影像中選取一點P,以P為圓心畫一個半徑為N畫素半徑的圓。圓周上如果有連續n個畫素點的灰度值比P點的灰度值大或者小,則認為P為特徵點。
  在這裡插入圖片描述

步驟二:機器學習的方法篩選最優特徵點。

  通俗來說就是使用ID3演算法訓練一個決策樹,將特徵點圓周上的16個畫素輸入決策樹中,以此來篩選出最優的FAST特徵點。

步驟三:非極大值抑制去除區域性較密集特徵點。

  使用非極大值抑制演算法去除臨近位置多個特徵點的問題。為每一個特徵點計算出其響應大小。計算方式是特徵點P和其周圍16個特徵點偏差的絕對值和。在比較臨近的特徵點中,保留響應值較大的特徵點,刪除其餘的特徵點。

步驟四:使用金字塔來實現多尺度不變形。

步驟五:使用影像的矩判斷特徵點的旋轉不變性

  ORB演算法提出使用矩(moment)法來確定FAST特徵點的方向。也就是說通過矩來計算特徵點以r為半徑範圍內的質心,特徵點座標到質心形成一個向量作為該特徵點的方向。

ORB類的使用

cv::Ptr<cv::ORB> _pOrb = cv::ORB::create();
std::vector<cv::KeyPoint> keyPoints1;
//特徵點檢測
_pOrb->detect(srcMat, keyPoints1);

ORB相關函式原型

static Ptr<ORB> create(int nfeatures=500,
                       float scaleFactor=1.2f,
                       int nlevels=8,
                       int edgeThreshold=31,
                       int firstLevel=0,
                       int WTA_K=2,
                       int scoreType=ORB::HARRIS_SCORE,
                       int patchSize=31,
                       int fastThreshold=20);
  • 引數一:int型別的nfeatures,用於ORB的,保留最大的關鍵點數,預設值500;
  • 引數二:float型別的scaleFactor,比例因子,大於1時為金字塔抽取比。的等於2表示經典的金字塔,每一個下一層的畫素比上一層少4倍,但是比例係數太大了將顯著降低特徵匹配分數。另一方面,太接近1個比例因子這意味著要覆蓋一定的範圍,你需要更多的金字塔級別,所以速度會受影響的,預設值1.2f;
  • 引數三:int型別的nlevels,nlevels金字塔級別的數目。最小級別的線性大小等於輸入影像線性大小/功率(縮放因子,nlevels-第一級),預設值為8;
  • 引數四:int型別的edgeThreshold,edgeThreshold這是未檢測到功能的邊框大小。它應該大致匹配patchSize引數。;
  • 引數五:int型別的firstLevel,要將源影像放置到的金字塔級別。以前的圖層已填充使用放大的源影像;
  • 引數六:int型別的WTA_K,生成定向簡短描述符的每個元素的點數。這個預設值2是指取一個隨機點對並比較它們的亮度,所以我們得到0/1的響應。其他可能的值是3和4。例如,3表示我們取3隨機點(當然,這些點座標是隨機的,但是它們是由預定義的種子,因此簡短描述符的每個元素都是從畫素確定地計算出來的矩形),找到最大亮度點和獲勝者的輸出索引(0、1或2)。如此輸出將佔用2位,因此需要一個特殊的漢明距離變數,表示為NORM_HAMMING2(每箱2位)。當WTA_K=4時,我們取4個隨機點計算每個點bin(也將佔用可能值為0、1、2或3的2位)。;
  • 引數七:int型別的scoreType,HARRIS_SCORES表示使用HARRIS演算法對特徵進行排序(分數寫入KeyPoint::score,用於保留最佳nfeatures功能);FAST_SCORE是產生稍微不穩定關鍵點的引數的替代值,但計算起來要快一點;
  • 引數八:int型別的patchSize,定向簡短描述符使用的修補程式的大小。當然,在較小的金字塔層特徵覆蓋的感知影像區域將更大;
  • 引數九:int型別的fastThreshold,快速閾值;
void xfeatures2d::SURT::detect( InputArray image,
                                std::vector<KeyPoint>& keypoints,
                                InputArray mask=noArray() );
  • 引數一:InputArray型別的image,輸入cv::Mat;
  • 引數二:std::Vector型別的keypoints,檢測到的關鍵點;
  • 引數三:InputArray型別的mask,預設為空,指定在何處查詢關鍵點的掩碼(可選)。它必須是8位整數感興趣區域中具有非零值的矩陣。;
void xfeatures2d::SURT::compute( InputArray image,
                                 std::vector<KeyPoint>& keypoints,
                                 OutputArray descriptors );
  • 引數一:InputArray型別的image,輸入cv::Mat;
  • 引數二:std::Vector型別的keypoints,描述符不能為其已刪除計算的。有時可以新增新的關鍵點,例如:SIFT duplicates keypoint有幾個主要的方向(每個方向);
  • 引數三:OutputArray型別的descriptors,計算描述符;
// 該函式結合了detect和compute,參照detect和compute函式引數
void xfeatures2d::SURT::detectAndCompute( InputArray image,
                                          InputArray mask,
                                          std::vector<KeyPoint>& keypoints,
                                          OutputArray descriptors,
                                          bool useProvidedKeypoints=false );

繪製關鍵點函式原型

void drawKeypoints( InputArray image,
                    const std::vector<KeyPoint>& keypoints,
                    InputOutputArray outImage,
                    const Scalar& color=Scalar::all(-1),
                    int flags=DrawMatchesFlags::DEFAULT );
  • 引數一:InputArray型別的image,;
  • 引數二:std::Vector型別的keypoints,原圖的關鍵點;
  • 引數三:InputOutputArray型別的outImage,其內容取決於定義在輸出影像。請參閱引數五的標誌flag);
  • 引數四:cv::Scalar型別的color,繪製關鍵點的顏色,預設為Scalar::all(-1)隨機顏色,每個點都是這個顏色,那麼隨機時,每個點都是隨機的;
  • 引數五:int型別的flags,預設為DEFAULT,具體參照DrawMatchesFlags列舉如下:
    在這裡插入圖片描述
 

相關部落格

 

特徵點總結

  根據前面連續三篇的特徵點,我們其實可以猜到了所有的匹配都是這樣提取特徵點,然後使用一些演算法來匹配,至於使用什麼特徵點提取就是需要開發者根據實際的經驗去選取,單一的特徵點/多種特徵點提取混合/自己寫特徵點等等多種方式去提取特徵點,為後一步的特徵點匹配做準備,特徵點通用的就到此篇,後續會根據實際開發專案中使用的到隨時以新的篇章博文去補充。
  《OpenCV開發筆記(六十三):紅胖子8分鐘帶你深入瞭解SIFT特徵點(圖文並茂+淺顯易懂+程式原始碼)
  《OpenCV開發筆記(六十四):紅胖子8分鐘帶你深入瞭解SURF特徵點(圖文並茂+淺顯易懂+程式原始碼
  《OpenCV開發筆記(六十五):紅胖子8分鐘帶你深入瞭解ORB特徵點(圖文並茂+淺顯易懂+程式原始碼)》

 

Demo原始碼

void OpenCVManager::testOrbFeatureDetector()
{
    QString fileName1 = "13.jpg";
    int width = 400;
    int height = 300;

    cv::Mat srcMat = cv::imread(fileName1.toStdString());
    cv::resize(srcMat, srcMat, cv::Size(width, height));

    cv::String windowName = _windowTitle.toStdString();
    cvui::init(windowName);

    cv::Mat windowMat = cv::Mat(cv::Size(srcMat.cols * 2, srcMat.rows * 3),
                                srcMat.type());
    cv::Ptr<cv::ORB> _pObr = cv::ORB::create();

    int k1x = 0;
    int k1y = 0;
    int k2x = 100;
    int k2y = 0;
    int k3x = 100;
    int k3y = 100;
    int k4x = 0;
    int k4y = 100;
    while(true)
    {
        windowMat = cv::Scalar(0, 0, 0);

        cv::Mat mat;

        // 原圖先copy到左邊
        mat = windowMat(cv::Range(srcMat.rows * 1, srcMat.rows * 2),
                        cv::Range(srcMat.cols * 0, srcMat.cols * 1));
        cv::addWeighted(mat, 0.0f, srcMat, 1.0f, 0.0f, mat);

        {
            std::vector<cv::KeyPoint> keyPoints1;
            std::vector<cv::KeyPoint> keyPoints2;

            cvui::printf(windowMat, 0 + width * 1, 10 + height * 0, "k1x");
            cvui::trackbar(windowMat, 0 + width * 1, 20 + height * 0, 165, &k1x, 0, 100);
            cvui::printf(windowMat, 0 + width * 1, 70 + height * 0, "k1y");
            cvui::trackbar(windowMat, 0 + width * 1, 80 + height * 0, 165, &k1y, 0, 100);

            cvui::printf(windowMat, width / 2 + width * 1, 10 + height * 0, "k2x");
            cvui::trackbar(windowMat, width / 2 + width * 1, 20 + height * 0, 165, &k2x, 0, 100);
            cvui::printf(windowMat, width / 2 + width * 1, 70 + height * 0, "k2y");
            cvui::trackbar(windowMat, width / 2 + width * 1, 80 + height * 0, 165, &k2y, 0, 100);

            cvui::printf(windowMat, 0 + width * 1, 10 + height * 0 + height / 2, "k3x");
            cvui::trackbar(windowMat, 0 + width * 1, 20 + height * 0 + height / 2, 165, &k3x, 0, 100);
            cvui::printf(windowMat, 0 + width * 1, 70 + height * 0 + height / 2, "k3y");
            cvui::trackbar(windowMat, 0 + width * 1, 80 + height * 0 + height / 2, 165, &k3y, 0, 100);

            cvui::printf(windowMat, width / 2 + width * 1, 10 + height * 0 + height / 2, "k4x");
            cvui::trackbar(windowMat, width / 2 + width * 1, 20 + height * 0 + height / 2, 165, &k4x, 0, 100);
            cvui::printf(windowMat, width / 2 + width * 1, 70 + height * 0 + height / 2, "k4y");
            cvui::trackbar(windowMat, width / 2 + width * 1, 80 + height * 0 + height / 2, 165, &k4y, 0, 100);

            std::vector<cv::Point2f> srcPoints;
            std::vector<cv::Point2f> dstPoints;

            srcPoints.push_back(cv::Point2f(0.0f, 0.0f));
            srcPoints.push_back(cv::Point2f(srcMat.cols - 1, 0.0f));
            srcPoints.push_back(cv::Point2f(srcMat.cols - 1, srcMat.rows - 1));
            srcPoints.push_back(cv::Point2f(0.0f, srcMat.rows - 1));

            dstPoints.push_back(cv::Point2f(srcMat.cols * k1x / 100.0f, srcMat.rows * k1y / 100.0f));
            dstPoints.push_back(cv::Point2f(srcMat.cols * k2x / 100.0f, srcMat.rows * k2y / 100.0f));
            dstPoints.push_back(cv::Point2f(srcMat.cols * k3x / 100.0f, srcMat.rows * k3y / 100.0f));
            dstPoints.push_back(cv::Point2f(srcMat.cols * k4x / 100.0f, srcMat.rows * k4y / 100.0f));

            cv::Mat M = cv::getPerspectiveTransform(srcPoints, dstPoints);
            cv::Mat srcMat2;
            cv::warpPerspective(srcMat,
                                srcMat2,
                                M,
                                cv::Size(srcMat.cols, srcMat.rows),
                                cv::INTER_LINEAR,
                                cv::BORDER_CONSTANT,
                                cv::Scalar::all(0));

            mat = windowMat(cv::Range(srcMat.rows * 1, srcMat.rows * 2),
                            cv::Range(srcMat.cols * 1, srcMat.cols * 2));
            cv::addWeighted(mat, 0.0f, srcMat2, 1.0f, 0.0f, mat);

            //特徵點檢測
            _pObr->detect(srcMat, keyPoints1);
            //繪製特徵點(關鍵點)
            cv::Mat resultShowMat;
            cv::drawKeypoints(srcMat,
                             keyPoints1,
                             resultShowMat,
                             cv::Scalar(0, 0, 255),
                             cv::DrawMatchesFlags::DRAW_RICH_KEYPOINTS);
            mat = windowMat(cv::Range(srcMat.rows * 2, srcMat.rows * 3),
                            cv::Range(srcMat.cols * 0, srcMat.cols * 1));
            cv::addWeighted(mat, 0.0f, resultShowMat, 1.0f, 0.0f, mat);

            //特徵點檢測
            _pObr->detect(srcMat2, keyPoints2);
            //繪製特徵點(關鍵點)
            cv::Mat resultShowMat2;
            cv::drawKeypoints(srcMat2,
                             keyPoints2,
                             resultShowMat2,
                             cv::Scalar(0, 0, 255),
                             cv::DrawMatchesFlags::DRAW_RICH_KEYPOINTS);
            mat = windowMat(cv::Range(srcMat.rows * 2, srcMat.rows * 3),
                           cv::Range(srcMat.cols * 1, srcMat.cols * 2));
            cv::addWeighted(mat, 0.0f, resultShowMat2, 1.0f, 0.0f, mat);

            cv::imshow(windowName, windowMat);
        }
        // 更新
        cvui::update();
        // 顯示
        // esc鍵退出
        if(cv::waitKey(25) == 27)
        {
            break;
        }
    }
}
 

工程模板:對應版本號v1.59.0

  對應版本號v1.59.0

 
 

相關文章