相机标定与3D重建——使用 OpenCV 标定相机 OpenCV v4.8.0

上一个教程使用方形棋盘标定相机

下一个教程纹理对象的实时姿态估计

原作者Bernát Gábor
兼容性OpenCV >= 4.0

相机的历史由来已久。然而,随着 20 世纪末廉价针孔摄像头的问世,摄像头在我们的日常生活中变得司空见惯。不幸的是,这种廉价也带来了代价:明显的失真。幸运的是,这些都是常量,通过标定和一些重映射,我们可以纠正这种情况。此外,通过标定,您还可以确定相机的自然单位(像素)与实际单位(例如毫米)之间的关系。

原理

对于失真,OpenCV 会考虑径向和切向因子。径向因子使用以下公式计算:

x d i s t o r t e d = x ( 1 + k 1 r 2 + k 2 r 4 + k 3 r 6 ) y d i s t o r t e d = y ( 1 + k 1 r 2 + k 2 r 4 + k 3 r 6 ) x_{distorted} = x( 1 + k_1 r^2 + k_2 r^4 + k_3 r^6) \\ y_{distorted} = y( 1 + k_1 r^2 + k_2 r^4 + k_3 r^6) xdistorted=x(1+k1r2+k2r4+k3r6)ydistorted=y(1+k1r2+k2r4+k3r6)
因此,对于位于 (x,y) 坐标处的未失真像素点,其在失真图像上的位置将为 ( x d i s t o r t e d y d i s t o r t e d ) (x_{distorted} y_{distorted}) (xdistortedydistorted)径向失真表现为 "桶形 "或 "鱼眼 "效果。

切向畸变产生的原因是取像镜头与成像平面不完全平行。它可以用以下公式表示
x d i s t o r t e d = x + [ 2 p 1 x y + p 2 ( r 2 + 2 x 2 ) ] y d i s t o r t e d = y + [ p 1 ( r 2 + 2 y 2 ) + 2 p 2 x y ] x_{distorted} = x + [ 2p_1xy + p_2(r^2+2x^2)] \\ y_{distorted} = y + [ p_1(r^2+ 2y^2)+ 2p_2xy] xdistorted=x+[2p1xy+p2(r2+2x2)]ydistorted=y+[p1(r2+2y2)+2p2xy]
因此,我们有五个失真参数,在 OpenCV 中,这些参数以一行矩阵五列的形式显示:

d i s t o r t i o n _ c o e f f i c i e n t s = ( k 1 k 2 p 1 p 2 k 3 ) distortion\_coefficients=(k_1 \hspace{10pt} k_2 \hspace{10pt} p_1 \hspace{10pt} p_2 \hspace{10pt} k_3) distortion_coefficients=(k1k2p1p2k3)
现在,我们使用以下公式进行单位转换:

[ x y w ] = [ f x 0 c x 0 f y c y 0 0 1 ] [ X Y Z ] \left [ \begin{matrix} x \\ y \\ w \end{matrix} \right ] = \left [ \begin{matrix} f_x & 0 & c_x \\ 0 & f_y & c_y \\ 0 & 0 & 1 \end{matrix} \right ] \left [ \begin{matrix} X \\ Y \\ Z \end{matrix} \right ] xyw = fx000fy0cxcy1 XYZ
这里,w 的存在是因为使用了同向坐标系(w=Z)。未知参数是 fx 和 fy(摄像机焦距)以及 (cx,cy),后者是以像素坐标表示的光学中心。如果两个轴都使用一个给定长宽比(通常为 1)的共同焦距,则 fy=fx∗a 而在上式中,我们将得到一个单一焦距 f。虽然无论使用哪种分辨率的摄像机,畸变系数都是相同的,但这些畸变系数应根据标定分辨率与当前分辨率一起进行缩放。

确定这两个矩阵的过程就是标定。这些参数的计算是通过基本几何方程完成的。所使用的方程取决于所选择的标定对象。目前,OpenCV 支持三种类型的标定对象:

  • 经典黑白棋盘
  • 黑白棋盘图案
  • 对称圆图案
  • 不对称圆图案

