转自 https://blog.csdn.net/u014652390/article/details/71169927
本文试图从宏观的视角,解释这些个问题:这个校正是干嘛的,为啥要作这个立体校正呢,以及如何做。本文分享给像我一样“白手起家”的小伙伴们,要进行更深入的研究,可以参考文章后面的干货列表。
如果用一句话来解释立体校正,那么,敲黑板,划重点“立体校正就是,把实际中非共面行对准的两幅图像,校正成共面行对准。”这话读起来有点深奥,配个图,就好理解啦!
(1)未校正以前左右眼视图
(2)校正后的左右眼视图
果然一图胜似千言万语啊。
好了,到这里,第一个问题,你应该清楚了吧,不清楚的话,请举手!
那么接下来第二个问题来了,我把这左右眼图像对齐了,有什么用呢?我是没事找点事情干吗?我确实是闲来没事,但是OpenCV不会。先给张图感受一下~
我们知道,立体匹配是三维重建、立体导航、非接触测距等技术的关键步骤,它通过匹配两幅或者多幅图像来获取深度信息。并且广泛应用于,工业生产自动化、流水线控制、无人驾驶汽车(测距,导航)、安防监控、遥感图像分析、机器人智能控制等方面。”而立体图像校正是降低立体匹配计算复杂性的最有效方法之一。 因为当两个图像平面是完全共面行对准时,立体匹配从二维搜索降至一维搜索,并且可以过滤掉无匹配点。但是,在现实的双目立体视觉系统中,是不存在完全的共面行对准的两个摄像机图像平面的,所以我们要进行立体校正。
最后一个问题,那么作为小白的我,怎么做立体校正呢?
OpenCV的大致流程是这样的:
具体做法,参考调用代码:
-
#include “opencv2/calib3d/calib3d.hpp”
-
#include “opencv2/highgui/highgui.hpp”
-
#include “opencv2/imgproc/imgproc.hpp”
-
-
#include <vector>
-
#include <string>
-
#include <algorithm>
-
#include <iostream>
-
#include <iterator>
-
#include <stdio.h>
-
#include <stdlib.h>
-
#include <ctype.h>
-
-
using
namespace cv;
-
using
namespace
std;
-
-
static void StereoCalib(const vector<string>& 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;
//true;
-
const
int maxScale =
2;
-
const
float squareSize =
1.f;
// Set this to your actual square size
-
// ARRAY AND VECTOR STORAGE:
-
-
vector<
vector<Point2f> > imagePoints[
2];
-
vector<
vector<Point3f> > objectPoints;
-
Size imageSize;
-
-
int i, j, k, nimages = (
int)imagelist.size()/
2;
-
-
imagePoints[
0].resize(nimages);
-
imagePoints[
1].resize(nimages);
-
vector<
string> 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;
-
vector<Point2f>& corners = imagePoints[k][j];
-
for(
int scale =
1; scale <= maxScale; scale++ )
-
{
-
Mat timg;
-
if( scale ==
1 )
-
timg = img;
-
else
-
resize(img, timg, Size(), scale, scale);
-
found = findChessboardCorners(timg, boardSize, corners,
-
CV_CALIB_CB_ADAPTIVE_THRESH | CV_CALIB_CB_NORMALIZE_IMAGE);
-
if( found )
-
{
-
if( scale >
1 )
-
{
-
Mat cornersMat(corners);
-
cornersMat
= 1./scale;
-
}
-
break;
-
}
-
}
-
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;
-
cornerSubPix(img, corners, Size(
11,
11), Size(
-1,
-1),
-
TermCriteria(CV_TERMCRIT_ITER+CV_TERMCRIT_EPS,
-
30,
0.01));
-
}
-
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);
-
-
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(k
squareSize, jsquareSize,
0));
-
}
-
-
cout <<
“Running stereo calibration …\n”;
-
-
Mat cameraMatrix[
2], distCoeffs[
2];
-
cameraMatrix[
0] = Mat::eye(
3,
3, CV_64F);
-
cameraMatrix[
1] = Mat::eye(
3,
3, CV_64F);
-
Mat R, T, E, F;
-
-
double rms = stereoCalibrate(objectPoints, imagePoints[
0], imagePoints[
1],
-
cameraMatrix[
0], distCoeffs[
0],
-
cameraMatrix[
1], distCoeffs[
1],
-
imageSize, R, T, E, F,
-
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;
-
-
// CALIBRATION QUALITY CHECK
-
// because the output fundamental matrix implicitly
-
// includes all the output information,
-
// we can check the quality of calibration using the
-
// epipolar geometry constraint: m2^tFm1=0
-
double err =
0;
-
int npoints =
0;
-
vector<Vec3f> 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]);
-
computeCorrespondEpilines(imgpt[k], k+
1, F, lines[k]);
-
}
-
for( j =
0; j < npt; j++ )
-
{
-
double errij =
fabs(imagePoints[
0][i][j].x
lines[1][j][0] +
-
imagePoints[
0][i][j].ylines[
1][j][
1] + lines[
1][j][
2]) +
-
fabs(imagePoints[
1][i][j].x
lines[0][j][0] +
-
imagePoints[
1][i][j].ylines[
0][j][
1] + lines[
0][j][
2]);
-
err += errij;
-
}
-
npoints += npt;
-
}
-
cout <<
"average reprojection err = " << err/npoints <<
endl;
-
-
// save intrinsic parameters
-
FileStorage fs(“intrinsics.yml”, CV_STORAGE_WRITE);
-
if( fs.isOpened() )
-
{
-
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(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]);
-
-
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 extrinsic parameters\n”;
-
-
// OpenCV can handle left-right
-
// or up-down camera arrangements
-
bool isVerticalStereo =
fabs(P2.at<
double>(
1,
3)) >
fabs(P2.at<
double>(
0,
3));
-
-
// COMPUTE AND DISPLAY RECTIFICATION
-
if( !showRectified )
-
return;
-
-
Mat rmap[
2][
2];
-
// IF BY CALIBRATED (BOUGUET’S METHOD)
-
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<Point2f> 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()
H1cameraMatrix[
0];
-
R2 = cameraMatrix[
1].inv()
H2cameraMatrix[
1];
-
P1 = cameraMatrix[
0];
-
P2 = cameraMatrix[
1];
-
}
-
-
//Precompute maps for cv::remap()
-
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]);
-
-
Mat canvas;
-
double sf;
-
int w, h;
-
if( !isVerticalStereo )
-
{
-
sf =
600./MAX(imageSize.width, imageSize.height);
-
w = cvRound(imageSize.width
sf);
-
h = cvRound(imageSize.heightsf);
-
canvas.create(h, w*
2, CV_8UC3);
-
}
-
else
-
{
-
sf =
300./MAX(imageSize.width, imageSize.height);
-
w = cvRound(imageSize.width
sf);
-
h = cvRound(imageSize.heightsf);
-
canvas.create(h*
2, w, CV_8UC3);
-
}
-
-
for( i =
0; i < nimages; i++ )
-
{
-
for( k =
0; k <
2; k++ )
-
{
-
Mat img = imread(goodImageList[i*
2+k],
0), rimg, cimg;
-
remap(img, rimg, rmap[k][
0], rmap[k][
1], CV_INTER_LINEAR);
-
imshow(
“单目相机校正”,rimg);
-
waitKey();
-
cvtColor(rimg, cimg, COLOR_GRAY2BGR);
-
Mat canvasPart = !isVerticalStereo ? canvas(Rect(w
k, 0, w, h)) : canvas(Rect(0, hk, w, h));
-
resize(cimg, canvasPart, canvasPart.size(),
0,
0, CV_INTER_AREA);
-
if( useCalibrated )
-
{
-
Rect vroi(cvRound(validRoi[k].xsf), cvRound(validRoi[k].ysf),
-
cvRound
(validRoi[k].widthsf),
cvRound
(validRoi[k].heightsf));
-
rectangle(canvasPart, vroi, Scalar(
0,
0,
255),
3,
8);
-
}
-
}
-
-
if( !isVerticalStereo )
-
for( j =
0; j < canvas.rows; j +=
16 )
-
line(canvas, Point(
0, j), Point(canvas.cols, j), Scalar(
0,
255,
0),
1,
8);
-
else
-
for( j =
0; j < canvas.cols; j +=
16 )
-
line(canvas, Point(j,
0), Point(j, canvas.rows), Scalar(
0,
255,
0),
1,
8);
-
imshow(
“双目相机校正对齐”, canvas);
-
waitKey();
-
char c = (
char)waitKey();
-
if( c ==
27 || c ==
‘q’ || c ==
‘Q’ )
-
break;
-
}
-
}
-
-
-
static bool readStringList( const string& filename, vector<string>& l )
-
{
-
l.resize(
0);
-
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 )
-
l.push_back((
string)
it);
-
return
true;
-
}
-
-
int main(int argc, char* argv)
-
{
-
Size boardSize;
-
string imagelistfn;
-
bool showRectified =
true;
-
-
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
-1;
-
}
-
}
-
else
if(
string(argv[i]) ==
“-h” )
-
{
-
if(
sscanf(argv[++i],
“%d”, &boardSize.height) !=
1 || boardSize.height <=
0 )
-
{
-
cout <<
“invalid board height” <<
endl;
-
return
-1;
-
}
-
}
-
else
if(
string(argv[i]) ==
“-nr” )
-
showRectified =
false;
-
else
if(
string(argv[i]) ==
“–help” )
-
return
-1;
-
else
if( argv[i][
0] ==
‘-’ )
-
{
-
cout <<
"invalid option " << argv[i] <<
endl;
-
return
0;
-
}
-
else
-
imagelistfn = argv[i];
-
}
-
-
if( imagelistfn ==
“” )
-
{
-
imagelistfn =
“stereo_calib.xml”;
-
boardSize = Size(
9,
6);
-
}
-
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<
string> imagelist;
-
bool ok = readStringList(imagelistfn, imagelist);
-
if(!ok || imagelist.empty())
-
{
-
cout <<
“can not open " << imagelistfn <<
" or the string list is empty” <<
endl;
-
return
-1;
-
}
-
-
StereoCalib(imagelist, boardSize,
true, showRectified);
-
return
0;
-
}