opencv學習筆記十一:相機模型與標定(一:理論部分)

前言

我將這一部分分成兩個部分來寫,一個是理論部分,一個是具體實踐部分。相機模型標定是我們拋去圖像處理後接觸的第一個比較“高級點”的東西,也是深入學習視覺其他東西的基礎,比如做雙目測距或者VO里程計。除此,相機標定也涉及一些好玩的數學方法,好好鑽研一下也是很有好處和樂趣。

相機模型

我們高中都做過小孔成像的實驗,小孔相機模型就是最簡單通用的一種相機模型,這個模型我們就用下面一個圖帶過好了:
在這裏插入圖片描述
其中f爲我們熟知的相機參數——焦距,而光軸與圖像平面的角點稱爲主點
實際中,主點並不是成像裝置的中心,因爲我們無法拿着鑷子和膠水把相機裏面的成像裝置以微米級別精確安裝,也沒有必要。芯片的中心通常不在光軸上,這時我們就要引入兩個新的參數Cx , Cy(用來表示對投影屏幕座標中心的偏移量),這樣,物理世界中的點 Q ,其座標爲(X,Y,Z),根據下式投射到成像裝置的摸個像素位置(Xscreen,Yscreen):
在這裏插入圖片描述
注意,我們引入了兩個不同的焦距 fxfy ,原因時單個像素在低價成像裝置上時矩形而不是正方形。
現在我們知道相機模型的基本結構和原理,就要考慮現實世界的點與相機成像的像素點座標之間的關係了,我們將物理世界中座標爲(Xi,Yi,Zi)的點Qi 映射到投影平面上座標爲(xi,yi,zi)的點上的過程叫做 “ 射影變換 ”;
採用這種變換,可以方便的適應我們所熟知的齊次座標把位數爲n的投影空間的點用(n+1)維向量(例如x,y,z變爲x,y,z,w)表示。其實我對齊次座標並不熟悉。。所以查了一下,這是原文鏈接:https://blog.csdn.net/xiaoshen0121/article/details/79428678
因爲感覺寫的很好,就搬過來存上了//

對於一個向量v以及基oabc,可以找到一組座標(v1,v2,v3),使得v = v1 a + v2 b + v3 c (1)
而對於一個點p,則可以找到一組座標(p1,p2,p3),使得 p – o = p1 a + p2 b + p3 c (2),
從上面對向量和點的表達,我們可以看出爲了在座標系中表示一個點(如p),我們把點的位置看作是對這個基的原點o所進行的一個位移,即一個向量——p – o(有的書中把這樣的向量叫做位置向量——起始於座標原點的特殊向量),我們在表達這個向量的同時用等價的方式表達出了點p:p = o + p1 a + p2 b + p3 c (3)
(1)(3)是座標系下表達一個向量和點的不同表達方式。這裏可以看出,雖然都是用代數分量的形式表達向量和點,但表達一個點比一個向量需要額外的信息。如果我寫出一個代數分量表達(1, 4, 7),誰知道它是個向量還是個點!
我們現在把(1)(3)寫成矩陣的形式:v = (v1 v2 v3 0) X (a b c o)
p = (p1 p2 p3 1) X (a b c o),這裏(a,b,c,o)是座標基矩陣,右邊的列向量分別是向量v和點p在基下的座標。這樣,向量和點在同一個基下就有了不同的表達:3D向量的第4個代數分量是0,而3D點的第4個代數分量是1。像這種這種用4個代數分量表示3D幾何概念的方式是一種齊次座標表示。
這樣,上面的(1, 4, 7)如果寫成(1,4,7,0),它就是個向量;如果是(1,4,7,1),它就是個點。下面是如何在普通座標(Ordinary Coordinate)和齊次座標(Homogeneous Coordinate)之間進行轉換:
(1)從普通座標轉換成齊次座標時
如果(x,y,z)是個點,則變爲(x,y,z,1);
如果(x,y,z)是個向量,則變爲(x,y,z,0)
(2)從齊次座標轉換成普通座標時
如果是(x,y,z,1),則知道它是個點,變成(x,y,z);
如果是(x,y,z,0),則知道它是個向量,仍然變成(x,y,z)
以上是通過齊次座標來區分向量和點的方式。從中可以思考得知,對於平移T、旋轉R、縮放S這3個最常見的仿射變換,平移變換隻對於點纔有意義,因爲普通向量沒有位置概念,只有大小和方向.
而旋轉和縮放對於向量和點都有意義,你可以用類似上面齊次表示來檢測。從中可以看出,齊次座標用於仿射變換非常方便。
此外,對於一個普通座標的點P=(Px, Py, Pz),有對應的一族齊次座標(wPx, wPy, wPz, w),其中w不等於零。比如,P(1, 4, 7)的齊次座標有(1, 4, 7, 1)、(2, 8, 14, 2)、(-0.1, -0.4, -0.7, -0.1)等等。因此,如果把一個點從普通座標變成齊次座標,給x,y,z乘上同一個非零數w,然後增加第4個分量w;如果把一個齊次座標轉換成普通座標,把前三個座標同時除以第4個座標,然後去掉第4個分量。
由於齊次座標使用了4個分量來表達3D概念,使得平移變換可以使用矩陣進行,從而如F.S. Hill, JR所說,仿射(線性)變換的進行更加方便。由於圖形硬件已經普遍地支持齊次座標與矩陣乘法,因此更加促進了齊次座標使用,使得它似乎成爲圖形學中的一個標準。
以上很好的闡釋了齊次座標的作用及運用齊次座標的好處。其實在圖形學的理論中,很多已經被封裝的好的API也是很有研究的,要想成爲一名專業的計算機圖形學的學習者,除了知其然必須還得知其所以然。這樣在遇到問題的時候才能迅速定位問題的根源,從而解決問題。

