使用 OpenCV 进行相机校准

您所在的位置:网站首页 c3200校正相机 使用 OpenCV 进行相机校准

使用 OpenCV 进行相机校准

2024-06-29 09:47| 来源: 网络整理| 查看: 265

相机已经存在了很长时间。然而,随着 20 世纪后期廉价针孔相机的推出,它们在我们的日常生活中变得司空见惯。不幸的是,这种廉价是有代价的:严重的失真。幸运的是,这些是常数,通过校准和一些重新映射,我们可以纠正这一点。此外,通过校准,您还可以确定相机的自然单位(像素)与现实世界单位(例如毫米)之间的关系。

理论

对于畸变,OpenCV 考虑了径向和切向因素。对于径向因子,使用以下公式:

在这里插入图片描述

因此,对于 (x,y) 坐标处未失真的像素点,它在失真图像上的位置将为 (x_{distorted} y_{distorted})。径向畸变的存在以“桶”或“鱼眼”效应的形式表现出来。(x,y)(xdIS T ORTEdydIS T ORTEd)

切向畸变的发生是因为拍摄图像的镜头与成像平面不完全平行。它可以通过以下公式表示:

在这里插入图片描述

因此,我们有五个失真参数,在 OpenCV 中,这些参数表示为具有 5 列的一行矩阵:

在这里插入图片描述

现在对于单位转换,我们使用以下公式:

在这里插入图片描述

在这里,w 的存在通过使用单调坐标系(和 w=Z)来解释。未知参数是 f_x 和 f_y(相机焦距)和 (c_x, c_y),它们是以像素坐标表示的光学中心。如果对于两个轴,使用具有给定纵横比(通常为 1)的公共焦距,则 f_y=f_xa,在上面的公式中,我们将有一个焦距 f。包含这四个参数的矩阵称为相机矩阵*。虽然无论使用何种相机分辨率,失真系数都是相同的,但这些失真系数应与校准分辨率的当前分辨率一起缩放。ww=Zfxfy(cx,cy)一个fy=fx∗af

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

经典黑白棋盘ChArUco板图案对称圆形图案不对称圆形图案

基本上,您需要用相机拍摄这些模式的快照,并让 OpenCV 找到它们。每个找到的模式都会产生一个新方程。要求解方程,您至少需要预定数量的模式快照来形成一个适配方程组。这个数字对于棋盘模式来说较高,而对于圆盘模式来说,这个数字较小。例如,从理论上讲,棋盘模式至少需要两个快照。然而,在实践中,我们的输入图像中存在大量的噪点,因此为了获得良好的效果,您可能需要至少 10 张不同位置的输入模式的良好快照。

目标

示例应用程序将:

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

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

应用程序从配置文件中读取设置后启动。虽然这是其中的一个重要部分,但它与本教程的主题无关:相机校准。因此,我选择不在此处发布该部分的代码。有关如何执行此操作的技术背景,请参阅使用 XML 和 YAML 文件的文件输入和输出教程。

解释

阅读设置

设置 s;

​ const string inputSettingsFile = parser.get(0);

FileStorage fs(inputSettingsFile, FileStorage::READ);读取设置

​ 如果 (!fs.isOpened())

​ {

cout

s.delay1e-3CLOCKS_PER_SEC) ) )

​ {

imagePoints.push_back(点);

prevTimestamp = 时钟();

blinkOutput = s.inputCapture.isOpened();

​ }

​ 画出角落。

​ if(s.calibrationPattern == 设置::CHARUCOBOARD)

​ drawChessboardCorners( view, cv::Size(s.boardSize.width-1, s.boardSize.height-1), Mat(pointBuf), 找到 );

​ 还

​ drawChessboardCorners( view, s.boardSize, Mat(pointBuf), 找到 );

​ }

向用户显示状态和结果,以及应用程序的命令行控制

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

​ string msg = (mode == CAPTURING) ?“100/100”:

模式 == 校准 ?“Calibrated” : “按’g’开始”;

​ int 基线 = 0;

​ 尺寸 textSize = getTextSize(msg, 1, 1, 1, &baseLine);

​ 点 textOrigin(view.cols - 2textSize.width - 10, view.rows - 2baseLine - 10);

​ if( 模式 == 捕获 )

