OpenCv相机标定——圆形标定板标定

article/2025/9/22 2:35:07

OpenCv相机标定——圆形标定板标定

  • 0.前言
  • 1.标定图案
  • 2.OpenCv标定
  • 3.标定结果分析

0.前言

  OpenCv中,相机标定所使用的标定图案分为棋盘格、对称圆形及非对称圆形特征图、ArUco板和ChArUco板等。在OpenCV的官方例程中,采用的是棋盘格图案,因为其操作简单、快速,标定精度满足一般应用场景的需求。对于标定精度要求高的场景,则一般采用圆形标定图案。本文主要介绍如何使用圆形标定图案(对称和非对称)完成相机的标定,并将OpenCv标定结果与Halcon标定结果进行对比分析。

1.标定图案

  OpenCv中使用的圆形标定图案如图1所示:
在这里插入图片描述
OpenCv中,使用圆形标定图案用到的函数为 cv::findCirclesGrid()。函数原型如下:
 bool cv::findCirclesGrid(//找到圆心坐标返回True
     cv::InputArray,//输入标定图像,8位单通道或三通道
     cv::Size patternSize,//标定图案的尺寸
     cv::OutputArray centers,//输出数组,为检测到的圆心坐标
     int flags,//标志位,对称图案——cv::CALIB_CB_SYMMETRIC_GRID,非对称图案——  cv::CALIB_CB_ASYMMETRIC_GRID
     const cv::Ptrcv::FeatureDetector&blobDetector=new SimpleBlobDetector()
);
  图1所示的非对称圆形标定图案,其width=11,height=6。在计算标定图案上标志点圆心的世界坐标时,参数squareSize即为图1中标注的圆心距。关于圆的半径大小,可以自行设定,因为在提取圆心坐标时不涉及圆的半径(这点和halcon标定不同,halcon在进行相机标定时,圆的半径作为标定文件中的已知参数)。圆心距一般取圆直径的4倍左右。
  图2为本文使用的标定板,其为高精度铝制标定板,精度为±0.01mm,是200x200mm的halcon标准标定板,圆的直径为12.5mm,圆心距为25mm。
在这里插入图片描述

2.OpenCv标定

  本文采用的标定为离线标定,先由相机采集N幅图像,再由标定程序读取图像。为了保证标定精度,建议采集10幅或更多的视图,尽量使得标定板的移动范围覆盖相机视野。
  在OpenCv官方相机标定代码的基础上进行了修改,得到了下面的对圆形标定图案标定的代码。由于代码近500行,为了缩短篇幅,省略的一些头文件、说明性文字、函数的实现。省略部分可参考:OpenCv/sources/samples/cpp/tutorial_code/calib3d/camera_calibration/camera_calibration.cpp.

