前言

  通过相机图片可以识别出棋盘角点了,这时候我们需要通过角点去计算相机内参矩阵,通过上篇得知畸变的原理,所以我们尽可能要全方位都能获取标定图片,全方位意思是提供的多张图综合起来基本覆盖了相机所有的像素,同时还要注意远近和斜着
  本篇通过一张图片来识别计算得到相机内参矩阵,并矫正相机畸形。

 

补充

  做项目一定要多张且基本覆盖相机所有区域,要保证每一张截取的图片也要被识别,可以做成个软件,识别出棋盘都在一个预先指定的区域内则截图,然后下一个区域,实现半自动半人工化标定。

 

Demo

  

  这里只用了一张图校准,所以可能内参矩阵经度不那么高:
   

 

一张图校准的实例

   注意:这里demo只使用了可识别的一张图作为计算,可能没覆盖的区域则出现不可预期的图像问题。

步骤一:世界坐标系初始化

   这里是直接填充行列的坐标,第三个是z坐标直接设置为0,为视口处:

// 步骤八:角点对应的三维坐标(一张图一组)
std::vector<std::vector<cv::Point3f>> vectorObjectPoint;
std::vector<cv::Point3f> objectPoints; // 三维世界坐标系
for(int i = 0; i < chessboardRowCornerCount; i++)
{
for(int j = 0; j < chessboardColCornerCount; j++)
{
objectPoints.push_back(cv::Point3f(j, i, 0));
}
}
vectorObjectPoint.push_back(objectPoints);

步骤二:识别的角点放入列表

   多张图放入多次,这里只有一张图:

// 步骤九:图像识别出来的角点(一张图一组)
std::vector<std::vector<cv::Point2f>> vectorImagePoint;
vectorImagePoint.push_back(vectorPoint2fCorners);

步骤三:计算内参和畸变系数

   输出的参数有点多,输入的参数却不多:

// 步骤十:计算内参和畸变系数
cv::Mat cameraMatrix; // 相机矩阵(接收输出)
cv::Mat distCoeffs; // 畸变系数(接收输出)
cv::Mat Rotate; // 旋转量(接收输出)
cv::Mat Translate; // 偏移量(接收输出)
cv::calibrateCamera(vectorObjectPoint,
vectorImagePoint,
grayMat.size(),
cameraMatrix,
distCoeffs,
Rotate,
Translate);
std::cout << "cameraMatrix:" << std::endl;
std::cout << cameraMatrix << std::endl; std::cout << "distCoeffs:" << std::endl;
std::cout << distCoeffs << std::endl; std::cout << "Rotate:" << std::endl;
std::cout << Rotate << std::endl; std::cout << "Translate:" << std::endl;
std::cout << Translate << std::endl;

步骤四:畸变函数校准

   这里校准相对容易,所以难点在于标定校准,做项目肯定要自己写一个标定软件了,每次这么手动查看校准肯定不行的。

// 步骤十一:畸变图像校准
cv::Mat dstMat;
cv::undistort(srcMat, dstMat, cameraMatrix, distCoeffs);
cv::imshow("6", dstMat);
 

函数原型

calibrateCamera:相机标定求解函数

   OpenCV中的一个函数,用于相机标定。相机标定是估计相机内参(如焦距、主点坐标等)和畸变系数的过程,这些参数对于后续的图像处理任务(如三维重建、目标跟踪等)至关重要。

