視覺slam十四講 ---CH5 相機與影像
視覺slam中,作為主要感測器的相機自然起到著重要的作用,而相機拍攝的影像及其處理也是我們要做的工作之一。
1. 相機模型
單目相機的針孔模型
上圖中的模型即為常見的單目相機的針孔模型示意。
從5-1的左邊我們可以看到有很多座標系交雜在一起。其中以相機光心O為原點所成的右手係為相機座標系,而物理成像平面上x,y軸以及原點與相機座標系所平行的座標系為成像座標系,而實際上拍攝的圖片一般會量化為畫素單位,因此一般在影像的左上角設定一個畫素座標系。接下來我們就來研究這如何將一個三維世界下的點轉換為畫素座標系下的點。
對於相機座標系如圖所示,是一個Z軸向前的右手座標系。其中有一些相機本身的引數。
- 鏡頭的成像中心叫做光心
- 鏡頭到成像平面的距離叫做焦距f
- 約定相機座標系下的座標下標為C
- 約定世界座標系下的座標下標為W
對於三維空間上的一個點P從相機到成像平面的軌跡沿Z-Y軸所成平面剖開,所見即為5-1右圖。顯然兩個三角形為相似關係。在注意成像平面成倒像的前提下,不妨有以下推導。
同理對於Z-X軸剖面進行同樣的相似三角形分析,有
即
考慮相機會自動糾正倒像為正像,不妨認為小孔模型直接成正像。因此有
這樣我們就完成了相機座標系到成像座標系的轉換。再來考慮成像座標系到畫素座標系。
上圖是成像座標系與畫素座標系的關係示意圖。其中u,v軸相當於畫素座標下的x,y軸。不難看出,從成像平面座標到畫素座標我們只需要讓成像平面上的點進行伸縮變換以及原點的平移即可。不過注意成像平面的點座標單位為mm,但是畫素座標的基本單位為單個的畫素點。因此還要進行單位的量化。
假設對X,Y的縮放以及量化操作為\(\alpha\)和\(\beta\),畫素座標的平移量分別記為\(c_x\)和\(c_y\),有
帶入上述相機座標到成像平面座標的轉換關係,消掉X'與Y‘有
這樣我們就得出了由相機座標到畫素座標的轉換公式。不妨做一些齊次化處理,將上述兩個式子寫為矩陣相乘的形式。
即
上式中,我們稱矩陣K為相機的 內參矩陣,這個引數在相機生產出之後即視為固定不變的引數,一般可以直接獲得或者透過對相機進行標定來獲得。
在上式中可以看到對於相機座標系下的座標,前面都會乘以一個\(\frac{1}{Z}\),其最終的座標為\((X_C/Z,Y_C/Z,1)\),可以看出這些點都匯聚在\(Z=1\)這個平面上,丟失了Z軸的資訊。我們將這個平面稱作歸一化平面。處理所有的相機座標系下的點都可以先轉換到歸一化平面上去處理。
我們一般知道的是物體的世界座標系,因此在轉換之前還要將世界座標系轉換為相機座標系下的座標才可。這就是兩個三維座標系的轉換,由第三章的知識可知只需要讓世界座標系下的座標左乘一個變換矩陣或者左乘旋轉矩陣然後加上一個平移向量即可。
總結一下
- 對於世界座標下的點\(P_W\),根據三維剛體運動知識,左乘變換矩陣\(T_{CW}\)將其變換到相機座標系下的點\(P_C\)。
- 接著將相機座標系下的點投影到歸一化平面上,其座標也變為\((X_C/Z,Y_C/Z,1)\)
- 然後經過對其歸一化座標左乘相機的內參矩陣,即應用公式\(P_{uv} = \frac{1}{Z}KP\),將歸一化座標轉化為畫素座標下的畫素座標。
PS:若有畸變,畸變修正的操作對第二步中的歸一化座標進行處理。
相機影像的畸變
由於各種各樣的原因,相機拍攝出的影像相較於本身會有一定的畸變,也就是會變形。常見的畸變型別有徑向畸變與切向畸變這兩種。
下圖是徑向畸變的兩種表現
徑向畸變一般是由於相機的透鏡邊緣彎曲形變導致光線聚焦的彎曲,因此一般影像越邊緣的地方徑向畸變越嚴重,類似膨脹拉長的感覺。一些全景相機以及魚眼相機在能看得更廣的同時,也伴隨著非常嚴重明顯的徑向畸變。
對於徑向畸變我們可以使用如下的多項式來描述這個畸變。多項式的項數越多,描述程度越精細,計算起來也就越複雜。
其中\(x_{distorted}\quad y_{distorted}\)為修正後的座標,\(r = \sqrt{x^2 + y^2}\)。值得注意的是,修正這一操作發生在歸一化平面向成像平面的轉換這一步。也就是說這裡的x,y代表的是歸一化後的座標,即\(x = X_C/Z_C \quad y = Y_C/Z_C\)。其中\(k_1,k_2....\)是多項式的係數,決定著畸變的程度,也是描述畸變的引數。
對於切向畸變,一般是由於相機內部的感光元件感測器安裝的並非垂直,導致成像平面不垂直,從而導致切向畸變。
對於切向畸變,可以使用以下多項式模型對其進行描述
其中的\(p_1,p_2\)也為係數,\(x,y,r\)的定義與徑向畸變相同。
可以將徑向畸變與切向畸變合併在一起,變成如下對畸變的綜合描述。
同理這也是在歸一化座標下進行修正。當我們獲得畫素座標時,想對其進行畸變修正,首先要由\(u = f_x\frac{X}{Z} + c_x\)求出歸一化座標\(\frac{X}{Z}\),簡單的移項運算即可,然後根據上面的公式進行畸變的修正。
雙目相機測距模型
由上面可以知道對於單目相機所成的像丟失了深度資訊,因此無法根據一張影像判斷影像中物體的深度(即遠近或者Z軸的座標)。使用雙目相機可以解決這個問題。上圖就是雙目相機模型。
一些引數:兩個相機光心之間的距離叫做基線(base),依舊以相機光心到成像平面的距離為焦距f。上圖中的幾何模型是成像圖的Z-X平面。測距的原理依舊是三角形相似。
設同一物體在兩個成像平面所成像的距離為\(l\),也就是圖中\(P_l\)與\(P_r\)之間的距離,Z為\(P\)點距離基線終點的三維深度。有如下推導。
經過以上的式子就可以求出P點的深度。其中d稱為視差。可以看出視差越大距離越近,視差越小距離越遠。就像人眼一樣,看太陽的視差小(左右眼看位置相差不大),看近在眼前的物體視差大(左右眼位置差距大)。而基線越大的相機所能支援的精度與所能測到的有效距離就更高。因為大的基線會使得視差的波動誤差變得相對不明顯。
雙目相機看似可以使用一個很簡單的公式來求出影像的深度的資訊,但是如何確定兩個畫素與一個物體的匹配是比較困難的,對每一個畫素的匹配加計算的計算量也是相當大的。
RGBD相機以及ToF相機
這部分我暫時用不到,就先放在這吧。
影像
在相機採集完後會生成影像,這些影像使用一定的格式儲存。
對於一個影像,可以有很多型別。但是大多都是使用矩陣的形式來儲存每個畫素點,在變成語言中常用類陣列矩陣或者列表矩陣的連續資料結構來儲存同樣連續且分立的畫素。
- 二值化影像,也就是黑白影像,取值只有01二值,該點畫素取0為黑色,取1為白色,只有一個色彩通道。
- 灰度圖,相較於黑白圖的01,黑到白之間多了255個過度色,也就是每個畫素的可能取值為0-255,正好可由一個8位無符號字元變數(unsigned char)來儲存。同樣只有一個色彩通道。
- RGB彩色圖,有三個色彩通道,分別對應R(red),G(green),B(blue)三種顏色的程度,這三種顏色由各有0-255的程度值,因此一個24位RGB影像需要3個8位無符號字元來儲存。在不同的軟體中RGB的排列順序可能是不同的,如OpenCV預設的色彩通道排序是BGR。
對於圖片來說,它的畫素座標系與前文中講到的畫素座標系是一樣的,u軸是橫向的x軸,代表著有多少列(cols), v軸是豎向的y軸,代表著有多少行(rows)。
因此比較反直覺的是對於給出的一個畫素座標\((x,y)\),代表著這個畫素位於第y行第x列,而非大多數程式語言中的先行後列。
這裡特別說明一下,對於OpenCV中,Mat類的at介面可以用來訪問影像中的畫素點。但是其傳入的引數
img.at<Vec3b>(i,j)
,中的i和j卻直接表示的是第i行第j列,而非使用畫素座標來定義。而其他的類如Point
類或者Rect
類之類的點定義都是嚴格按照上述畫素座標來進行定義的。、
使用OpenCV讀取和操作影像
在opencv中,用於儲存和操作影像的類名稱為Mat(Matrix),給出
一些主要成員。
class CV_EXPORTS Mat
{
public:
// ... a lot of methods ...
...
//! the number of rows and columns or (-1, -1) when the array has more than 2 dimensions
int rows, cols;
//! pointer to the data
uchar* data;
// other members
...
};
可以看出,Mat類儲存了影像的行數以及列數,同時使用一個指標指向儲存畫素的二維矩陣的開頭。想要訪問影像的畫素,可以有如下方式。
Mat img = imread(“/影像位置”);
img.at<Vec3b>(i,j)[0]; //訪問B通道的第i行第j列的畫素
uchar *row = img.ptr<uchar>(y); //獲取第y行的行頭指標
for(int i = 0 ; i < img.rows ; i++)
{
row = img.ptr<uchar>(i);
for(int j = 0 ; j < img.cols ; j++)
{
cout << *(row + j) << ' ';
}
cout << '\n';
}
opencv中的Mat互相賦值並不沒有過載等號運算子為進行複製,因此會出現以下情況
Mat img = imread(/road);
Mat copy = img; //引用性質,兩個物件的data指標指向同一片區域
img.at<Vec3b>(20,20)[0] = 251;//修改img同時也會修改copy
想要完全實現複製賦值,可以使用Mat的clone介面或者copyto介面
Mat copy = img.clone();
img.copyto(copy);
區別就是clone會不由分說直接給copy重新分配一塊記憶體,而copyto會看原矩陣的行列與現矩陣是否吻合,若吻合就直接用,不會新分配。
先寫到這吧。