【機器視覺教學】視覺裡程計的入門實踐

2021-02-16 電子芯吧客
1.典型特徵1)前言

在SLAM系統問題中,根據經典的框架,分為:前端、後端、迴環、建圖四大部分。所謂前端,就是視覺裡程計的部分,視覺裡程計又稱前端。在視覺構建中,前端的作用就是把圖像的信息處理為相關後續可以使用的計算向量,就是通過圖像得出相機的運動信息,為後續計算提供可能。

圖像處理中,特徵點指的是圖像灰度值發生劇烈變化的點或者在圖像邊緣上曲率較大的點(即兩個邊緣的交點)。圖像特徵點在基於特徵點的圖像匹配算法中有著十分重要的作用。圖像特徵點能夠反映圖像本質特徵,能夠標識圖像中目標物體。通過特徵點的匹配能夠完成圖像的匹配。

在傳統的計算機視覺中,特徵點是描述圖像特徵的像素描述,一般由像素特徵和像素描述組成,這裡的像素描述也可以稱為特徵描述。在我們這一章所講述的視覺裡程計中,在像素級的描述,將視覺裡程計的算法大致分為2大類,一類為我們的特徵點法,一類為我們的直接法。

2)特徵點

為了準確的估計出相機的運動信息,我們需要選擇合適的特徵點。圖像的本質是像素,像素的本質是色彩和亮度,在像素的層面上考慮,所謂特徵點就是具有代表性的像素點或者像素點的集合。當然,這個特徵點必須足夠穩定,足夠有代表性,否則就不能描述出一個圖像的特徵。

圖像中,角點、邊緣、塊等等,都可能出現比較有代表性的特徵點,所謂代表性,就是它比較特殊。在傳統的角點特徵點上,研究出現了很多特徵點提取的相關算法,比如:Harris、GFTT等等,各有千秋,那麼在SLAM系統中,在像素特徵點的層面上,比較有代表性的特徵點有哪些呢?

特徵點,在學術界的描述為:關鍵點+描述組成。在SLAM中,比較合適、典型的特徵點就是ORB特徵點。

3)ORB

ORB是ORiented Brief的簡稱。他的提出論文的原文在:Ethan Rublee and Vincent Rabaud and Kurt Konolige and Gary Bradski, ORB: an efficient alternative to SIFT or SURF, ICCV 2011

其由2部分組成:FAST角點和BRIEF描述。

FAST角點,正如其名,特點就是快。為什麼快呢,其實也非常簡單,就是其只比較像素的亮度值,其具體的方法和理論這裡不做贅述。

ORB特徵點對其的改進有哪些呢?就是利用圖像金字塔,建立了尺度不變性,何為圖像金字塔,就是可以理解為多個大小圖像的合集。除此之外還有什麼呢?還有尺度不變性,這是如何構建的呢?其實也非常簡單,利用灰度質心法,簡單得出一個方向向量。就大大改進了FAST角點。

BRIEF的優點當然在於速度快,缺點當然也很多:不具備旋轉不變性、對噪聲敏感、不具備尺度不變性等等,看到這裡,是不是感覺上面的改進有點聰明了。BRIEF描述子是用二進位進行描述的描述向量。對於關鍵點的描述子,BRIEF描述子也非常簡單粗暴,直接在關鍵點附近隨機生成像素,通過二進位編碼組成描述向量。這裡就對關鍵點進行了描述。

2.特徵代碼實踐1)特徵匹配

特徵匹配有多種方式方法:暴力(Brute-Force)匹配:一種描述符匹配算法,該方法會比較兩個描述符,並產生匹配結果列表,第一個描述符的所有特徵都拿來和第二個進行比較。除此之外,還有K-最近鄰(knn)匹配:在所有的機器學習suan算法中,knn可能是最簡單的。