基本上,您需要用相机拍摄这些图案的快照,然后让 OpenCV 找到它们。每个找到的图案都会产生一个新方程。要解方程,您至少需要预定数量的图案快照才能形成一个拟合良好的方程系统。棋盘图案的快照数较多,而圆形图案的快照数较少。例如,理论上棋盘图案至少需要两个快照。但实际上,我们的输入图像中存在大量噪声,因此要想取得好的结果,可能至少需要 10 张不同位置的输入图案快照。

目标

示例应用程序将

  • 确定失真矩阵
  • 确定相机矩阵
  • 从摄像机、视频和图像文件列表中获取输入信息
  • 从 XML/YAML 文件中读取配置
  • 将结果保存到 XML/YAML 文件中
  • 计算重投影误差

源代码

您也可以在 OpenCV 源代码库的 samples/cpp/tutorial_code/calib3d/camera_calibration/ 文件夹中找到源代码,或从此处下载。要使用该程序,请使用 -h 参数运行它。程序有一个重要参数:配置文件名。如果没有参数,程序将尝试打开名为 "default.xml "的配置文件。下面是一个 XML 格式的配置文件示例。在配置文件中,您可以选择使用摄像头作为输入、视频文件或图像列表。如果选择最后一种,则需要创建一个配置文件,在其中枚举要使用的图像。下面是一个例子。需要记住的重要一点是,必须使用应用程序工作目录的绝对路径或相对路径指定图片。你可以在上面提到的示例目录中找到所有这些内容。

应用程序启动时会读取配置文件中的设置。虽然这是重要的一部分,但与本教程的主题–摄像机标定–无关。因此,我选择不在这里发布这部分的代码。关于如何完成此操作的技术背景,请参阅使用 XML 和 YAML 文件进行文件输入和输出 教程。

说明

  1. 读取设置
 Settings s;
 const string inputSettingsFile = parser.get<string>(0);
 FileStorage fs(inputSettingsFile, FileStorage::READ); // 读取设置
 if (!fs.isOpened())
 {
 cout << "Could not open the configuration file: \"" << inputSettingsFile << "\"" << endl;
 parser.printMessage()return -1}
 fs["Settings"] >> s;
 fs.release(); // 关闭设置文件

为此,我使用了简单的 OpenCV 类输入操作。读取文件后,我还使用了一个额外的后处理函数来检查输入的有效性。只有当所有输入都正确时,goodInput 变量才会为真。
2. 获取下一个输入,如果失败或我们有足够多的输入–标定

在此之后,我们会有一个大循环,进行以下操作:从图像列表、摄像头或视频文件中获取下一张图像。如果失败或有足够的图像,则运行标定过程。如果有图像,我们将跳出循环,否则将通过从检测模式切换到标定模式,使剩余帧不失真(如果设置了选项)。

 for(;;)
 {
 Mat view;
 bool blinkOutput = false;
 view = s.nextImage()//----- 如果没有更多图像或图像数量足够,则停止标定并显示结果 -------------
 if( mode == CAPTURING && imagePoints.size() >= (size_t)s.nrFrames )
 {
 ifrunCalibrationAndSave(s, imageSize, cameraMatrix, distCoeffs, imagePoints, grid_width、
 release_object))
 mode = CALIBRATED;
 else
 mode = DETECTION;
 }
 if(view.empty()) // 如果没有更多图像,停止循环
 {
 // 如果尚未达到标定阈值,则现在进行标定
 if( mode != CALIBRATED && !imagePoints.empty() )
 runCalibrationAndSave(s, imageSize, cameraMatrix, distCoeffs, imagePoints, grid_width、
 release_object)break}

对于某些摄像机,我们可能需要翻转输入图像。在此我们也会这样做。

  1. 查找当前输入中的模式

