0. 简要

立体相机标定是立体视觉深度测量的重要步骤,相机标定的精度很大程度上决定了深度的精度,因此掌握立体相机的标定算法和过程至关重要。由于相机标定原理可以在网上找到很多相关资料,因此本文不展开讲原理部分,感兴趣的同学可以移步到https://blog.csdn.net/rs_lys/article/details/118661215理解原理,本文主要讲如何利用Opencv进行立体相机标定,首先对用到的函数展开讲解,最后附上标定的代码。

1. 函数

stereoCalibrate:标定立体相机

#include <opencv2/calib3d.hpp>double cv::stereoCalibrate   (   InputArrayOfArrays  objectPoints,
InputArrayOfArrays  imagePoints1,
InputArrayOfArrays  imagePoints2,
InputOutputArray    cameraMatrix1,
InputOutputArray    distCoeffs1,
InputOutputArray    cameraMatrix2,
InputOutputArray    distCoeffs2,
Size    imageSize,
InputOutputArray    R,
InputOutputArray    T,
OutputArray     E,
OutputArray     F,
OutputArray     perViewErrors,
int     flags = CALIB_FIX_INTRINSIC,
TermCriteria    criteria = TermCriteria(TermCriteria::COUNT+TermCriteria::EPS, 30, 1e-6)
)

参数:

  • objectPoints:Vector of vectors of the calibration pattern points.
  • imagePoints1:Vector of vectors of the projections of the calibration pattern points, observed by the first camera.
  • imagePoints2:Vector of vectors of the projections of the calibration pattern points, observed by the second camera.
  • cameraMatrix1:Input/output first camera matrix,Opencv没有考虑倾斜因子
  • distCoeffs1:Input/output vector of distortion coefficients (k1,k2,p1,p2[,k3[,k4,k5,k6[,s1,s2,s3,s4[,τx,τy]]]]) of 4, 5, 8, 12 or 14 elements. 输出向量的长度取决于flags
  • cameraMatrix2:Input/output second camera matrix.
  • distCoeffs2:Input/output lens distortion coefficients for the second camera.
  • imageSize:图像尺寸,(width, height)
  • R:Output rotation matrix between the 1st and the 2nd camera coordinate systems.
  • T:Output translation vector between the coordinate systems of the cameras.
  • E:Output essential matrix.
  • F:Output fundamental matrix.
  • perViewErrors:Output vector of the RMS re-projection error estimated for each pattern view.
  • flags:Different flags that may be zero or a combination of the following values:
    • CALIB_FIX_INTRINSIC:固定内参和畸变,只优化R、T、E、F
    • CALIB_USE_INTRINSIC_GUESS:根据指定的flags优化部分或全部内参,并提供初始值
    • CALIB_USE_EXTRINSIC_GUESS:根据提供的外参初始值进一步优化外参,否则R, T被初始化为两个视图的中值
    • CALIB_FIX_PRINCIPAL_POINT:固定主点
    • CALIB_FIX_FOCAL_LENGTH:固定焦距
    • CALIB_FIX_ASPECT_RATIO:固定fx / fy,优化fy
    • CALIB_SAME_FOCAL_LENGTH:施加约束,使得fx_0=fx_1, fy_0=fy_1
    • CALIB_ZERO_TANGENT_DIST:将切向畸变固定为零
    • CALIB_FIX_K1,…,CALIB_FIX_K6:固定畸变系数k1,…k6。如果设置CALIB_USE_INTRINSIC_GUESS,则畸变系数固定为传入的distCoeffs向量,否则固定为零
    • 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不发生变化。如果设置了CALIB_USE_INTRINSIC_GUESS,则使用提供的distCoeffs矩阵中的系数。否则,它被设置为0。
    • CALIB_TILTED_MODEL:启用系数tauX和tauY。如果设置这个标志,标定函数使用倾斜传感器模型并返回14个系数。如果没有设置标志,函数只计算并返回5个失真系数。
    • CALIB_FIX_TAUX_TAUY:优化过程中不改变倾斜传感器模型的系数tauX和tauY。如果设置了CALIB_USE_INTRINSIC_GUESS,则使用提供的distCoeffs矩阵中的系数。否则,它被设置为0。
  • criteria:算法迭代终止条件

除了立体相关信息外,该函数还可以对两个摄像头各进行单目标定。然而,由于参数空间的高维性和输入数据中的噪声,函数可能会偏离正确的解。建议先使用calibrateCamera单独标定每个相机的内参和畸变,然后将CALIB_FIX_INTRINSIC标志连同计算的内部参数一起传递给函数,以达到更高的标定精度。否则,如果一次性估计所有参数,那么限制一些参数是有意义的,例如,传递CALIB_SAME_FOCAL_LENGTH和CALIB_ZERO_TANGENT_DIST标志,这通常是一个合理的假设。

