分類
發燒車訊

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

若該文為原創文章,未經允許不得轉載
原博主博客地址:https://blog.csdn.net/qq21497936
原博主博客導航:https://blog.csdn.net/qq21497936/article/details/102478062
本文章博客地址:https://blog.csdn.net/qq21497936/article/details/106926496
各位讀者,知識無窮而人力有窮,要麼改需求,要麼找專業人士,要麼自己研究
紅胖子(紅模仿)的博文大全:開發技術集合(包含Qt實用技術、樹莓派、三維、OpenCV、OpenGL、ffmpeg、OSG、單片機、軟硬結合等等)持續更新中…(點擊傳送門)

OpenCV開發專欄(點擊傳送門)

上一篇:《OpenCV開發筆記(六十四):紅胖子8分鐘帶你深入了解SURF特徵點(圖文並茂+淺顯易懂+程序源碼)》
下一篇:持續補充中…

 

前言

  紅胖子,來也!
  識別除了傳統的模板匹配之外就是體征點了,前面介紹了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分鐘帶你深入了解透視變換(圖文並茂+淺顯易懂+程序源碼)》

 

特徵點總結

  根據前面連續三篇的特徵點,我們其實可以猜到了所有的匹配都是這樣提取特徵點,然後使用一些算法來匹配,至於使用什麼特徵點提取就是需要開發者根據實際的經驗去選取,單一的特徵點/多種特徵點提取混合/自己寫特徵點等等多種方式去提取特徵點,為後一步的特徵點匹配做準備,特徵點通用的就到此篇,後續會根據實際開發項目中使用的到隨時以新的篇章博文去補充。
  《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

 

上一篇:《OpenCV開發筆記(六十四):紅胖子8分鐘帶你深入了解SURF特徵點(圖文並茂+淺顯易懂+程序源碼)》
下一篇:持續補充中…

 

原博主博客地址:https://blog.csdn.net/qq21497936
原博主博客導航:https://blog.csdn.net/qq21497936/article/details/102478062
本文章博客地址:https://blog.csdn.net/qq21497936/article/details/106926496

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※教你寫出一流的銷售文案?

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

※回頭車貨運收費標準

※別再煩惱如何寫文案,掌握八大原則!

※超省錢租車方案