OpenCV开发笔记(七十六):相机标定(一):识别棋盘并绘制角点
前言
知道图像畸变矫映射的原理之后,那么如何得到相机的内参是矫正的第一步,内参决定了内参矩阵(中心点、焦距等),用内参矩阵才能计算出投影矩阵,从而将原本畸变的图像矫正为平面投影图像。
本篇描述了相机成形的原理,并绘制出识别的角点。
Demo



相机成形的原理
小孔成像原理

得到矩阵计算原理:

得到计算过程:

相机的畸变
相机的畸变是指相机镜头对物体所成的像相对于物体本身而言的失真程度,它是光学透镜的固有特性。畸变产生的原因主要是透镜的边缘部分和中心部分的放大倍率不一样。
畸变分为以下几类:
- 径向畸变
- 切向畸变
- 薄棱镜畸变
通常情况下,径向畸变的影响要远远大于其他畸变。畸变是不可消除的,但在实际的应用中,可以通过一些软件来进行畸变的补偿,如OpenCV、MATLAB等。
径向畸变
主要由透镜不同部位放大倍率不同造成,它又分为枕形畸变和桶形畸变两种。枕形畸变,也称为鞍形形变,视野中边缘区域的放大率远大于光轴中心区域的放大率,常用在远摄镜头中。桶形畸变则与枕形畸变相反,视野中光轴中心区域的放大率远大于边缘区域的放大率,常出现在广角镜头和鱼眼镜头中。

切向畸变
主要由透镜安装与成像平面不平行造成,类似于透视原理,如近大远小、圆变椭圆等。

薄棱镜畸变
由透镜设计缺陷和加工安装误差造成,又称为线性畸变。其影响较小,一般忽略不计。
棋牌识别步骤
步骤一:标定采集的数据图像
采集一张棋盘图片,要确认他是可以被识别的。