double calibrateCamera(InputArrayOfArrays objectPoints,
InputArrayOfArrays imagePoints,
Size imageSize,
OutputArray cameraMatrix,
OutputArray distCoeffs,
OutputArray rvecs,
OutputArray tvecs,
int flags=0,
TermCriteria criteria=TermCriteria(TermCriteria::COUNT+TermCriteria::EPS, 30, 1e-6));

   参数说明:

  • objectPoints:世界坐标系中的三维点。通常,这些点是通过在标定板上定义的一系列点来获取的,这些点的坐标是已知的。对于每个图像,它应该是一个 Nx3 的数组(或数组列表),其中 N 是点的数量,而 3 表示每个点的 (X, Y, Z) 坐标。
  • imagePoints:图像坐标系中的二维点,即对应于 objectPoints 中的三维点在图像中的投影。对于每个图像,它应该是一个 Nx2 的数组(或数组列表),其中 N 是点的数量,而 2 表示每个点的 (x, y) 坐标。
  • imageSize:图像的大小,表示为 Size 类型的对象,包含图像的宽度和高度。
  • cameraMatrix:输出参数,存储 3x3 的相机内参矩阵。
  • distCoeffs:输出参数,存储畸变系数。通常有 5 个系数(k1, k2, p1, p2, k3)对于径向和切向畸变,或 8 个系数(k1, k2, k3, k4, k5, k6, p1, p2)对于鱼眼相机模型。
  • rvecs:输出参数,对于每个图像,存储旋转向量的数组。
  • tvecs:输出参数,对于每个图像,存储平移向量的数组。
  • flags:不同标志的组合,用于指定标定过程中使用的算法。
    CV_CALIB_USE_INTRINSIC_GUESS:使用该参数时,将包含有效的fx,fy,cx,cy的估计值的内参矩阵cameraMatrix,作为初始值输入,然后函数对其做进一步优化。如果不使用这个参数,用图像的中心点初始化光轴点坐标(cx, cy),使用最小二乘估算出fx,fy(这种求法好像和张正友的论文不一样,不知道为何要这样处理)。注意,如果已知内部参数(内参矩阵和畸变系数),就不需要使用这个函数来估计外参,可以使用solvepnp()函数计算外参数矩阵。
    CV_CALIB_FIX_PRINCIPAL_POINT:在进行优化时会固定光轴点,光轴点将保持为图像的中心点。当CV_CALIB_USE_INTRINSIC_GUESS参数被设置,保持为输入的值。
    CV_CALIB_FIX_ASPECT_RATIO:固定fx/fy的比值,只将fy作为可变量,进行优化计算。当 CV_CALIB_USE_INTRINSIC_GUESS没有被设置,fx和fy的实际输入值将会被忽略,只有fx/fy的比值被计算和使用。
    CV_CALIB_ZERO_TANGENT_DIST:切向畸变系数(P1,P2)被设置为零并保持为零。
    CV_CALIB_FIX_K1,…,CV_CALIB_FIX_K6:对应的径向畸变系数在优化中保持不变。如果设置了CV_CALIB_USE_INTRINSIC_GUESS参数,就从提供的畸变系数矩阵中得到。否则,设置为0。
    CV_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。如果cv_calib_use_intrinsic_guess设置,使用提供的畸变系数矩阵中的值。否则,设置为0。
    CALIB_TILTED_MODEL (倾斜模型):启用畸变系数tauX and tauY。标定函数使用倾斜传感器模型并返回14个系数。如果不设置标志,则函数计算并返回只有5个失真系数。
    CALIB_FIX_TAUX_TAUY :在优化过程中,倾斜传感器模型的系数不被改变。如果cv_calib_use_intrinsic_guess设置,从提供的畸变系数矩阵中得到。否则,设置为0。
  • criteria:迭代优化的终止条件。通常包含最大迭代次数和收敛的精度。

   这个函数返回一个双精度浮点数,表示重投影误差的估计值,即实际图像点与通过相机参数和畸变系数计算出的图像点之间的平均误差。
   为了获得准确的相机标定结果,通常需要多个视图(即多张不同角度和姿态拍摄的标定板图像),**并确保标定板在不同图像中占据足够的视场。**此外,图像应该清晰,且标定板上的特征点(如棋盘格的角点)应准确检测。

initUndistortRectifyMap:计算畸变参数

   OpenCV中用于初始化用于图像去畸变和校正的映射表的函数。这个函数的目的是生成两个映射,一个用于x坐标,另一个用于y坐标,它们可以被用于 remap函数来校正图像的畸变。