与calibrateCamera类似,该函数将两个相机的所有可用视图中所有点的总重投影误差降至最低。该函数返回重投影错误的最终值。

calibrateCamera:单目标定

#include <opencv2/calib3d.hpp>double cv::calibrateCamera   (   InputArrayOfArrays  objectPoints,
InputArrayOfArrays  imagePoints,
Size    imageSize,
InputOutputArray    cameraMatrix,
InputOutputArray    distCoeffs,
OutputArrayOfArrays     rvecs,
OutputArrayOfArrays     tvecs,
OutputArray     stdDeviationsIntrinsics,
OutputArray     stdDeviationsExtrinsics,
OutputArray     perViewErrors,
int     flags = 0,
TermCriteria    criteria = TermCriteria(TermCriteria::COUNT+TermCriteria::EPS, 30, DBL_EPSILON)
)

参数:

  • objectPoints:Vector of vectors of the calibration pattern points.如果是一个平面标定板,那么所有的vectors都是相同的
  • imagePoints:vector of vectors of the projections of calibration pattern points (e.g. std::vector<std::vector<cv::Vec2f>>). imagePoints.size() must be equal to objectPoints.size(), and imagePoints[i].size() must be equal to objectPoints[i].size() for each i.
  • imageSize:图像尺寸,(width, height)
  • cameraMatrix:Output 3x3 floating-point camera matrix
  • distCoeffs:Output vector of distortion coefficients (k1,k2,p1,p2[,k3[,k4,k5,k6[,s1,s2,s3,s4[,τx,τy]]]]) of 4, 5, 8, 12 or 14 elements.
  • rvecs:Output vector of rotation vectors (see Rodrigues ) estimated for each pattern view (e.g. std::vector<cv::Mat>>).
  • tvecs: Output vector of translation vectors estimated for each pattern view.
  • stdDeviationsIntrinsics:Output vector of standard deviations estimated for intrinsic parameters. Order of deviations values: (fx,fy,cx,cy,k1,k2,p1,p2,k3,k4,k5,k6,s1,s2,s3,s4,τx,τy) If one of parameters is not estimated, it’s deviation is equals to zero.
  • stdDeviationsExtrinsics:Output vector of standard deviations estimated for extrinsic parameters. Order of deviations values: (R1,T1,…,RM,TM) where M is number of pattern views, Ri,Ti are concatenated 1x3 vectors.
  • perViewErrors:Output vector of the RMS re-projection error estimated for each pattern view.
  • flags:Different flags that may be zero or a combination of the following values:
    • CALIB_USE_INTRINSIC_GUESS:cameraMatrix包含fx, fy, cx, cy的有效初始值,并进一步优化。否则,(cx, cy)初始设置为图像中心(使用imageSize),焦距以最小二乘方式计算。注意,如果内参数已知,就不需要用这个函数来估计外参数。使用solvePnP代替。
    • CALIB_FIX_PRINCIPAL_POINT:全局优化过程中不改变主点。当设置CALIB_USE_INTRINSIC_GUESS时,它会停留在中心或者不同的位置,这取决于cameraMatrix是否包含有效初始值。
    • CALIB_FIX_ASPECT_RATIO:函数只考虑fy作为自由参数。fx/fy的比率与输入的摄像矩阵保持相同。当没有设置CALIB_USE_INTRINSIC_GUESS时,fx和fy的实际输入值将被忽略,只计算它们的比率并进一步使用。
    • CALIB_ZERO_TANGENT_DIST:切向失真系数(p1,p2)固定为零
    • CALIB_FIX_K1,…,CALIB_FIX_K6:在优化过程中,相应的径向畸变系数不发生变化。如果设置了CALIB_USE_INTRINSIC_GUESS,则使用提供的distCoeffs矩阵中的系数。否则,它被设置为0。
    • 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不发生变化。如果设置了CALIB_USE_INTRINSIC_GUESS,则使用提供的distCoeffs矩阵中的系数。否则,它被设置为0。
    • CALIB_TILTED_MODEL:启用系数tauX和tauY。如果设置这个标志,标定函数使用倾斜传感器模型并返回14个系数。如果没有设置标志,函数只计算并返回5个失真系数。
    • CALIB_FIX_TAUX_TAUY:优化过程中不改变倾斜传感器模型的系数tauX和tauY。如果设置了CALIB_USE_INTRINSIC_GUESS,则使用提供的distCoeffs矩阵中的系数。否则,它被设置为0。
  • criteria:算法迭代终止条件

