? ? 如果標定過程是通過OpenCV張正友標定法實現的,得到的內參外參保存在.txt文件中是這樣的形式:
① 內參intrinsics.txt:
?② 外參extrinsics.txt:
? ? 那么可以通過如下方法讀取.txt文件獲取左右相機內外參,主要包括三維解算需要用到的左右相機內參矩陣、畸變系數,以及左右相機構成雙目系統的旋轉矩陣和平移矩陣,具體代碼如下:
std::string intrinsicsPath{ "D:\\Program Files\\edge下載文件\\030716.16\\030716.16\\intrinsics.txt" }; // 左右相機內參數文件路徑
std::string extrinsicsPath{ "D:\\Program Files\\edge下載文件\\030716.16\\030716.16\\extrinsics.txt" }; // 左右相機外參數文件路徑// 加載左右相機內參數
cv::Mat cameraMatrixL; // 左相機內參矩陣
cv::Mat distCoeffsL; // 左相機畸變參數
cv::Mat cameraMatrixR; // 右相機內參矩陣
cv::Mat distCoeffsR; // 右相機畸變參數cv::FileStorage fs(intrinsicsPath, cv::FileStorage::READ);
fs["cameraMatrixL"] >> cameraMatrixL;
fs["cameraDistcoeffL"] >> distCoeffsL;
fs["cameraMatrixR"] >> cameraMatrixR;
fs["cameraDistcoeffR"] >> distCoeffsR;
fs.release();//std::cout << "左相機內參矩陣......" << std::endl;
//std::cout << cameraMatrixL << std::endl;
//std::cout << "左相機畸變參數......" << std::endl;
//std::cout << distCoeffsL << std::endl;
//std::cout << std::endl;
//std::cout << "右相機內參矩陣......" << std::endl;
//std::cout << cameraMatrixR << std::endl;
//std::cout << "右相機畸變參數......" << std::endl;
//std::cout << distCoeffsR << std::endl;
//std::cout << std::endl;// 加載相機外參數
cv::Mat R; // 旋轉矩陣
cv::Mat T; // 平移向量fs.open(extrinsicsPath, cv::FileStorage::READ);
fs["R"] >> R;
fs["T"] >> T;
fs.release();
? ? 在得到二維像素坐標之后可以通過畸變校正,三角測量法(Triangulation) 來計算三維點坐標。通常是基于 OpenCV 提供的 cv::triangulatePoints
進行計算,這是一個標準的立體視覺技術,用于通過兩個相機視角中的匹配點估算其三維坐標。
? ? 步驟如下代碼所示:
? ? 二維坐標→畸變校正→轉換到相機坐標系→三維解算。
std::vector<cv::Point2f> leftPointsUndistort, rightPointsUndistort;cv::undistortPoints(targetsL, leftPointsUndistort, cameraMatrixL, distCoeffsL, cv::Mat(), cameraMatrixL);// targetsL是未做畸變矯正前處理得到的二維中心點坐標(左相機)cv::undistortPoints(targetsR, rightPointsUndistort, cameraMatrixR, distCoeffsR, cv::Mat(), cameraMatrixR);// targetsR是未做畸變矯正前處理得到的二維中心點坐標(右相機)// 轉換到相機坐標系std::vector<cv::Point2f> leftPointsCam = pixel2cam(leftPointsUndistort, cameraMatrixL);std::vector<cv::Point2f> rightPointsCam = pixel2cam(rightPointsUndistort, cameraMatrixR);// 求解三維坐標std::vector<cv::Point3f> points3d = triangulation(leftPointsCam, rightPointsCam, R, T);
調用的函數代碼:
std::vector<cv::Point3f> triangulation(const std::vector<cv::Point2f>& pts1, const std::vector<cv::Point2f>& pts2, cv::Mat& R, cv::Mat& T)
{cv::Mat T1 = (cv::Mat_<float>(3, 4) << 1, 0, 0, 0,0, 1, 0, 0,0, 0, 1, 0);R.convertTo(R, CV_64FC1);T.convertTo(T, CV_64FC1);cv::Mat T2 = (cv::Mat_<float>(3, 4) <<R.at<double>(0, 0), R.at<double>(0, 1), R.at<double>(0, 2), T.at<double>(0, 0),R.at<double>(1, 0), R.at<double>(1, 1), R.at<double>(1, 2), T.at<double>(1, 0),R.at<double>(2, 0), R.at<double>(2, 1), R.at<double>(2, 2), T.at<double>(2, 0));cv::Mat pts4d;cv::triangulatePoints(T1, T2, pts1, pts2, pts4d);std::vector<cv::Point3f> pts3d;for (int i = 0; i < pts4d.cols; ++i){float x = pts4d.at< float >(0, i) / pts4d.at< float >(3, i);float y = pts4d.at< float >(1, i) / pts4d.at< float >(3, i);float z = pts4d.at< float >(2, i) / pts4d.at< float >(3, i);pts3d.emplace_back(cv::Point3f(x, y, z));}return pts3d;
}std::vector<cv::Point2f> pixel2cam(const std::vector<cv::Point2f>& pts, const cv::Mat& cameraMatrix)
{std::vector<cv::Point2f> ptsCam;for (const auto& p : pts){cv::Point2f c((p.x - cameraMatrix.at<double>(0, 2)) / cameraMatrix.at<double>(0, 0),(p.y - cameraMatrix.at<double>(1, 2)) / cameraMatrix.at<double>(1, 1));ptsCam.emplace_back(c);}return ptsCam;
}
?注:pixel2cam 函數的作用是將像素坐標轉換為歸一化相機坐標(normalized camera coordinates)。這是必要的,因為 三角測量法 計算三維點的位置時,假設輸入的點是在無畸變的相機坐標系下。
這里的 (xc,yc) 是歸一化相機坐標,表示 光學中心?歸一化后的坐標,它們不再依賴于攝像機的焦距和像素比例,因此可以用于三角測量。?
三角測量的數學原理
-
三角測量基于 兩個不同視角的相機投影矩陣(Projection Matrix)。
-
如果使用 像素坐標,那么投影矩陣應該是 P=K[R∣T](包含相機內參)。
-
但如果使用 歸一化相機坐標,那么投影矩陣可以簡化為 P=[R∣T](去除了相機內參)。
-
這樣可以直接利用 相機外參(R, T) 進行計算,提高準確性。
三角測量計算的流程
將像素坐標轉換為歸一化相機坐標(
pixel2cam
)構造相機投影矩陣
第一個相機位于世界坐標系的原點(通常以左相機為原點):
P1=[I∣0]第二個相機的投影矩陣由外參 旋轉矩陣 R 和 平移向量 T 給出:
P2=[R∣T]使用 OpenCV 的
triangulatePoints
進行三角測量
通過求解一組線性方程,得到齊次坐標 (X,Y,Z,W)
通過 X′=X/W,Y′=Y/W,Z′=Z/W 得到真實的三維坐標
?