//双目标定无非就是重复两次单目标定的流程,单目标定参考我上一篇博客。
//在学习双目视觉之前,建议大家补充下,双目视觉模型,对极几何的知识,今天只讲源码的流程,以后出一篇对极几何的讲解。
//老规矩先来一段源码
#include "opencv2/core.hpp"
#include
#include
#include "opencv2/calib3d.hpp"
#include "opencv2/imgcodecs.hpp"
#include "opencv2/videoio.hpp"
#include "opencv2/highgui.hpp"
#include
#include
#include
#include
#include
using namespace std;
using namespace cv;
static double 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;
int i, totalPoints = 0;
double totalErr = 0, err;
for (i = 0; i < (int)objectPoints.size(); i++)
{
projectPoints(Mat(objectPoints[i]), rvecs[i], tvecs[i],
cameraMatrix, distCoeffs, imagePoints2);
err = norm(Mat(imagePoints[i]), Mat(imagePoints2), NORM_L2);
int n = (int)objectPoints[i].size();
totalErr += err*err;
totalPoints += n;
}
return std::sqrt(totalErr / totalPoints);
}
int main(int argc, char** argv)
{
vector<string> files_left;
vector<string> files_right;
glob("E:\\mul_cam_images\\left1", files_left);
glob("E:\\mul_cam_images\\right1", files_right);
// 定义变量
vector<vector<Point2f>> image_leftPoints,image_rightPoints;//像点
vector<vector<Point3f>> objectPoints;//物点
TermCriteria criteria = TermCriteria(TermCriteria::EPS + TermCriteria::MAX_ITER, 30, 0.001);//进行亚像素精度的调整,获得亚像素级别的角点坐标
int numCornersHor = 8;//heigh
int numCornersVer = 11;//width
int numSquares = 15;//单位mm
Mat gray_l, gray_r;
Mat image_l, image_r;
vector<Point3f> obj;
for (int i = 0; i < numCornersHor; i++)
for (int j = 0; j < numCornersVer; j++)
obj.push_back(Point3f((float)j * numSquares, (float)i * numSquares, 0));
//存放每张图的角点坐标,并存入obj中(物点)
Size s1,s2;
//像点
for (int i = 0; i < 16; i++) {
printf("image file : %s \n", files_left[i].c_str());
image_l = imread(files_left[i]);
printf("image file : %s \n", files_right[i].c_str());
image_r = imread(files_right[i]);
s1 = image_l.size();
s2 = image_r.size();
cvtColor(image_l, gray_l, COLOR_BGR2GRAY);//转灰度
cvtColor(image_r, gray_r, COLOR_BGR2GRAY);//转灰度
vector<Point2f> corners_r;
vector<Point2f> corners_l;
bool ret1 = findChessboardCorners(gray_r, Size(11, 8), corners_r, CALIB_CB_ADAPTIVE_THRESH | CALIB_CB_FILTER_QUADS);//该函数的功能就是判断图像内是否包含完整的棋盘图,若能检测完全,就把他们的角点坐标(从上到下,从左到右)记录,并返回true,否则为false,CALIB_CB_FILTER_QUADS用于去除检测到的错误方块。
bool ret2 = findChessboardCorners(gray_l, Size(11, 8), corners_l, CALIB_CB_ADAPTIVE_THRESH | CALIB_CB_FILTER_QUADS);
if (ret1)
{
cornerSubPix(gray_l, corners_l, Size(11, 11), Size(-1, -1), criteria);//用于发现亚像素精度的角点位置
drawChessboardCorners(image_l, Size(11, 8), corners_l, ret1);//将每一个角点出做标记,此为物点对应的像点坐标
//imshow("calibration-demo1", image_l);
waitKey(500);
}
if (ret2)
{
cornerSubPix(gray_r, corners_r, Size(11, 11), Size(-1, -1), criteria);//用于发现亚像素精度的角点位置
drawChessboardCorners(image_r, Size(11, 8), corners_r, ret2);//将每一个角点出做标记,此为物点对应的像点坐标
//将角点坐标存入imagePoints中,此为像点坐标
//imshow("calibration-demo", image_r);
waitKey(500);
}
if (ret1&&ret2)
{
image_rightPoints.push_back(corners_r);
image_leftPoints.push_back(corners_l);//将角点坐标存入imagePoints中,此为像点坐标
objectPoints.push_back(obj);
}
}
//计算内参与畸变系数
Mat intrinsic_left = Mat(3, 3, CV_32FC1);
Mat distCoeffs_left;//畸变矩阵
vector<Mat> rvecs_l;//旋转向量R
vector<Mat> tvecs_l;//平移向量T
//内参矩阵
intrinsic_left.ptr<float>(0)[0] = 1;
intrinsic_left.ptr<float>(1)[1] = 1;
calibrateCamera(objectPoints, image_leftPoints, s1, intrinsic_left, distCoeffs_left, rvecs_l, tvecs_l);
//计算内参与畸变系数
Mat intrinsic_right = Mat(3, 3, CV_32FC1);
Mat distCoeffs_right;//畸变矩阵
vector<Mat> rvecs_r;//旋转向量R
vector<Mat> tvecs_r;//平移向量T
Mat R_total;//旋转向量R
Vec3d T_total;//平移向量T
Mat E ;//本质矩阵
Mat F ;//基本矩阵
intrinsic_right.ptr<float>(0)[0] = 1;
intrinsic_right.ptr<float>(1)[1] = 1;
calibrateCamera(objectPoints, image_rightPoints, s2, intrinsic_right, distCoeffs_right, rvecs_r, tvecs_r);
FileStorage fs("C:\\Users\\Administrator\\Desktop\\claibration——list\\双目参数\\out_calibration.yml", FileStorage::WRITE);//存储标定结果,这里可以选择自己的存放路径
fs << "intrinsic_left" << intrinsic_left;//存放内参矩阵
fs << "distCoeffs_left" << distCoeffs_left;//存放畸变矩阵
fs << "board_width" << 11;//存放标定板长度信息
fs << "board_height" << 8;//存放标定板宽度信息
fs << "square_size" << 0.015;//存放标定板格子尺寸信息
fs << "R_left" << rvecs_l;//R
fs << "T_left" << tvecs_l;//T
fs << "intrinsic_right" << intrinsic_right;//存放内参矩阵
fs << "distCoeffs_right" << distCoeffs_right;//存放畸变矩阵
fs << "R_right" << rvecs_r;//R
fs << "T_right" << tvecs_r;//T
printf("Done Calibration\n");
Mat R_L;//由R_total拆分的左相机旋转量
Mat R_R;//由R_total拆分的右相机旋转量
Mat P1;
Mat P2;
Mat Q;
Rect validROIL, validROIR;
printf("Starting Rectification\n");
stereoRectify(intrinsic_left, distCoeffs_left, intrinsic_right, distCoeffs_right, s1, R_total, T_total, R_L, R_R, P1, P2, Q, CALIB_ZERO_DISPARITY, -1, s1, &validROIL, &validROIR);
fs << "R_left" << R_L;//存放内参矩阵
fs << "R_right" << R_R;//存放畸变矩阵
fs << "P1" << P1;//存放标定板长度信息
fs << "P2" << P2;//存放标定板宽度信息
fs << "Q" << Q;//存放标定板格子尺寸信息
cout << "left_Calibration error: " << computeReprojectionErrors(objectPoints, image_leftPoints, rvecs_l, tvecs_l, intrinsic_left, distCoeffs_left) << endl;//输出投影的误差
cout << "right_Calibration error: " << computeReprojectionErrors(objectPoints, image_rightPoints, rvecs_r, tvecs_r, intrinsic_right, distCoeffs_right) << endl;//输出投影的误差
printf("Done Rectification\n");
//根据stereoRectify计算出来的R和P来计算图像的映射表 mapx,mapy
//mapx,mapy这两个映射表接下来可以给remap()函数调用,校正图像(使图像共勉并且行对准)
//ininUndistortRectifyMap()的参数newCameraMatrix就是校正后的相机矩阵,
Mat mapLx, mapLy, mapRx, mapRy;
initUndistortRectifyMap(intrinsic_left, distCoeffs_left, R_L, P1, s1, CV_16SC2, mapLx, mapLy);
initUndistortRectifyMap(intrinsic_right, distCoeffs_right, R_R, P2, s1, CV_16SC2, mapRx, mapRy);
////https://docs.opencv.org/4.2.0/d9/d0c/group__calib3d.html#ga7dfb72c9cf9780a347fbe3d1c47e5d5a
printf("get map\n");
Mat rectifyImageL2, rectifyImageR2;
remap(image_l, rectifyImageL2, mapLx, mapLy, INTER_LINEAR);
remap(image_r, rectifyImageR2, mapRx, mapRy, INTER_LINEAR);
printf("rectify done\n");
//cout << "按Q2退出 ..." << endl;
imshow("rectifyImageL", rectifyImageL2);
imshow("rectifyImageR", rectifyImageR2);
waitKey(10000);
Mat canvas;
double sf;
int w, h;
sf = 600./MAX(s1.width, s1.height);
w = cvRound(s1.width * sf);
h = cvRound(s1.height * sf);
canvas.create(h, w * 2, CV_8UC3);
/*左图像画到画布上*/
Mat canvasPart = canvas(Rect(w * 0, 0, w, h));
resize(rectifyImageL2, canvasPart, canvasPart.size(), 0, 0, INTER_AREA);//把图像缩放到跟canvasPart一样大小
Rect vroiL(cvRound(validROIL.x*sf), cvRound(validROIL.y*sf),//获得被截取的区域
cvRound(validROIL.width*sf), cvRound(validROIL.height*sf));
rectangle(canvasPart, vroiL, Scalar(0, 0, 255), 3, 8); //画上一个矩形
/*右图像画到画布上*/
canvasPart = canvas(Rect(w, 0, w, h)); //获得画布的另一部分
resize(rectifyImageR2, canvasPart, canvasPart.size(), 0, 0, INTER_LINEAR);
Rect vroiR(cvRound(validROIR.x * sf), cvRound(validROIR.y*sf),
cvRound(validROIR.width * sf), cvRound(validROIR.height * sf));
rectangle(canvasPart, vroiR, Scalar(0, 255, 0), 3, 8);
cout << "Painted ImageR" << endl;
/*画上对应的线条*/
for (int i = 0; i < canvas.rows; i += 16)
line(canvas, Point(0, i), Point(canvas.cols, i), Scalar(0, 255, 0), 1, 8);
imshow("rectified", canvas);
cout << "wait key" << endl;
waitKey(0);
return 0;
}
//下面对源码实现流程分段讲解
//先定义一些变量,物点像点,图像路径,图像信息…
//将物点保存
for (int i = 0; i < numCornersHor; i++)
for (int j = 0; j < numCornersVer; j++)
obj.push_back(Point3f((float)j * numSquares, (float)i * numSquares, 0));
//我在遍历像点与物点的时候,出现了如下错误:
OpenCV(4.2.0) Error: Unspecified error (> Number of object and image points must be equal (expected: ‘numberOfObjectPoints == numberOfImagePoints’)
原因是:物点,与提取的数量角点不匹配,之前我将提取物点的for循环放到提取像点的for循环中,导致不匹配,
解决方法:物点与像点提取流程分开编写
//保存像点
for (int i = 0; i < 16; i++) {
printf("image file : %s \n", files_left[i].c_str());
image_l = imread(files_left[i]);
printf("image file : %s \n", files_right[i].c_str());
image_r = imread(files_right[i]);
s1 = image_l.size();
s2 = image_r.size();
cvtColor(image_l, gray_l, COLOR_BGR2GRAY);//转灰度
cvtColor(image_r, gray_r, COLOR_BGR2GRAY);//转灰度
vector<Point2f> corners_r;
vector<Point2f> corners_l;
bool ret1 = findChessboardCorners(gray_r, Size(11, 8), corners_r, CALIB_CB_ADAPTIVE_THRESH | CALIB_CB_FILTER_QUADS);//该函数的功能就是判断图像内是否包含完整的棋盘图,若能检测完全,就把他们的角点坐标(从上到下,从左到右)记录,并返回true,否则为false,CALIB_CB_FILTER_QUADS用于去除检测到的错误方块。
bool ret2 = findChessboardCorners(gray_l, Size(11, 8), corners_l, CALIB_CB_ADAPTIVE_THRESH | CALIB_CB_FILTER_QUADS);
if (ret1)
{
cornerSubPix(gray_l, corners_l, Size(11, 11), Size(-1, -1), criteria);//用于发现亚像素精度的角点位置
drawChessboardCorners(image_l, Size(11, 8), corners_l, ret1);//将每一个角点出做标记,此为物点对应的像点坐标
//imshow("calibration-demo1", image_l);
waitKey(500);
}
if (ret2)
{
cornerSubPix(gray_r, corners_r, Size(11, 11), Size(-1, -1), criteria);//用于发现亚像素精度的角点位置
drawChessboardCorners(image_r, Size(11, 8), corners_r, ret2);//将每一个角点出做标记,此为物点对应的像点坐标
//将角点坐标存入imagePoints中,此为像点坐标
//imshow("calibration-demo", image_r);
waitKey(500);
}
if (ret1&&ret2)
{
image_rightPoints.push_back(corners_r);
image_leftPoints.push_back(corners_l);//将角点坐标存入imagePoints中,此为像点坐标
objectPoints.push_back(obj);
}
}
注意· if (ret1&&ret2)
{
image_rightPoints.push_back(corners_r);
image_leftPoints.push_back(corners_l);//将角点坐标存入imagePoints中,此为像点坐标
objectPoints.push_back(obj);
}
//此处易犯错误,每写入一张图像的像点,要同时写入其物点与其对应。
///////////////////////////////////////////////////////////////////////////////////////////
//标定准备工作完成,现在开始标定
calibrateCamera(objectPoints, image_rightPoints, s2, intrinsic_right, distCoeffs_right, rvecs_r, tvecs_r);
calibrateCamera(objectPoints, image_leftPoints, s1, intrinsic_left, distCoeffs_left, rvecs_l, tvecs_l);
//标定过程结束
//立体校正的时候需要两幅图像共面且行对准,
是两幅图像共面,每个相机都需要一个旋转矩阵(Rl,Rr),
Pl,Pr为两个相机的投影矩阵,作用是将3D点的坐标转换为2D点的坐标。
//Q为重投影矩阵,矩阵Q把2D平面的点投影到3D空间
//此函数包含两步,畸变校正和极线校正。
```cpp
stereoRectify(intrinsic_left, distCoeffs_left, intrinsic_right, distCoeffs_right, s1, R_total, T_total, R_L, R_R, P1, P2, Q, CALIB_ZERO_DISPARITY, -1, s1, &validROIL, &validROIR);
//P1,P2的前三列与内参矩阵类似
//函数输出的R,P来计算图像的映射表 mapx,mapy,给remap()调用,是两幅图像共面且行对准。
//ininUndistorRectifyMap() 的参数newCameraMatrix就是校正后的相机矩阵(内参)。投影矩阵P中包含新的相机矩阵(内参).
initUndistortRectifyMap(intrinsic_left, distCoeffs_left, R_L, P1, s1, CV_16SC2, mapLx, mapLy);
initUndistortRectifyMap(intrinsic_right, distCoeffs_right, R_R, P2, s1, CV_16SC2, mapRx, mapRy);
//映射表map输入remap()完成双目校正
printf("get map\n");
Mat rectifyImageL2, rectifyImageR2;
remap(image_l, rectifyImageL2, mapLx, mapLy, INTER_LINEAR);
remap(image_r, rectifyImageR2, mapRx, mapRy, INTER_LINEAR);
printf("rectify done\n");
//cout << "按Q2退出 ..." << endl;
imshow("rectifyImageL", rectifyImageL2);
imshow("rectifyImageR", rectifyImageR2);
waitKey(10000);