1. 程式人生 > >第7講3 三角測量

第7講3 三角測量

之所以把SLAM初始化和三角測量放在一起是因為它們之間有一定的關係,理解了三角測量之後才能理解初始化。

三角測量

       在得到了相機的運動之後,下一步我們需要用相機的運動來估計特徵點的空間位置,但是在單目SLAM中,僅通過單張影象是無法獲得畫素的深度資訊的,我們需要用三角測量(三角化)的方法來估計地圖點的深度。

先來看一些什麼是三角測量?三角測量是指通過在兩處觀察同一個點的夾角,來確定該點的距離。以下圖為例:

考慮影象I_{1},I_{2},相機的光心為O_{1},O_{2},以左圖為參考,右圖的變換矩陣為T

假設在I_{1}中有特徵點p_{1},對應到I_{2}中的特徵點p_{2},理論上講O_{1}p_{1}O_{2}p_{2}會相交於某一點P,該點即是兩個特徵點所對應的地圖點在三維場景中的位置,但是由於噪聲的影響,這兩條直線往往無法相交,因此可以通過最小二乘法來求解出距離最近的那個點作為相交點。

按照對極幾何中的定義,如果設x_1,x_{2}為兩個特徵點的歸一化座標,於是有下列關係式:

s_{1}x_{1}=s_{2}Rx_{2}+t

ps: 我對上面這個公式不是很理解或者說看法不一樣。{\color{Blue} x_{1}}是歸一化座標,所以{\color{Blue} s_{1}x_{1}}可以認為是去歸一化的座標(也就是相機光心為{\color{Blue} O_{1}}時的相機座標),同理{\color{Blue} s_{2}x_{2}}{\color{Blue} P}點在相機光心為{\color{Blue} O_{2}}時的相機座標,所以我認為是{\color{Blue} s_{2}x_{2}=s_{1}Rx_{1}+t}。但是計算{\color{Blue} s_{1},s_{2}}的思路還是不變的。

上面公式中的R、t、x_{1},x_{2}都是知道的,現在要求的就是兩個特徵點的深度s_{1},s_{2}

我們可以分開來求,首先求s_{2}

上式的兩邊同時左乘x_{1}^{\Lambda },得到:s_{1}x_{1}^{\Lambda }x_{1}=0=s_{2}x_{1}^{\Lambda }Rx_{2}+x_{1}^{\Lambda }t

等式的左邊可以看成是一個方程,只有s_{2}是未知的,根據它可以直接求解出s_{2}

有了s_{2},反代入也可以很簡單的求出s_{1}

但是,由於噪聲的存在,我們求出來的R,t不一定能夠使得上式精確等於0,因此在實際情況中,更常見的做法是求最小二乘解而不是零解。

實踐部分:如何使用OpenCV進行三角測量

下面是進行三角化的簡單思路:

下面是用OpenCV進行實現的程式碼:

triangulation.hpp

#include "feature_extract_match.hpp"
#include "estimation.hpp"

//畫素座標轉換為歸一化座標,返回的是(X/Z, Y/Z), 真正的歸一化座標為(X/Z, Y/Z, 1)
Point2d pixel_to_camera(Point2d p, Mat K)
{
    return Point2d(
        (p.x - K.at<double>(0, 2)) / K.at<double>(0, 0),
        (p.y - K.at<double>(1, 2)) / K.at<double>(1, 1)
    );
}