void initUndistortRectifyMap(InputArray cameraMatrix,
InputArray distCoeffs,
InputArray R,
InputArray newCameraMatrix,
Size size,
int m1type,
OutputArray map1,
OutputArray map2)

   参数说明

  • cameraMatrix:相机的内参矩阵,一个3x3的浮点数矩阵。
  • distCoeffs:畸变系数,一个1x5或1x8的向量,包含径向和切向畸变系数。
  • R:可选的旋转矩阵,一个3x3的浮点数矩阵,表示从原相机坐标系到新的相机坐标系的旋转。如果这个参数是空的,那么newCameraMatrix必须是cameraMatrix。
  • newCameraMatrix:新的相机内参矩阵,一个3x3的浮点数矩阵。这个矩阵可以是原始相机矩阵,或者经过getOptimalNewCameraMatrix调整后的矩阵,以考虑图像的有效视场。
  • size:输出映射的尺寸,表示为Size类型的对象,包含图像的宽度和高度。
  • m1type:输出映射的类型,可以是CV_32FC1或CV_16SC2。
  • map1:输出的第一个映射,用于x坐标,可以被传递给remap函数。
  • map2:输出的第二个映射,用于y坐标,可以被传递给remap函数。

   这两个映射map1和map2可以被传递给remap函数,以对图像进行去畸变和校正。
   如果有一个畸变的图像distortedImage和想要得到校正后的图像undistortedImage,可以这样使用这两个函数:

Mat map1,map2;
initUndistortRectifyMap(cameraMatrix, distCoeffs, R, newCameraMatrix, size, CV_32FC1, map1, map2);
remap(distortedImage, undistortedImage, map1, map2, INTER_LINEAR);

   在这个例子中,INTER_LINEAR是插值方法的类型,用于remap函数。其他的插值方法,如INTER_NEAREST、INTER_CUBIC等也可以被使用,具体取决于应用需求。

 

Demo源码