我上面提到的方程式的形成旨在找出输入中的主要模式:对于国际象棋棋盘来说,主要模式是方格的角,而对于圆来说,主要模式是圆本身。ChArUco 棋盘等同于国际象棋棋盘,但四角使用 ArUco 标记。这些标记的位置将形成结果并写入 pointBuf 向量。

 vector<Point2f> pointBuf;
 bool found;
 int chessBoardFlags = CALIB_CB_ADAPTIVE_THRESH | CALIB_CB_NORMALIZE_IMAGE;
 if(!s.useFisheye) {
 // 快速检查在鱼眼等高失真情况下错误失败
 chessBoardFlags |= CALIB_CB_FAST_CHECK;
 }
 switch( s.calibrationPattern ) // 在输入格式上查找特征点
 {
 case Settings::CHESSBOARD:
 found = findChessboardCorners( view, s.boardSize, pointBuf, chessBoardFlags)breakcase Settings::CHARUCOBOARD:
 ch_detector.detectBoard( view, pointBuf, markerIds);
 found = pointBuf.size() == (size_t)((s.boardSize.height - 1)*(s.boardSize.width - 1))breakcase Settings::CIRCLES_GRID:
 found = findCirclesGrid( view, s.boardSize, pointBuf )breakcase Settings::ASYMMETRIC_CIRCLES_GRID:
 found = findCirclesGrid( view, s.boardSize, pointBuf, CALIB_CB_ASYMMETRIC_GRID )breakdefault:
 found = falsebreak}

根据输入图案的类型,您可以使用 cv::findChessboardCornerscv::findCirclesGrid 函数或 cv::aruco::CharucoDetector::detectBoard 方法。cv::findChessboardCornerscv::findCirclesGrid 都会返回一个布尔变量,说明是否在输入中找到了图案(我们只需要考虑那些为真的图像)。CharucoDetector::detectBoard 可以检测部分可见的图案,并返回可见内角的坐标和 ID。

注意事项
棋盘、圆网格和 ChArUco
的棋盘大小和匹配点数量是不同的。所有与棋盘相关的算法都将内角的数量作为棋盘的宽度和高度。圆网格的棋盘大小只是两个网格尺寸的圆的数量。ChArUco
的棋盘大小是以方格为单位定义的,但检测结果是内角列表,因此在两个尺寸上都小 1。

同样,对于摄像头,我们只有在输入延迟时间过后才会拍摄摄像头图像。这样做是为了让用户可以移动棋盘,获得不同的图像。相似的图像会产生相似的方程,而相似的方程在标定步骤中会形成问题,因此标定会失败。对于正方形图像,角的位置只是近似值。我们可以通过调用 cv::cornerSubPix 函数来改善这一问题。(winSize 用于控制搜索窗口的边长。winSize可以通过命令行参数--winSize=<number>来更改)。这将产生更好的标定结果。之后,我们将有效输入结果添加到 imagePoints 向量中,将所有方程收集到一个容器中。最后,为了实现可视化反馈,我们将使用 cv::findChessboardCorners 函数在输入图像上绘制找到的点。

 if (found) // 如果成功找到点、
 {
 // 提高找到的棋盘角的坐标精度
 if( s.calibrationPattern == Settings::CHESSBOARD)
 {
 Mat viewGray;
 cvtColor(view, viewGray, COLOR_BGR2GRAY);
 cvtColor(view,viewGray,COLOR_BGR2GRAY); cornerSubPix(viewGray,pointBuf,Size(winSize,winSize)、
 size(-1,-1),TermCriteria(TermCriteria::EPS+TermCriteria::COUNT,30,0.0001 ))}
 if( mode == CAPTURING && // 摄像机只在延迟时间后采集新样本
 (!s.inputCapture.isOpened() || clock() - prevTimestamp > s.delay*1e-3*CLOCKS_PER_SEC) )
 {
 imagePoints.push_back(pointBuf);
 prevTimestamp = clock();
 blinkOutput = s.inputCapture.isOpened()}
 // 绘制边角。
 if(s.calibrationPattern == Settings::CHARUCOBOARD)
 drawChessboardCorners( view, cv::Size(s.boardSize.width-1, s.boardSize.height-1), Mat(pointBuf), found )else
 drawChessboardCorners( view, s.boardSize, Mat(pointBuf), found )}
  1. 向用户显示状态和结果,以及应用程序的命令行控制

这部分显示图像上的文本输出。

 string msg = (mode == CAPTURING) ? "100/100" :
 mode == CALIBRATED ? "Calibrated" : "Press 'g' to start"int baseLine = 0;
 Size textSize = getTextSize(msg, 1, 1, 1, &baseLine);
 Point textOrigin(view.cols - 2*textSize.width - 10, view.rows - 2*baseLine - 10)if( mode == CAPTURING )
 {
 if( s.showUndistorted)
 msg = cv::format( "%d/%d Undist", (int)imagePoints.size(), s.nrFrames )else
 msg = cv::format( "%d/%d", (int)imagePoints.size(), s.nrFrames )}
 putText( view, msg, textOrigin, 1, 1, mode == CALIBRATED ? GREEN : RED)if( blinkOutput )
 bitwise_not(view, view)