#include "stdafx.h"
//此处省略各种头文件
using namespace cv;
using namespace std;
//此处省略help()函数
enum { DETECTION = 0, CAPTURING = 1, CALIBRATED = 2 };
enum Pattern { CHESSBOARD, CIRCLES_GRID, ASYMMETRIC_CIRCLES_GRID };//计算重投影误差函数
static 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)
{//此处省略...
}static void calcChessboardCorners(Size boardSize, float squareSize, vector<Point3f>& corners, Pattern patternType = CIRCLES_GRID)
{//省略...//本文中用到的标定板,在该函数中的参数为:boardSize.width=7,boardSize.height=7,squareSize=0.025(此处单位为米)
}
//执行标定,包括计算重投影误差
static bool runCalibration(vector<vector<Point2f> > imagePoints,Size imageSize, Size boardSize, Pattern patternType,float squareSize, float aspectRatio,int flags, Mat& cameraMatrix, Mat& distCoeffs,vector<Mat>& rvecs, vector<Mat>& tvecs,vector<float>& reprojErrs,double& totalAvgErr)
{//省略...
}//保存相机参数
static void saveCameraParams(const string& filename,Size imageSize, Size boardSize,float squareSize, float aspectRatio, int flags,const Mat& cameraMatrix, const Mat& distCoeffs,const vector<Mat>& rvecs, const vector<Mat>& tvecs,const vector<float>& reprojErrs,const vector<vector<Point2f> >& imagePoints,double totalAvgErr)
{//省略...
}//读取字符串
static bool readStringList(const string& filename, vector<string>& l)
{l.resize(0);FileStorage fs(filename, FileStorage::READ);if (!fs.isOpened())return false;FileNode n = fs["images"];if (n.type() != FileNode::SEQ)return false;FileNodeIterator it = n.begin(), it_end = n.end();for (; it != it_end; ++it)l.push_back((string)*it);return true;
}//运行并保存
static bool runAndSave(const string& outputFilename,const vector<vector<Point2f> >& imagePoints,Size imageSize, Size boardSize, Pattern patternType, float squareSize,float aspectRatio, int flags, Mat& cameraMatrix,Mat& distCoeffs, bool writeExtrinsics, bool writePoints)
{//省略...
}int main(int argc, char** argv)
{cout << argc << endl;for (size_t i = 0; i < argc; i++){cout << argv[i] << endl;}Size boardSize, imageSize;float squareSize, aspectRatio;Mat cameraMatrix, distCoeffs;string outputFilename;string inputFilename = "";int i, nframes;bool writeExtrinsics, writePoints;bool undistortImage = false;int flags = 0;VideoCapture capture;bool flipVertical;bool showUndistorted;bool videofile;int delay;clock_t prevTimestamp = 0;int mode = DETECTION;int cameraId = 0;vector<vector<Point2f> > imagePoints;vector<string> imageList;Pattern pattern = CIRCLES_GRID;//标定图案类型,对称圆形图案cv::CommandLineParser parser(argc, argv,"{help ||}{w|7|}{h|7|}{pt|circles|}{n|30|}{d|1000|}{s|0.025|}{o|D:/opencv/cameracalibration/out_camera_params_25x25_circleboard.yml|}""{op|D:/opencv/cameracalibration/Detected_feature_points.yml|}{oe|D:/opencv/cameracalibration/Extrinsic_parameters_circleboard.yml|}{zt||}{a|1|}{p||}{v||}{V||}{su||}""{input_data|D:/opencv/cameracalibration/VID25x25_CircleGrid.xml|}");//命令行参数赋值,参数说明:w,h为标定板宽,高; pt为标定图案类型; n为读取图片的张数; d为相机在线抓图的时间间隔(ms)(本代码//为离线标定,该参数可以不设置); o为程序输出的相机内参、外参文件(自定义的文件); op为输出检测到特征点的文件(自定义的文件); //oe为输出的相机外参数(这里可以不用设置,因为外参数已经在o中输出了,标定完后该文件为空文件); a为比例系数,默认为1; //input_data为存放图片路径的xml文件,本代码读取的VID25X25_CircleGrid.xml文件内容见图3。if (parser.has("help")){help();return 0;}boardSize.width = parser.get<int>("w");boardSize.height = parser.get<int>("h");if (parser.has("pt")){string val = parser.get<string>("pt");if (val == "circles")pattern = CIRCLES_GRID;else if (val == "acircles")pattern = ASYMMETRIC_CIRCLES_GRID;else if (val == "chessboard")pattern = CHESSBOARD;elsereturn fprintf(stderr, "Invalid pattern type: must be chessboard or circles\n"), -1;}squareSize = parser.get<float>("s");nframes = parser.get<int>("n");aspectRatio = parser.get<float>("a");delay = parser.get<int>("d");writePoints = parser.has("op");writeExtrinsics = parser.has("oe");if (parser.has("a"))flags |= CALIB_FIX_ASPECT_RATIO;if (parser.has("zt"))flags |= CALIB_ZERO_TANGENT_DIST;if (parser.has("p"))flags |= CALIB_FIX_PRINCIPAL_POINT;flipVertical = parser.has("v");videofile = parser.has("V");if (parser.has("o"))outputFilename = parser.get<string>("o");showUndistorted = parser.has("su");if (isdigit(parser.get<string>("input_data")[0]))cameraId = parser.get<int>("input_data");elseinputFilename = parser.get<string>("input_data");if (!parser.check()){help();parser.printErrors();return -1;}if (squareSize <= 0)return fprintf(stderr, "Invalid board square width\n"), -1;if (nframes <= 3)return printf("Invalid number of images\n"), -1;if (aspectRatio <= 0)return printf("Invalid aspect ratio\n"), -1;if (delay <= 0)return printf("Invalid delay\n"), -1;if (boardSize.width <= 0)return fprintf(stderr, "Invalid board width\n"), -1;if (boardSize.height <= 0)return fprintf(stderr, "Invalid board height\n"), -1;if (!inputFilename.empty()){if (!videofile && readStringList(inputFilename, imageList))mode = CAPTURING;elsecapture.open(inputFilename);}elsecapture.open(cameraId);if (!capture.isOpened() && imageList.empty())return fprintf(stderr, "Could not initialize video (%d) capture\n", cameraId), -2;if (!imageList.empty())nframes = (int)imageList.size();if (capture.isOpened())printf("%s", liveCaptureHelp);namedWindow("Image View", 1);for (i = 0;; i++){Mat view, viewGray;bool blink = false;if (capture.isOpened()){Mat view0;capture >> view0;view0.copyTo(view);}else if (i < (int)imageList.size())view = imread(imageList[i], 1);if (view.empty()){if (imagePoints.size() > 0)runAndSave(outputFilename, imagePoints, imageSize,boardSize, pattern, squareSize, aspectRatio,flags, cameraMatrix, distCoeffs,writeExtrinsics, writePoints);break;}imageSize = view.size();if (flipVertical)flip(view, view, 0);vector<Point2f> pointbuf;cvtColor(view, viewGray, COLOR_BGR2GRAY);bool found;switch (pattern){case CHESSBOARD:found = findChessboardCorners(view, boardSize, pointbuf,CALIB_CB_ADAPTIVE_THRESH | CALIB_CB_FAST_CHECK | CALIB_CB_NORMALIZE_IMAGE);break;case CIRCLES_GRID:found = findCirclesGrid(view, boardSize, pointbuf,CALIB_CB_SYMMETRIC_GRID);break;case ASYMMETRIC_CIRCLES_GRID:found = findCirclesGrid(view, boardSize, pointbuf, CALIB_CB_ASYMMETRIC_GRID);break;default:return fprintf(stderr, "Unknown pattern type\n"), -1;}if (found)drawChessboardCorners(view, boardSize, Mat(pointbuf), found);//在原图中绘制找到的圆心点,图4为其中的一幅图string msg = mode == CAPTURING ? "100/100" :mode == CALIBRATED ? "Calibrated" : "Press 'g' to start";int baseLine = 0;Size textSize = getTextSize(msg, 1, 1, 1, &baseLine);Point textOrigin(view.cols - 2 * textSize.width - 10, view.rows - 2 * baseLine - 10);if (mode == CAPTURING){if (undistortImage)msg = format("%d/%d Undist", (int)imagePoints.size(), nframes);elsemsg = format("%d/%d", (int)imagePoints.size(), nframes);}putText(view, msg, textOrigin, 1, 1,mode != CALIBRATED ? Scalar(0, 0, 255) : Scalar(0, 255, 0));if (blink)bitwise_not(view, view);if (mode == CALIBRATED && undistortImage){Mat temp = view.clone();undistort(temp, view, cameraMatrix, distCoeffs);}imshow("Image View", view);char key = (char)waitKey(capture.isOpened() ? 50 : 500);if (key == 27)break;if (key == 'u' && mode == CALIBRATED)undistortImage = !undistortImage;if (capture.isOpened() && key == 'g'){mode = CAPTURING;imagePoints.clear();}if (mode == CAPTURING && imagePoints.size() >= (unsigned)nframes){if (runAndSave(outputFilename, imagePoints, imageSize,boardSize, pattern, squareSize, aspectRatio,flags, cameraMatrix, distCoeffs,writeExtrinsics, writePoints))mode = CALIBRATED;elsemode = DETECTION;if (!capture.isOpened())break;}}if (!capture.isOpened() && showUndistorted){Mat view, rview, map1, map2;initUndistortRectifyMap(cameraMatrix, distCoeffs, Mat(),getOptimalNewCameraMatrix(cameraMatrix, distCoeffs, imageSize, 1, imageSize, 0),imageSize, CV_16SC2, map1, map2);for (i = 0; i < (int)imageList.size(); i++){view = imread(imageList[i], 1);if (view.empty())continue;//undistort( view, rview, cameraMatrix, distCoeffs, cameraMatrix );remap(view, rview, map1, map2, INTER_LINEAR);imshow("Image View", rview);char c = (char)waitKey();if (c == 27 || c == 'q' || c == 'Q')break;}}return 0;
}