读取图像,这里由于图片较大,我们重设大小为原来宽高的1/2:
// 使用图片
std::string srcFilePath = "D:/qtProject/openCVDemo/openCVDemo/modules/openCVManager/images/chessboard.png";
// std::string srcFilePath = "D:/qtProject/openCVDemo/openCVDemo/modules/openCVManager/images/24.jpg";
cv::Mat srcMat = cv::imread(srcFilePath);
int chessboardColCornerCount = 6;
int chessboardRowCornerCount = 9;
// 步骤一:读取文件
// 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);
步骤三:进行亚像素角点计算,进一步提取图片准确性
// 步骤六:进一步提取亚像素角点
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(11, 11),
cv::Size(-1, -1),
criteria);
函数原型
findChessboardCorners:识别预制棋盘角点数量的棋盘
OpenCV 中用于检测图像中棋盘角点的函数。
bool cv::findChessboardCorners(InputArray image,
Size patternSize,
OutputArray corners,
int flags=CALIB_CB_ADAPTIVE_THRESH+CALIB_CB_NORMALIZE_IMAGE)
参数解释:
- image:输入的图像,通常是一个灰度图像,因为角点检测在灰度空间中进行更为准确。
- patternSize:棋盘的内角点数量,例如一个 8x6 的棋盘会有 48 个内角点,所以 patternSize 会是 Size(8, 6)。
- corners:检测到的角点输出数组。
- flags:不同的标志,用于指定角点检测的不同方法。可以是以下的一个或多个标志的组合:
CALIB_CB_ADAPTIVE_THRESH:使用自适应阈值将图像转换为二值图像,而不是使用固定的全局阈值。
CALIB_CB_NORMALIZE_IMAGE:在寻找角点之前,先对图像进行归一化,以提高鲁棒性。
CALIB_CB_FAST_CHECK:仅检查角点候选者中的少量点,用于快速检测,但可能不如标准方法准确。
函数返回值是一个布尔值,如果找到足够的角点以形成一个棋盘模式,则返回 true;否则返回 false。
findChessboardCorners 函数通常用于相机标定,通过检测棋盘角点来确定图像与真实世界之间的对应关系。一旦角点被检测到,就可以使用这些点来估计相机的内参(如焦距、主点)和外参(如旋转和平移矩阵)。
drawChessboardCorners:绘制棋盘角点
OpenCV中的一个函数,用于在检测到的棋盘角点周围绘制方框。这对于相机标定、图像对齐等应用非常有用。
void cv::drawChessboardCorners(InputOutputArray image,
Size patternSize,
InputArray corners,
bool patternWasFound)
参数解释:
- image:输入的图像,通常是一个彩色图像,函数会在这个图像上绘制角点。
- patternSize:棋盘的内角点数量,例如一个 8x6 的棋盘会有 48 个内角点,所以 patternSize 会是 Size(8, 6)。
- corners:检测到的角点,通常是通过 findChessboardCorners 函数得到的。
- patternWasFound:一个布尔值,表示是否找到了足够的角点来形成一个棋盘模式。如果为 true,则函数会在角点周围绘制彩色的方框;如果为 false,则只会绘制白色的方框。
这个函数通常与 findChessboardCorners 结合使用,以检测图像中的棋盘角点,并在检测到的角点周围绘制方框。这对于视觉校准和相机标定等任务非常有用。
TermCriteria:迭代终止模板类
TermCriteria是OpenCV中用于指定迭代算法终止条件的模板类。它取代了之前的CvTermCriteria,并且在许多OpenCV算法中作为迭代求解的结构被使用。
struct TermCriteria {
enum { COUNT=1, MAX_ITER=COUNT, EPS=2 };
TermCriteria();
TermCriteria(int type, int maxCount, double epsilon);
TermCriteria(const CvTermCriteria& criteria);
};
构造时需要三个参数:
- 类型(type):它决定了迭代终止的条件。类型可以是CV_TERMCRIT_ITER、CV_TERMCRIT_EPS或CV_TERMCRIT_ITER+CV_TERMCRIT_EPS。在C++中,这些宏对应的版本分别为TermCriteria::COUNT、TermCriteria::EPS。
CV_TERMCRIT_ITER或TermCriteria::COUNT:表示迭代终止条件为达到最大迭代次数;
CV_TERMCRIT_EPS或TermCriteria::EPS:表示迭代到特定的阈值就终止;
CV_TERMCRIT_ITER+CV_TERMCRIT_EPS:则表示两者都作为迭代终止条件。 - 迭代的最大次数(maxCount):这是算法可以执行的最大迭代次数。
- 特定的阈值(epsilon):当满足这个精确度时,迭代算法会停止。
cornerSubPix:亚像素角点提取
OpenCV中用于精确化角点位置,其函数原型如下:
void cv::cornerSubPix(InputArray image,
InputOutputArray corners,
Size winSize,
Size zeroZone,
TermCriteria criteria);
参数解释:
- image:输入图像的像素矩阵,最好是8位灰度图像,这样检测效率会更高。
- corners:初始的角点坐标向量,同时作为亚像素坐标位置的输出,因此需要是浮点型数据。
- winSize:搜索窗口的大小,它表示的是搜索窗口的一半尺寸。
- zeroZone:死区的一半尺寸,死区是搜索窗口内不对中央位置做求和运算的区域。这是为了避免自相关矩阵出现某些可能的奇异性。
- criteria:角点搜索的停止条件,通常包括迭代次数、角点位置变化量或角点误差变化量等。
cornerSubPix函数用于在初步提取的角点信息上进一步提取亚像素信息,从而提高相机标定的精度。在相机标定、目标跟踪和三维重建等应用中,精确的角点位置是非常重要的,因此cornerSubPix函数在这些领域有广泛的应用。
Demo源码
void OpenCVManager::testFindChessboardCorners()
{
#define FindChessboardCornersUseCamera 1
#if !FindChessboardCornersUseCamera
// 使用图片
std::string srcFilePath = "D:/qtProject/openCVDemo/openCVDemo/modules/openCVManager/images/chessboard.png";
// std::string srcFilePath = "D:/qtProject/openCVDemo/openCVDemo/modules/openCVManager/images/24.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;
// 步骤一:读取文件
// 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 FindChessboardCornersUseCamera
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(11, 11),
cv::Size(-1, -1),
criteria);
// 步骤七:绘制棋盘点
cv::drawChessboardCorners(srcMat3,
cv::Size(chessboardColCornerCount, chessboardRowCornerCount),
vectorPoint2fCorners,
patternWasFound);
cv::imshow("5", srcMat3);
// cv::waitKey(0);
#if FindChessboardCornersUseCamera
cv::waitKey(1);
}
// cv::imshow(_windowTitle.toStdString(), dstMat);
#else
cv::waitKey(0);
#endif
}
对应工程模板v1.67.0