如果我们进行了标定并获得了带有畸变系数的相机矩阵,我们可能需要使用 cv::undistort 函数来标定图像:

 if( mode == CALIBRATED && s.showUndistorted )
 {
 Mat temp = view.clone()if (s.useFisheye)
 {
 Mat newCamMat;
 fisheye::estimateNewCameraMatrixForUndistortRectify(cameraMatrix, distCoeffs, imageSize、
 matx33d::eye(),newCamMat,1);
 cv::fisheye::undistortImage(temp, view, cameraMatrix, distCoeffs, newCamMat)}
 else
 undistort(temp, view, cameraMatrix, distCoeffs)}

然后,我们将显示图像并等待输入键,如果输入键为 u,我们将切换失真消除;如果输入键为 g,我们将重新开始检测过程;最后,如果输入键为 ESC,我们将退出应用程序:

 imshow("Image View", view)char key = (char)waitKey(s.inputCapture.isOpened() ? 50 : s.delay)if( key == ESC_KEY )
 breakif( key == 'u' && mode == CALIBRATED )
 s.showUndistorted = !s.showUndistorted;
 if( s.inputCapture.isOpened() && key == 'g' )
 {
 mode = CAPTURING;
 imagePoints.clear()}
  1. 同时显示图像的失真消除

在处理图像列表时,不可能在循环内消除变形。因此,必须在循环之后进行。利用这一点,现在我将扩展 cv::undistort 函数,它实际上首先调用 cv::initUndistortRectifyMap 来查找变换矩阵,然后使用 cv::remap 函数执行变换。由于标定成功后只需进行一次映射计算,因此使用这种扩展形式可以加快应用程序的运行速度:

 if( s.inputType == Settings::IMAGE_LIST && s.showUndistorted && !cameraMatrix.empty())
 {
 Mat view, rview, map1, map2;
 if(s.useFisheye)
 {
 Mat newCamMat;
 fisheye::estimateNewCameraMatrixForUndistortRectify(cameraMatrix, distCoeffs, imageSize、
 matx33d::eye(),newCamMat,1);
 fisheye::initUndistortRectifyMap(cameraMatrix, distCoeffs, Matx33d::eye(), newCamMat, imageSize、
 CV_16SC2, map1, map2);
 }
 else
 {
 initUndistortRectifyMap(
 cameraMatrix,distCoeffs,Mat()getOptimalNewCameraMatrix(cameraMatrix, distCoeffs, imageSize, 1, imageSize, 0), imageSize、
 CV_16SC2,map1,map2);
 }
 for(size_t i = 0; i < s.imageList.size(); i++ )
 {
 view = imread(s.imageList[i], IMREAD_COLOR)if(view.empty())
 continue;
 remap(view、rview、map1、map2、INTER_LINEAR);
 imshow("图像视图",rview);
 char c = (char)waitKey()if( c == ESC_KEY || c == 'q' || c == 'Q' )
 break}
 }

标定和保存

由于标定只需对每台摄像机进行一次,因此在标定成功后将其保存是合理的。这样以后就可以将这些值加载到程序中。因此,我们首先进行标定,如果标定成功,我们会将结果保存到 OpenCV 风格的 XML 或 YAML 文件中,具体取决于您在配置文件中提供的扩展名。

因此,在第一个函数中,我们只是将这两个过程分开。因为我们要保存许多标定变量,所以我们将在这里创建这些变量,并将它们同时传递给标定和保存函数。同样,我不会展示保存部分,因为这部分与标定几乎没有共同之处。请查看源文件,了解如何保存和保存什么:

bool runCalibrationAndSave(Settings& s, Size imageSize, Mat& cameraMatrix, Mat& distCoeffs、
 vector<vector<Point2f> > imagePoints, float grid_width, bool release_object)
{
 vector<Mat> rvecs, tvecs;
 vector<float> reprojErrs;
 double totalAvgErr = 0;
 vector<Point3f> newObjPoints;
 bool ok = runCalibration(s、imageSize、cameraMatrix、distCoeffs、imagePoints、rvecs、tvecs、reprojErrs、
 totalAvgErr、newObjPoints、grid_width、release_object);
 cout << (ok ? "标定成功""标定失败")
 << ". avg re projection error = " << totalAvgErr << endl;
 if(ok)
 saveCameraParams(s, imageSize, cameraMatrix, distCoeffs, rvecs, tvecs, reprojErrs, imagePoints、
 totalAvgErr、newObjPoints);
 return ok;
}

