OpenCv相机标定——圆形标定板标定[通俗易懂]

OpenCv相机标定——圆形标定板标定[通俗易懂]Opencv相机标定之圆形标定板标定——本文主要介绍了OpenCv中圆形标定板的标定,并将标定结果与Halcon标定进行比较分析,得出OpenCv标定算法对图像品质的鲁棒性较高,标定精度较高。同时也从侧面反映出使用Halcon进行高精度标定时,对图像品质的要求较高,当然这也是高精度标定下的高要求。

大家好,又见面了,我是你们的朋友全栈君。

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

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;
else
return 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");
else
inputFilename = 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;
else
capture.open(inputFilename);
}
else
capture.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);
else
msg = 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;
else
mode = 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,图像尺寸为2040×1200。
  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:如有错误,谢谢指出。转载请注明出处。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/140493.html原文链接:https://javaforall.cn

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)
blank

相关推荐

  • VS2019+OpenCV安装与配置教程

    VS2019+OpenCV安装与配置教程目录VS2019的下载安装OpenCV的下载安装OpenCV的配置配置系统变量给VS中的工程一键配置OpenCV测试配置的效果最近要用到很多OpenCV的库,所以开始学了点OpenCV,本文记录VS和OpenCV的安装、配置过程。配置OpenCV使用配置文件的方法,配置完一次就可以给其他工程使用,非常方便。VS2019的下载安装这里有VisualStudio2019的详细教程,社区版是免费使用的,登录一下微软账户就行。OpenCV的下载安装OpenCV下载链接。我装的是4.3、Window

  • 2021版idea激活码99年-激活码分享

    (2021版idea激活码99年)最近有小伙伴私信我,问我这边有没有免费的intellijIdea的激活码,然后我将全栈君台教程分享给他了。激活成功之后他一直表示感谢,哈哈~IntelliJ2021最新激活注册码,破解教程可免费永久激活,亲测有效,下面是详细链接哦~https://javaforall.cn/100143.html…

  • 几种更新(Update语句)查询的方法

    几种更新(Update语句)查询的方法

    2021年12月13日
  • linux aarch64启动不了,引导AArch64 Linux

    linux aarch64启动不了,引导AArch64 Linux前注:本文是Documentation/arm64/booting的翻译。这篇文章基于RussellKing所写的《theARMbootingdocument》,并与AArch64Linuxkernel的所有公开版本相关。AArch64异常模型由几级异常组成,分别是EL0-EL3,EL0和EL1又分别有安全和非安全模式,EL2是hypervisor级别,仅存在于安全模式,EL3是最…

    2022年10月16日
  • Dreamweaver8的安装

    Dreamweaver8的安装安装步骤:Step1:双击<Dreamweaver8-chs>Step2:单击<下一步>Step3:选中<我接受该许可证协议中的条款>,单击<下一步>按钮Step4:选中<在桌面上创建快捷方式(针对所有用户)>,单击<下一步>Step5:单击<下一步>S…

  • bt3使用_手机怎么下载视频到u盘上

    bt3使用_手机怎么下载视频到u盘上BT3U盘版下载软件类型:国产软件授权方式:免费软件界面语言:简体中文软件大小:783M文件类型:.iso运行环境:Win2003,WinXP,Win2000,Win9X软件等级:★★★★★发布时间:2010-12-26官方网址:http://www.backtrack-linux.org演示网址:http://www.backtrack-linux.org下载次数:

发表回复

您的电子邮箱地址不会被公开。

关注全栈程序员社区公众号