入坑
入坑一:无法检测出角点
问题
检测角点失败

原因
输入棋牌横向竖向角点的数量入函数,而不是输入行数和列数。
解决
输入正确的横向纵向角点数量即可。

入坑二:检测亚像素角点崩溃
问题
检测亚像素角点函数崩溃

原因
输入要是灰度mat
解决
将灰度图输入即可。
OpenCV开发笔记(七十六):相机标定(一):识别棋盘并绘制角点的更多相关文章
- .net开发笔记(十六) 对前部分文章的一些补充和总结
补充有两个: 一个是系列(五)中讲到的事件编程(网址链接),该文提及到了事件编程的几种方式以及容易引起的一些异常,本文补充“多线程事件编程”这一块. 第二个是前三篇博客中提及到的“泵”结构在编程中的应 ...
- 安卓开发笔记(十六):'Request(okhttp3.Request.Builder)' has private access in 'okhttp3.Request
当出现了'Request(okhttp3.Request.Builder)' has private access in 'okhttp3.Request的错误的时候,实际上是我们在写代码的时候少打了 ...
- Java开发笔记(十六)非此即彼的条件分支
前面花了大量篇幅介绍布尔类型及相应的关系运算和逻辑运算,那可不仅仅是为了求真值或假值,更是为了通过布尔值控制流程的走向.在现实生活中,常常需要在岔路口抉择走去何方,往南还是往北,向东还是向西?在Jav ...
- Android笔记(七十六) 点菜DEMO
一个朋友让看一下他的代码,一个点菜的功能,他和我一样,初学者,代码比我的都混乱,也是醉了,干脆想着自己写个demo给他看,原本想着听简单,半个小时应该就可以搞定,真正写的时候,画了3h+,汗颜... ...
- OpenCV开发笔记(六十五):红胖子8分钟带你深入了解ORB特征点(图文并茂+浅显易懂+程序源码)
若该文为原创文章,未经允许不得转载原博主博客地址:https://blog.csdn.net/qq21497936原博主博客导航:https://blog.csdn.net/qq21497936/ar ...
- OpenCV开发笔记(六十九):红胖子8分钟带你使用传统方法识别已知物体(图文并茂+浅显易懂+程序源码)
若该文为原创文章,未经允许不得转载原博主博客地址:https://blog.csdn.net/qq21497936原博主博客导航:https://blog.csdn.net/qq21497936/ar ...
- OpenCV开发笔记(六十四):红胖子8分钟带你深入了解SURF特征点(图文并茂+浅显易懂+程序源码)
若该文为原创文章,未经允许不得转载原博主博客地址:https://blog.csdn.net/qq21497936原博主博客导航:https://blog.csdn.net/qq21497936/ar ...
- OpenCV开发笔记(七十二):红胖子8分钟带你使用opencv+dnn+tensorFlow识别物体
前言 级联分类器的效果并不是很好,准确度相对深度学习较低,本章使用opencv通过tensorflow深度学习,检测已有模型的分类. Demo 可以猜测,1其实是人,18序号类是狗 ...
- OpenCV开发笔记(七十四):OpenCV3.4.1+ffmpeg3.4.8交叉编译移植到海思平台Hi35xx平台
前言 移植opencv到海思平台,opencv支持对视频进行解码,需要对应的ffmpeg支持. Ffmpeg的移植 Ffmpeg的移植请参考之前的文章:<FFmpeg开发笔记(十): ...
- OpenCV开发笔记(五十六):红胖子8分钟带你深入了解多种图形拟合逼近轮廓(图文并茂+浅显易懂+程序源码)
若该文为原创文章,未经允许不得转载原博主博客地址:https://blog.csdn.net/qq21497936原博主博客导航:https://blog.csdn.net/qq21497936/ar ...
随机推荐
- 手撕Vue-构建Vue实例
前言 要想使用Vue必须先创建Vue的实例, 创建Vue的实例通过new来创建, 所以说明Vue是一个类, 所以我们要想使用自己的Vue, 就必须定义一个名称叫做Vue的类. 只要创建好了Vue的实例 ...
- Mysql索引失效场景
Mysql索引失效场景 序言 众所周知在Mysql中,通过使用索引的方式可以加快查询速度,从而避免全文搜索:而索引本身就像图书馆中所有书籍目录,通过查询关键字就能快速找到目标书籍在几列几行,这 ...
- paddle之visualDL工具使用,可视化利器。
相关链接: [一]AI Studio 项目详解[(一)VisualDL工具.环境使用说明.脚本任务.图形化任务.在线部署及预测]PARL_汀.的博客-CSDN博客 isualDL 是一个面向深度学习任 ...
- 强化学习从基础到进阶-常见问题和面试必知必答[6]:演员-评论员算法(advantage actor-critic,A2C),异步A2C、与生成对抗网络的联系等详解
强化学习从基础到进阶-常见问题和面试必知必答[6]:演员-评论员算法(advantage actor-critic,A2C),异步A2C.与生成对抗网络的联系等详解 1.核心词汇 优势演员-评论员(a ...
- Linux 配置Quota磁盘配额
由于Linux是一个多用户管理的操作系统,而Linux默认情况下并不限制每个用户使用磁盘空间的大小,假如某个用户疏忽或者恶意占满磁盘空间,将导致系统磁盘无法写入甚至崩溃,为了保证系统磁盘的有足够的剩余 ...
- P9474 [yLOI2022] 长安幻世绘题解
题目链接: [yLOI2022] 长安幻世绘 比较不错的综合题.考虑下处理极差的绝对值我们应该怎么做,很显然排序是有必要的,我们需要带着下标排序. 考虑几个核心点: 1.假如没有其他限制考虑极差与序列 ...
- .NET 云原生架构师训练营(模块二 基础巩固 依赖注入)--学习笔记
2.2.1 核心模块--依赖注入 什么是依赖注入 .NET Core DI 生命周期 服务设计 服务范围检查 ASP.NET Core 依赖注入:https://docs.microsoft.com/ ...
- 《ASP.ENT Core 与 RESTful API 开发实战》(第3章)-- 读书笔记(中)
第 3 章 ASP.NET Core 核心特性 3.3 依赖注入 通常情况下,应用程序由多个组件构成,而组件与组件之间往往存在依赖关系 当我们需要获取数据时,通常的做法是实例化依赖的类,然后调用类里面 ...
- ABC 314
F 每次相当于创建一个包含 \(p_i,q_i\) 各自所在集合的点的大点 \(u\),然后 \(u\) 向 \(p_i,q_i\) 各自所在集合连边,边权就是胜率. 连完之后求每个点到根结点(\(\ ...
- 吉特日化MES & 日化制药工厂信息化系统架构图
作者:情缘 出处:http://www.cnblogs.com/qingyuan/ 关于作者:从事仓库,生产软件方面的开发,在项目管理以及企业经营方面寻求发展之路 版权声明:本文版权归作者和博客园 ...