该函数估计每个视图的相机内参数和外部参数。3D物体点的坐标和它们在每个视图中对应的2D投影必须指定。这可以通过使用具有已知几何形状的物体容易检测到的特征点来实现。这样的对象被称为标定模式,OpenCV内置了对棋盘作为校准装置的支持(参见findChessboardCorners)。

算法执行如下步骤:

  1. 计算初始内在参数(该选项仅适用于平面校准模式)或从输入参数中读取它们。畸变系数最初都设置为零,除非使用CALIB_FIX_K指定。
  2. 假设内在参数已经知,估计初始相机姿态。这是用solvePnP完成的。
  3. 运行全局Levenberg-Marquardt优化算法来最小化重投影误差,即观测特征点imagePoints和投影(使用当前对相机参数和位姿的估计)物体点objectPoints之间距离的平方和。详见projectPoints

See also

findChessboardCorners, solvePnP, initCameraMatrix2D, stereoCalibrate, undistort

findChessboardCorners():找出棋盘角点的位置

#include <opencv2/calib3d.hpp>bool cv::findChessboardCorners   (   InputArray  image,
Size    patternSize,
OutputArray     corners,
int     flags = CALIB_CB_ADAPTIVE_THRESH+CALIB_CB_NORMALIZE_IMAGE
)

参数:

  • image:棋盘格图像。It must be an 8-bit grayscale or color image.
  • patternSize:Number of inner corners per a chessboard row and column ( patternSize = cvSize(points_per_row,points_per_colum)).
  • corners:Output array of detected corners.
  • flags:Various operation flags that can be zero or a combination of the following values:
    • CALIB_CB_ADAPTIVE_THRESH:使用自适应阈值对图像进行二值化处理
    • CALIB_CB_NORMALIZE_IMAGE:在应用固定阈值或自适应阈值之前,使用equalizeHist将图像归一化。
    • CALIB_CB_FILTER_QUADS:使用额外的标准(如轮廓面积,周长,正方形形状)来过滤掉在轮廓检索阶段提取的假四边形。
    • CALIB_CB_FAST_CHECK:对图像进行快速检查,查找棋盘角点,如果没有找到,则使用快捷方式调用。这可以大大加快退化状态下的调用,当没有棋盘被观察到。

该函数试图确定输入图像是否为棋盘图案的视图,并定位内部棋盘角。如果找到了所有的角点,并且按一定的顺序放置(每行从左到右),则该函数返回一个非零值。否则,如果函数未能找到所有的角或重新排序它们,它将返回0。例如,一个普通的棋盘有8 x 8个正方形和7 x 7个内角,也就是黑色方块相互接触的点。检测到的坐标是近似的,为了更准确地确定它们的位置,该函数调用cornerSubPix。如果返回的坐标不够精确,您还可以使用带有不同参数的函数cornerSubPix。

该函数需要在棋盘格周围留出空白(比如方形粗的边框,越宽越好),以使检测在各种环境中更稳健。否则,如果没有边框且背景为深色,则无法正确分割出外部的黑色方块,从而导致方块分组排序算法失败。

Sample:

Size patternsize(8, 6); //interior number of corners
Mat gray = ....; //source image
vector<Point2f> corners; //this will be filled by the detected corners
//CALIB_CB_FAST_CHECK saves a lot of time on images
//that do not contain any chessboard corners
bool patternfound = findChessboardCorners(gray, patternsize, corners,CALIB_CB_ADAPTIVE_THRESH + CALIB_CB_NORMALIZE_IMAGE+ CALIB_CB_FAST_CHECK);
if (patternfound)cornerSubPix(gray, corners, Size(11, 11), Size(-1, -1),TermCriteria(CV_TERMCRIT_EPS + CV_TERMCRIT_ITER, 30, 0.1));
drawChessboardCorners(img, patternsize, Mat(corners), patternfound);

cornerSubPix():对初始的整数角点坐标进行亚像素精度的优化

#include <opencv2/imgproc.hpp>void cv::cornerSubPix    (   InputArray  image,
InputOutputArray    corners,
Size    winSize,
Size    zeroZone,
TermCriteria    criteria
)

原理解释:亚像素角点的求法

