目錄
1.4大座標系
首先,相機的成像過程涉及到四個座標系:世界座標系、相機座標系、圖像座標系、像素座標系。接下來依次介紹:
- 世界座標系
客觀三維世界的絕對座標系。因爲相機安放在三維空間中,我們需要世界座標系這個基準座標系來描述相機的位置,並且用它來描述安放在此三維環境中的其它任何物體的位置,用(X, Y, Z)表示其座標值。單位:m。
- 相機座標系
以相機的光心爲座標原點,X 軸和Y 軸分別平行於圖像座標系的 X 軸和Y 軸,相機的光軸爲Z 軸,用(Xc, Yc, Zc)表示其座標值。單位:m
- 圖像座標系
以CCD 圖像平面的中心爲座標原點,X軸和Y 軸分別平行於圖像平面的兩條垂直邊,用( x , y )表示其座標值。圖像座標系是用物理單位(例如毫米)表示像素在圖像中的位置。單位:mm
- 像素座標系
以圖像的左上角點爲座標原點,水平方向向右爲x軸(u軸),數值方向向下爲y軸(v軸)。單位:像素(pixel)。
2.相機成像原理
如何成像:
- 在物體和膠片之間,增加一塊帶有小孔的屏障,這就構成了小孔成像,用數學模型表示就叫做:針孔相機模型,而屏障上的小孔稱之爲光圈,並且膠片上獲得倒立的圖像。
- object到barrier的距離爲Z,也就是深度,film到barrier的距離爲f,也就是焦距。
- 針孔相機模型: f爲焦距,X爲三維座標中點,x爲相機座標系中的點
3.光圈(控制進光量)
-
光圈越小,進的光線減少,畫面越暗,背景虛化越小
-
光圈越大,進的光線增加,畫面越亮,背景虛化越大
4.現實生活中的相機系統
這也說明了,儘管從物理原理來說,小孔成像的像應該是倒像,但是由於相機的自身設計,像一般變爲正的了。
5.座標系的關係
-
圖像座標系和相機座標的關係
首先,我們可以通過兩個正的相似三角形可以得到圖像座標系和相機座標系的關係:
用齊次座標系和矩陣表示上述關係:
-
像素座標系和圖像座標系的關係
接着,對於像素座標系和圖像座標系的關係,它們之間相差了一個縮放和一個原點的平移。我們設像素座標在 u 軸上縮放了 1/dx倍,在 v 上縮放了 1/dy倍,那麼我們可以表示爲:
用矩陣的形式表示:
- 兩種關係合併:
整理可以得到:
其中:K爲相機的內參
-
相機座標系到世界座標系的關係
相機座標系到世界座標系存在一個位姿的變換,[R|T],寫成矩陣的形式如下:
組合在一起:
簡化表示爲:
其中:投影矩陣P= K[R |t]
6.相機畸變
1.徑向畸變(Radial Distortion)
簡單來說,由透鏡形狀(相機鏡頭徑向曲率的不規則變化)引起的畸變稱爲徑向畸變,是導致相機成像變形的主要因素。徑向畸變主要分爲桶形畸變和枕型畸變。在針孔模型中,一條直線投影到像素平面上還是一條直線。 但在實際中,相機的透鏡往往使得真實環境中的一條直線在圖片中變成了曲線。越靠近圖像的邊緣現象越明顯。 由於透鏡往往是中心對稱的,這使得不規則畸變通常徑向對稱。(成像中心處的徑向畸變最小,距離中心越遠,產生的變形越大,畸變也越明顯 )
實際攝像機的透鏡總是在成像儀的邊緣產生顯著的畸變,這種現象來源於“筒形”或“魚眼”的影響。如下圖,光線在原理透鏡中心的地方比靠近中心的地方更加彎曲。對於常用的普通透鏡來說,這種現象更加嚴重。筒形畸變在便宜的網絡攝像機中非常厲害,但在高端攝像機中不明顯,因爲這些透鏡系統做了很多消除徑向畸變的工作。
徑向畸變模型: r 爲像平面座標系中點(x, y)與圖像中心(x0, y0)的像素距離。
2.切向畸變(Tangential Distortion)
切向畸變是由於相機鏡頭在製造安裝過程中並非完全平行於成像平面造成的。不同於徑向畸變在圖像中心徑向方向上發生偏移變形,切向畸變主要表現爲圖像點相對理想成像點產生切向偏移。
切向畸變模型可以描述爲:p 1和p 2 —鏡頭的切向畸變係數。
最終需要得到的5個畸變參數:
畸變參數的一般順序是k1,k2,p1,p2,k3。之所以把k3放在最後其實也很容易理解,因爲前面說了一般k1,k2用來處理徑向畸變足矣,k3相對而言用的比較少。 在獲得了畸變參數以後,也就找到了真實觀測的帶畸變的像素與無畸變的像素間的關係,重採樣即可實現影像校正。
7.標定
理論:
相機標定的基本原理:確定場景中一系列點的三維座標並拍攝這個場景,然後觀測這些點在圖像上投影的位置。有了足夠多的三維點和圖像上對應的二維點,就可以根據投影方程推斷出準確的相機參數。
- 實質:已知x、X,求K、R、t
DLT求解相機標定原理:
- 一組2D-3D對應點提供關於P的兩個線性方程
- n組2D-3D對應點提供關於P的2n個線性方程
- 當n≥6時,P可以通過DLT線性解出最小二乘解(P的自由度爲11),然後從P中分解相機內外參數K、R、t,這樣我們就得到了其內參和外參。
但是,對於DLT標定方法來說,存在一定的問題,因爲我們都知道相機得到的圖像不是理想的,還存在着畸變,然而該方法沒有考慮相機的畸變。
張正友標定法:
張正友標定法主要利用平面標定板進行實現,雖然精度上不及使用三維標定物,但是這種方法更加的實用(打印一張黑白棋盤格、一塊足夠平的木板)
實現過程:
OpenCV推薦使用國際象棋棋盤的圖案生成用於校準的三維場景點的集合。這個圖案在每個方塊的角點位置創建場景點,並且由於圖案是平面的,因此我們可以假設棋盤位於Z=0且X 和Y 的座標軸與網格對齊的位置。這樣,校準時就只需從不同的視角拍攝棋盤圖案。下面是一個6 × 9的圖案:
- 尋找棋盤格中的角點
bool found = cv::findChessboardCorners(image,boardSize, imageCorners);
- 對尋找到的角點進行亞像素提純
cornerSubPix(gray,corners,cv::Size(5, 5), cv::Size(-1, -1),TermCriteria(CV_TERMCRIT_EPS | CV_TERMCRIT_ITER, 30, 0.1));
- 可以打印並顯示檢測到的角點
drawChessboardCorners(img,board_size,corners,found);
- 保存2D角點和3D點
image_points.push_back(corners); object_points.push_back(objectCorners);
- 標定
calibrateCamera(object_points,image_points,img.size(),instrisincMatrix,distortionCoeff,rvecs,tvecs);
矯正
得到標定參數以後,我們需要使用畸變參數對圖像進行矯正,OpenCV中對畸變圖像進行畸變校正主要用的函數有:
- undistort()函數
- initUndistortRectifyMap()結合remap()函數
其實undistort()就是initUndistortRectifyMap()和remap()的簡單組合,效果是一樣的。
- 計算重投影誤差
projectPoints(Mat(objectPoints[i]), rvecs[i], tvecs[i], cameraMatrix,distCoeffs, imagePoints2);
注意:根據經驗,10~20個棋盤圖像就足夠了,但是必須在不同的深度,從不同的視角拍攝。這個函數的兩個重要輸出對象是相機矩陣和畸變參數。
實驗代碼:
完整代碼鏈接:裏面包含了實現的代碼和採集的29張棋盤格照片
#include <iostream>
#include <opencv2/core/core.hpp>
#include <opencv2/calib3d/calib3d.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <string>
#include <vector>
using namespace std;
using namespace cv;
void computeReprojectionErrors(const vector< vector< Point3f > >& objectPoints,
const vector< vector< Point2f > >& imagePoints,
const vector< Mat >& rvecs, const vector< Mat >& tvecs,
const Mat& cameraMatrix , const Mat& distCoeffs) {
vector< Point2f > imagePoints2;
for (int i = 0; i < (int)objectPoints.size(); ++i) {
double err;
projectPoints(Mat(objectPoints[i]), rvecs[i], tvecs[i], cameraMatrix,
distCoeffs, imagePoints2);
err =(double) norm(Mat(imagePoints[i]), Mat(imagePoints2), CV_L2)/objectPoints[i].size();
cout<<"第"<<i+1<<"圖片的誤差爲: "<<err<<endl;
}
}
vector< vector< Point3f > > object_points;
vector< vector< Point2f > > image_points;
int main() {
// 水平和垂直方向內部角點的數量
int board_width=6;
int board_height=9;
//圖片的數量
int num_imgs=29;
string base_path="../calib_imgs/1";
Size board_size=Size(board_width,board_height);
vector<Point2f> corners;
std::vector<cv::Point3f> objectCorners;
// 處理所有視角
Mat img,gray;
for (int k = 1; k <= num_imgs; k++)
{
char filename[100];
sprintf(filename,"%s/left%d.jpg",base_path.c_str(),k);
img=imread(filename,CV_LOAD_IMAGE_COLOR);
cvtColor(img,gray,CV_BGR2GRAY);
bool found=false;
found=findChessboardCorners(img,board_size,corners,CV_CALIB_CB_ADAPTIVE_THRESH | CV_CALIB_CB_FILTER_QUADS);
if(found)
{
cornerSubPix(gray,corners,cv::Size(5, 5), cv::Size(-1, -1),TermCriteria(CV_TERMCRIT_EPS | CV_TERMCRIT_ITER, 30, 0.1));
drawChessboardCorners(img,board_size,corners,found);
}
// 場景中的三維點:
// 在棋盤座標系中,初始化棋盤中的角點
// 角點的三維座標(X,Y,Z)= (j,i,0)
// 將世界座標系建在標定板上,所有點的Z座標全部爲0,所以只需要賦值x和y
for (int i=0; i<board_size.height; i++) {
for (int j=0; j<board_size.width; j++) {
objectCorners.push_back(cv::Point3f(j, i, 0.0f));
// cout<<"("<<j<<","<<i<<")"<<endl;
}
}
if (found) {
cout << k << ". Found corners!" << endl;
image_points.push_back(corners);
object_points.push_back(objectCorners);
objectCorners.clear();
}
// imshow("1",img);
// waitKey(0);
}
cv::Mat instrisincMatrix=cv::Mat::eye(3,3,CV_64F);
cv::Mat distortionCoeff=cv::Mat::zeros(4,1,CV_64F);
vector<Mat> rvecs, tvecs;
int flag = 0;
flag |= CV_CALIB_FIX_K4;
flag |= CV_CALIB_FIX_K5;
calibrateCamera(object_points,image_points,img.size(),instrisincMatrix,distortionCoeff,rvecs,tvecs);
cout<<"instrisincMatrix: "<<endl<<instrisincMatrix<<endl;
cout<<"distortionCoeff: "<<endl<<distortionCoeff<<endl;
computeReprojectionErrors(object_points, image_points, rvecs, tvecs, instrisincMatrix, distortionCoeff);
//通過畸變校正效果查看攝像機標定效果
cv::Mat R = cv::Mat::eye(3, 3, CV_32FC1);
cv::Mat mapx, mapy, undistortImg;
cv::initUndistortRectifyMap(instrisincMatrix, distortionCoeff, R, instrisincMatrix, img.size(), CV_32FC1, mapx, mapy);
cv::remap(img, undistortImg, mapx, mapy, CV_INTER_LINEAR);
cv::imshow("undistortImg", undistortImg);
cv::waitKey(0);
return 0;
}
函數講解:
-
FindChessboardCorners()
bool findChessboardCorners( InputArray image,
Size patternSize,
OutputArray corners,
int flags = CALIB_CB_ADAPTIVE_THRESH + CALIB_CB_NORMALIZE_IMAGE );
- 功能:用來尋找棋盤圖的內角點位置。
- 參數:
- Image:輸入的棋盤圖,必須是8位的灰度或者彩色圖像。
- patternSize:棋盤圖中每行和每列角點的個數(內點)。
- imageCorners:存儲檢測到的內部角點的像素座標
- Flags:各種操作標誌,可以是0或者下面值的組合:
CV_CALIB_CB_ADAPTIVE_THRESH -使用自適應閾值(通過平均圖像亮度計算得到)將圖像轉換爲黑白圖,而不是一個固定的閾值。
CV_CALIB_CB_NORMALIZE_IMAGE -在利用固定閾值或者自適應的閾值進行二值化之前,先使用cvNormalizeHist來均衡化圖像亮度。
CV_CALIB_CB_FILTER_QUADS -使用其他的準則(如輪廓面積,周長,方形形狀)來去除在輪廓檢測階段檢測到的錯誤方塊。
-
drawChessboardCorners()
cv::drawChessboardCorners(image,boardSize, imageCorners,found); // 找到的角點
- 功能:畫出棋盤圖像上的角點,用線條依次連接起來
- 參數:
- Image:輸入的棋盤圖,必須是8位的灰度或者彩色圖像。
- boardSize:棋盤圖中每行和每列角點的個數(內點)。
- imageCorners:存儲檢測到的內部角點的像素座標
- found:是否成功找到(bool)
-
cornerSubPix()
void cornerSubPix(
InputArray image,
InputOutputArray corners,
Size winSize,
Size zeroZone,
TermCriteria criteria);
- 功能:亞像素角點檢測
- 參數:
- cv::InputArray image:輸入圖像
- cv::InputOutputArray corners: 角點(既作爲輸入,也作爲輸出)
- cv::Size winSize:區域大小爲 NXN; N=(winSize*2+1)
- cv::Size zeroZone:類似於winSize,但是總具有較小的範圍,Size(-1,-1)表示忽略
- cv::TermCriteria criteria : 停止優化的標準
TermCriteria:
cv::TermCriteria::TermCriteria ( int type, int maxCount, double epsilon)
- type:迭代終止的條件類型(cv::TermCriteria::MAX_ITER (迭代次數達到了最大次數)、cv::TermCriteria::EPS(角點位置變化的最小值),可以選其一,或兩者均選)
- maxCount:最大迭代的次數
- epsilon:收斂的閾值
-
calibrateCamera()
double calibrateCamera( InputArrayOfArrays objectPoints,
InputArrayOfArrays imagePoints,
Size imageSize,
InputOutputArray cameraMatrix,
InputOutputArray distCoeffs,
OutputArrayOfArrays rvecs,
OutputArrayOfArrays tvecs,
int flags = 0, TermCriteria criteria = TermCriteria(TermCriteria::COUNT + TermCriteria::EPS, 30, DBL_EPSILON) );
- 功能:相機標定
- 參數:
- objectPoints: 三維點
- imagePoints,: 圖像點
- imageSize:圖像尺寸
- cameraMatrix:輸出相機矩陣
- distCoeffs: 輸出畸變矩陣
- rvecs, tvecs: Rs、Ts
- flag:設置選項
-
initUndistortRectifyMap()
void initUndistortRectifyMap( InputArray cameraMatrix,
InputArray distCoeffs,
InputArray R,
InputArray newCameraMatrix,
Size size,
int m1type,
OutputArray map1,
OutputArray map2 )
- 功能:
這個函數用於計算無畸變和修正轉換關係,並以映射的形式表示結果以進行重新映射。無畸變的圖像看起來就像原始的圖像,就像這個圖像是用內參爲newCameraMatrix的且無畸變的相機採集得到的。
在單目相機例子中,newCameraMatrix一般和cameraMatrix相等,或者可以用cv::getOptimalNewCameraMatrix來計算,獲得一個更好的有尺度的控制結果。
在雙目相機例子中,newCameraMatrix一般是用cv::stereoRectify計算而來的,設置爲P1或P2。 此外,根據R,新的相機在座標空間中的取向是不同的。例如,它幫助配準雙目相機的兩個相機方向,從而使得兩個圖像的極線是水平的,且y座標相同(在雙目相機的兩個相機誰水平放置的情況下)。 該函數實際上爲反向映射算法構建映射,供反向映射使用。也就是,對於在已經修正畸變的圖像中的每個像素(u,v),該函數計算原來圖像(從相機中獲得的原始圖像)中對應的座標系
- 參數:
-
remap()
void remap(InputArray src,
OutputArray dst,
InputArray map1,
InputArray map2,
int interpolation,
int borderMode=BORDER_CONSTANT,
const Scalar& borderValue=Scalar())
- 功能:重映射,把一副圖像中某位置的像素放到另一幅圖像的指定位置的過程。
例如:g(x,y)爲目標圖像函數,f(x,y)爲源圖像,h(x,y)爲映射函數
如果要是實現按x的翻轉,則h(x,y)=(l.cols-x,y);
- 參數:
src:輸入圖像,即原圖像,需要單通道8位或者浮點類型的圖像
dst :輸出圖像,即目標圖像,需和原圖形一樣的尺寸和類型
map1:它有兩種可能表示的對象:(1)表示點(x,y)的第一個映射;(2)表示CV_16SC2,CV_32FC1等
map2:它有兩種可能表示的對象:(1)若map1表示點(x,y)時,這個參數不代表任何值;(2)表示 CV_16UC1,CV_32FC1類型的Y值
interpolation:插值方式,有四中插值方式:(1)INTER_NEAREST——最近鄰插值
(2)INTER_LINEAR——雙線性插值(默認)(3)INTER_CUBIC——雙三樣條插值(默認)
(4)INTER_LANCZOS4——lanczos插值(默認)
borderMode:邊界模式,默認BORDER_CONSTANT
borderValue:邊界顏色,默認Scalar()黑色
-
undistort()
void undistort(InputArray src,
OutputArray dst,
InputArray cameraMatrix,
InputArray distCoeffs,
InputArray newCameraMatrix=noArray() )
- 功能:矯正畸變
- 參數:
- src –(輸入)失真圖像。
- dst –(輸出)校正的圖像,其大小和類型與相同
src
。- cameraMatrix –相機內參矩陣 。
- distCoeffs – 相機的畸變矩陣 。如果向量爲空,則假定零畸變係數。
- newCameraMatrix –新的內參矩陣。默認情況下,它與
cameraMatrix
相同。可以使用getOptimalNewCameraMatrix()計算newCameraMatrix。
-
projectPoints()
void projectPoints( InputArray objectPoints,
InputArray rvec, InputArray tvec,
InputArray cameraMatrix, InputArray distCoeffs,
OutputArray imagePoints,
OutputArray jacobian = noArray(),
double aspectRatio = 0 );
- 功能:通過給定的內參數和外參數計算三維點投影到二維圖像平面上的座標
- 參數:
- objectPoints:輸入三維點
- rvec,tvec:相機的位姿R,T
- cameraMatrix:內參矩陣
- distCoeffs:畸變矩陣
- imagePoints:輸出像素座標系座標
- jacobian:
參考:
https://www.cnblogs.com/zyly/p/9373991.html
https://blog.csdn.net/tiemaxiaosu/article/details/51734667
相機畸變與標定:http://zhaoxuhui.top/blog/2018/04/17/CameraCalibration.html
綜述|相機標定方法:https://cloud.tencent.com/developer/article/1500292
最詳細、最完整的相機標定講解:https://blog.csdn.net/a083614/article/details/78579163
OpenCV相機標定和姿態更新:https://www.cnblogs.com/mikewolf2002/p/5746667.html
SLAM 中常用的相機模型&畸變模型總結:https://blog.csdn.net/OKasy/article/details/90665534
Opencv畸變矯正原理與損失有效像素原理分析:https://www.cnblogs.com/riddick/p/6711263.html
雙目視覺自標定技術的研究:https://wenku.baidu.com/view/f8639568011ca300a6c39057.html?sxts=1585838430474
Zhengyou Zhang: A Flexible New Technique for Camera Calibration. IEEE T-PAMI), 2000.