//返回的是空間三維點
void triangulation(vector<KeyPoint> key_points_1, vector<KeyPoint> key_points_2, vector<DMatch> matches, Mat R, Mat t, vector<Point3d> &space_points)
{
    Mat T1 = (Mat_<double>(3, 4) << 
        1, 0, 0, 0,
        0, 1, 0, 0,
        0, 0, 1, 0
    );

    //注意Mat_類的使用
    Mat T2 = (Mat_<double> (3, 4) << 
        R.at<double>(0, 0), R.at<double>(0, 1), R.at<double>(0, 2), t.at<double>(0, 0),
        R.at<double>(1, 0), R.at<double>(1, 1), R.at<double>(1, 2), t.at<double>(1, 0),
        R.at<double>(2, 0), R.at<double>(2, 1), R.at<double>(2, 2), t.at<double>(2, 0)
    );

    //定義相機的內參矩陣
    Mat K = (Mat_<double>(3, 3) <<
        529.0, 0, 325.1,
        0, 521.0, 249.9,
        0, 0, 1
    );

    //將所有匹配的特徵點轉化為歸一化座標
    //KeyPoints ---> Point2f
    vector<Point2d> points_1, points_2;
    for(int i=0; i< matches.size(); i++)
    {
        points_1.push_back( pixel_to_camera(key_points_1[matches[i].queryIdx].pt, K) );
        points_2.push_back( pixel_to_camera(key_points_2[matches[i].trainIdx].pt, K) );
    }

    /*
    呼叫cv::triangulatePoints(InputArray projMatr1, InputArray projMatr2, InputArray projPoints1, InputArray projPoints2, OutputArray points4D)進行三角測量
        引數1: 第一張影象的[R, t]組成的3*4矩陣
        引數2: 第二張影象的[R, t]組成的3*4矩陣
        引數3: 第一張影象的匹配的特徵點的歸一化座標, 型別為vector<Point2d>
        引數4: 第二張影象的匹配的特徵點的歸一化座標, 型別為vector<Point2d>
        引數5: 輸出的3d座標, 是一個4*N矩陣表示的齊次座標(每一列都是一個點的座標), 因此要將所有的元素除以最後一維的數得到非齊次座標XYZ
    */
    Mat pts_4d;
    cv::triangulatePoints(T1, T2, points_1, points_2, pts_4d);

    // cout << "pts_4d = " << endl;
    // cout << pts_4d << endl; 

    //轉換為非齊次座標
    for(int i = 0; i < pts_4d.cols; i++)
    {
        Mat x = pts_4d.col(i);  //獲取第i列
        x /= x.at<double>(3, 0);    //歸一化
        space_points.push_back(
            Point3d(x.at<double>(0, 0), x.at<double>(1, 0), x.at<double>(2, 0))
        );
    }
}

test_triangulation.cpp

#include "feature_extract_match.hpp"
#include "estimation.hpp"
#include "triangulation.hpp"

int main(int argc, char **argv)
{
    Mat img1, img2;
    img1 = imread("../datas/1.png");
    img2 = imread("../datas/2.png");

    vector<KeyPoint> key_points_1, key_points_2;
    vector<DMatch> matches;
    feature_extract_match(img1, img2, key_points_1, key_points_2, matches);

    cout << "一共找到了: " << matches.size() << "個匹配點" << endl;

    Mat R, t;
    pose_estimation_2d2d(key_points_1, key_points_2, matches, R, t);

    cout << "R = " << R << endl;
    cout << "t = " << t << endl;

    //進行三角測量
    vector<Point3d> space_points;
    triangulation(key_points_1, key_points_2, matches, R, t, space_points);

    cout << "space_points.size() = " << space_points.size() << endl;

    //內參矩陣
    Mat K = (Mat_<double>(3, 3) <<
        529.0, 0, 325.1,
        0, 521.0, 249.9,
        0, 0, 1
    );
    //驗證三角化點和特徵點的重投影關係
    //驗證方法: 三角化點進行歸一化, 特徵點重投影到歸一化平面
    for(int i = 0; i < matches.size(); i++)
    {
        //將畫素點投影到歸一化平面上
        Point2d pix_cam_1 = pixel_to_camera(key_points_1[ matches[i].queryIdx ].pt, K);       
        //將空間點投影到歸一化平面上
        Point2d space_cam_1 (
            space_points[i].x / space_points[i].z,
            space_points[i].y / space_points[i].z
        );          
        cout << "pix_cam_1 in first frame is : " << pix_cam_1 << endl;
        cout << "space_cam_1 in first frame is : " << space_cam_1 << ", d = " << space_points[i].z << endl;

        //the second image
        //畫素座標直接可以轉換為歸一化座標
        Point2d pix_cam_2 = pixel_to_camera( key_points_2[ matches[i].trainIdx ].pt, K );
        //R * 世界座標 + t ----> I2的相機座標系下的座標,然後再投影到歸一化平面
        Mat point_cam_frame2 = R * (Mat_<double>(3, 1) << space_points[i].x, space_points[i].y, space_points[i].z) + t;
        point_cam_frame2 /= point_cam_frame2.at<double>(2, 0);  //歸一化
        // Point2d space_cam_2 (
        //     point_cam_frame2.at<double>(0, 0),
        //     point_cam_frame2.at<double>(1, 0)
        // );
        cout << "pix_cam_2 in two frame is : " << pix_cam_2 << endl;
        cout << "space_cam_2 in two frame is : " << point_cam_frame2.t() << endl;

        cout << endl;
    }

    return 0;   
}