在这里插入图片描述
在这里插入图片描述

3.标定结果分析

  OpenCv标定得到的相机参数矩阵为:
在这里插入图片描述
  本次标定使用的镜头焦距 f=8mm, 像元尺寸为3.45μm,图像尺寸为2040x1200。
  Halcon标定得到的内参为(k,sx,sy,cx,cy)将其转换为式(1)中的矩阵。表1为OpenCv和Halcon标定的对比数据。
在这里插入图片描述
  本实验中,镜头与世界坐标系z=0平面的距离为112cm左右。从表中可以看出,OpenCv标定的重投影误差为0.01759,精度较高,小于Halcon标定的0.069。(OpenCv标定过程中采用了5项畸变系数k1,k2,p1,p2,k3;Halcon标定中只考虑径向畸变k,表中没有列出)
  需要指出的是,实验数据来源于对同一组图片的标定。Halcon中对相机的标定,采用的方法是Tsai两步标定法,需要预先给出相机的内参数,理论上具有较高的标定精度。但是在本次的Halcon标定中,由于采用的是离线采集的图片,在标定过程中提示图片过曝、旋转角度没有覆盖全、标定图案偏小、光照不均匀等图像品质问题,因此标定的精度不高。如果使用halcon在线抓图标定,可以有效避免图像品质问题,从而大幅度提高标定精度,预计标定精度和OpenCv标定相当或者更高。标定结果表明,OpenCv标定算法的鲁棒性更好,而Halcon标定算法对采集到的图像品质要求较高,也可以理解为高精度标定下对图像品质的高要求。


