第7講3 三角測量
之所以把SLAM初始化和三角測量放在一起是因為它們之間有一定的關係,理解了三角測量之後才能理解初始化。
三角測量
在得到了相機的運動之後,下一步我們需要用相機的運動來估計特徵點的空間位置,但是在單目SLAM中,僅通過單張影象是無法獲得畫素的深度資訊的,我們需要用三角測量(三角化)的方法來估計地圖點的深度。
先來看一些什麼是三角測量?三角測量是指通過在兩處觀察同一個點的夾角,來確定該點的距離。以下圖為例:
考慮影象,相機的光心為,以左圖為參考,右圖的變換矩陣為。
假設在中有特徵點,對應到中的特徵點,理論上講和會相交於某一點,該點即是兩個特徵點所對應的地圖點在三維場景中的位置,但是由於噪聲的影響,這兩條直線往往無法相交,因此可以通過最小二乘法來求解出距離最近的那個點作為相交點。
按照對極幾何中的定義,如果設為兩個特徵點的歸一化座標,於是有下列關係式:
ps: 我對上面這個公式不是很理解或者說看法不一樣。是歸一化座標,所以可以認為是去歸一化的座標(也就是相機光心為時的相機座標),同理為點在相機光心為時的相機座標,所以我認為是。但是計算的思路還是不變的。
上面公式中的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也有一個尺度等價性。而本身具有約束,所以我們認為t具有一個尺度。換言之,在分解過程中,對t乘以任意非零常數,分解都是成立的。因此,我們通常把t進行歸一化,讓它的長度等於1.
對t長度的歸一化,直接導致了單目視覺的尺度不確定性。因為對t乘以任意一個比例常數後,對極約束仍然是成立的。換言之,在單目SLAM中,對軌跡和地圖同時縮放任意倍數,我們得到的圖仍然是一樣的。
單目視覺的初始化問題
在單目視覺中,我們對兩張影象的t進行歸一化,相當於固定了一個尺度。雖然我們不知道它的實際長度是多少,但是我們可以以這時的t為單目1,計算相機運動和特徵點的3D位置,這一步稱為單目SLAM的初始化。
在初始化之後,就可以用3D-2D來計算相機的運動了,初始化之後的軌跡和地圖的單位,就是初始化時固定的尺度。因此,初始化也是單目SLAM中不可避免的一個步驟。
初始化的兩張圖片必須要有一定程度的平移,而後的軌跡和地圖都將會以這一步的平移為單位。單目初始化不能只有純旋轉,必須要有一定程度的平移,如果沒有平移,單目將無法初始化。
這裡還要提一個三角化的相互矛盾的地方
在同樣的相機解析度下,平移越大,三角化測量就會越精確;但是平移越大,將會導致影象的外觀發生明確變化,這會使得特徵的提取和匹配變得更困難---這就是三角化的矛盾。
相關推薦
第7講3 三角測量
之所以把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 正則表示式特殊符號及用法(詳細列表)這個表格講完: 上節課我們介紹了正則表示式的特殊字元中的 元字元,正則表示式的特殊字元除了 元字元之外呢,還有 一種就是通過反斜槓加上一個普通字元組成的特殊符號。我們接下來談談它們的含義。 \序
第3章第1講算法與流程圖
blog images 技術 bsp mage -1 ima right mar 第3章第1講算法與流程圖
第7章第1講一維數組
min display %d mar image 技術分享 分享 lock images main() { int a[10],i,max,min; float ave=0; for(i=0;i<N;i++) sc
第7章第2講字符數組
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)有沒有構造方法? 答案:抽象類也有構造方法。實際上,任何類都有自己的構造方法