我们使用 cv::calibrateCameraRO 函数进行标定。该函数的参数如下

  • 对象点。这是一个 Point3f 向量,用于描述每张输入图像的图案外观。如果是平面图案(如棋盘),则只需将所有 Z 坐标设为零即可。这是这些重要点的集合。由于我们对所有输入图像都使用一个图案,因此只需计算一次,然后将其乘以所有其他输入视图即可。我们使用函数 calcBoardCornerPositions 计算角点的位置:
static void calcBoardCornerPositions(Size boardSize, float squareSize, vector<Point3f>& corners、
 Settings::Pattern patternType /*= Settings::CHESSBOARD*/)
{
 corners.clear()switch(patternType)
 {
 case Settings::CHESSBOARD:
 case Settings::CIRCLES_GRID:
 for (int i = 0; i < boardSize.height; ++i) {
 for (int j = 0; j < boardSize.width; ++j) {
 corners.push_back(Point3f(j*squareSize, i*squareSize, 0))}
 }
 breakcase Settings::CHARUCOBOARD:
 for (int i = 0; i < boardSize.height - 1; ++i) {
 for (int j = 0; j < boardSize.width - 1; ++j) {
 corners.push_back(Point3f(j*squareSize, i*squareSize, 0))}
 }
 breakcase Settings::ASYMMETRIC_CIRCLES_GRID:
 for (int i = 0; i < boardSize.height; i++) {
 for (int j = 0; j < boardSize.width; j++) {
 corners.push_back(Point3f((2 * j + i % 2)*squareSize, i*squareSize, 0))}
 }
 breakdefaultbreak}
}

然后乘以

vector<vector<Point3f> > objectPoints(1)calcBoardCornerPositions(s.boardSize, s.squareSize, objectPoints[0], s.calibrationPattern);
objectPoints[0][s.boardSize.width - 1].x = objectPoints[0][0].x + grid_width;
newObjPoints = objectPoints[0];
objectPoints.resize(imagePoints.size(),objectPoints[0])

注释
如果您的标定板是不准确、未经测量的大致平面目标(使用现成打印机在纸上打印的棋盘图案是最方便的标定目标,但它们大多不够准确),则可以使用 [239] 中的一种方法来显著提高估计的摄像机本征参数的准确性。如果提供命令行参数 -d=<number>,就会调用这种新的标定方法。在上述代码片段中,grid_width 实际上是 -d=<number> 设置的值。它是图案网格点左上角(0,0,0)和右上角(s.squareSize*(s.boardSize.width-1),0,0)之间的距离。应使用直尺或游标卡尺精确测量。标定后,newObjPoints 将更新为对象点的细化 3D 坐标。

  • 图像点。这是一个 Point2f 向量,每个输入图像都包含重要点的坐标(棋盘的角和圆形图案的圆心)。我们已经从 cv::findChessboardCornerscv::findCirclesGrid 函数中收集了这些数据。我们只需将其传递下去即可。
  • 从摄像头、视频文件或图像中获取的图像大小。
  • 要固定的对象点的索引。我们将其设置为-1,以请求标准标定方法。如果使用新的对象释放方法,则将其设置为标定板网格右上角点的索引。详细说明请参阅 cv::calibrateCameraRO
int iFixedPoint = -1if (release_object)
 iFixedPoint = s.boardSize.width-1
  • 摄像机矩阵。如果我们使用了固定宽高比选项,则需要设置 fx:
 cameraMatrix = Mat::eye(3, 3, CV_64F)if( !s.useFisheye && s.flag & CALIB_FIX_ASPECT_RATIO )
 cameraMatrix.at<double>(0,0) = s.aspectRatio;
  • 失真系数矩阵。初始化为零。