下面就是我們將物理世界的點投影到相機上的數學表示了:
在這裏插入圖片描述
opencv中有專門的齊次座標與非齊次座標相互轉換的函數:

	convertPointsToHomogeneous(
		InputArray	src,
		OutputArray	dst
	);

需要一個N維點的向量(用一般形式表示),並且從該向量中構造出(N+1)維點的向量。所有新構造的向量所添加的維度值設置爲1。

	convertPointsFromHomogeneous(
		InputArray	src,
		OutputArray	dst
	);

從齊次座標轉換爲普通座標,給定N維點的輸入向量,首先將每個點的所有分量除以改點的最後一個分量,再將最後一個分量丟棄,從而構造出(N-1)維點的向量。

至此,在將座標系轉換的過程中,我們共加了四個參數:Fx Fy Cx Cy , 他們所構成的矩陣M成爲內參矩陣。

透鏡畸變

我們知道了世界座標與相機座標之間的轉換,但在針孔相機中,由於只有少量光線通過針孔,在實際中,無論使用何種圖像採集器,都需要等待積累足夠光線,因此成像速度非常慢。對於快速生成圖像的相機而言,必須利用更大面積的光線,甚至讓光線扭曲以達到讓更多光線進來的目的。
我們使用透鏡來達到這個目的,但自然也引入了畸變。
畸變分爲徑向畸變和切向畸變,徑向畸變是由於透鏡形狀造成的,切向畸變是由於整個相機的組裝過程造成的。

徑向畸變

在這裏插入圖片描述
鏡像畸變是有凸透鏡本身形狀引起的,好的透鏡,經過一些精密處理,畸變並不明顯,但在普通網絡相機上畸變顯得特別突出。我們可以把畸變看作r=0附近的泰勒奇數展開的前幾項來便是。一般爲前兩項 k1 , k2,對於魚眼透鏡 ,會用前三項 k3 .
成像裝置上某點的徑向位置可以根據以下等式進行調整:
在這裏插入圖片描述
這裏(x,y)是成像裝置上畸變點的原始位置,(Xcorrected,Ycorrected)是矯正後的新位置。

切向畸變

在這裏插入圖片描述
切向畸變是由於製造上的缺陷使透鏡不與成像平面平行而產生的。切向畸變可以用兩個參數p1 和 p2 來表示:
在這裏插入圖片描述
至此,我們得到了共五個參數:K1 K2 K3 P1 P2 ,這五個參數是我們消除畸變所必須的,稱爲畸變向量,也叫相機外參。

標定

首先,標定自然需要計算相機內參矩陣和畸變向量,opencv提供了函數cv::calibrateCamera()函數來計算,在使用這個函數之前,我們先了解下標定板,最常用的標定板就是棋盤了,如下圖:
在這裏插入圖片描述
棋盤要求精度需要很高,格子是正方形,買一張標定板很貴的,在csdn上下棋盤圖也要畫好多c幣,即使給大家在這兒貼一張棋盤圖,大家保存完也會有碼,所以大家可以用word畫一張,很簡單的,只要做一個5*7的表格再塗色就可以了。
標定板有了,opencv爲我們提供了一系列函數來計算畸變參數和外部參數,使用的方法是張正友標定法,但我們有matlab這麼好用的工具軟件,就不使用函數矯正了。因爲電腦系統前段時間重裝了,MATLAB還沒下,標定過程以後再更新吧。