void OpenCVManager::testCorrectingChessboard()
{
#define TestCorrectingChessboardUseCamera 0
#if !TestCorrectingChessboardUseCamera
// 使用图片
// std::string srcFilePath = "D:/qtProject/openCVDemo/openCVDemo/modules/openCVManager/images/chessboard.png";
// std::string srcFilePath = "D:/qtProject/openCVDemo/openCVDemo/modules/openCVManager/images/24.jpg";
// std::string srcFilePath = "D:/qtProject/openCVDemo/openCVDemo/modules/openCVManager/images/27.png";
// std::string srcFilePath = "D:/qtProject/openCVDemo/openCVDemo/modules/openCVManager/images/28.png";
std::string srcFilePath = "D:/qtProject/openCVDemo/openCVDemo/modules/openCVManager/images/28.jpg";
cv::Mat srcMat = cv::imread(srcFilePath);
#else
// 使用摄像头
cv::VideoCapture capture;
// 插入USB摄像头默认为0
if(!capture.open(0))
{
qDebug() << __FILE__ << __LINE__ << "Failed to open camera: 0";
}else{
qDebug() << __FILE__ << __LINE__ << "Succeed to open camera: 0";
}
while(true)
{
cv::Mat srcMat;
capture >> srcMat;
#endif
int chessboardColCornerCount = 6;
int chessboardRowCornerCount = 9;
// int chessboardColCornerCount = 7;
// int chessboardRowCornerCount = 7;
// 步骤一:读取文件
// cv::imshow("1", srcMat);
// cv::waitKey(0);
// 步骤二:缩放,太大了缩放下(可省略)
cv::resize(srcMat, srcMat, cv::Size(srcMat.cols / 2, srcMat.rows / 2));
cv::Mat srcMat2 = srcMat.clone();
cv::Mat srcMat3 = srcMat.clone();
cv::imshow("2", srcMat);
// cv::waitKey(0);
// 步骤三:灰度化
cv::Mat grayMat;
cv::cvtColor(srcMat, grayMat, cv::COLOR_BGR2GRAY);
cv::imshow("3", grayMat);
// cv::waitKey(0);
// 步骤四:检测角点
std::vector<cv::Point2f> vectorPoint2fCorners;
bool patternWasFound = false;
patternWasFound = cv::findChessboardCorners(grayMat,
cv::Size(chessboardColCornerCount, chessboardRowCornerCount),
vectorPoint2fCorners,
cv::CALIB_CB_ADAPTIVE_THRESH | cv::CALIB_CB_FAST_CHECK | cv::CALIB_CB_NORMALIZE_IMAGE);
/*
enum { CALIB_CB_ADAPTIVE_THRESH = 1, // 使用自适应阈值将图像转化成二值图像
CALIB_CB_NORMALIZE_IMAGE = 2, // 归一化图像灰度系数(用直方图均衡化或者自适应阈值)
CALIB_CB_FILTER_QUADS = 4, // 在轮廓提取阶段,使用附加条件排除错误的假设
CALIB_CB_FAST_CHECK = 8 // 快速检测
};
*/
cvui::printf(srcMat, 0, 0, 1.0, 0xFF0000, "found = %s", patternWasFound ? "true" : "false");
cvui::printf(srcMat, 0, 24, 1.0, 0xFF0000, "count = %d", vectorPoint2fCorners.size());
qDebug() << __FILE__ << __LINE__ << vectorPoint2fCorners.size();
// 步骤五:绘制棋盘点
cv::drawChessboardCorners(srcMat2,
cv::Size(chessboardColCornerCount, chessboardRowCornerCount),
vectorPoint2fCorners,
patternWasFound);
#if TestCorrectingChessboardUseCamera
cv::imshow("0", srcMat);
cv::imshow("4", srcMat2);
if(!patternWasFound)
{
cv::imshow("5", srcMat3);
cv::waitKey(1);
continue;
}
#endif
// 步骤六:进一步提取亚像素角点
cv::TermCriteria criteria(CV_TERMCRIT_EPS | CV_TERMCRIT_ITER, // 类型
30, // 参数二: 最大次数
0.001); // 参数三:迭代终止阈值
/*
#define CV_TERMCRIT_ITER 1 // 终止条件为: 达到最大迭代次数终止
#define CV_TERMCRIT_NUMBER CV_TERMCRIT_ITER //
#define CV_TERMCRIT_EPS 2 // 终止条件为: 迭代到阈值终止
*/
qDebug() << __FILE__ << __LINE__ << vectorPoint2fCorners.size();
cv::cornerSubPix(grayMat,
vectorPoint2fCorners,
cv::Size(5, 5),
cv::Size(-1, -1),
criteria);
// 步骤七:绘制棋盘点
cv::drawChessboardCorners(srcMat3,
cv::Size(chessboardColCornerCount, chessboardRowCornerCount),
vectorPoint2fCorners,
patternWasFound);
cv::imshow("5", srcMat3);
// cv::waitKey(0); // 步骤八:角点对应的三维坐标(一张图一组)
std::vector<std::vector<cv::Point3f>> vectorObjectPoint;
std::vector<cv::Point3f> objectPoints; // 三维世界坐标系
for(int i = 0; i < chessboardRowCornerCount; i++)
{
for(int j = 0; j < chessboardColCornerCount; j++)
{
objectPoints.push_back(cv::Point3f(j, i, 0));
}
}
vectorObjectPoint.push_back(objectPoints); // 步骤九:图像识别出来的角点(一张图一组)
std::vector<std::vector<cv::Point2f>> vectorImagePoint;
vectorImagePoint.push_back(vectorPoint2fCorners); // 步骤十:计算内参和畸变系数 cv::Mat cameraMatrix; // 相机矩阵(接收输出)
cv::Mat distCoeffs; // 畸变系数(接收输出)
cv::Mat Rotate; // 旋转量(接收输出)
cv::Mat Translate; // 偏移量(接收输出)
cv::calibrateCamera(vectorObjectPoint,
vectorImagePoint,
grayMat.size(),
cameraMatrix,
distCoeffs,
Rotate,
Translate);
std::cout << "cameraMatrix:" << std::endl;
std::cout << cameraMatrix << std::endl; std::cout << "distCoeffs:" << std::endl;
std::cout << distCoeffs << std::endl; std::cout << "Rotate:" << std::endl;
std::cout << Rotate << std::endl; std::cout << "Translate:" << std::endl;
std::cout << Translate << std::endl; // 步骤十一:畸变图像校准
cv::Mat dstMat;
cv::undistort(srcMat, dstMat, cameraMatrix, distCoeffs);
cv::imshow("6", dstMat); #if TestCorrectingChessboardUseCamera
cv::waitKey(1);
}
// cv::imshow(_windowTitle.toStdString(), dstMat);
#else
cv::waitKey(0);
#endif
}
 