讓我們用代碼實踐一下特徵匹配算法:

    #include <iostream>    #include <opencv2 core="" core.hpp="">    #include <opencv2 features2d="" features2d.hpp="">    #include <opencv2 highgui="" highgui.hpp="">    #include <chrono>    using namespace std;    using namespace cv;    int main(int argc, char **argv) {      if (argc != 3) {        cout << "usage: feature_extraction img1 img2" << endl;        return 1;      }            Mat img_1 = imread(argv[1], CV_LOAD_IMAGE_COLOR);      Mat img_2 = imread(argv[2], CV_LOAD_IMAGE_COLOR);      assert(img_1.data != nullptr && img_2.data != nullptr);            std::vector<keypoint> keypoints_1, keypoints_2;      Mat descriptors_1, descriptors_2;      Ptr<featuredetector> detector = ORB::create();      Ptr<descriptorextractor> descriptor = ORB::create();      Ptr<descriptormatcher> matcher = DescriptorMatcher::create("BruteForce-Hamming");            chrono::steady_clock::time_point t1 = chrono::steady_clock::now();      detector->detect(img_1, keypoints_1);      detector->detect(img_2, keypoints_2);            descriptor->compute(img_1, keypoints_1, descriptors_1);      descriptor->compute(img_2, keypoints_2, descriptors_2);      chrono::steady_clock::time_point t2 = chrono::steady_clock::now();      chrono::duration<double> time_used = chrono::duration_cast<chrono::duration<double>>(t2 - t1);      cout << "extract ORB cost = " << time_used.count() << " seconds. " << endl;      Mat outimg1;      drawKeypoints(img_1, keypoints_1, outimg1, Scalar::all(-1), DrawMatchesFlags::DEFAULT);      imshow("ORB features", outimg1);            vector<dmatch> matches;      t1 = chrono::steady_clock::now();      matcher->match(descriptors_1, descriptors_2, matches);      t2 = chrono::steady_clock::now();      time_used = chrono::duration_cast<chrono::duration<double>>(t2 - t1);      cout << "match ORB cost = " << time_used.count() << " seconds. " << endl;                  auto min_max = minmax_element(matches.begin(), matches.end(),                                    [](const DMatch &m1, const DMatch &m2) { return m1.distance < m2.distance; });      double min_dist = min_max.first->distance;      double max_dist = min_max.second->distance;      printf("-- Max dist : %f \n", max_dist);      printf("-- Min dist : %f \n", min_dist);            std::vector<dmatch> good_matches;      for (int i = 0; i < descriptors_1.rows; i++) {        if (matches[i].distance <= max(2 * min_dist, 30.0)) {          good_matches.push_back(matches[i]);        }      }            Mat img_match;      Mat img_goodmatch;      drawMatches(img_1, keypoints_1, img_2, keypoints_2, matches, img_match);      drawMatches(img_1, keypoints_1, img_2, keypoints_2, good_matches, img_goodmatch);      imshow("all matches", img_match);      imshow("good matches", img_goodmatch);      waitKey(0);      return 0;    }

2)ORB特徵

對於ORB特徵,我們也用代碼實踐一下加深理解:

                                using namespace std;        string first_file = "./1.png";    string second_file = "./2.png";        typedef vector<uint32_t> DescType;     /@@**     * compute descriptor of orb keypoints     * @param img input image     * @param keypoints detected fast keypoints     * @param descriptors descriptors     *     * NOTE: if a keypoint goes outside the image boundary (8 pixels), descriptors will not be computed and will be left as     * empty     */    void ComputeORB(const cv::Mat &img, vector<cv::keypoint> &keypoints, vector<desctype> &descriptors);    /@@**     * brute-force match two sets of descriptors     * @param desc1 the first descriptor     * @param desc2 the second descriptor     * @param matches matches of two images     */    void BfMatch(const vector<desctype> &desc1, const vector<desctype> &desc2, vector<cv::dmatch> &matches);    int main(int argc, char **argv) {            cv::Mat first_image = cv::imread(first_file, 0);      cv::Mat second_image = cv::imread(second_file, 0);      assert(first_image.data != nullptr && second_image.data != nullptr);            chrono::steady_clock::time_point t1 = chrono::steady_clock::now();      vector<cv::keypoint> keypoints1;      cv::FAST(first_image, keypoints1, 40);      vector<desctype> descriptor1;      ComputeORB(first_image, keypoints1, descriptor1);            vector<cv::keypoint> keypoints2;      vector<desctype> descriptor2;      cv::FAST(second_image, keypoints2, 40);      ComputeORB(second_image, keypoints2, descriptor2);      chrono::steady_clock::time_point t2 = chrono::steady_clock::now();      chrono::duration<double> time_used = chrono::duration_cast<chrono::duration<double>>(t2 - t1);      cout << "extract ORB cost = " << time_used.count() << " seconds. " << endl;            vector<cv::dmatch> matches;      t1 = chrono::steady_clock::now();      BfMatch(descriptor1, descriptor2, matches);      t2 = chrono::steady_clock::now();      time_used = chrono::duration_cast<chrono::duration<double>>(t2 - t1);      cout << "match ORB cost = " << time_used.count() << " seconds. " << endl;      cout << "matches: " << matches.size() << endl;            cv::Mat image_show;      cv::drawMatches(first_image, keypoints1, second_image, keypoints2, matches, image_show);      cv::imshow("matches", image_show);      cv::imwrite("matches.png", image_show);      cv::waitKey(0);      cout << "done." << endl;      return 0;    }