討論:

       由於E(本質矩陣)本身具有尺度等價性,它分解得到的R、t也有一個尺度等價性。而R\epsilon SO(3)本身具有約束,所以我們認為t具有一個尺度。換言之,在分解過程中,對t乘以任意非零常數,分解都是成立的。因此,我們通常把t進行歸一化,讓它的長度等於1.

       對t長度的歸一化,直接導致了單目視覺的尺度不確定性。因為對t乘以任意一個比例常數後,對極約束仍然是成立的。換言之,在單目SLAM中,對軌跡和地圖同時縮放任意倍數,我們得到的圖仍然是一樣的。

單目視覺的初始化問題

       在單目視覺中,我們對兩張影象的t進行歸一化,相當於固定了一個尺度。雖然我們不知道它的實際長度是多少,但是我們可以以這時的t為單目1,計算相機運動和特徵點的3D位置,這一步稱為單目SLAM的初始化

       在初始化之後,就可以用3D-2D來計算相機的運動了,初始化之後的軌跡和地圖的單位,就是初始化時固定的尺度。因此,初始化也是單目SLAM中不可避免的一個步驟。

       初始化的兩張圖片必須要有一定程度的平移,而後的軌跡和地圖都將會以這一步的平移為單位。單目初始化不能只有純旋轉,必須要有一定程度的平移,如果沒有平移,單目將無法初始化

這裡還要提一個三角化的相互矛盾的地方

       在同樣的相機解析度下,平移越大,三角化測量就會越精確;但是平移越大,將會導致影象的外觀發生明確變化,這會使得特徵的提取和匹配變得更困難---這就是三角化的矛盾。

相關推薦

73 三角測量

之所以把SLAM初始化和三角測量放在一起是因為它們之間有一定的關係,理解了三角測量之後才能理解初始化。 三角測量        在得到了相機的運動之後,下一步我們需要用相機的運動來估計特徵點的空間位置,但是在單目SLAM中,僅通過單張影象是無法獲得畫素的深度資訊的,我們需

CS184.1X 計算機圖形學導論 7 V1-3 學習筆記

線上 物體 創建 strong 公式推導 導論 幾何 ng- 解決方法 L7V1:OPENGL 著色:學習動機 1.光照的重要性 1)能夠真正顯示出形狀感知的外觀; 2)準確的著色和光照對對傳達物體的形狀非常重要; 3)著色的方式也十分重要:平面著色(GL_FLAT)、平滑

7++創建數據表和約束

ref gin mar reat 數據 外鍵 唯一約束 log weight 二、創建數據表 1.創建簡單的數據表 --命令格式 --create table 表名 -- (列定義 列約束 [,……n]) --實例1:在xscj庫中,創

【Python例項7】真實資料集的異常檢測

機器學習訓練營——機器學習愛好者的自由交流空間(qq 群號:696721295) 在這個例子裡,我們闡述在真實資料集上的穩健協方差估計的必要性。這樣的協方差估計,對異常點檢測,以及更好地理解資料結構都是有益的。 為了方便資料視覺化,我們選擇來自波士頓房價資料集的兩個變數

7 7.ElasticSearch簡單查詢