对应工程模板v1.68.0

  

 

入坑

入坑一:无法识别图像

问题

  无法识别。
  

原理

  要全部棋盘视野内,且可以识别,这个确实识别不了。

解决

  换图重新来过(这是笔者随便找的图)。

入坑二:校准之后四角不准

问题

  四角明显不对。
  

原理

  这里需要多张图在能识别的情况下覆盖所有区域。

解决

  先这样,下次实际标定的时候再多张图看是否还存在该问题。

OpenCV开发笔记(七十七):相机标定(二):通过棋盘标定计算相机内参矩阵矫正畸变摄像头图像的更多相关文章

  1. OpenCV开发笔记(七十二):红胖子8分钟带你使用opencv+dnn+tensorFlow识别物体

    前言   级联分类器的效果并不是很好,准确度相对深度学习较低,本章使用opencv通过tensorflow深度学习,检测已有模型的分类.   Demo       可以猜测,1其实是人,18序号类是狗 ...

  2. OpenCV开发笔记(七十三):红胖子8分钟带你使用opencv+dnn+yolov3识别物体

      前言   级联分类器的效果并不是很好,准确度相对深度学习较低,上一章节使用了dnn中的tensorflow,本章使用yolov3模型,识别出具体的分类.   Demo   320x320,置信度0 ...

  3. OpenCV开发笔记(七十四):OpenCV3.4.1+ffmpeg3.4.8交叉编译移植到海思平台Hi35xx平台

    前言   移植opencv到海思平台,opencv支持对视频进行解码,需要对应的ffmpeg支持.   Ffmpeg的移植   Ffmpeg的移植请参考之前的文章:<FFmpeg开发笔记(十): ...

  4. OpenCV开发笔记(七十一):红胖子8分钟带你深入级联分类器训练

    前言   红胖子,来也!  做图像处理,经常头痛的是明明分离出来了(非颜色的),分为几块区域,那怎么知道这几块区域到底哪一块是我们需要的,那么这部分就涉及到需要识别了.  识别可以自己写模板匹配.特征 ...

  5. OpenCV开发笔记(六十九):红胖子8分钟带你使用传统方法识别已知物体(图文并茂+浅显易懂+程序源码)

    若该文为原创文章,未经允许不得转载原博主博客地址:https://blog.csdn.net/qq21497936原博主博客导航:https://blog.csdn.net/qq21497936/ar ...

  6. OpenCV开发笔记(五十六):红胖子8分钟带你深入了解多种图形拟合逼近轮廓(图文并茂+浅显易懂+程序源码)

    若该文为原创文章,未经允许不得转载原博主博客地址:https://blog.csdn.net/qq21497936原博主博客导航:https://blog.csdn.net/qq21497936/ar ...

  7. OpenCV开发笔记(六十五):红胖子8分钟带你深入了解ORB特征点(图文并茂+浅显易懂+程序源码)

    若该文为原创文章,未经允许不得转载原博主博客地址:https://blog.csdn.net/qq21497936原博主博客导航:https://blog.csdn.net/qq21497936/ar ...

  8. OpenCV开发笔记(六十四):红胖子8分钟带你深入了解SURF特征点(图文并茂+浅显易懂+程序源码)

    若该文为原创文章,未经允许不得转载原博主博客地址:https://blog.csdn.net/qq21497936原博主博客导航:https://blog.csdn.net/qq21497936/ar ...

  9. 【opencv学习笔记七】访问图像中的像素与图像亮度对比度调整

    今天我们来看一下如何访问图像的像素,以及如何改变图像的亮度与对比度. 在之前我们先来看一下图像矩阵数据的排列方式.我们以一个简单的矩阵来说明: 对单通道图像排列如下: 对于双通道图像排列如下: 那么对 ...

  10. OpenCV开发笔记(五十五):红胖子8分钟带你深入了解Haar、LBP特征以及级联分类器识别过程(图文并茂+浅显易懂+程序源码)

    若该文为原创文章,未经允许不得转载原博主博客地址:https://blog.csdn.net/qq21497936原博主博客导航:https://blog.csdn.net/qq21497936/ar ...