参数:

  • image:Input single-channel, 8-bit or float image.
  • corners:Initial coordinates of the input corners and refined coordinates provided for output.
  • winSize:Half of the side length of the search window. For example, if winSize=Size(5,5) , then a (5∗2+1)×(5∗2+1)=11×11 search window is used.
  • zeroZone:搜索区域中间的死区大小的一半,在此范围上没有进行下式求和它有时被用来避免自相关矩阵可能的奇异性。(-1,-1)表示不存在这样的大小。没弄明白怎么用,设置为(-1, -1)即可
  • criteria:角点亚像素精度优化的迭代过程的终止条件。要么达到最大迭代次数,要么相邻两次迭代的角点位置移动小于给定阈值,则终止。

drawChessboardCorners():绘制检测出来的棋盘角点。

#include <opencv2/calib3d.hpp>void cv::drawChessboardCorners   (   InputOutputArray    image,
Size    patternSize,
InputArray  corners,
bool    patternWasFound
)

参数:

  • image:Destination image. It must be an 8-bit color image.
  • patternSize:Number of inner corners per a chessboard row and column (patternSize = cv::Size(points_per_row,points_per_column)).
  • corners:Array of detected corners, the output of findChessboardCorners.
  • patternWasFound:Parameter indicating whether the complete board was found or not. The return value of findChessboardCorners should be passed here.

该函数绘制单独的棋盘角点,如果没有找到棋盘,则检测到红色圆圈,如果找到了棋盘,则检测到与线相连的彩色角点。

TermCriteria():为迭代算法定义终止标准的类。可以通过默认构造函数初始化它

#include <opencv2/core/types.hpp>cv::TermCriteria::TermCriteria    (   int     type,
int     maxCount,
double  epsilon
)

参数:

  • type:The type of termination criteria, one of TermCriteria::Type

    • COUNT
    • EPS
    • COUNT + EPS
  • maxCount:The maximum number of iterations or elements to compute.
  • epsilon:The desired accuracy or change in parameters at which the iterative algorithm stops.

stereoRectify():基于立体相机内外参,计算极线校正变换。

#include <opencv2/calib3d.hpp>void cv::stereoRectify   (   InputArray  cameraMatrix1,
InputArray  distCoeffs1,
InputArray  cameraMatrix2,
InputArray  distCoeffs2,
Size    imageSize,
InputArray  R,
InputArray  T,
OutputArray     R1,
OutputArray     R2,
OutputArray     P1,
OutputArray     P2,
OutputArray     Q,
int     flags = CALIB_ZERO_DISPARITY,
double  alpha = -1,
Size    newImageSize = Size(),
Rect *  validPixROI1 = 0,
Rect *  validPixROI2 = 0
)

参数:

  • cameraMatrix1:First camera matrix.
  • distCoeffs1:First camera distortion parameters. 畸变参数应该是为了配合alpha使用,没有其他的用处。
  • cameraMatrix2:Second camera matrix.
  • distCoeffs2:Second camera distortion parameters. 畸变参数应该是为了配合alpha使用,没有其他的用处。
  • imageSize:Size of the image used for stereo calibration.
  • R:Rotation matrix between the coordinate systems of the first and the second cameras.
  • T:Translation vector between coordinate systems of the cameras.
  • R1:Output 3x3 rectification transform (rotation matrix) for the first camera.
  • R2:Output 3x3 rectification transform (rotation matrix) for the second camera.
  • P1:Output 3x4 projection matrix in the new (rectified) coordinate systems for the first camera.
  • P2:Output 3x4 projection matrix in the new (rectified) coordinate systems for the second camera.
  • Q:Output 4×4 disparity-to-depth mapping matrix (see reprojectImageTo3D ).
  • flags:操作标志可以是零或CALIB_ZERO_DISPARITY。如果设置了该标志,该函数使每个摄像头的主要点在校正视图中具有相同的像素坐标。如果没有设置标志,该函数仍然可以在水平或垂直方向(取决于极线的方向)移动图像,以最大化有用的图像区域。
  • alpha:自由的尺度参数。如果是-1或不存在,函数执行默认缩放。否则,该参数应在0 ~ 1之间。Alpha =0意味着校正后的图像被缩放和移动,因此有效的像素可见(校正后没有黑色区域)。Alpha =1意味着校正后的图像被抽取和移位,因此所有来自相机的原始图像的像素都保留在修正后的图像中(没有源图像像素丢失)。显然,任何中间值都会产生介于这两种极端情况之间的中间结果。没弄明白原理,直接设置为-1或缺省就行。
  • newImageSize:校正后的新图像分辨率。同样的大小应该传递给initUndistortRectifyMap(参见OpenCV samples目录中的stereo_calib.cpp示例)。当(0,0)被传递时(默认),它被设置为原始的imageSize。将其设置为较大的值可以帮助您保存原始图像中的细节,特别是当有较大的径向失真时。
  • validPixROI1:可选输出校正图像内的矩形区域,其中所有像素是有效的。如果alpha=0, roi覆盖整个图像。否则,它们可能会更小(见下图)。
  • validPixROI2:可选输出校正图像内的矩形区域,其中所有像素是有效的。如果alpha=0, roi覆盖整个图像。否则,它们可能会更小(见下图)。