點擊閱讀全文,查看完整代碼點擊閱讀全文,查看完整代碼點擊閱讀全文,查看完整代碼
void ComputeORB(const cv::Mat &img, vector<cv::keypoint> &keypoints, vector<desctype> &descriptors) { const int half_patch_size = 8; const int half_boundary = 16; int bad_points = 0; for (auto &kp: keypoints) { if (kp.pt.x < half_boundary || kp.pt.y < half_boundary || kp.pt.x >= img.cols - half_boundary || kp.pt.y >= img.rows - half_boundary) { bad_points++; descriptors.push_back({}); continue; } float m01 = 0, m10 = 0; for (int dx = -half_patch_size; dx < half_patch_size; ++dx) { for (int dy = -half_patch_size; dy < half_patch_size; ++dy) { uchar pixel = img.at<uchar>(kp.pt.y + dy, kp.pt.x + dx); m10 += dx * pixel; m01 += dy * pixel; } } float m_sqrt = sqrt(m01 * m01 + m10 * m10) + 1e-18; float sin_theta = m01 / m_sqrt; float cos_theta = m10 / m_sqrt; DescType desc(8, 0); for (int i = 0; i < 8; i++) { uint32_t d = 0; for (int k = 0; k < 32; k++) { int idx_pq = i * 32 + k; cv::Point2f p(ORB_pattern[idx_pq * 4], ORB_pattern[idx_pq * 4 + 1]); cv::Point2f q(ORB_pattern[idx_pq * 4 + 2], ORB_pattern[idx_pq * 4 + 3]); cv::Point2f pp = cv::Point2f(cos_theta * p.x - sin_theta * p.y, sin_theta * p.x + cos_theta * p.y) + kp.pt; cv::Point2f qq = cv::Point2f(cos_theta * q.x - sin_theta * q.y, sin_theta * q.x + cos_theta * q.y) + kp.pt; if (img.at<uchar>(pp.y, pp.x) < img.at<uchar>(qq.y, qq.x)) { d |= 1 << k; } } desc[i] = d; } descriptors.push_back(desc); } cout << "bad/total: " << bad_points << "/" << keypoints.size() << endl; } void BfMatch(const vector<desctype> &desc1, const vector<desctype> &desc2, vector<cv::dmatch> &matches) { const int d_max = 40; for (size_t i1 = 0; i1 < desc1.size(); ++i1) { if (desc1[i1].empty()) continue; cv::DMatch m{i1, 0, 256}; for (size_t i2 = 0; i2 < desc2.size(); ++i2) { if (desc2[i2].empty()) continue; int distance = 0; for (int k = 0; k < 8; k++) { distance += _mm_popcnt_u32(desc1[i1][k] ^ desc2[i2][k]); } if (distance < d_max && distance < m.distance) { m.distance = distance; m.trainIdx = i2; } } if (m.distance < d_max) { matches.push_back(m); } } }

3.總結

以上,我們就簡單入門了視覺裡程計中的ORB相關特徵,特徵是圖像最基礎的概念之一,在傳統圖像算法中,這是非常重要的知識點,ORB,這是一個近年來非常經典的特徵描述,希望可以幫助到大家理解。                

  點擊閱讀原文,查看完整代碼