随机推荐

  1. 常见的for循环优化方式

    ?> 前言 经常使用一些循环,进行耗时计算的操作,特别是 for 循环,它是一种重复计算的操作,如果处理不好,耗时就比较大,如果处理书写得当,将大大提高效率,下面总结几条 for 循环的常见优化 ...

  2. 【druid切换hikari连接池】通过源码分析遇到的问题

    一.前言说明 如果不会配置druid连接池的话,可以参考我这篇博文:springboot整合druid: springboot整合所有的starter方法基本都差不多,添加依赖,开启注解,编写配置,增 ...

  3. CE修改器入门:查找共享代码

    本关我们将学习共享代码,在C语言中角色属性都是以结构体的方式进行存储的,而结构体所存储的信息都是连续性的,这一关我们将会解释如何处理游戏中的共用代码,这种代码是通用在除了自己以外的其他同类型对像上的 ...

  4. SpringBoot2.7集成Swagger3

    1.引入pom坐标 <!--swagger--> <dependency> <groupId>io.springfox</groupId> <ar ...

  5. React的组件通信与状态管理

    目录 1. 组件通讯-概念 1.组件的特点 2.知道组件通讯意义 总结: 2. 组件通讯-props 基本使用 1.传递数据和接收数据的过程 2.函数组件使用 props 3.类组件使用 props ...

  6. 小知识:Exadata平台去掉密码输错延迟10分钟登录

    生产环境不评价,若是测试环境实在受不了偶尔一次因为密码输错就要等待10分钟才能登陆的限制. 那测试环境下,如何关闭这个限制呢?很简单: # vi /etc/pam.d/sshd --找到并注释掉下面这 ...

  7. 利用ogg实现oracle到kafka的增量数据实时同步

    前言 ogg即Oracle GoldenGate是Oracle的同步工具,本文讲如何配置ogg以实现Oracle数据库增量数据实时同步到kafka中,其中同步消息格式为json. 下面是我的源端和目标 ...

  8. Hive数据导入与导出

    数据导入 ● 本地文件导入 -- 本地文件导入(local) LOAD DATA local INPATH '/home/hadoop/sourceA.txt' INTO TABLE testA PA ...

  9. AgileConfig-1.9.0 发布,支持 MongoDB 存储

    Hello 大家好,先祝福大家新年快乐. AgileConfig 1.9.0 版本终于赶在农历年前发布了. Mongodb 当前做为一款非常成熟的 Nosql 产品,已经有越来越多的产品或项目基于它来 ...

  10. NC16527 [NOIP2013]货车运输

    题目链接 题目 题目描述 A 国有 n 座城市,编号从 1 到 n ,城市之间有 m 条双向道路.每一条道路对车辆都有重量限制,简称限重.现在有 q 辆货车在运输货物, 司机们想知道每辆车在不超过车辆 ...