distCoeffs = Mat::zeros(8, 1, CV_64F)
  • 对于所有视图,函数将计算旋转和平移矢量,将对象点(在模型坐标空间中给定)转换为图像点(在世界坐标空间中给定)。第 7 和第 8 个参数是矩阵的输出向量,其中第 i 个位置包含第 i 个对象点到第 i 个图像点的旋转和平移向量。
  • 标定模式点的更新输出向量。标准标定方法忽略此参数。
  • 最后一个参数是标志。您需要在此指定一些选项,如固定焦距的长宽比、假设切向畸变为零或固定主点。这里我们使用 CALIB_USE_LU 来加快标定速度。
rms = calibrateCameraRO(objectPoints、imagePoints、imageSize、iFixedPoint、
 cameraMatrix,distCoeffs,rvecs,tvecs,newObjPoints、
 s.flag|CALIB_USE_LU);
  • 该函数返回平均重投影误差。这个数字可以很好地估计找到的参数的精度。该误差应尽可能接近于零。给定本征矩阵、变形矩阵、旋转矩阵和平移矩阵后,我们可以使用 cv::projectPoints 首先将对象点转换为图像点,从而计算出一个视图的误差。然后,我们计算转换后得到的结果与角/圆查找算法之间的绝对值。为了找出平均误差,我们计算所有标定图像计算误差的算术平均值。
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<float>& perViewErrors, bool fisheye)
{
 vector<Point2f> imagePoints2;
 size_t totalPoints = 0double totalErr = 0, err;
 perViewErrors.resize(objectPoints.size())for(size_t i = 0; i < objectPoints.size(); ++i )
 {
 if (fisheye)
 {
 fisheye::projectPoints(objectPoints[i], imagePoints2, rvecs[i], tvecs[i], cameraMatrix、
 distCoeffs);
 }
 else
 {
 projectPoints(objectPoints[i], rvecs[i], tvecs[i], cameraMatrix, distCoeffs, imagePoints2)}
 err = norm(imagePoints[i], imagePoints2, NORM_L2);
 size_t n = objectPoints[i].size();
 perViewErrors[i] = (float) std::sqrt(err*err/n);
 totalErr += err*err;
 totalPoints += n;
 }
 return std::sqrt(totalErr/totalPoints)}

结果

假设输入的棋盘尺寸大小为 9 X 6。。我使用 AXIS IP 摄像机创建了几张棋盘快照,并将其保存到 VID5 目录中。我将其放在工作目录的 images/CameraCalibration 文件夹中,并创建了以下 VID5.XML 文件,其中说明了要使用的图像:

<?xml version="1.0"?>
<opencv_storage>
<images>
images/CameraCalibration/VID5/xx1.jpg
images/CameraCalibration/VID5/xx2.jpg
images/CameraCalibration/VID5/xx3.jpg
images/CameraCalibration/VID5/xx4.jpg
images/CameraCalibration/VID5/xx5.jpg
images/CameraCalibration/VID5/xx6.jpg
images/CameraCalibration/VID5/xx7.jpg
images/CameraCalibration/VID5/xx8.jpg
</images>
</opencv_storage>

然后将 images/CameraCalibration/VID5/VID5.XML 作为配置文件的输入。下面是应用程序运行时发现的棋盘图案:

在这里插入图片描述

应用失真消除后,我们得到了

在这里插入图片描述

将输入宽度设为 4,高度设为 11,同样可以制作出不对称的圆形图案。这一次,我使用了一个实时摄像头,指定其 ID(“1”)作为输入。下面是检测到的图案的样子:

在这里插入图片描述

在这两种情况下,您都可以在指定的输出 XML/YAML 文件中找到摄像机和畸变系数矩阵:

<camera_matrix type_id="opencv-matrix">
<rows>3</rows>
<cols>3</cols>
<dt>d</dt>
<data>
 6.5746697944293521e+002 0. 3.1950000000000000e+002 0.
 6.5746697944293521e+002 2.3950000000000000e+002 0. 0. 1.</data></camera_matrix>
<distortion_coefficients type_id="opencv-matrix">
<rows>5</rows>
<cols>1</cols>
<dt>d</dt>
<data>
 -4.1802327176423804e-001 5.0715244063187526e-001 0. 0.
 -5.7843597214487474e-001</data></distortion_coefficients>

将这些值作为常量添加到您的程序中,调用 cv::initUndistortRectifyMapcv::remap 函数来消除失真,并享受廉价和低质量相机的无失真输入。

您可以在 YouTube 上观看运行时实例。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值