1,新增索引film(分片5,副本1),建立對映關係dongzuo類,和其它欄位(tittle,publishDate,content,director,   price),可以參考上一節知識;在Java程式碼中新增測試資料, {   "properties": {  

7 .資料庫自動建立表SpringBoot

1. 新增jar包,     新增jpa 的支援,mysql的支援,   2. 建立資料庫,     資料庫名為為:db_book       

7 視覺里程計2 --- 求解相機的運動

上一篇部落格中學習了特徵提取和匹配的概念,並且呼叫OpenCV庫實現了ORB特徵的提取和匹配。 找到了匹配點後,我們希望能夠根據匹配的  點對 來估計相機的運動。由於相機的原理不同,情況就變得有點複雜了: 當相機為單目的時候,我們只知道2D的畫素座標,因而問題是根據兩組2

視覺slam 14 7 程式碼執行問題 fatal error: g2o/solvers/eigen/linear_solver_eigen.h

測試高博的視覺slam 第7講的實驗時 到對應的目錄下 ch7裡,建立build資料夾,進入,cmake..   make這個時候出了個問題。 程式編譯的時候,到這一行 #include <g2o/solvers/csparse/linear_solver_csp

SpringBoot專欄_web:模板引擎Thymeleaf使用實戰,圖文結合附帶原始碼下載(7

簡介: Thymeleaf是一款用於渲染XML/XHTML/HTML5內容的模板引擎。類似JSP, Velocity,FreeMaker等,它也可以輕易的與Spring MVC等Web框架進行整合 作為Web應用的模板引擎。與其它模板引擎相比,Thymeleaf最大的特點是能夠 直接在

JVM系列7:JVM 類載入機制

當 Java 虛擬機器將 Java 原始碼編譯為位元組碼之後,虛擬機器便可以將位元組碼讀取進記憶體,從而進行解析、執行等整個過程,這個過程我們叫:Java 虛擬機器的類載入機制。JVM 虛擬機器執行 class 位元組碼的過程可以分為七個階段:載入、驗證、準備、解析、初始化、使用、解除安裝。 在開始聊之前,先

Java永續性API(JPA)7——實體生命週期及生命週期回撥方法

em.createQuery("delete from Ordertable o where o.orderid = ?1").setParameter(1,orderid).executeUpdate();

SpringCloud極簡入門|配置中心入門spring cloud config 7

一、 Spring Cloud Config簡介  微服務要實現集中管理微服務配置、不同環境不同配置、執行期間也可動態調整、配置修改後可以自動更新的需求,Spring Cloud Config同時滿足了以上要求。Spring Cloud Config 分為Config Serve

《視覺SLAM十四7 程式碼編譯g2o初始化出錯修改

1. pose_estimation_3d3d.cpp // 初始化g2o typedef g2o::BlockSolver< g2o::BlockSolverTraits<6,3> > Block; // pos

《零基礎入門學習Python》059:論一隻爬蟲的自我修養7:正則表示式3

今天我們先接著上節課的內容,把 Python3 正則表示式特殊符號及用法(詳細列表)這個表格講完: 上節課我們介紹了正則表示式的特殊字元中的 元字元,正則表示式的特殊字元除了 元字元之外呢,還有 一種就是通過反斜槓加上一個普通字元組成的特殊符號。我們接下來談談它們的含義。 \序

31算法與流程圖

blog images 技術 bsp mage -1 ima right mar 第3章第1講算法與流程圖

71一維數組

min display %d mar image 技術分享 分享 lock images main() { int a[10],i,max,min; float ave=0; for(i=0;i<N;i++) sc

72字符數組

width ++ mar block strcmp splay log for img main() { char ch[12]={‘G‘,‘o‘,‘o‘,‘d‘,‘ ‘,‘m‘,‘o‘,‘r‘,‘n‘,‘i‘,‘n‘,‘g‘}; int i

3星|《哈佛商業評論》2017年7

處理 放棄 進行 美國 收購 任務 自己 cnblogs 完成 老牌管理學雜誌,每期都值得精讀。本期幹貨略少,我評3星。 以下是本期的一些內容的摘抄: 1:長期以來,成功企業難以實現增長的一個最重要原因就是對失敗的恐懼。#93 2:如今,問題更多在於推薦工作時隱含

學習筆記-小甲魚Python3學習:了不起的分支和循環3

接收 實現 舉例 默認值 app 立方和 課後作業 bsp swe while循環:當條件真時,執行循環體while 條件: 循環體for循環:for 目標 in 表達式: 循環體舉例:>>> fruits = ['apple'

【學習筆記】 唐大仕—Java程式設計 5 深入理解Java語言之5.3 物件構造與初始化

物件構造與初始化 構造方法 構造方法(constructor) 物件都有構造方法 如果沒有,編譯器加一個default構造方法 抽象類(abstract)有沒有構造方法? 答案:抽象類也有構造方法。實際上,任何類都有自己的構造方法