该函数计算每个相机的旋转矩阵,使两个相机图像平面相同。因此,这使得所有的极线平行,从而简化了密集立体对应问题。该函数以sterecalibrate计算的矩阵作为输入。作为输出,它提供了两个旋转矩阵两个在新坐标中的投影矩阵。该函数区分以下两种情况:


正如你所看到的,P1和P2的前三列将有效地成为新的“校正”相机矩阵。校正矩阵,连同R1和R2,然后可以传递给initUndistortRectifyMap来初始化每个相机的校正坐标映射。

下面是来自stereo_calib.cpp示例的截图。一些红色的水平线穿过相应的图像区域。这意味着图像得到了很好的校正,这是大多数立体匹配算法所依赖的。绿色矩形是roi1和roi2。你可以看到它们的内部都是有效的像素。

initUndistortRectifyMap():计算去畸变和极线校正变换图。

#include <opencv2/imgproc.hpp>void cv::initUndistortRectifyMap (   InputArray  cameraMatrix,
InputArray  distCoeffs,
InputArray  R,
InputArray  newCameraMatrix,
Size    size,
int     m1type,
OutputArray     map1,
OutputArray     map2
)

参数:

  • cameraMatrix:Input camera matrix A
  • distCoeffs:Input vector of distortion coefficients (k1,k2,p1,p2[,k3[,k4,k5,k6[,s1,s2,s3,s4[,τx,τy]]]]) of 4, 5, 8, 12 or 14 elements. If the vector is NULL/empty, the zero distortion coefficients are assumed.
  • R:Optional rectification transformation in the object space (3x3 matrix). 对于单目相机,直接忽略,默认是Identity;对于立体相机,传入stereoRectify输出的R1/R2。
  • newCameraMatrix:New camera matrix
  • size:Undistorted image size.这个size必须与stereoRectify中的newImageSize一样
  • m1type:Type of the first output map that can be CV_32FC1, CV_32FC2 or CV_16SC2, see convertMaps
  • map1:The first output map.
  • map2:The second output map.

单目相机的情况下,newCameraMatrix通常等于cameraMatrix,或者它可以通过getOptimalNewCameraMatrix来计算,以便更好地控制缩放。对于立体相机来说,newCameraMatrix通常设置为通过立体矫正计算出的P1或P2。

该函数实际上为remap使用的反向映射算法构建映射。即,对于目标(校正和校正)图像中的每个像素(u,v),该函数计算源图像(即来自相机的原始图像)中对应的坐标。应用以下流程:


如果是立体相机,这个函数会被调用两次:每个相机调用一次。但是如果立体相机没有被校准,仍然可以使用stereoRectifyUncalibrated从基本矩阵直接计算校正变换。感兴趣的可以看Opencv官网文档。

remap():对图像进行几何变换

#include <opencv2/imgproc.hpp>void cv::remap   (   InputArray  src,
OutputArray     dst,
InputArray  map1,
InputArray  map2,
int     interpolation,
int     borderMode = BORDER_CONSTANT,
const Scalar &  borderValue = Scalar()
)

参数:

  • src:Source image.
  • dst:Destination image. It has the same size as map1 and the same type as src .
  • map1:The first map of either (x,y) points or just x values having the type CV_16SC2 , CV_32FC1, or CV_32FC2. See convertMaps for details on converting a floating point representation to fixed-point for speed.
  • map2:The second map of y values having the type CV_16UC1, CV_32FC1, or none (empty map if map1 is (x,y) points), respectively.
  • interpolation:Interpolation method (see InterpolationFlags). The method INTER_AREA is not supported by this function.
  • borderMode:Pixel extrapolation method (see BorderTypes). When borderMode=BORDER_TRANSPARENT, it means that the pixels in the destination image that corresponds to the “outliers” in the source image are not modified by the function.
  • borderValue:Value used in case of a constant border. By default, it is 0.

convertMaps():将图像变换映射从一种表示转换为另一种表示。