ps:如有错误,谢谢指出。转载请注明出处。


http://chatgpt.dhexx.cn/article/6Rx4pDEK.shtml

相关文章

线结构光三维重建(二)相机标定、光平面标定

线结构光三维重建&#xff08;一&#xff09;https://blog.csdn.net/beyond951/article/details/125771158 上文主要对线激光的三角测量原理、光平面的标定方法和激光条纹提取的方法进行了一个简单的介绍&#xff0c;本文则主要针对线激光三维重建系统的系统参数标定进…

工业相机标定(张正友标定法)

目录 相机标定的概念 a. 相机标定的定义 b. 相机标定的目的 相机标定的过程 a. 标定板选择 b. 标定板摆放及拍摄 c. 标定板角点提取 张正友标定法 a. 反解相机矩阵 b.反解畸变系数 使用Python进行相机标定 a. 安装OpenCV b. 准备标定板图片 c. 利用OpenCV进行角点…

详解机器人标定

相机固定不动, 上往下看引导机器人移动 机器人与视觉标定理论详解 相机固定不动, 上往下看引导机器人移动 1.相机非线性校正 使用标定板做非线性校正 2.相机与机器人做9点标定 可以使用机器人扎9个点&#xff0c;或者机器人抓住工件摆放9个位置&#xff0c;得到9个机械坐标…

标定系列二、9点标定以及5点圆心标定过程(代码详解)

一、九点标定过程 1.算法原理 9点标定就是通过9个点计算出相机坐标系到机械手坐标系下的一个仿射变换&#xff0c;&#xff08;实际上空间中的二维平面的仿射变换只需要3个点就足够了&#xff09;。在实际应用过程中&#xff0c;需要获取像素下特征点的坐标和对应机械手的坐标…

相机标定-张正友棋盘格标定法

目录 1.针孔相机模型 2.相机成像过程 2.1 各个坐标系之间的转换 2.1.1 图像坐标系到像素坐标系 2.1.2 相机坐标系到图像坐标系 2.1.3世界坐标系到相机坐标系 2.1.4世界坐标系到像素坐标系 3.畸变与畸变矫正 3.1 畸变 3.2 畸变公式 4.相机标定原理 5.张正友标定法介…

相机标定原理———标定原理介绍

声明&#xff1a;欢迎任何人和组织转载本blog中文章&#xff0c;但必须标记文章原始链接和作者信息。 本文链接&#xff1a;http://blog.csdn.net/li_007/archive/2010/10/30/5976261.aspx 开拓进取的小乌龟------->CSDN点滴点点滴滴Blog 由于在word中包含大量的公式和矩…

VisionMaster标定板标定

选择工具 标定板生成工具我比较喜欢用海康自己研发的标2定板 具体参数看自己需求 最后选择0 导出图像就行 一般不需要cad图纸 保存 去路径下打印看看你的按照路径 我的是D:\VisionMasterV4.2.0\VisionMaster4.2.0\Applications\Tools 找到这张图片 不要改变比例直接打印 …

9点标定方法

9点标定&#xff0c;旋转标定 1.9点标定2.旋转标定3.公式推导过程 1.9点标定 夹具夹取产品或者标定块&#xff0c;选取一个特征&#xff0c;开始进行标定 X轴、Y轴移动一个位置&#xff0c;记录轴的物理坐标&#xff1a;&#xff08;Qx1&#xff0c;Qy1&#xff09; 提取图像中…

相机标定(一)

