用OpenCV进行摄像机标定
用OpenCV进行摄像机标定
照相机已经存在很长时间了。然而,随着廉价针孔相机在20世纪末的引入,日常生活中变得司空见惯。不幸的是,这种廉价伴随着它的代价:显著的扭曲。幸运的是,这些常数,通过校准和一些重新映射,可以纠正这一点。此外,通过校准,还可以确定相机的自然单位(像素)和真实世界单位(例如毫米)之间的关系。
原理
对于畸变,OpenCV考虑了径向和切向因素。对于径向,使用以下公式:
So for an old pixel point at coordinate in the input image, for a corrected output image its position will be .
径向畸变的存在表现为“桶”或“鱼眼”效应。
由于摄像镜头与成像平面不完全平行,因此会发生切向失真。通过以下公式进行修正:
有五个失真参数,在OpenCV中被组织在一个1行5列的矩阵中:
Now for the unit conversion, we use the following formula:
Here the presence of the is cause we use a homography coordinate system (and
). The unknown parameters are
and
(camera focal lengths) and
what are the optical centers expressed in pixels coordinates. If for both axes a common focal length is used with a given
aspect ratio (usually 1), then
and in the upper formula we will have a single focal length. 包含这四个参数的矩阵称为摄像机矩阵。尽管失真系数是相同的,不管使用何种摄像头分辨率,但这些失真系数应与当前校准分辨率一起缩放。
确定这两个矩阵的变换过程就是校准。这些参数的计算是通过一些基本的几何方程来完成的。使用的方程式,取决于使用的校准对象。目前OpenCV支持三种类型的对象进行校准:
经典黑白棋盘
对称圆模式
不对称圆形图案
基本上,需要用相机拍下这些图案的快照,然后让OpenCV找到它们。在一个新的方程中,每一个可见的模式都相等。为了解这个方程,至少需要预定数量的模式快照,来形成一个适配的方程组。这个数字具有较高的棋盘图案和较少的圆的。例如,在理论上,一个棋盘至少需要两个快照。然而,实际上,输入图像中存在大量的噪声,因此为了获得好的结果,可能需要至少10个不同位置的输入模式的良好快照。
目标
示例应用程序将:
确定失真矩阵
确定摄像机矩阵
摄像机、视频和图像文件列表的输入
从XML/YAML文件进行配置
将结果保存到XML/YAML文件中
计算重投影误差
Source code
You may also find the source code in the samples/cpp/tutorial_code/calib3d/camera_calibration/ folder of the OpenCV source library or download it from here.
可以在OpenCV源代码库的samples/cpp/tutorial\u code/calib3d/camera\u calibration/文件夹中找到源代码。程序只有一个参数。其配置文件的名称。如果没有,它将尝试打开一个名为“default.xml”. 下面是一个XML格式的示例配置文件。在配置文件中,可以选择使用相机、视频文件或图像列表作为输入。如果选择后者,则需要创建一个配置文件,在其中枚举要使用的映像。下面是一个例子。需要记住的重要一点是,需要使用应用程序工作目录中的绝对路径或相对路径来指定映像。可以在前面提到的目录中找到所有这些。
应用程序首先从配置文件中读取设置。尽管这是其中的一个重要部分,但与本文的主题无关:相机校准。因此,选择不在这里发布代码部分。关于如何做到这一点的技术背景可以在XML YAML中找到。
Explanation
- Read the settings.
- Settings s;
- const string inputSettingsFile = argc > 1 ? argv[1] : "default.xml";
- FileStorage fs(inputSettingsFile, FileStorage::READ); // Read the settings
- if (!fs.isOpened())
- {
- cout << "Could not open the configuration file: \"" << inputSettingsFile << "\"" << endl;
- return -1;
- }
10.fs["Settings"] >> s;
11.fs.release(); // close Settings file
13.if (!s.goodInput)
14.{
- cout << "Invalid input detected. Application stopping. " << endl;
- return -1;
17.}
使用简单的OpenCV类输入操作。在读取文件之后,有一个额外的后处理函数,来检查输入的有效性。只有当它们都正常,goodInput变量才是正常。
获取下一个输入,如果失败,或者有足够的输入。此后,有一个大的循环,在这里,做以下操作:从图像列表中获取下一个图像,相机或视频文件。如果这失败了,或者有足够的图像,运行校准过程。在图像的情况下,步出循环,否则其余的帧将不失真(如果选项设置),通过改变检测模式到校准一。
18.for(int i = 0;;++i)
19.{
- Mat view;
- bool blinkOutput = false;
- view = s.nextImage();
- //----- If no more image, or got enough, then stop calibration and show result -------------
- if( mode == CAPTURING && imagePoints.size() >= (unsigned)s.nrFrames )
- {
- if( runCalibrationAndSave(s, imageSize, cameraMatrix, distCoeffs, imagePoints))
- mode = CALIBRATED;
- else
- mode = DETECTION;
- }
- if(view.empty()) // If no more images then run calibration, save and stop loop.
- {
- if( imagePoints.size() > 0 )
- runCalibrationAndSave(s, imageSize, cameraMatrix, distCoeffs, imagePoints);
- break;
- imageSize = view.size(); // Format input image.
- if( s.flipVertical ) flip( view, view, 0 );
- }
For some cameras we may need to flip the input image. Here we do this too.
- Find the pattern in the current input. The formation of the equations I mentioned above consists of finding the major patterns in the input: in case of the chessboard this is their corners of the squares and for the circles, well, the circles itself. The position of these will form the result and is collected into the pointBuf vector.
42.vector<Point2f> pointBuf;
44.bool found;
45.switch( s.calibrationPattern ) // Find feature points on the input format
46.{
47.case Settings::CHESSBOARD:
- found = findChessboardCorners( view, s.boardSize, pointBuf,
- CV_CALIB_CB_ADAPTIVE_THRESH | CV_CALIB_CB_FAST_CHECK | CV_CALIB_CB_NORMALIZE_IMAGE);
- break;
51.case Settings::CIRCLES_GRID:
- found = findCirclesGrid( view, s.boardSize, pointBuf );
- break;
54.case Settings::ASYMMETRIC_CIRCLES_GRID:
- found = findCirclesGrid( view, s.boardSize, pointBuf, CALIB_CB_ASYMMETRIC_GRID );
- break;
57.}
根据输入模式的类型,可以使用findChessboardCorners或findCirclesGrid函数。对于这两种情况,会传递当前的图像、电路板的大小,会得到图案的位置。此外,还返回一个布尔变量,说明在输入中是否可以找到模式(只需要考虑图像中的情况)。
同样,对于相机,只在输入延迟时间,过后才拍摄相机图像。用户移动棋盘,获得不同的图像。相同的图像意味着相同的方程,在标定时,相同的方程将形成不适定问题,因此标定将失败。对于正方形图像,角点的位置只是近似值。可以通过调用cornerSubPix函数来改进这一点。这样可以得到更好的标定结果。之后,将一个有效的输入结果添加到imagePoints向量中,将所有方程收集到一个容器中。最后,为了实现可视化反馈,将使用findChessboardCorners函数在输入图像上绘制找到的点。
if ( found) // If done with success,
{
// improve the found corners' coordinate accuracy for chessboard
if( s.calibrationPattern == Settings::CHESSBOARD)
{
Mat viewGray;
cvtColor(view, viewGray, CV_BGR2GRAY);
cornerSubPix( viewGray, pointBuf, Size(11,11),
Size(-1,-1), TermCriteria( CV_TERMCRIT_EPS+CV_TERMCRIT_ITER, 30, 0.1 ));
}
if( mode == CAPTURING && // For camera only take new samples after delay time
(!s.inputCapture.isOpened() || clock() - prevTimestamp > s.delay*1e-3*CLOCKS_PER_SEC) )
{
imagePoints.push_back(pointBuf);
prevTimestamp = clock();
blinkOutput = s.inputCapture.isOpened();
}
// Draw the corners.
drawChessboardCorners( view, s.boardSize, Mat(pointBuf), found );
}
为用户显示状态和结果,以及应用程序的命令行控制。显示部分由实时文本输出组成,对于要显示“捕获”帧的视频或相机输入,只需按位求反输入图像。
58.//----------------------------- Output Text ------------------------------------------------
59.string msg = (mode == CAPTURING) ? "100/100" :
- mode == CALIBRATED ? "Calibrated" : "Press 'g' to start";
61.int baseLine = 0;
62.Size textSize = getTextSize(msg, 1, 1, 1, &baseLine);
63.Point textOrigin(view.cols - 2*textSize.width - 10, view.rows - 2*baseLine - 10);
65.if( mode == CAPTURING )
66.{
- if(s.showUndistorsed)
- msg = format( "%d/%d Undist", (int)imagePoints.size(), s.nrFrames );
- else
- msg = format( "%d/%d", (int)imagePoints.size(), s.nrFrames );
71.}
73.putText( view, msg, textOrigin, 1, 1, mode == CALIBRATED ? GREEN : RED);
75.if( blinkOutput )
- bitwise_not(view, view);
If we only ran the calibration and got the camera matrix plus the distortion coefficients we may just as correct the image with the undistort function:
//------------------------- Video capture output undistorted ------------------------------
if( mode == CALIBRATED && s.showUndistorsed )
{
Mat temp = view.clone();
undistort(temp, view, cameraMatrix, distCoeffs);
}
//------------------------------ Show image and check for input commands -------------------
imshow("Image View", view);
Then we wait for an input key and if this is u we toggle the distortion removal, if it is g we start all over the detection process (or simply start it), and finally for the ESC key quit the application:
char key = waitKey(s.inputCapture.isOpened() ? 50 : s.delay);
if( key == ESC_KEY )
break;
if( key == 'u' && mode == CALIBRATED )
s.showUndistorsed = !s.showUndistorsed;
if( s.inputCapture.isOpened() && key == 'g' )
{
mode = CAPTURING;
imagePoints.clear();
}
显示图像的失真消除。使用图像列表时,无法消除循环中的失真。因此,必须在循环之后附加。利将展开无失真函数,实际上是首先调用initundortrectitymap来找出变换矩阵,然后用remap函数进行变换。在成功校准后,地图计算只需进行一次,通过使用此扩展表格,可以加快应用程序的速度:
77.if( s.inputType == Settings::IMAGE_LIST && s.showUndistorsed )
78.{
- Mat view, rview, map1, map2;
- initUndistortRectifyMap(cameraMatrix, distCoeffs, Mat(),
- getOptimalNewCameraMatrix(cameraMatrix, distCoeffs, imageSize, 1, imageSize, 0),
- imageSize, CV_16SC2, map1, map2);
- for(int i = 0; i < (int)s.imageList.size(); i++ )
- {
- view = imread(s.imageList[i], 1);
- if(view.empty())
- continue;
- remap(view, rview, map1, map2, INTER_LINEAR);
- imshow("Image View", rview);
- char c = waitKey();
- if( c == ESC_KEY || c == 'q' || c == 'Q' )
- break;
- }
95.}
The calibration and save
每个摄像机只需要校准一次,在成功校准后,保存它们是有意义的。这样以后就可以将这些值加载到程序中。因此,首先进行校准,如果校准成功,将结果保存到OpenCV样式的XML或YAML文件中,具体取决于在配置文件中给出的扩展名。
因此,在第一个函数中,只是将这两个过程分开。因为想保存许多校准变量,所以将在这里创建这些变量,并将它们传递给校准和保存函数。再次,将不显示保存部分,因为与校准几乎没有共同点。浏览源文件以了解如何和内容:
bool runCalibrationAndSave(Settings& s, Size imageSize, Mat& cameraMatrix, Mat& distCoeffs,vector<vector<Point2f> > imagePoints )
{
vector<Mat> rvecs, tvecs;
vector<float> reprojErrs;
double totalAvgErr = 0;
bool ok = runCalibration(s,imageSize, cameraMatrix, distCoeffs, imagePoints, rvecs, tvecs,
reprojErrs, totalAvgErr);
cout << (ok ? "Calibration succeeded" : "Calibration failed")
<< ". avg re projection error = " << totalAvgErr ;
if( ok ) // save only if the calibration was done with success
saveCameraParams( s, imageSize, cameraMatrix, distCoeffs, rvecs ,tvecs, reprojErrs,
imagePoints, totalAvgErr);
return ok;
}
在calibleCamera函数进行校准。具有以下参数:
物体角点。这是点3f向量的向量,对于每个输入图像,它描述了模式的外观。如果有一个平面图案(如棋盘),可以简单地将所有Z坐标设置为零。这是这些要点所在的要点的集合。对所有的输入图像使用一个模式,只需计算一次,然后乘以所有其他的输入视图。使用CalcBoardCornePositions函数计算角点,如下所示:
- 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(float( j*squareSize ), float( i*squareSize ), 0));
- break;
- case Settings::ASYMMETRIC_CIRCLES_GRID:
- for( int i = 0; i < boardSize.height; i++ )
- for( int j = 0; j < boardSize.width; j++ )
- corners.push_back(Point3f(float((2*j + i % 2)*squareSize), float(i*squareSize), 0));
- break;
- }
- }
And then multiply it as:
vector<vector<Point3f> > objectPoints(1);
calcBoardCornerPositions(s.boardSize, s.squareSize, objectPoints[0], s.calibrationPattern);
objectPoints.resize(imagePoints.size(),objectPoints[0]);
- 图像点。这是点2f向量的向量,对于每个输入图像,该向量包含找到重要点(棋盘的角点和圆模式的圆心)的位置。已经从findChessboardCorners或findCirclesGrid函数返回的内容中,收集了这个。只需要把它传下去。
- 从相机、视频文件或图像中获取的图像大小。
- 摄像机矩阵。如果使用“固定纵横比”选项,则需要将设置
为零:
- cameraMatrix = Mat::eye(3, 3, CV_64F);
- if( s.flag & CV_CALIB_FIX_ASPECT_RATIO )
- cameraMatrix.at<double>(0,0) = 1.0;
- The distortion coefficient matrix. Initialize with zero.
- distCoeffs = Mat::zeros(8, 1, CV_64F);
- 该函数将为所有视图计算旋转和平移矢量,该矢量将对象点(在模型坐标空间中给出)转换为图像点(在世界坐标空间中给出)。第7和第8参数是矩阵的输出向量,该矩阵在第i个位置中,包含第i个目标角点到第i个图像点的旋转和平移向量。
- 最后一个参数是flag。需要在这里指定选项,如固定焦距的纵横比、假设零切向失真或固定主点。
double rms = calibrateCamera(objectPoints, imagePoints, imageSize, cameraMatrix,
distCoeffs, rvecs, tvecs, s.flag|CV_CALIB_FIX_K4|CV_CALIB_FIX_K5);
- 函数返回平均重投影误差。这个数字很好地估计了,找到的参数到底有多精确。应该尽可能接近于零。在给定内禀矩阵、畸变矩阵、旋转矩阵和平移矩阵的情况下,可以利用投影点,将物体点变换为像点,来计算一个视图的误差。然后,计算得到的绝对范数之间的转换和角/圆发现算法。为了找出平均误差,计算了所有校准图像的误差算术平均值。
- 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)
- {
- vector<Point2f> imagePoints2;
- int i, totalPoints = 0;
- double totalErr = 0, err;
- perViewErrors.resize(objectPoints.size());
- for( i = 0; i < (int)objectPoints.size(); ++i )
- {
- projectPoints( Mat(objectPoints[i]), rvecs[i], tvecs[i], cameraMatrix, // project
- distCoeffs, imagePoints2);
- err = norm(Mat(imagePoints[i]), Mat(imagePoints2), CV_L2); // difference
- int n = (int)objectPoints[i].size();
- perViewErrors[i] = (float) std::sqrt(err*err/n); // save for this view
- totalErr += err*err; // sum it up
- totalPoints += n;
- }
- return std::sqrt(totalErr/totalPoints); // calculate the arithmetical mean
- }
Results
设置这个输入棋盘图案的大小为9 X 6。用一个AXIS IP摄像头,创建几个板的快照,并将其保存到VID5目录中。我已将其放入工作目录的images/cameracalibration文件夹中,并创建了以下VID5.XML文件,该文件描述了要使用的图像:
<?xml version="1.0"?>
<opencv_storage>
<images>
images/CameraCalibraation/VID5/xx1.jpg
images/CameraCalibraation/VID5/xx2.jpg
images/CameraCalibraation/VID5/xx3.jpg
images/CameraCalibraation/VID5/xx4.jpg
images/CameraCalibraation/VID5/xx5.jpg
images/CameraCalibraation/VID5/xx6.jpg
images/CameraCalibraation/VID5/xx7.jpg
images/CameraCalibraation/VID5/xx8.jpg
</images>
</opencv_storage>
然后将images/cameracalibration/VID5/VID5.XML指定为配置文件中的输入。在应用程序运行时,可见棋盘模式:
应用失真消除后,得到:
通过将“输入宽度”(input width)设置为4,将“高度”(height)设置为11,同样的方法也适用于这种不对称的圆形图案。使用了一个实时摄像机feed,为输入指定了它的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>
将这些值作为常量添加到程序中,调用initundortrectitymap和remap函数以消除失真,并使用廉价和低质量的相机享受无失真输入。
用OpenCV进行摄像机标定的更多相关文章
- [OpenCV-Python] OpenCV 中摄像机标定和 3D 重构 部分 VII
部分 VII摄像机标定和 3D 重构 OpenCV-Python 中文教程(搬运)目录 42 摄像机标定 目标 • 学习摄像机畸变以及摄像机的内部参数和外部参数 • 学习找到这些参数,对畸变图像进行修 ...
- 【OpenCV】摄像机标定+畸变校正
摄像机标定 本文目的在于记录如何使用MATLAB做摄像机标定,并通过OpenCV进行校正后的显示. 首先关于校正的基本知识通过OpenCV官网的介绍即可简单了解: http://docs.open ...
- 机器视觉学习笔记(5)——基于OpenCV的单目摄像机标定
本文CameraCalibrator类源代码来自于OpenCV2 计算机视觉编程手册(Robert Laganiere 著 张静 译) 强烈建议阅读机器视觉学习笔记(4)--单目摄像机标定参数说明之后 ...
- [zt]摄像机标定(Camera calibration)笔记
http://www.cnblogs.com/mfryf/archive/2012/03/31/2426324.html 一 作用建立3D到2D的映射关系,一旦标定后,对于一个摄像机内部参数K(光心焦 ...
- 使用OpenCV进行相机标定
1. 使用OpenCV进行标定 相机已经有很长一段历史了.但是,伴随着20世纪后期的廉价针孔照相机的问世,它们已经变成我们日常生活的一种常见的存在.不幸的是,这种廉价是由代价的:显著的变形.幸运的是, ...
- Matlab 摄像机标定+畸变校正
博客转载自:http://blog.csdn.net/Loser__Wang/article/details/51811347 本文目的在于记录如何使用MATLAB做摄像机标定,并通过opencv进行 ...
- 基于EmguCV的摄像机标定及矫正
标签: EmguCV摄像头标定C# 2015-05-03 14:55 501人阅读 评论(6) 收藏 举报 分类: C# 版权声明:本文为博主原创文章,未经博主允许不得转载. 目录(?)[+] ...
- 张正友相机标定Opencv实现以及标定流程&&标定结果评价&&图像矫正流程解析(附标定程序和棋盘图)
使用Opencv实现张正友法相机标定之前,有几个问题事先要确认一下,那就是相机为什么需要标定,标定需要的输入和输出分别是哪些? 相机标定的目的:获取摄像机的内参和外参矩阵(同时也会得到每一幅标定图像的 ...
- halcon摄像机标定
摄像机标定程序: 注意:E:/calibration_image :为标定图像文件路径 'E:/calibration_description/caltab_123mm.descr:为标定 ...
随机推荐
- hdu4756 最小树+树形dp
题意: 给你一个完全图,让你在上面找到一颗最小树,然后问破坏这个最小树的某一条边后用其他边连接(要求最小)成新的树,然后输出破坏每一条边后最小树中最大的那个. 思路: 先跑出一 ...
- hdu1572 水搜索
题意: 中文的不解释; 思路: 其实就是一个水的搜索,直接搜索不会超时,还有别跑最短路了,题目没要求跑最短路,别读错题,刚开始自己嘚嗖的跑了一边最短路 wa了 ,正好最近看了STL ...
- SVCHOST启动服务实战
本文转载自:https://blog.csdn.net/huanglong8/article/details/70666987 转载出处: https://sanwen8.cn/p/2cenbHs.h ...
- Python脚本破解压缩文件口令(zipfile)
环境:Windows python版本2.7.15 Python中操作zip压缩文件的模块是 zipfile . 相关文章:Python中zipfile压缩文件模块的使用 我们破解压缩文件的口令也是用 ...
- 易酷CMS2.5本地文件包含漏洞复现
易酷CMS是一款影片播放CMS.该CMS2.5版本存在本地文件包含漏洞.我们可以利用这个漏洞,让其包含日志文件,然后再利用报错信息将一句话木马写入日志中.然后利用文件包含漏洞包含该日志文件,再用菜刀连 ...
- spring-boot-maven-plugin not found的解决方案
spring-boot-maven-plugin not found 在maven测试的生命周期都没有错,但是就是爆红 参考了很多的链接,没有成功解决,最后得到真正有帮助的方法,添加springboo ...
- Linux的基础操作
1.概念 Linux是基于Unix的开源免费的操作系统,由于系统的稳定性和安全性几乎成为程序代码运行的最佳系统环境. 2.Linux的分类 1.按市场需求分为: 图形化界面版.服务器版 2.按原生程度 ...
- What are CBR, VBV and CPB?
转自:https://codesequoia.wordpress.com/2010/04/19/what-are-cbr-vbv-and-cpb/ It's common mistake to to ...
- 【BUAA软工】Beta阶段测试报告
项目 内容 班级:北航2020春软件工程 博客园班级博客 作业:Beta阶段测试报告 测试报告 发现的bug Beta阶段的bug [已解决]CPP项目无法进行调试 [已解决] 注册界面注册后不能够自 ...
- PHPcms v9.6.0 文件上传漏洞
title: PHPcms v9.6.0 文件上传漏洞 date: 2021-4-5 tags: 渗透测试,CVE漏洞复现,文件上传 categories: 渗透测试 CVE漏洞复现 文件上传 PHP ...