前言

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

 

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开发笔记(七十六):相机标定(一):识别棋盘并绘制角点的更多相关文章

  1. .net开发笔记(十六) 对前部分文章的一些补充和总结

    补充有两个: 一个是系列(五)中讲到的事件编程(网址链接),该文提及到了事件编程的几种方式以及容易引起的一些异常,本文补充“多线程事件编程”这一块. 第二个是前三篇博客中提及到的“泵”结构在编程中的应 ...

  2. 安卓开发笔记(十六):'Request(okhttp3.Request.Builder)' has private access in 'okhttp3.Request

    当出现了'Request(okhttp3.Request.Builder)' has private access in 'okhttp3.Request的错误的时候,实际上是我们在写代码的时候少打了 ...

  3. Java开发笔记(十六)非此即彼的条件分支

    前面花了大量篇幅介绍布尔类型及相应的关系运算和逻辑运算,那可不仅仅是为了求真值或假值,更是为了通过布尔值控制流程的走向.在现实生活中,常常需要在岔路口抉择走去何方,往南还是往北,向东还是向西?在Jav ...

  4. Android笔记(七十六) 点菜DEMO

    一个朋友让看一下他的代码,一个点菜的功能,他和我一样,初学者,代码比我的都混乱,也是醉了,干脆想着自己写个demo给他看,原本想着听简单,半个小时应该就可以搞定,真正写的时候,画了3h+,汗颜... ...

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

    若该文为原创文章,未经允许不得转载原博主博客地址: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分钟带你深入了解SURF特征点(图文并茂+浅显易懂+程序源码)

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

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

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

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

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

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

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

随机推荐

  1. minIO系列文章02---linux安装

    目录 1.Minio介绍 2.安装MinIO 3. MinIO客户端 1.Minio介绍MinIO 是一个基于Apache License v2.0开源协议的对象存储服务.适合于存储大容量非结构化的数 ...

  2. 离线下载和安装UWP(windows应用商店)软件

    离线下载uwp安装包 打开商店,然后搜索您要的应用程序名称,进入应用界面 点击 分享按钮,在弹出窗口中选择[复制链接] 把链接粘贴到:https://store.rg-adguard.net/ 默认选 ...

  3. Unity Editor开发中查找属性的两种写法对比

    从2017开始,在editor脚本中查找属性是这样写的 var m_Script = serializedObject.FindProperty("m_Script"); Seri ...

  4. 数据挖掘机器学习[四]---汽车交易价格预测详细版本{嵌入式特征选择(XGBoots,LightGBM),模型调参(贪心、网格、贝叶斯调参)}

    题目出自阿里天池赛题链接:零基础入门数据挖掘 - 二手车交易价格预测-天池大赛-阿里云天池 相关文章: 特征工程详解及实战项目[参考] 数据挖掘---汽车车交易价格预测[一](测评指标:EDA) 数据 ...

  5. C/C++ 关于继承与多态笔记

    继承的基本语法: 继承的目的就是用于提高代码的可用性,减少代码的重复内容,高效开发. #include <iostream> using namespace std; class Base ...

  6. Java连接kubernates集群最优雅的两种方式

    创建maven工程,pom.xml中引入连接k8s的客户端jar包: <properties> <maven.compiler.source>8</maven.compi ...

  7. PHP 会话(Session)实现用户登陆功能

    PHP 会话(Session)实现用户登陆功能 Session 的工作机制是:为每个访客创建一个唯一的 id (UID),并基于这个 UID 来存储变量.UID 存储在 cookie 中,或者通过 U ...

  8. 19c ADG Switchover 切换测试

    背景: 环境未配置DG Broker,手工切换ADG,19c也要比11g时代的切换更简单. 使用自己的测试环境,具体可参见: 单实例Primary快速搭建Standby RAC参考手册(19.16 A ...

  9. Oracle 19c RAC 自动应用RU补丁简明版

    环境:Oracle RAC(GI 19.3 + DB 19.3) 本文应用补丁信息, 19.16 RU: p34130714_190000_Linux-x86-64.zip 本文主要演示使用opatc ...

  10. 下载安装JDK 和 IntelliJ IDEA 和 ActiveMq

    wget http://yun.diandaxia.com/other/jdk-8u92-linux-x64.rpm rpm -ivh jdk-8u92-linux-x64.rpm wget http ...