相机标定 相机成像模型参考坐标系针孔模型畸变模型相机参数 相机成像模型 参考坐标系 通常畸变分为两种&#xff0c;径向畸变和切向畸变。 图像像素坐标系&#xff1a;表示场景中三维点在图像平面上的投影&#xff0c;其坐标原点在CCD图像平面的左上角&#xff0c;u轴平行于…

张正友相机标定Opencv实现以及标定流程标定结果评价图像矫正流程解析(附标定程序和棋盘图)

使用Opencv实现张正友法相机标定之前&#xff0c;有几个问题事先要确认一下&#xff0c;那就是相机为什么需要标定&#xff0c;标定需要的输入和输出分别是哪些&#xff1f; 相机标定的目的&#xff1a;获取摄像机的内参和外参矩阵&#xff08;同时也会得到每一幅标定图像的选择…

标定工具介绍

作者 | WenDao_Engineer 微信公众号 | 闻道工程师之家 在前面标定相关的系列文章文对标定的基本介绍、标定的实现过程以及标定所涉及的相关协议都进行了介绍&#xff0c;从今天开始我们介绍一下标定实现过程中的标定工具相关知识。 标定系统组成 我们已经知道通过CCP或者XCP…

单目相机标定实现--张正友标定法

文章目录 一&#xff1a;相机坐标系&#xff0c;像素平面坐标系&#xff0c;世界坐标系&#xff0c;归一化坐标系介绍1&#xff1a;概述公式 二:实现1&#xff1a;整体流程4&#xff1a;求出每张图像的单应性矩阵并用LMA优化5&#xff1a;求解理想无畸变情况下的摄像机的内参数…

摄像机标定和立体标定

尝试用OpenCV来实现立体视觉也有一段时间了&#xff0c;主要的参考资料就是Learning OpenCV十一、十二章和OpenCV论坛上一些前辈的讨论。过程中磕磕碰碰&#xff0c;走了不少弯路&#xff0c;终于在前不久解决了最头大的问题&#xff0c;把整个标定、校准、匹配的流程调试成功。…

相机标定——张正友棋盘格标定法

目录 为什么需要相机标定&#xff1f; 相机标定可以做什么&#xff1f; 相机标定后可以得到什么&#xff1f; 什么情况下需要借助相机标定的方法&#xff1f; 相机标定的原理 实现相机标定的方法 为什么需要相机标定&#xff1f; 一个是由于每个镜头的在生产和组装过程中的…

标定方法——张正友标定法

标定 标定是联系世界坐标与像素坐标的环节&#xff0c;目的是求出相机和投影仪的内外参数&#xff0c;对于3D成像来说至关重要 张正友标定法 通过各种方法的对比&#xff0c;为了方便&#xff0c;我们采用的是张正友标定。我们主要对张正友标定法的原理进行介绍&#xff0c;…

笔记总结-相机标定(Camera calibration)原理、步骤

这已经是我第三次找资料看关于相机标定的原理和步骤&#xff0c;以及如何用几何模型&#xff0c;我想十分有必要留下这些资料备以后使用。这属于笔记总结。 1.为什么要相机标定&#xff1f; 在图像测量过程以及机器视觉应用中&#xff0c;为确定空间物体表面某点的三维几何位置…

如何实现标定?

上一篇《什么是标定》对标定进行了初步的介绍&#xff0c;让大家有了一个感性的认识。标定是一项非常复杂的工作的&#xff0c;涉及方方面面的知识非常多&#xff0c;本文将对标定具体实现的过程进行介绍。 控制器对标定的支持 在前面的文章中介绍了控制算法是在软件编程的时候…

标定的分类(一)

关于标定的分类及说明(一) 现在工业机器视觉和计算机视觉大量应用标定算法&#xff0c;但是对于初学者来说&#xff0c;存在概念模糊&#xff0c;理论理解错误的现状&#xff0c;因此&#xff0c;需要对标定进行梳理&#xff0c;防止大家在学习过程中混淆各种标定概念。话不多…

什么是标定?

标定这两个字在汽车行业里的工程师基本都听过&#xff0c;但是在其他行业里大部分人都不知道什么是标定&#xff0c;甚至都没有听说过标定。什么是标定&#xff1f;举一个常见的例子&#xff0c;家里买了电视&#xff0c;连接网络就可以看节目了&#xff0c;与其他任何环境影响…

JMS及其API介绍

Java Message Service是java ee的规范之一&#xff0c;可以用来发送异步消息&#xff0c;在某些场景下&#xff0c;可以作为不同系统&#xff0c;或者不同模块之间的集成方式。 可以类比为通过数据库来集成的方式&#xff0c;模块A完成逻辑以后&#xff0c;往数据库插…