相關焦點

  • 【泡泡一分鐘】利用點和線特徵的雷射雷達單目視覺裡程計
    and Shi-Min Hu來源:2020 IEEE International Conference on Robotics and Automation (ICRA)編譯:姚潘濤審核:柴毅,王靖淇這是泡泡一分鐘推送的第 694 篇文章,歡迎個人轉發朋友圈;其他機構或自媒體如需轉載,後臺留言申請授權我們介紹了一種新的雷射雷達-單目視覺裡程計方法
  • 推薦閱讀 | 端到端EKF的深度視覺-慣導裡程計
    : Cirstan審核:wyc這是泡泡圖靈智庫推送的第 530 篇文章,歡迎個人轉發朋友圈;其他機構或自媒體如需轉載,後臺留言申請授權  大家好,今天為大家帶來的文章是Towards End-to-end Learning of Visual Inertial Odometry with an EKF經典的視覺慣性融合在很大程度上依賴於手工製作的圖像處理框架
  • 技術分享 | 視覺小白學習之路
    學校幾乎所有專業都會開設c或c++相關的課程,其實只要掌握了課上所學的內容,基本上就可以滿足視覺組日常的使用。工作室視覺組對python的要求並不是很高,一般在需要使用神經網絡進行機器學習的時候會用到。因為c++和python都是面向對象的編程,很多地方都是互通的,在掌握了c++基本知識的基礎上很快就可以掌握基本的python語法。
  • 機器視覺技術原理解析及解決方案(一)
    第一篇簡單概述機器視覺,普及基本概念,第二篇第三篇重點講具體應用,敬請期待!機器視覺就是用機器代替人眼來做測量和判斷。
  • 一文詳解固態雷射雷達的裡程計(loam_livox)
    固態雷射雷達裡程計的工作現階段還是比較少的,大疆自己出了固態雷射雷達後在LOAM的基礎上改進了一個適用於固態雷射雷達的裡程計,該系統可以用在小視場角和非重複性掃描的雷達上。通過稍後詳細介紹的適當實現,我們可以實現20Hz的實時裡程計和建圖。
  • 機器視覺傳感器基礎應用知識詳解
    為了能讓更多用戶獲取機器視覺的相關基礎知識,包括機器視覺技術是如何工作的、它為什麼是實現流程自動化和質量改進的正確選擇等。小編為你準備了這篇機器視覺入門學習資料。機器視覺是一門學科技術,廣泛應用於生產製造檢測等工業領域,用來保證產品質量,控制生產流程,感知環境等。
  • 雙十一免費送|魏秀參新書《解析深度學習:卷積神經網絡原理與視覺實踐》
    在本文下留言深度學習或者卷積神經網絡遇到的難點,以及你對深度學習的見解,點讚數前5位可獲贈由電子工業出版社出版的魏秀參博士所著《解析深度學習:卷積神經網絡原理與視覺實踐》紙質書籍,要學深度學習,本書是不可錯過的好書。
  • 機器視覺的現狀及發展趨勢
    摘要:介紹了機器視覺的概念和機器視覺的組成,闡述了機器視覺技術的發展現狀,如光源照明技術、光學鏡頭、攝 像機及圖像採集卡、 圖像信號處理、執行機構等,分析了機器視覺技術在工業、民用、科學、軍事領域的應用狀況
  • 機器視覺圖像處理——基於LabVIEW圖像管理與顯示
    10.IMAQ Get Custom Keys:返回所有用戶自定義數據的鍵值11.IMAQ Remove Vision Info2:刪除機器視覺系統信息以及(所有圖像中)用戶自定義數據 如果圖像包含無損圖層等機器視覺系統信息,則只能保存在PNG格式的文件中。
  • 終於有人講透了什麼是機器視覺!
    機器視覺 就是用機器代替人眼來做測量和判斷。機器視覺系統是指通過機器視覺產品(即圖像攝取裝置,分 CMOS 和CCD 兩種)將被攝取目標轉換成圖像信號,傳送給專用的圖像處理系統,根據像素分布和亮度、顏色等信息,轉變成數位化信號;圖像系統對這些信號進行各種運算來抽取目標的特徵,進而根據判別的結果來控制現場的設備動作。
  • 雙目視覺慣性雷達SLAM簡介
    ,它在全局坐標系中的位置是 迴環約束系統首先獲得視覺迴環約束為一個初始估計。因為我們使用非結構化構架用於視覺路標,在迴環候選中的所有雙目匹配的特徵中的三角化被執行來獲得他們的3D位置。它們和當前關鍵圖片的關聯被給定通過描述子匹配。視覺迴環約約束然後被計算使用EPNP[37]。為了提高視覺迴環約束的準確性,我們在對應LiDAR關鍵幀的特徵點上使用ICP對齊。
  • 立體視覺+慣導+雷射雷達SLAM系統
    VIL-SLAM通過將緊密耦合的立體視覺慣性裡程計(VIO)與雷射雷達建圖和雷射雷達增強視覺環路閉合相結合來實現這一目標。該系統實時生成環閉合校正的6自由度雷射雷達姿態和接近實時的1cm體素稠密點雲。與最先進的雷射雷達方法相比,VIL-SLAM顯示了更高的精確度和魯棒性。VIL-SLAM系統圖。傳感器為灰色,模塊為綠色。箭頭指示消息在系統中的流動方式。
  • 一文帶你了解機器視覺系統光源(基礎知識+選型技巧+工作流程)
    視覺檢測系統在智能製造領域的使用非常的普遍,一套完整的視覺檢測系統主要包含圖像採集部分和圖像分析部分,而圖像採集部分主要有工業相機、工業鏡頭以及機器視覺光源承擔,今天我們主要介紹機器視覺光源的相關基礎知識、光源選擇以及工作流程。  1.
  • CCD(視覺檢測)
    一、結構一個機器視覺系統包括以下三大塊:1、照明2、鏡頭3、相機二、圖像採集圖像採集卡只是完整的機器視覺系統的一個部件,但是它扮演一個非常重要的角色。圖像採集卡直接決定了攝像頭的接口:黑白、彩色、模擬、數字等等。
  • 機器視覺光源照明使用指南
    為了設計一個有效的視覺照明解決方案,除了了解照明類型,技術,幾何形狀,濾波,傳感器特性和顏色之外,還需要對檢測環境進行全面的分析,包括樣品表現和樣品/光線的相互作用。 設計並遵循嚴格的照明分析序列提供了一致和強大的環境,從而最大限度地利用時間,精力和資源 – 在視覺系統設計,測試和實施的其他關鍵方面得到更好的使用。 本教程演示了機器視覺照明的概念和理論。
  • 機器視覺光源照明設計基本要素
    在一幅機器視覺的圖像中,對比度代表著圖像信號的質量,它反應了兩個區域間的差別,比如物體和背景的差別。因此,設計光源照明的第一步是確定區域間的不同,然後用光源來突出這些不同之處。  判斷機器視覺的照明的好壞,首先必須了解什麼是光源需要做到的!顯然光源應該不僅僅是使檢測部件能夠被攝像頭「看見」。有時候,一個完整的機器視覺系統無法支持工作,但是僅僅優化一下光源就可以使系統正常工作。
  • 重用地圖的單目視覺慣導SLAM系統
    摘要近些年來有很多優秀的視覺慣導融合的裡程計系統,計算高精度和魯棒性的傳感器的增量運動。C.閉環檢測閉環的主要作用是機器人又回到原來到過的地方的時候降低裡程計帶來的累計誤差。場景充實別模塊匹配最近的關鍵幀和原來的一個關鍵幀。利用兩幀的詞袋匹配可以得到剛體變換,然後執行優化的過程來降低裡程計帶來的累計誤差。這種優化在大場景中很耗時,所以執行位姿圖優化,==這樣就可以忽略結構==,也可以很好的收斂。本文可以執行六自由度的位姿圖優化,因為尺度是可觀的。
  • 簡單明了,一文入門視覺SLAM
    因此本文以簡單清晰的文字為大家介紹了視覺 V-SLAM。簡單的V-SLAM介紹,就當入門:)。首先要提到概念 VO,即視覺裡程計(visual odometry);VO 是 SLAM 的一部分,VO 主要是估計視角之間的變換,或者運動參數,它不需要輸出製圖(mapping)的結果,而且 BA 就是 motion-only 的模式;ii.
  • 【應用】機器視覺的機器人取代四臺傳統的火焰處理機器人
    機器專題:在Fitz-Thors Engineering Inc.公司向一家一級汽車供應商提供的設計中,配備機器視覺的機器人取代了四臺傳統機器人
  • 基於PL-ICP的雷射雷達裡程計
    (雖然是一個失敗的雷射雷達裡程計…)實現裡程計,就不得不提及TF。所以,我將先簡要介紹一下ROS的TF2庫。3.6 運行結果紅色軌跡為通過小車自身編碼器累加出來的裡程計,黃色軌跡為通過PL-ICP算法累加出來的雷射雷達裡程計。