矯正函數應用

我們可以通過MATLAB來獲取內參和畸變矩陣,接下來就是矯正了,opencv給我們提供了兩種矯正方法,雖說是兩種,但各有不同的特性和應用。在介紹這兩個函數之前,先了解一下矯正映射。
在進行圖像矯正時,我們必須指定輸入圖像中的每個像素在輸出圖像中移動的位置,稱爲“矯正映射”。這樣的映射有三種表示方式:

  1. 雙通道浮點數表示,這種表示最爲直接。NM圖像的重映射由雙通道浮點數NM矩陣表示,對與像素圖像下標位置爲(i,j)的點的值爲(i0,j0),(i0,j0)就表示該像素在矯正後的位置。
  2. 雙矩陣浮點表示,就是用兩個單通道的矩陣N*M分別表示原像素點在矯正後的X座標和Y座標。
  3. 定點表示。映射由雙通道有符號整數矩陣表示,表示方法與雙通道浮點數表示一樣,但更快一些。
    opencv給我們提供了函數cv::convertMaps()函數在不同表示方式之間轉換矯正映射。
    下面,我們就要進行矯正了,我們有兩種方法:
    方法一:
    使用cv::initUndistortRecitifyMap()函數計算矯正映射,函數原型如下:
initUndistortRectifyMap(
	InputArray	cameraMaxtrix,    3*3內參矩陣 
	InputArray	distCoeffs,		  畸變係數1*4向量
	InputArray	R,				  可以使用或者設置爲noArray()。是一個旋轉矩陣,將在矯正前
							預先使用,來補償相機相對於相機所處的全局座標系的旋轉。
	InputArray	newCameraMatrix,  單目成像時一般不會使用它
	Size		size,			  輸出映射的尺寸,對應於用來矯正的圖像的尺寸
	int			m1type,			  最終的映射類型,可能只爲CV_32FC1  32_16SC2,對應於map1的表示類型
	OutputArray	map1,			  
	OutputArray	map2
);

我們只需在程序開頭使用該函數計算一次矯正映射,就可以使用cv::remap()函數將該矯正應用到視頻每一幀圖像。
方法二:
在某些情況下,我們只需要矯正一個圖像,或者對一個圖像重新計算矯正映射。這時候適應更加簡潔的cv::undistort()函數即可。
在這裏插入圖片描述
下面時一段矯正魚眼攝像頭的程序,相機內參和畸變參數已經有MATLAB求出:

#include <opencv2/opencv.hpp>
#include <opencv2\highgui\highgui.hpp>
#include <iostream>
#include <stdio.h>

using namespace std;
using namespace cv;

const int imageWidth = 640; //圖片大小    //攝像頭的分辨率  
const int imageHeight = 480;
Size imageSize = Size(imageWidth, imageHeight);
Mat mapx, mapy;
// 相機內參
Mat cameraMatrix = (Mat_<double>(3, 3) << 273.4985, 0, 321.2298,
	0, 273.3338, 239.7912,
	0, 0, 1);
// 相機外參
Mat distCoeff = (Mat_<double>(1, 4) << -0.3551, 0.1386,0, 0);
Mat R = Mat::eye(3, 3, CV_32F);

VideoCapture cap1;

void img_init(void)
{
	//這初始化攝像頭,但是我這種攝像頭不知道是哪不好,設備號已經到700了而且必須關閉自帶攝像頭才能用
	int cont = -1;
	cap1.open(0);
	cap1.set(CAP_PROP_FOURCC, 'GPJM');
	cap1.set(CAP_PROP_FRAME_WIDTH, imageWidth);
	cap1.set(CAP_PROP_FRAME_HEIGHT, imageHeight);
}

int main()
{
	initUndistortRectifyMap(cameraMatrix, distCoeff, R, cameraMatrix, imageSize, CV_32FC1, mapx, mapy);
	Mat frame;
	img_init();

	while (1)
	{
		cap1>>frame;
		imshow("yuan",frame);
		remap(frame,frame,mapx,mapy, INTER_LINEAR);
		imshow("change",frame);
		waitKey(30);
	}
    return 0;
}

可以看到原本屏幕邊緣是弧形的,矯正後是直的。
在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章