使用 OpenCV 进行相机校准 |
您所在的位置:网站首页 › c3200校正相机 › 使用 OpenCV 进行相机校准 |
相机已经存在了很长时间。然而,随着 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 |