相機標定
相機標定:簡單的說,就是獲得相機參數的過程。參數如:相機內參數矩陣,投影矩陣,旋轉矩陣和平移矩陣等
什么叫相機參數?
簡單的說,將現實世界中的人、物,拍成一張圖像(二維)。人或物在世界中的三維坐標,和圖像上對應的二維坐標間的關系。表達兩種不同維度坐標間的關系用啥表示?用相機參數。
相機的成像原理
先來看一下,相機的成像原理:
如圖所示,這是一個相機模型。將物體簡化看成一個點。來自物體的光,通過鏡頭,擊中圖像平面(圖像傳感器),以此成像。d0是物體到鏡頭的距離,di是鏡頭到圖像平面的距離,f是鏡頭的焦距。三者滿足以下關系。
現在,簡化上面的相機模型。
將相機孔徑看成無窮小,只考慮中心位置的射線,這樣就忽視了透鏡的影響。然后由於d0遠遠大於di,將圖像平面放在焦距處,這樣物體在圖像平面上成像為倒立的影像(沒有透鏡的影響,只考慮從中心的孔徑進入的光線)。這個簡化的模型就是小孔攝像機模型。然后,我們在鏡頭前,將圖像平面放在焦距距離的位置,就可以簡單獲得一個筆直的圖像(不倒立)。當然,這只是理論上的,你不可能將圖像傳感器從相機里拿出來,放在鏡頭前面。實際應用中,小孔攝像機應該是將成像后的圖像倒過來,以獲得正立的圖像。
到此,我們獲得了一個簡化的模型,如下圖:
h0是物體的高,hi是圖像上物體的高,f是焦距(距離),d0是圖像到鏡頭的距離。四者滿足如下關系:
物體在圖像中的高度hi,和d0成反比。也就是說,離鏡頭越遠,物體在圖像中越小,離得越近越大(好吧,這句話是廢話)。
但通過這個式子,我們便能夠預測三維中的物體,在圖像(二維)中的位置。那么怎么預測?
相機標定
如下圖所示,根據上面簡化的模型,考慮三維世界中的一個點,和其在圖像(二維)中的坐標關系。
(X,Y,Z)為點的三維坐標,(x,y)為其通過相機成像后在圖像(二維)上的坐標。u0和v0是相機的中心點(主點),該點位於圖像平面中心(理論上是這樣。但實際的相機會有幾個像素的偏差)
現在只考慮y方向上,由於需要將三維世界中的坐標,轉換為圖像上的像素(圖像上的坐標,實際上是像素的位置),需要求y方向上焦距
等於多少個像素(用像素值表示焦距),Py表示像素的高,焦距f(米或毫米)。垂直像素表示的焦距為
根據式子(1),只考慮y方向。我們三維世界中得點,在圖像(二維)中y的坐標。
同理,得到x的坐標。
現在,將上圖中的坐標系的原點O,移動到圖像的左上角。由於(x,y)是關於(u0,v0)的偏移,上面表示圖像(二維)中點的坐標的式子不變。將式子以矩陣的形式重寫,得。
其中,等式左邊的第一個矩陣,叫做“相機內參數矩陣”,第二個矩陣叫(投影矩陣)。
更為一般的情況,開始時的參考坐標系不位於主點(中心點),需要額外兩個參數“旋轉向量”和“平移向量”來表示這個式子,這兩個參數在不同視角中是不一樣的。整合后,上述式子重寫為。
校正畸變
通過相機標定,獲得了相機參數后,可以計算兩個映射函數(x坐標和y坐標),它們分別給出了沒有畸變的圖像坐標。將畸變的圖像重新映射成為沒有畸變的圖像。
代碼:
做相機標定時,一般用標定板(棋盤)拍攝一組圖像,利用這些圖像提取角點,通過角點在圖像中得坐標和三維世界中的坐標(通常自定義3維坐標),計算相機參數。
1 std::vector<cv::Point2f>imageConers; 2 //提取標定圖像角點,保存角點坐標(二維)
3 cv::findChessboardCorners(image, 4 boardSize, //角點數目如(6,4)六行,四列
5 imageConers);
函數calibrateCamera完成相機標定工作。
1 cv::calibrateCamera(objectPoints,//三維坐標
2 imagePoints, //二維坐標
3 imageSize,//圖像大小
4 camerMatirx,//相機內參數矩陣
5 disCoeffs,//投影矩陣
6 rvecs, //旋轉
7 tvecs,//平移
8 flag //標記opencv提供幾種參數,可以參看在線的opencv document
9 );
計算畸變參數,去畸變
1 //計算畸變參數
2 cv::initUndistortRectifyMap(camerMatirx, disCoeffs, 3 cv::Mat(), cv::Mat(), image.size(), CV_32FC1, 4 map1, //x映射函數
5 map2 //y映射函數
6 ); 7 //應用映射函數
8 cv::remap(image, //畸變圖像
9 undistorted, //去畸變圖像
10 map1, map2, cv::INTER_LINEAR);
現在整合代碼。
- 示例:
標頭.h
1 #include<opencv2\core\core.hpp>
2 #include<opencv2\highgui\highgui.hpp>
3 #include<opencv2\imgproc\imgproc.hpp>
4 #include<opencv2\calib3d\calib3d.hpp>
5 #include <opencv2/features2d/features2d.hpp>
6 #include<string>
7 #include<vector>
8 class CameraCalibrator 9 { 10 private: 11 //世界坐標
12 std::vector < std::vector<cv::Point3f >> objectPoints; 13 //圖像坐標
14 std::vector <std::vector<cv::Point2f>> imagePoints; 15 //輸出矩陣
16 cv::Mat camerMatirx; 17 cv::Mat disCoeffs; 18 //標記
19 int flag; 20 //去畸變參數
21 cv::Mat map1, map2; 22 //是否去畸變
23 bool mustInitUndistort; 24
25 ///保存點數據
26 void addPoints(const std::vector<cv::Point2f>&imageConers, const std::vector<cv::Point3f>&objectConers) 27 { 28 imagePoints.push_back(imageConers); 29 objectPoints.push_back(objectConers); 30 } 31 public: 32 CameraCalibrator() :flag(0), mustInitUndistort(true){} 33 //打開棋盤圖片,提取角點
34 int addChessboardPoints(const std::vector<std::string>&filelist,cv::Size &boardSize) 35 { 36 std::vector<cv::Point2f>imageConers; 37 std::vector<cv::Point3f>objectConers; 38 //輸入角點的世界坐標
39 for (int i = 0; i < boardSize.height; i++) 40 { 41 for (int j = 0; j < boardSize.width; j++) 42 { 43 objectConers.push_back(cv::Point3f(i, j, 0.0f)); 44 } 45 } 46 //計算角點在圖像中的坐標
47 cv::Mat image; 48 int success = 0; 49 for (int i = 0; i < filelist.size(); i++) 50 { 51 image = cv::imread(filelist[i],0); 52 //找到角點坐標
53 bool found = cv::findChessboardCorners(image, boardSize, imageConers); 54 cv::cornerSubPix(image, 55 imageConers, 56 cv::Size(5, 5), 57 cv::Size(-1, -1), 58 cv::TermCriteria(cv::TermCriteria::MAX_ITER + cv::TermCriteria::EPS, 59 30, 0.1)); 60 if (imageConers.size() == boardSize.area()) 61 { 62 addPoints(imageConers, objectConers); 63 success++; 64 } 65 //畫出角點
66 cv::drawChessboardCorners(image, boardSize, imageConers, found); 67 cv::imshow("Corners on Chessboard", image); 68 cv::waitKey(100); 69 } 70 return success; 71 } 72
73 //相機標定
74 double calibrate(cv::Size&imageSize) 75 { 76 mustInitUndistort = true; 77 std::vector<cv::Mat>rvecs, tvecs; 78 //相機標定
79 return cv::calibrateCamera(objectPoints, imagePoints, imageSize, 80 camerMatirx, disCoeffs, rvecs, tvecs, flag); 81 } 82 ///去畸變
83 cv::Mat remap(const cv::Mat &image) 84 { 85 cv::Mat undistorted; 86 if (mustInitUndistort) 87 { 88 //計算畸變參數
89 cv::initUndistortRectifyMap(camerMatirx, disCoeffs, 90 cv::Mat(), cv::Mat(), image.size(), CV_32FC1, map1, map2); 91 mustInitUndistort = false; 92 } 93 //應用映射函數
94 cv::remap(image, undistorted, map1, map2, cv::INTER_LINEAR); 95 return undistorted; 96 } 97 //常成員函數,獲得相機內參數矩陣、投影矩陣數據
98 cv::Mat getCameraMatrix() const { return camerMatirx; } 99 cv::Mat getDistCoeffs() const { return disCoeffs; } 100 };
源.cpp
1 #include"標頭.h"
2 #include<iomanip>
3 #include<iostream>
4 int main() 5 { 6 CameraCalibrator Cc; 7 cv::Mat image; 8 std::vector<std::string> filelist; 9 cv::namedWindow("Image"); 10 for (int i = 1; i <= 22; i++) 11 { 12 ///讀取圖片
13 std::stringstream s; 14 s << "D:/images/chessboards/chessboard" << std::setw(2) << std::setfill('0') << i << ".jpg"; 15 std::cout << s.str() << std::endl; 16
17 filelist.push_back(s.str()); 18 image = cv::imread(s.str(),0); 19 cv::imshow("Image", image); 20 cv::waitKey(100); 21 } 22 //相機標定
23 cv::Size boardSize(6, 4); 24 Cc.addChessboardPoints(filelist, boardSize); 25 Cc.calibrate(image.size()); 26
27 //去畸變
28 image = cv::imread(filelist[1]); 29 cv::Mat uImage=Cc.remap(image); 30 cv::imshow("原圖像", image); 31 cv::imshow("去畸變", uImage); 32 //顯示相機內參數矩陣
33 cv::Mat cameraMatrix = Cc.getCameraMatrix(); 34 std::cout << " Camera intrinsic: " << cameraMatrix.rows << "x" << cameraMatrix.cols << std::endl; 35 std::cout << cameraMatrix.at<double>(0, 0) << " " << cameraMatrix.at<double>(0, 1) << " " << cameraMatrix.at<double>(0, 2) << std::endl; 36 std::cout << cameraMatrix.at<double>(1, 0) << " " << cameraMatrix.at<double>(1, 1) << " " << cameraMatrix.at<double>(1, 2) << std::endl; 37 std::cout << cameraMatrix.at<double>(2, 0) << " " << cameraMatrix.at<double>(2, 1) << " " << cameraMatrix.at<double>(2, 2) << std::endl; 38
39 cv::waitKey(0); 40 }
實驗結果:
看以看到,相機內參數矩陣為
172.654 、0、157.829
0、184.195、118.635
0 、0 、1