前言
通過相機圖片可以識別出棋盤角點了,這時候我們需要通過角點去計算相機內參矩陣,通過上篇得知畸變的原理,所以我們儘可能要全方位都能獲取標定圖片,全方位意思是提供的多張圖綜合起來基本覆蓋了相機所有的像素,同時還要注意遠近和斜着
本篇通過一張圖片來識別計算得到相機內參矩陣,並矯正相機畸形。
做項目一定要多張且基本覆蓋相機所有區域,要保證每一張截取的圖片也要被識別,可以做成個軟件,識別出棋盤都在一個預先指定的區域內則截圖,然後下一個區域,實現半自動半人工化標定。
這裏只用了一張圖校準,所以可能內參矩陣經度不那麼高:
注意:這裏demo只使用了可識別的一張圖作爲計算,可能沒覆蓋的區域則出現不可預期的圖像問題。
這裏是直接填充行列的座標,第三個是z座標直接設置爲0,爲視口處:
// 步驟八:角點對應的三維座標(一張圖一組)
std::vector<std::vector<cv::Point3f>> vectorObjectPoint;
std::vector<cv::Point3f> objectPoints; // 三維世界座標系
for(int i = 0; i < chessboardRowCornerCount; i++)
{
for(int j = 0; j < chessboardColCornerCount; j++)
{
objectPoints.push_back(cv::Point3f(j, i, 0));
}
}
vectorObjectPoint.push_back(objectPoints);
多張圖放入多次,這裏只有一張圖:
// 步驟九:圖像識別出來的角點(一張圖一組)
std::vector<std::vector<cv::Point2f>> vectorImagePoint;
vectorImagePoint.push_back(vectorPoint2fCorners);
輸出的參數有點多,輸入的參數卻不多:
// 步驟十:計算內參和畸變係數
cv::Mat cameraMatrix; // 相機矩陣(接收輸出)
cv::Mat distCoeffs; // 畸變係數(接收輸出)
cv::Mat Rotate; // 旋轉量(接收輸出)
cv::Mat Translate; // 偏移量(接收輸出)
cv::calibrateCamera(vectorObjectPoint,
vectorImagePoint,
grayMat.size(),
cameraMatrix,
distCoeffs,
Rotate,
Translate);
std::cout << "cameraMatrix:" << std::endl;
std::cout << cameraMatrix << std::endl;
std::cout << "distCoeffs:" << std::endl;
std::cout << distCoeffs << std::endl;
std::cout << "Rotate:" << std::endl;
std::cout << Rotate << std::endl;
std::cout << "Translate:" << std::endl;
std::cout << Translate << std::endl;
這裏校準相對容易,所以難點在於標定校準,做項目肯定要自己寫一個標定軟件了,每次這麼手動查看校準肯定不行的。
// 步驟十一:畸變圖像校準
cv::Mat dstMat;
cv::undistort(srcMat, dstMat, cameraMatrix, distCoeffs);
cv::imshow("6", dstMat);
OpenCV中的一個函數,用於相機標定。相機標定是估計相機內參(如焦距、主點座標等)和畸變係數的過程,這些參數對於後續的圖像處理任務(如三維重建、目標跟蹤等)至關重要。
double calibrateCamera(InputArrayOfArrays objectPoints,
InputArrayOfArrays imagePoints,
Size imageSize,
OutputArray cameraMatrix,
OutputArray distCoeffs,
OutputArray rvecs,
OutputArray tvecs,
int flags=0,
TermCriteria criteria=TermCriteria(TermCriteria::COUNT+TermCriteria::EPS, 30, 1e-6));
參數說明:
- objectPoints:世界座標系中的三維點。通常,這些點是通過在標定板上定義的一系列點來獲取的,這些點的座標是已知的。對於每個圖像,它應該是一個 Nx3 的數組(或數組列表),其中 N 是點的數量,而 3 表示每個點的 (X, Y, Z) 座標。
- imagePoints:圖像座標系中的二維點,即對應於 objectPoints 中的三維點在圖像中的投影。對於每個圖像,它應該是一個 Nx2 的數組(或數組列表),其中 N 是點的數量,而 2 表示每個點的 (x, y) 座標。
- imageSize:圖像的大小,表示爲 Size 類型的對象,包含圖像的寬度和高度。
- cameraMatrix:輸出參數,存儲 3x3 的相機內參矩陣。
- distCoeffs:輸出參數,存儲畸變係數。通常有 5 個係數(k1, k2, p1, p2, k3)對於徑向和切向畸變,或 8 個係數(k1, k2, k3, k4, k5, k6, p1, p2)對於魚眼相機模型。
- rvecs:輸出參數,對於每個圖像,存儲旋轉向量的數組。
- tvecs:輸出參數,對於每個圖像,存儲平移向量的數組。
- flags:不同標誌的組合,用於指定標定過程中使用的算法。
CV_CALIB_USE_INTRINSIC_GUESS:使用該參數時,將包含有效的fx,fy,cx,cy的估計值的內參矩陣cameraMatrix,作爲初始值輸入,然後函數對其做進一步優化。如果不使用這個參數,用圖像的中心點初始化光軸點座標(cx, cy),使用最小二乘估算出fx,fy(這種求法好像和張正友的論文不一樣,不知道爲何要這樣處理)。注意,如果已知內部參數(內參矩陣和畸變係數),就不需要使用這個函數來估計外參,可以使用solvepnp()函數計算外參數矩陣。
CV_CALIB_FIX_PRINCIPAL_POINT:在進行優化時會固定光軸點,光軸點將保持爲圖像的中心點。當CV_CALIB_USE_INTRINSIC_GUESS參數被設置,保持爲輸入的值。
CV_CALIB_FIX_ASPECT_RATIO:固定fx/fy的比值,只將fy作爲可變量,進行優化計算。當 CV_CALIB_USE_INTRINSIC_GUESS沒有被設置,fx和fy的實際輸入值將會被忽略,只有fx/fy的比值被計算和使用。
CV_CALIB_ZERO_TANGENT_DIST:切向畸變係數(P1,P2)被設置爲零並保持爲零。
CV_CALIB_FIX_K1,…,CV_CALIB_FIX_K6:對應的徑向畸變係數在優化中保持不變。如果設置了CV_CALIB_USE_INTRINSIC_GUESS參數,就從提供的畸變係數矩陣中得到。否則,設置爲0。
CV_CALIB_RATIONAL_MODEL(理想模型):啓用畸變k4,k5,k6三個畸變參數。使標定函數使用有理模型,返回8個係數。如果沒有設置,則只計算其它5個畸變參數。
CALIB_THIN_PRISM_MODEL (薄棱鏡畸變模型):啓用畸變係數S1、S2、S3和S4。使標定函數使用薄棱柱模型並返回12個係數。如果不設置標誌,則函數計算並返回只有5個失真係數。
CALIB_FIX_S1_S2_S3_S4 :優化過程中不改變薄棱鏡畸變係數S1、S2、S3、S4。如果cv_calib_use_intrinsic_guess設置,使用提供的畸變係數矩陣中的值。否則,設置爲0。
CALIB_TILTED_MODEL (傾斜模型):啓用畸變係數tauX and tauY。標定函數使用傾斜傳感器模型並返回14個係數。如果不設置標誌,則函數計算並返回只有5個失真係數。
CALIB_FIX_TAUX_TAUY :在優化過程中,傾斜傳感器模型的係數不被改變。如果cv_calib_use_intrinsic_guess設置,從提供的畸變係數矩陣中得到。否則,設置爲0。 - criteria:迭代優化的終止條件。通常包含最大迭代次數和收斂的精度。
這個函數返回一個雙精度浮點數,表示重投影誤差的估計值,即實際圖像點與通過相機參數和畸變係數計算出的圖像點之間的平均誤差。
爲了獲得準確的相機標定結果,通常需要多個視圖(即多張不同角度和姿態拍攝的標定板圖像),**並確保標定板在不同圖像中佔據足夠的視場。**此外,圖像應該清晰,且標定板上的特徵點(如棋盤格的角點)應準確檢測。
OpenCV中用於初始化用於圖像去畸變和校正的映射表的函數。這個函數的目的是生成兩個映射,一個用於x座標,另一個用於y座標,它們可以被用於 remap函數來校正圖像的畸變。
void initUndistortRectifyMap(InputArray cameraMatrix,
InputArray distCoeffs,
InputArray R,
InputArray newCameraMatrix,
Size size,
int m1type,
OutputArray map1,
OutputArray map2)
參數說明
- cameraMatrix:相機的內參矩陣,一個3x3的浮點數矩陣。
- distCoeffs:畸變係數,一個1x5或1x8的向量,包含徑向和切向畸變係數。
- R:可選的旋轉矩陣,一個3x3的浮點數矩陣,表示從原相機座標系到新的相機座標系的旋轉。如果這個參數是空的,那麼newCameraMatrix必須是cameraMatrix。
- newCameraMatrix:新的相機內參矩陣,一個3x3的浮點數矩陣。這個矩陣可以是原始相機矩陣,或者經過getOptimalNewCameraMatrix調整後的矩陣,以考慮圖像的有效視場。
- size:輸出映射的尺寸,表示爲Size類型的對象,包含圖像的寬度和高度。
- m1type:輸出映射的類型,可以是CV_32FC1或CV_16SC2。
- map1:輸出的第一個映射,用於x座標,可以被傳遞給remap函數。
- map2:輸出的第二個映射,用於y座標,可以被傳遞給remap函數。
這兩個映射map1和map2可以被傳遞給remap函數,以對圖像進行去畸變和校正。
如果有一個畸變的圖像distortedImage和想要得到校正後的圖像undistortedImage,可以這樣使用這兩個函數:
Mat map1,map2;
initUndistortRectifyMap(cameraMatrix, distCoeffs, R, newCameraMatrix, size, CV_32FC1, map1, map2);
remap(distortedImage, undistortedImage, map1, map2, INTER_LINEAR);
在這個例子中,INTER_LINEAR是插值方法的類型,用於remap函數。其他的插值方法,如INTER_NEAREST、INTER_CUBIC等也可以被使用,具體取決於應用需求。
void OpenCVManager::testCorrectingChessboard()
{
#define TestCorrectingChessboardUseCamera 0
#if !TestCorrectingChessboardUseCamera
// 使用圖片
// std::string srcFilePath = "D:/qtProject/openCVDemo/openCVDemo/modules/openCVManager/images/chessboard.png";
// std::string srcFilePath = "D:/qtProject/openCVDemo/openCVDemo/modules/openCVManager/images/24.jpg";
// std::string srcFilePath = "D:/qtProject/openCVDemo/openCVDemo/modules/openCVManager/images/27.png";
// std::string srcFilePath = "D:/qtProject/openCVDemo/openCVDemo/modules/openCVManager/images/28.png";
std::string srcFilePath = "D:/qtProject/openCVDemo/openCVDemo/modules/openCVManager/images/28.jpg";
cv::Mat srcMat = cv::imread(srcFilePath);
#else
// 使用攝像頭
cv::VideoCapture capture;
// 插入USB攝像頭默認爲0
if(!capture.open(0))
{
qDebug() << __FILE__ << __LINE__ << "Failed to open camera: 0";
}else{
qDebug() << __FILE__ << __LINE__ << "Succeed to open camera: 0";
}
while(true)
{
cv::Mat srcMat;
capture >> srcMat;
#endif
int chessboardColCornerCount = 6;
int chessboardRowCornerCount = 9;
// int chessboardColCornerCount = 7;
// int chessboardRowCornerCount = 7;
// 步驟一:讀取文件
// cv::imshow("1", srcMat);
// cv::waitKey(0);
// 步驟二:縮放,太大了縮放下(可省略)
cv::resize(srcMat, srcMat, cv::Size(srcMat.cols / 2, srcMat.rows / 2));
cv::Mat srcMat2 = srcMat.clone();
cv::Mat srcMat3 = srcMat.clone();
cv::imshow("2", srcMat);
// cv::waitKey(0);
// 步驟三:灰度化
cv::Mat grayMat;
cv::cvtColor(srcMat, grayMat, cv::COLOR_BGR2GRAY);
cv::imshow("3", grayMat);
// cv::waitKey(0);
// 步驟四:檢測角點
std::vector<cv::Point2f> vectorPoint2fCorners;
bool patternWasFound = false;
patternWasFound = cv::findChessboardCorners(grayMat,
cv::Size(chessboardColCornerCount, chessboardRowCornerCount),
vectorPoint2fCorners,
cv::CALIB_CB_ADAPTIVE_THRESH | cv::CALIB_CB_FAST_CHECK | cv::CALIB_CB_NORMALIZE_IMAGE);
/*
enum { CALIB_CB_ADAPTIVE_THRESH = 1, // 使用自適應閾值將圖像轉化成二值圖像
CALIB_CB_NORMALIZE_IMAGE = 2, // 歸一化圖像灰度係數(用直方圖均衡化或者自適應閾值)
CALIB_CB_FILTER_QUADS = 4, // 在輪廓提取階段,使用附加條件排除錯誤的假設
CALIB_CB_FAST_CHECK = 8 // 快速檢測
};
*/
cvui::printf(srcMat, 0, 0, 1.0, 0xFF0000, "found = %s", patternWasFound ? "true" : "false");
cvui::printf(srcMat, 0, 24, 1.0, 0xFF0000, "count = %d", vectorPoint2fCorners.size());
qDebug() << __FILE__ << __LINE__ << vectorPoint2fCorners.size();