#include <opencv2/imgproc.hpp>void cv::convertMaps (   InputArray  map1,
InputArray  map2,
OutputArray     dstmap1,
OutputArray     dstmap2,
int     dstmap1type,
bool    nninterpolation = false
)

参数:

  • map1:The first input map of type CV_16SC2, CV_32FC1, or CV_32FC2 .
  • map2:The second input map of type CV_16UC1, CV_32FC1, or none (empty matrix), respectively.
  • dstmap1:The first output map that has the type dstmap1type and the same size as src .
  • dstmap2:The second output map.
  • dstmap1type:Type of the first output map that should be CV_16SC2, CV_32FC1, or CV_32FC2 .
  • nninterpolation:标志,指示定点映射是用于最近邻插值(false)还是用于更复杂的插值(true)。

该函数将一对映射从一种表示转换为另一种表示。支持以下选项:

initCameraMatrix2D():计算初始相机内参,目前仅适用与平面标定板。具体原理参考张正友标定法

#include <opencv2/calib3d.hpp>Mat cv::initCameraMatrix2D   (   InputArrayOfArrays  objectPoints,
InputArrayOfArrays  imagePoints,
Size    imageSize,
double  aspectRatio = 1.0
)

参数:

  • objectPoints:Vector of vectors of the calibration pattern points in the calibration pattern coordinate space. In the old interface all the per-view vectors are concatenated. See calibrateCamera for details.
  • imagePoints:Vector of vectors of the projections of the calibration pattern points. In the old interface all the per-view vectors are concatenated.
  • imageSize:Image size in pixels used to initialize the principal point.
  • aspectRatio:If it is zero or negative, both fx and fy are estimated independently. Otherwise, fx=fy∗aspectRatio .

2. 代码

首先我们来展示一下Opencv立体标定的流程,这样更利于我们理解下面的代码。