​ {

​ if(s.showUndistorted)

msg = cv::format( “%d/%d Undist”, (int)imagePoints.size(), s.nrFrames );

​ 还

msg = cv::format( “%d/%d”, (int)imagePoints.size(), s.nrFrames );

​ }

​ putText( view, msg, textOrigin, 1, 1, mode == 校准 ?绿色:红色);

​ 如果( blink输出 )

​ bitwise_not(视图,视图);

如果我们运行校准并得到带有失真系数的相机矩阵,我们可能需要使用 cv::undistort 函数校正图像:

​ if( mode == 校准 && s.showUndistorted )

​ {

垫温度 = view.clone();

​ 如果 (s.useFisheye)

​ {

垫子 newCamMat;

​ fisheye::estimateNewCameraMatrixForUndistortRectify(cameraMatrix, distCoeffs, imageSize,

Matx33d::eye(), newCamMat, 1);

​ cv::fisheye::undistortImage(temp, view, cameraMatrix, distCoeffs, newCamMat);

​ }

​ 还

​ undistort(temp, view, cameraMatrix, distCoeffs);

​ }

然后我们显示图像并等待输入键,如果这是 u,我们切换失真消除,如果是 g,我们再次开始检测过程,最后对于 ESC 键,我们退出应用程序:

​ imshow(“图像视图”, 视图);

​ char key = (char)waitKey(s.inputCapture.isOpened() ? 50 : s.delay);

​ if( 键 == ESC_KEY )

​ 破;

​ if( key == ‘u’ && mode == 校准 )

s.showUndistorted = !s.showUndistorted;

​ if( s.inputCapture.isOpened() && 键 == ‘g’ )

​ {

mode = 捕获;

imagePoints.clear();

​ }

同时显示图像的失真消除

使用图像列表时,无法消除循环内部的失真。因此,您必须在循环之后执行此操作。现在,我将利用这一点来扩展 cv::undistort 函数,该函数实际上首先调用 cv::initUndistortRectifyMap 来查找变换矩阵,然后使用 cv::remap 函数执行变换。因为,在成功校准图计算后,只需执行一次,因此通过使用此扩展表单,您可以加快应用速度:

​ if( s.inputType == Settings::IMAGE_LIST && s.showUndistorted && !cameraMatrix.empty())

​ {

垫视图、rview、map1、map2;

​ 如果 (s.useFisheye)

​ {

垫子 newCamMat;

​ fisheye::estimateNewCameraMatrixForUndistortRectify(cameraMatrix, distCoeffs, imageSize,

Matx33d::eye(), newCamMat, 1);

​ fisheye::initUndistortRectifyMap(cameraMatrix, distCoeffs, Matx33d::eye(), newCamMat, imageSize,

​ CV_16SC2, map1, map2);

​ }

​ 还

​ {

​ 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++ )

​ {

视图 = imread(s.imageList[i], IMREAD_COLOR);

​ 如果(view.empty())

​ 继续;

​ remap(view, rview, map1, map2, INTER_LINEAR);

​ imshow(“图像视图”, rview);

​ char c = (char)waitKey();

​ if( c == ESC_KEY || c == ‘q’ || c == ‘Q’ )

​ 破;

​ }

​ }

校准和保存

由于每台相机只需进行一次校准,因此在校准成功后保存校准是有意义的。这样,以后您就可以将这些值加载到程序中。因此,我们首先进行校准,如果校准成功,我们将结果保存到 OpenCV 样式的 XML 或 YAML 文件中,具体取决于您在配置文件中提供的扩展名。

因此,在第一个函数中,我们只是拆分了这两个进程。由于我们想要保存许多校准变量,因此我们将在此处创建这些变量,并将这两个变量传递给校准和保存函数。同样,我不会显示保存部分,因为这与校准几乎没有共同之处。浏览源文件,以了解如何以及内容:

bool runCalibrationAndSave(Settings&s, Size imageSize, Mat&cameraMatrix, Mat&distCoeffs,

vectorimagePoints、float grid_width、bool release_object)

{

vectorrvecs、tvecs;

vector reprojErrs;

​ 双倍总计AvgErr = 0;

vector newObjPoints;

​ bool ok = runCalibration(s, imageSize, cameraMatrix, distCoeffs, imagePoints, rvecs, tvecs, reprojErrs,

totalAvgErr, newObjPoints, grid_width, release_object);

cout



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3