///张正友标定法opencv实现///
#include "opencv2/highgui/highgui.hpp"
#include "opencv2/imgproc/imgproc.hpp"
#include
#include
#include
#include
#include
#include
#include
#include
#include
using namespace cv;
using namespace std;
static int print_help()
{
cout <<
" Given a list of chessboard images, the number of corners (nx, ny)\n"
" on the chessboards, and a flag: useCalibrated for \n"
" calibrated (0) or\n"
" uncalibrated \n"
" (1: use cvStereoCalibrate(), 2: compute fundamental\n"
" matrix separately) stereo. \n"
" Calibrate the cameras and display the\n"
" rectified results along with the computed disparity images. \n" << endl;
cout << "Usage:\n ./stereo_calib -w board_width -h board_height [-nr /*dot not view results*/] \n" << endl;
return 0;
}
static bool readStringList(const string& filename, vector& list)
{
list.resize(0);
//以FileStorage::READ 方式打开一个已经存在的文件stereo_calib.xml是官方存在的文件,保存图片名称left01.jpg....
FileStorage fs(filename, FileStorage::READ);
if (!fs.isOpened())
return false;
FileNode n = fs.getFirstTopLevelNode();
if (n.type() != FileNode::SEQ)
return false;
FileNodeIterator it = n.begin(), it_end = n.end();
for (; it != it_end; ++it)
list.push_back((string)*it);
return true;
}
//标定函数
static void StereoCalib(const vector& imagelist, Size boardSize,
bool useCalibrated = true, bool showRectified = true)
{
if (imagelist.size() % 2 != 0)//图片数要成双
{
cout << "Error: the image list contains odd (non-even) number of elements\n";
return;
}
bool displayCorners = true;
const int maxScale = 2;
// 设置真实方格大小,以毫米或者像素为单位的keypoint之间间隔距离, 棋盘间隔为1
const float squareSize = 20.f;
// 数组储存
vector > imagePoints[2];
vector > objectPoints;
Size imageSize;
//左右相分割,单个相机图片数,vector.size()是容器的数据个数
int i, j, k, nimages = (int)imagelist.size() / 2;
imagePoints[0].resize(nimages);//15
imagePoints[1].resize(nimages);//15
vector goodImageList;
for (i = j = 0; i < nimages; i++)
{
for (k = 0; k < 2; k++)
{
//奇数读取右图片数组名,偶数左图像名
const string& filename = imagelist[i * 2 + k];
Mat img = imread(filename, 0);//读取图像
if (img.empty())
break;
//第一次输入时都是空,那么把第一张图的大小赋值
if (imageSize == Size())
imageSize = img.size();
else if (img.size() != imageSize)
{
cout << "The image " << filename << " has the size different from the first image size. Skipping the pair\n";
break;
}
bool found = false;
//k=0左图所有角点,k=1右图所有角点,通过findChessboardCorners对向量传参,
//每一组左右图像用一个向量组(坐标是左右的平均处理)
//寻找角点,保存到imagePoints
vector& corners = imagePoints[k][j];
for (int scale = 1; scale <= maxScale; scale++)
{
Mat timg;
if (scale == 1)
{
timg = img;
//cv::bitwise_not(timg, timg);//反相处理
}
else
resize(img, timg, Size(), scale, scale);
//scale来防止检测不到,调整图幅的在下面的if( scale > 1 )会用到,如果一次就检测到就不会进入
/*SimpleBlobDetector::Params parameters;
parameters.maxArea = 10000000;
Ptr blobDetector = new SimpleBlobDetector(parameters);*/
//boardSize棋盘格内部角点的行、列数 ;timg输入的图像;corners输出的棋盘格角点
found = findCirclesGrid(timg, boardSize, corners,
CALIB_CB_ASYMMETRIC_GRID);
/*found = findChessboardCorners(timg, boardSize, corners,
CV_CALIB_CB_ADAPTIVE_THRESH | CV_CALIB_CB_NORMALIZE_IMAGE);*/
//若找到角点,found为真
if (found)
{
if (scale > 1)
{
//)找到的vector角点转化为Mat矩阵,方便计算
Mat cornersMat(corners);
cornersMat *= 1. / scale;
}
break;
}
}
//displayCorners=false,永远不会进入
if (displayCorners)
{
cout << filename << endl;
Mat cimg, cimg1;
cvtColor(img, cimg, COLOR_GRAY2BGR);
drawChessboardCorners(cimg, boardSize, corners, found);
double sf = 640. / MAX(img.rows, img.cols);
resize(cimg, cimg1, Size(), sf, sf);
imshow("corners", cimg1);
char c = (char)waitKey(500);
if (c == 27 || c == 'q' || c == 'Q') //Allow ESC to quit
exit(-1);
}
else
putchar('.');
if (!found)
break;
//插值亚像素角点,用来精确得到的corners坐标参数
/*cornerSubPix(img, corners, Size(11,11), Size(-1,-1),
TermCriteria(CV_TERMCRIT_ITER+CV_TERMCRIT_EPS,
30, 0.01));*/
}
//上个for( k = 0; k < 2; k++ )循环结束!那么k==2了!每对左右照片都会执行一次这个
//对图像在做拷贝imagelist时传入的vector的string数组,包含的是图片的路径
if (k == 2)
{
goodImageList.push_back(imagelist[i * 2]);
goodImageList.push_back(imagelist[i * 2 + 1]);
j++;
}
}
cout << j << " pairs have been successfully detected.\n";
nimages = j;
if (nimages < 2)
{
cout << "Error: too little pairs to run the calibration\n";
return;
}
imagePoints[0].resize(nimages);
imagePoints[1].resize(nimages);
objectPoints.resize(nimages);//定义Point3f的向量大小是nimages
//通过角点长宽个数以及squareSize每个角点的步长算出角点位置
for (i = 0; i < nimages; i++)
{
/* for( j = 0; j < boardSize.height; j++ )
for( k = 0; k < boardSize.width; k++ )
objectPoints[i].push_back(Point3f(j*squareSize, k*squareSize, 0));*/
for (int j = 0; j < boardSize.height; j++)
for (int k = 0; k< boardSize.width; k++)
objectPoints[i].push_back(Point3f(float((2 * k + j % 2)*squareSize), float(j*squareSize), 0));
}
cout << "Running stereo calibration ...\n";
Mat cameraMatrix[2], distCoeffs[2];
cameraMatrix[0] = Mat::eye(3, 3, CV_64F);//定义左相机3*3内参数矩阵
cameraMatrix[1] = Mat::eye(3, 3, CV_64F);//定义右相机3*3内参数矩阵
Mat R, T, E, F;
vector rvecs1, tvecs1, rvecs2, tvecs2;
calibrateCamera(objectPoints, imagePoints[0], imageSize, cameraMatrix[0],
distCoeffs[0], rvecs1, tvecs1, CV_CALIB_FIX_K4 | CV_CALIB_FIX_K5);
cout << "左相机内参" << cameraMatrix[0] << endl;
calibrateCamera(objectPoints, imagePoints[1], imageSize, cameraMatrix[1],
distCoeffs[1], rvecs2, tvecs2, CV_CALIB_FIX_K4 | CV_CALIB_FIX_K5);
cout << "右相机内参" << cameraMatrix[1] << endl;
//
//
double rms = stereoCalibrate(objectPoints, //校正的图像点向量组
imagePoints[0], imagePoints[1], //左右相机观测到的图像上面的向量组
cameraMatrix[0], distCoeffs[0], //输入或者输出相机的内参数矩阵
cameraMatrix[1], distCoeffs[1], //输入/输出相机的畸变系数向量
imageSize, R, T, E, F, 256, //输出俩相机坐标系之间的旋转矩阵、平移向量、本征矩阵、基础矩阵,
TermCriteria(CV_TERMCRIT_ITER + CV_TERMCRIT_EPS, 100, 1e-5)/*,
CV_CALIB_FIX_ASPECT_RATIO+
CV_CALIB_ZERO_TANGENT_DIST+
CV_CALIB_SAME_FOCAL_LENGTH+
CV_CALIB_RATIONAL_MODEL+
CV_CALIB_FIX_K3+
CV_CALIB_FIX_K4+
CV_CALIB_FIX_K5*/);
cout << "done with RMS error=" << rms << endl;
//cout<<"R"< lines[2];
for (i = 0; i < nimages; i++)
{
int npt = (int)imagePoints[0][i].size();//左某图所有角点数量
Mat imgpt[2];
for (k = 0; k < 2; k++)
{
imgpt[k] = Mat(imagePoints[k][i]); //某图的角点向量矩阵,校正前坐标
undistortPoints(imgpt[k], imgpt[k], cameraMatrix[k], distCoeffs[k], Mat(), cameraMatrix[k]);//cameraMatrix 畸变参数四个变形系数组成的向量
computeCorrespondEpilines(imgpt[k], k + 1, F, lines[k]); //计算对应点的外极线epilines是一个三元组(a,b,c),表示点在另一视图中对应的外极线ax+by+c=0;
//为一幅图像中的点计算其在另一幅图像中对应的对极线。
}
for (j = 0; j < npt; j++)
{
//fbs绝对值函数
double errij = fabs(imagePoints[0][i][j].x*lines[1][j][0] +
imagePoints[0][i][j].y*lines[1][j][1] + lines[1][j][2]) +
fabs(imagePoints[1][i][j].x*lines[0][j][0] +
imagePoints[1][i][j].y*lines[0][j][1] + lines[0][j][2]);
err += errij;
}
npoints += npt;
}
//检查图像上点与另一幅图像的极线的距离的远近来评价标定的精度。
//使用undistortPoints对原始点做去畸变处理。
//使用computeCorrespondEpilines来计算极线。
//然后,计算这些点和线的点积(理想情况,这些点积都为0)。
//累计的绝对距离形成了误差。
cout << "average reprojection err = " << err / npoints << endl;
// save intrinsic parameters
FileStorage fs("intrinsics.yml", CV_STORAGE_WRITE);
if (fs.isOpened())
{
//相机的内参矩阵M1,M2,畸变矩阵D1,D2
fs << "M1" << cameraMatrix[0] << "D1" << distCoeffs[0] <<
"M2" << cameraMatrix[1] << "D2" << distCoeffs[1];
fs.release();
}
else
cout << "Error: can not save the intrinsic parameters\n";
//计算外参数
Mat R1, R2, P1, P2, Q;
Rect validRoi[2];
//stereoRectify根据内参和畸变系数计算右相机相对左相机的旋转R和平移矩阵T
stereoRectify(cameraMatrix[0], distCoeffs[0],
cameraMatrix[1], distCoeffs[1],
imageSize, R, T, R1, R2, P1, P2, Q,
CALIB_ZERO_DISPARITY, 1, imageSize, &validRoi[0], &validRoi[1]);
//Q.at(3, 2) = -Q.at(3, 2);//Z取反
cout << Q << endl;
fs.open("Q.yml", CV_STORAGE_WRITE);
if (fs.isOpened())
{
fs << "Q" << Q;
fs.release();
}
else
cout << "Error: can not save the Q parameters\n";
fs.open("extrinsics.yml", CV_STORAGE_WRITE);
if (fs.isOpened())
{
//旋转矩阵 平移矩阵 左旋转矫正参数 右旋转矫正参数 左平移矫正参数 右平移矫正参数 深度矫正参数
fs << "R" << R << "T" << T << "R1" << R1 << "R2" << R2 << "P1" << P1 << "P2" << P2 << "Q" << Q;
fs.release();
}
else
cout << "Error: can not save the intrinsic parameters\n";
// OpenCV 能处理左右或者上下相机的布置
bool isVerticalStereo = fabs(P2.at(1, 3)) > fabs(P2.at(0, 3));
// 计算和显示校准
if (!showRectified)
return;
Mat rmap[2][2]; //查找映射表
// 如果校准(用的是BOUGUET极线方法)
if (useCalibrated)
{
// we already computed everything
}
// OR ELSE HARTLEY'S METHOD
else
// use intrinsic parameters of each camera, but
// compute the rectification transformation directly
// from the fundamental matrix
{
vector allimgpt[2];
for (k = 0; k < 2; k++)
{
for (i = 0; i < nimages; i++)
std::copy(imagePoints[k][i].begin(), imagePoints[k][i].end(), back_inserter(allimgpt[k]));
}
//计算基础矩阵
F = findFundamentalMat(Mat(allimgpt[0]), Mat(allimgpt[1]), FM_8POINT, 0, 0);//第一/二个相机所得到的点
Mat H1, H2;//计算第一/二幅图像矫正后单应性矩阵
stereoRectifyUncalibrated(Mat(allimgpt[0]), Mat(allimgpt[1]), F, imageSize, H1, H2, 3);//输入基础矩阵
R1 = cameraMatrix[0].inv()*H1*cameraMatrix[0];
R2 = cameraMatrix[1].inv()*H2*cameraMatrix[1];
P1 = cameraMatrix[0];
P2 = cameraMatrix[1];
}
//Precompute maps for cv::remap()
//八个参数:输入的摄像机内参数矩阵、输入的摄像机畸变系数矩阵、输入的第一和第二相机坐标系之间的旋转矩阵、
//输入的校正后的3X3摄像机矩阵、摄像机采集的无失真图像尺寸、map1的数据类型,可以是CV_32FC1或CV_16SC2、
//输出的X坐标重映射参数、输出的Y坐标重映射参数
initUndistortRectifyMap(cameraMatrix[0], distCoeffs[0], R1, P1, imageSize, CV_16SC2, rmap[0][0], rmap[0][1]);
initUndistortRectifyMap(cameraMatrix[1], distCoeffs[1], R2, P2, imageSize, CV_16SC2, rmap[1][0], rmap[1][1]);
fs.open("map.yml", CV_STORAGE_WRITE);
if (fs.isOpened())
{
fs << "leftmapx" << rmap[0][0] << "leftmapy" << rmap[0][1] << "rightmapx" << rmap[1][0] << "rightmapy" << rmap[1][1];
fs.release();
}
else
cout << "Error: can not save the map parameters\n";
/*fs.open("leftmapy.yml", CV_STORAGE_WRITE);
if( fs.isOpened() )
{
fs << "leftmapy" <preFilterSize=41;
BMState->preFilterCap=31;
BMState->SADWindowSize=41;
BMState->minDisparity=-64;
BMState->numberOfDisparities=128;
BMState->textureThreshold=10;
BMState->uniquenessRatio=15;
cvFindStereoCorrespondenceBM(leftimg, rightimg, disp,
BMState);
cvNormalize( disp, vdisp, 0, 256, CV_MINMAX );
cvNamedWindow( "disparity" );
cvShowImage( "disparity", disp );*/
char c = (char)waitKey();
if (c == 27 || c == 'q' || c == 'Q')
break;
}
}
int main(int argc, char** argv)
{
Size boardSize; //标定板尺寸
string imagelistfn;
bool showRectified = true;
argc = 6; //命令行总的参数个数
argv[0] = "stereocalib";//项目名称
argv[1] = "-w";//图片宽度方向上的交点个数 -11
argv[2] = "11";
argv[3] = "-h";//图片高度方向上的交点个数 -4
argv[4] = "4";
argv[5] = "stereo_calib.xml";
for (int i = 1; i < argc; i++) //标定板在根目录读取
{
if (string(argv[i]) == "-w")
{
if (sscanf(argv[++i], "%d", &boardSize.width) != 1 || boardSize.width <= 0)
{
cout << "invalid board width" << endl;
return print_help();
}
}
else if (string(argv[i]) == "-h")
{
if (sscanf(argv[++i], "%d", &boardSize.height) != 1 || boardSize.height <= 0)
{
cout << "invalid board height" << endl;
return print_help();
}
}
else if (string(argv[i]) == "-nr")
showRectified = false;
else if (string(argv[i]) == "--help")
return print_help();
else if (argv[i][0] == '-')
{
cout << "invalid option " << argv[i] << endl;
return 0;
}
else
imagelistfn = argv[i]; //读取stereo_calib.xml
}
if (imagelistfn == "")
{
imagelistfn = "stereo_calib1.xml";
//imagelistfn = "stereo_calib_tiff.xml";
boardSize = Size(4, 11);
}
else if (boardSize.width <= 0 || boardSize.height <= 0)
{
cout << "if you specified XML file with chessboards, you should also specify the board width and height (-w and -h options)" << endl;
return 0;
}
vector imagelist;
//此函数在上面定义,第一个参数是"stereo_calib.xml"数组,第二个参数是向量
bool ok = readStringList(imagelistfn, imagelist);
if (!ok || imagelist.empty())
{
cout << "can not open " << imagelistfn << " or the string list is empty" << endl;
return print_help();
}
StereoCalib(imagelist, boardSize, true, showRectified);
return 0;
}