#include "opencv2/calib3d.hpp"
#include "opencv2/imgcodecs.hpp"
#include "opencv2/highgui.hpp"
#include "opencv2/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, float squareSize, bool displayCorners = false, bool showRectified = true)
{if (imagelist.size() % 2 != 0){cout << "Error: the image list contains odd (non-even) number of elements\n";return;}const int maxScale = 2;// ARRAY AND VECTOR STORAGE:vector<vector<Point2f> > imagePoints[2]; //这是一个数组,imagePoints[0]和imagePoints[1]都是一个vector<vector<Point2f> >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;//k指代左右图像,j指代角点提取成功的图像对(好图像对),提取成功后,j++,否则,下一次成功的覆盖当前不成功的像对的角点vector<Point2f>& corners = imagePoints[k][j];//在原尺度和两倍尺度下提取角点,提高提取成功的几率,如果原尺度提取成功,则不用进行两倍尺度下的提取for (int scale = 1; scale <= maxScale; scale++){Mat timg;if (scale == 1)timg = img;elseresize(img, timg, Size(), scale, scale, INTER_LINEAR_EXACT);//findChessboardCorners寻找角点,自适应阈值+归一化图像,返回是否成功的标志found = findChessboardCorners(timg, boardSize, corners,CALIB_CB_ADAPTIVE_THRESH | CALIB_CB_NORMALIZE_IMAGE);if (found){if (scale > 1){Mat cornersMat(corners);cornersMat *= 1. / scale;}break;}}//显示提取的角点if (displayCorners){cout << filename << endl;Mat cimg, cimg1;//drawChessboardCorners的输入图像必须是彩色图cvtColor(img, cimg, COLOR_GRAY2BGR);drawChessboardCorners(cimg, boardSize, corners, found);double sf = 640. / MAX(img.rows, img.cols);resize(cimg, cimg1, Size(), sf, sf, INTER_LINEAR_EXACT);imshow("corners", cimg1);char c = (char)waitKey(500);if (c == 27 || c == 'q' || c == 'Q') //Allow ESC to quitexit(-1);}elseputchar('.');//如果图像对任何一张提取不成功,则结束当前图像对的角点提取if (!found)break;//角点亚像素精度优化cornerSubPix(img, corners, Size(11, 11), Size(-1, -1),TermCriteria(TermCriteria::COUNT + TermCriteria::EPS,30, 0.01));}//如果图像对的两张图像都提取角点成功,那么j++,并且保存好的像对的路径if (k == 2){goodImageList.push_back(imagelist[i * 2]);goodImageList.push_back(imagelist[i * 2 + 1]);j++;}}//如果有效图像对的数量小于2,则不够相机标定(2的原因是固定了部分参数)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[k]的长度和objectPoints的长度必须一致imagePoints[0].resize(nimages);imagePoints[1].resize(nimages);objectPoints.resize(nimages);//objectPoints初始化,以左上角第一个角点为原点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, j * squareSize, 0));}//开始立体标定cout << "Running stereo calibration ...\n";//计算两个相机内参的初始值,畸变初始化为零。这里我们可以使用CalibrateCamera来计算初始内参、外参、畸变Mat cameraMatrix[2], distCoeffs[2];cameraMatrix[0] = initCameraMatrix2D(objectPoints, imagePoints[0], imageSize, 0);cameraMatrix[1] = initCameraMatrix2D(objectPoints, imagePoints[1], imageSize, 0);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,CALIB_FIX_ASPECT_RATIO +CALIB_ZERO_TANGENT_DIST +CALIB_USE_INTRINSIC_GUESS +CALIB_SAME_FOCAL_LENGTH +CALIB_RATIONAL_MODEL +CALIB_FIX_K3 + CALIB_FIX_K4 + CALIB_FIX_K5,TermCriteria(TermCriteria::COUNT + TermCriteria::EPS, 100, 1e-5));cout << "done with RMS error=" << rms << endl;// save intrinsic parametersFileStorage fs("intrinsics.yml", FileStorage::WRITE); //FileStorage是cv的XML/YAML/JSON 文件存储类,封装了向文件写入数据或从文件读取数据所需的所有信息。if (fs.isOpened()){fs << "M1" << cameraMatrix[0] << "D1" << distCoeffs[0] <<"M2" << cameraMatrix[1] << "D2" << distCoeffs[1];fs.release();}elsecout << "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, 0, imageSize, &validRoi[0], &validRoi[1]);fs.open("extrinsics.yml", FileStorage::WRITE);if (fs.isOpened()){fs << "R" << R << "T" << T << "R1" << R1 << "R2" << R2 << "P1" << P1 << "P2" << P2 << "Q" << Q;fs.release();}elsecout << "Error: can not save the extrinsic parameters\n";// OpenCV can handle left-right// or up-down camera arrangements//Opencv能够自行判断是垂直摆放还是水平摆放,如何判断bool isVerticalStereo = fabs(P2.at<double>(1, 3)) > fabs(P2.at<double>(0, 3));// COMPUTE AND DISPLAY RECTIFICATIONif (!showRectified)return;Mat rmap[2][2];//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); //cv的四舍五入h = cvRound(imageSize.height * sf);canvas.create(h, w * 2, CV_8UC3);}else //垂直摆放{sf = 300. / MAX(imageSize.width, imageSize.height);w = cvRound(imageSize.width * sf);h = cvRound(imageSize.height * sf);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], INTER_LINEAR);cvtColor(rimg, cimg, COLOR_GRAY2BGR);Mat canvasPart = !isVerticalStereo ? canvas(Rect(w * k, 0, w, h)) : canvas(Rect(0, h * k, w, h));resize(cimg, canvasPart, canvasPart.size(), 0, 0, INTER_AREA); //INTER_AREA不能用在二维图像的插值Rect vroi(cvRound(validRoi[k].x * sf), cvRound(validRoi[k].y * sf),cvRound(validRoi[k].width * sf), cvRound(validRoi[k].height * sf));cout << vroi << endl;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);elsefor (j = 0; j < canvas.cols; j += 16)line(canvas, Point(j, 0), Point(j, canvas.rows), Scalar(0, 255, 0), 1, 8);imshow("rectified", canvas);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)
{string imagelistfn = "E:\\opencv\\opencv\\sources\\samples\\data\\stereo_calib.xml";bool showRectified = true;Size boardSize;boardSize.width = 9;boardSize.height = 6;float squareSize = 1.0;vector<string> imagelist;bool ok = readStringList(imagelistfn, imagelist);int index = imagelistfn.find_last_of("\\");string dir = imagelistfn.substr(0, index);for (auto& image_path : imagelist) {image_path = dir + "\\" + image_path;}for (const auto& image_path : imagelist) {cout << image_path << endl;}StereoCalib(imagelist, boardSize, squareSize, true, showRectified); return 0;
}

下面直接放出一组极线校正的图像对,从图中可以看出,极线是水平对齐的。


至此,Opencv立体标定的过程和代码都已经讲完了,希望大家有所收获,有疑问的话请留言评论区。

Opencv立体相机标定相关推荐

  1. 基于OpenCV的立体相机标定StereoCalibration与目标三维坐标定位

    说明:以下涉及到的一些公式以及图片来自于Learning OpenCV. 做了快2个月的立体相机标定,遇到了一些问题,也有了一些体会,在这里记下来. 1.在做立体相机标定的时候,标定板的规范与否直接影 ...

  2. 使用OpenCV进行相机标定

    1. 使用OpenCV进行标定 相机已经有很长一段历史了.但是,伴随着20世纪后期的廉价针孔照相机的问世,它们已经变成我们日常生活的一种常见的存在.不幸的是,这种廉价是由代价的:显著的变形.幸运的是, ...

  3. python利用opencv进行相机标定获取参数,并根据畸变参数修正图像附有全部代码(流畅无痛版)

    python利用opencv进行相机标定获取参数,并根据畸变参数修正图像附有全部代码 一.前言 今天的低价单孔摄像机(照相机)会给图像带来很多畸变.畸变主要有两 种:径向畸变和切想畸变.如下图所示,用 ...

  4. OpenCV | 双目相机标定之OpenCV获取左右相机图像+MATLAB单目标定+双目标定

    博主github:https://github.com/MichaelBeechan 博主CSDN:https://blog.csdn.net/u011344545 原本网上可以搜到很多关于双目相机标 ...

  5. opencv+pythons相机标定源码解析

    相机标定原理,这里不再赘述,一般使用张友正相机标定法.这里只介绍了标定相机内参的方法,即3x3的matrix. import cv2 import numpy as np import glob# 设 ...

  6. opencv双目相机标定-示例代码分析

      在这里我使用的是Learning OpenCV3的示例,本节使用的项目代码可以在这里下载到. 一.运行示例   在下载完整个工程以后,按照工程使用说明,下载配置Opencv,运行VS2019项目即 ...

  7. 基于OpenCV进行相机标定

    相机已经存在了很长一段时间. 随着二十世纪末廉价针孔相机的推出,相机已经在日常生活中普及.虽然价格便宜,但是成像存在严重的畸变.不过,这些畸变是固定的形式,基于标定和重映技术可以纠正畸变.此外,基于标 ...

  8. 立体相机标定数据集_超全的3D视觉数据集汇总

    作者:Tom Hardy Date:2019-12-31 文章来源:超全的3D视觉数据集汇总 1.KITTI数据集 KITTI数据集由德国卡尔斯鲁厄理工学院和丰田美国技术研究院联合创办,是目前国际上最 ...

  9. 一分钟详解OpenCV之相机标定函数calibrateCamera()

最新文章

  1. C++ __gnu_pbds(平板电视)超详细教程(C++内置的平衡树,字典树,hash)
  2. eclipse安装birt插件
  3. 缓存与库先删哪个(转自网络,侵删)
  4. 关于mysql的wait_timeout参数 设置不生效的问题
  5. ssms 缺少索引信息_MySQL3:索引
  6. android gps 串口,Android GPS数据上报(基于gps_qemu.c)
  7. jQuery EasyUI API 中文文档 - 可调整尺寸
  8. Filecoin网络目前总质押量约为3570万枚FIL
  9. mysql无法连接10061错误1067_解决MySQL启动的error 2003和1067 10061错误问题
  10. 第十四章 C语言头文件的编写_C语言标准库以及标准头文件
  11. 【毕业设计】基于单片机的智能饮水控制系统 - 物联网 嵌入式 stm32 c51
  12. 使用hexo+icarus快速搭建属于自己的博客网站
  13. docker docker安装app
  14. 如何自动删除您的YouTube历史记录
  15. 简析国内外电商的区别
  16. endnote 使用方法
  17. 微信小程序扫描二维码条形码 (wx.scanCode)
  18. 为什么独热编码会引起维度诅咒,以及避免他的几个办法
  19. linux心跳出血漏洞,heartbleeder 自动检测 OpenSSL 心脏出血漏洞 (附修复指南)
  20. python软件和rost软件哪个更好_Python几种并发实现方案的性能比较

热门文章

  1. VSCode+MinGW64搭建c/c++开发环境
  2. 《见与不见》——仓央嘉措
  3. 2021海口市华侨中学高考成绩查询,2021海口高中排名前十
  4. android 界面布局-各个布局的属性介绍,sharedpreferences原理
  5. 黑苹果的心路历程(matebook14 2020版)
  6. 进程间通信——几种方式的比较和详细实例
  7. linux qt compiler叹号,qt里感叹号什么意思
  8. 《企业内部控制应用指引第7号——采购业务》全文及解读
  9. 零基础如何学习项目管理?
  10. 智信精密深交所上市:市值46亿 实控人李晓华武大本科肄业