C++ OpenCV相机标定—实心圆点、棋盘格

目录


在相机内参标定中,采用二维靶标标定主要分为两种方式:棋盘格标定、实心圆点标定。注意棋盘格和实心圆点在标定过程中注意保持清晰,且与水平(竖直)成一定角度15-30°,实验所得,仅供参考。

棋盘格标定

棋盘格标定相机内参主要采用两种方法:Matlab的相机工具包、OpenCV函数findChessboardCorner函数调用。

Matlab相机工具包计算相机内参:详情参考链接

C++ OpenCV相机标定---实心圆点、棋盘格

OpenCV函数调用:

findChessboardCorners参数设置,该函数检测鲁棒性较差,提取易失败,不同背景下的提取效果也不同,背景白色较多时提取成功率较高

findChessboardCorners(
InputArray image,
Size patternSize,
OutputArray corners,
int flags=CALIB_CB_ADAPTIVE_THRESH+CALIB_CB_NORMALIZE_IMAGE)                                                                                                                                                                 

关键参数:

OutputArray corners–

提取的棋盘格角点像素坐标,数据类型为vector<vector<Point2f>>;

int flags–

滤波处理:CALIB_CB_ADAPTIVE_THRESH自适应阈值法+CALIB_CB_NORMALIZE_IMAGE归一化(默认值)

find4QuadCornerSubpix参数设置,亚像素坐标提取函数

bool cv::find4QuadCornerSubpix  (
  InputArray  img,             //8位灰度图
  InputOutputArray  corners,   //保存计算所得亚像素坐标,数据类型同findChessboardCorners
  Size  region_size            //设置搜索框尺寸
 )  

Size  region_size  —

角点搜索具体操作:

1. 根据窗口尺寸,确定以该角点为中心的ROI区域,并计算该区域的直方图

2. 找出直方图中最大的一段segment,以此进行图像二值化,之后进行腐蚀操作,共进行两次

3. 对两幅图使用findContours进行轮廓提取,对于轮廓根据到该角点的最小距离排序

4. 以每幅图最小的两个轮廓为研究对象,先进行多边形逼近approxPolyDP(道格拉斯普克算子),并使用findCorner找到轮廓中到该角点最近的点

5. 对找到的四个点使用findLinesCrossPoint,获取精确的角点位置

drawChessboardCorners函数,角点绘制函数

void cv::drawChessboardCorners  ( 
  InputOutputArray  image,  
  Size  patternSize,      //棋盘格行列数(下角有标注)
  InputArray  corners,    
  bool  patternWasFound   //true检测到棋盘,将corners连接起来;
                          //false未检测到棋盘,将检测到的角点用红圈标注
 )  

具体实现参考点云侠-OpenCV——单目视觉:方形标定板角点提取

实心圆点标定

实心圆点标定板又分为对称型和非对称型,本文主要探讨对称型。

findCircleGrid参数设置,源码实质是调用findCIirclesGrid2函数(这里不做解析);

bool findCirclesGrid(
 InputArray _image, Size patternSize,
 OutputArray _centers, int flags,
 const Ptr<FeatureDetector> &blobDetector)
{
  return cv::findCirclesGrid2(_image, patternSize, _centers, 
  flags, blobDetector, CirclesGridFinderParameters2());
}

关键参数:

int flags—

对称型圆心      cv::CALIB_CB_SYMMETRIC_GRID;

非对称型圆心  cv::CALIB_CB_ASYMMETRIC_GRID;

cv::CALIB_CB_CLUSTEREING透镜失真严重时使用  通常用于宽视野相机(用“|”与对称型和非对称型配合使用)

const Ptr<FeatureDetector> &blobDetector :创建用于检测圆的智能指针(方法Blob算子)

对于大图片一定要设置检测圆面积阈值,本文图片分辨率为5472×3648

C++ OpenCV相机标定---实心圆点、棋盘格

相机标定

calibrateCamera函数,相机标定函数,用于标定相机内外参数

CV_EXPORTS_W double calibrateCamera( 
      InputArrayOfArrays objectPoints,
      InputArrayOfArrays imagePoints,
      CV_OUT InputOutputArray cameraMatrix,
      CV_OUT InputOutputArray distCoeffs,
      OutputArrayOfArrays rvecs, 
      OutputArrayOfArrays tvecs,
      int flags=0, TermCriteria criteria = TermCriteria(TermCriteria::COUNT+TermCriteria
::EPS, 30, DBL_EPSILON) );

函数的12个参数(一般设置前7个参数即可,后面默认。
1.objectPoints :世界坐标系中的点。在使用时,应该输入vector< vector< Point3f > >。
2.imagePoints :其对应的图像点。和objectPoints一样,应该输入vector< vector< Point2f > >型的变量。
3.imageSize :图像的大小,在计算相机的内参数和畸变矩阵需要用到;
4.cameraMatrix :内参数矩阵。输入一个Mat cameraMatrix即可。
5.distCoeffs :畸变矩阵。输入一个Mat distCoeffs即可。
6.rvecs :旋转向量;应该输入一个Mat的vector,即vector< Mat > rvecs因为每个vector< Point3f >会得到一个rvecs。
7.tvecs :位移向量;和rvecs一样,也应该为vector tvecs。
8.stdDeviationsIntrinsics :内参数的输出向量。输出顺序为: (fx,fy,cx,cy,k1,k2,p1,p2,k3,k4,k5,k6,s1,s2,s3,s4,τx,τy) ,如果不估计其中某一个参数,值等于0
9.stdDeviationsExtrinsics :外参数的输出向量。输出顺序: (R1,T1,…,RM,TM) ,M是标定图片的个数, Ri,Ti 是1×3的向量 。
10.perViewErrors 每个标定图片的重投影均方根误差的输出向量。
11.criteria: 迭代优化算法的终止准则
12.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。

   vector<Point2f> —-用于存放float类型的数据,这里是二维点向量,也可以将Point2f换成int等其他类型;
        vector<Point2i> —-用于存放int类型的数据;
        vector<Point2d> —-用于存放double类型的数据;
        vector<vector<Point2f>> points;—-表示定义一个二维数组,
                其中的points[0].size(),表示第一行的列数;points[0]第一行。ponts[1]第二行

Vec4f为二维直线类型;Vec6f为三维直线类型
Vec4f line ; line[0].line[1]存放的是直线方向向量,斜率为line[1]/line[0]  point.x=line[2];point.y=line[3];

标定结果-重投影误差

注意:重投影误差reprojectionError不能完全作为标定精度的评判标准,重投影误差结果的好坏与图像大小,图像质量等等有关。

建议:通过标定的内外参数计算角点/圆心靶标坐标系下坐标与实际坐标(角点间距、圆点中心距已知)进行比较。

关键函数:projectPoints函数

void projectPoints(
    InputArray objectPoints,          //靶标坐标系角点坐标
    InputArray rvec,                  //外参旋转向量
    InputArray tvec,                  //外参平移向量
    InputArray cameraMatrix,          //内参矩阵
    InputArray distCoeffs,            //畸变系数
    OutputArray imagePoints,          //输出像素坐标(重新投影)
    OutputArray jacobian=noArray(),   //雅可比行列式
    double aspectRatio=0 
)

雅可比行列式不做解释,没整明白(以后补充,欢迎大佬解释),一般参数设置到第6个即可

完整代码展示

structure_light .h

// use for calibration
#ifndef STRUCTURE_LIGHT_H
#define STRUCTURE_LIGHT_H

#include "struct_light_calib.h"


class structure_light 
{
public:
	structure_light(int x, cv::Size patternSize, cv::Size patternLength);
	
	
	cv::Mat cameraMatrix;
	cv::Mat distCoeffs;
	/*cv::Mat cam2laser;*/

	std::vector<cv::Mat> R;
	std::vector<cv::Mat> T;
	

	std::vector<bool> isRecognize;

	std::vector<std::vector<cv::Point3f>> calibBoardPoint;

	
	std::vector<std::vector<cv::Point2f>> calibImagePoint;


	std::vector<std::vector<cv::Point2f>>featureOriImagePoint;//特征点的像素中心点坐标
	std::vector<std::vector<cv::Point2f>>featureSubImagePoint;//特征点的亚像素中心点坐标

	std::vector<cv::Mat> laserPoints;//特征点的三维坐标
	

	int imageCount;

	cv::Size patternSize;
	cv::Size PatternLength;

	
};

#endif

structure_light.cpp

// use for calibration
#include "structure_light.h"
#include "struct_light_calib.h"
//函数定义构造函数structure_light
structure_light::structure_light(int x, cv::Size patternSize, cv::Size patternLength) 
{
	structure_light::imageCount = x;
	structure_light::patternSize = patternSize;
	structure_light::PatternLength = patternLength;
}

struct_light_calib.h

#ifndef TTTTT_STRUCT_LIGHT_CALIB_H
#define TTTTT_STRUCT_LIGHT_CALIB_H


#include <vector>
#include <string>
#include <iostream>


//#include "structure_light.h"

using namespace std;
//using namespace cv;


class structure_light;

int cameraCalib(structure_light &a, double &reprojectionError);
#endif //TTTTT_STRUCT_LIGHT_CALIB_H

struct_light_calib.cpp

//摄像机标定
int cameraCalib(structure_light &a, double &reprojectionError)
{
	string format = ".jpg";
	for (int i = 0; i < a.imageCount; i++)
	{
		string index = to_string(i);
		string name = "./calib_picture/calib" + index + format;
		cv::Mat pic = cv::imread(name);

		/*Mat greyImage;
		cvtColor(pic, greyImage, COLOR_BGR2GRAY);*/

		bool result = true;
		vector<cv::Point2f> targetPoint;
		//圆形靶标提取圆心点
		if (iscircle)
		{
			//图像尺寸大,需要对检测圆面积的阈值进行设定
			cv::SimpleBlobDetector::Params params;
			params.maxArea = 90000;
			params.minArea = 500;
			params.filterByArea = true;
			cv::Ptr<cv::FeatureDetector> blobDetector = cv::SimpleBlobDetector::create(params);
			if (0 == findCirclesGrid(pic, a.patternSize, targetPoint, cv::CALIB_CB_SYMMETRIC_GRID | cv::CALIB_CB_CLUSTERING, blobDetector))//提取靶标上圆斑的圆心
			{
				result = false;
				a.isRecognize.push_back(result);
				cout << "false-calib" << endl;
				continue;
			}
			else
			{
				result = true;
				a.isRecognize.push_back(result);
				a.calibImagePoint.push_back(targetPoint);
				cout << "true-calib" << endl;
			}
		}
		//棋盘格靶标提取角点
		else
		{
			if (0 == findChessboardCorners(pic, a.patternSize, targetPoint))
			{
				result = false;
				a.isRecognize.push_back(result);
				cout << "false-calib" << endl;
				continue;
			}
			else
			{
				result = true;
				a.isRecognize.push_back(result);
				find4QuadCornerSubpix(pic, targetPoint, cv::Size(5, 5));
				a.calibImagePoint.push_back(targetPoint);
				drawChessboardCorners(pic, patternSize, targetPoint, true);
				cout << "true-calib" << endl;
				imwrite("./save/Corner.jpg", pic);
			}
		}
	}

	a.generateCalibboardPoint();//自定义的标定板原点靶标坐标
	/* 摄像头标定得到相机内参和畸变矩阵以及外参(旋转矩阵R和平移矩阵T)*/
	double rms = calibrateCamera(a.calibBoardPoint, a.calibImagePoint, image_size, a.cameraMatrix, a.distCoeffs, a.R, a.T);
	cout << "Reprojection error:" << rms << endl;
	
	a.Rw = a.R[0];
	a.Tw = a.T[0];
	/* 重投影评估单目摄像头标定精度 */
	double err = 0.0;
	double mean_err = 0.0;
	double total_err = 0.0;
	int i;
	vector<cv::Point2f> reprojectionPoint;

	for (i = 0; i < a.calibBoardPoint.size(); i++)
	{
		vector<cv::Point3f> tempPointSet = a.calibBoardPoint[i];//标定板圆点靶标三维坐标
		/*cout << "标定板圆点三维坐标" << a.calibBoardPoint[i] << endl;*/
		/* 通过得到的摄像机内外参数,对空间的三维点进行重新投影计算,得到新的投影点 */
		projectPoints(tempPointSet, a.R[i], a.T[i], a.cameraMatrix, a.distCoeffs, reprojectionPoint);
		/* 计算新的投影点和旧的投影点之间的误差*/
		vector<cv::Point2f> tempImagePoint = a.calibImagePoint[i];
		cv::Mat tempImagePointMat = cv::Mat(1, tempImagePoint.size(), CV_32FC2);//旧投影点(49个点)
		cv::Mat image_points2Mat = cv::Mat(1, reprojectionPoint.size(), CV_32FC2);//新投影点(49个点(畸变校正后))
		for (int j = 0; j < tempImagePoint.size(); j++)
		{
			image_points2Mat.at<cv::Vec2f>(0, j) = cv::Vec2f(reprojectionPoint[j].x, reprojectionPoint[j].y);
			tempImagePointMat.at<cv::Vec2f>(0, j) = cv::Vec2f(tempImagePoint[j].x, tempImagePoint[j].y);
		}
		err = norm(image_points2Mat, tempImagePointMat, cv::NORM_L2);

		mean_err = err / (a.patternSize.width*a.patternSize.height);
		total_err += mean_err;
	}
	reprojectionError = total_err / a.calibBoardPoint.size();
	return 0;
}

主函数

#include "structure_light.h"
#include "struct_light_calib.h"
#include <fstream>
#include <math.h>
#include <stdlib.h>  
#include <cmath>
#include <iostream>
#include <iomanip>
#include <cstdlib>
#include <cstring>
#include <vector>
//定义一些全局变量作为标定的靶标参数
int imageCount = 11;//图像的数量
cv::Size patternSize(7, 7);//靶标(圆的数量)
cv::Size2f patternLength(15.0, 15.0);//两个圆形标记之间的距离
cv::Size image_size(5472,3468);
//patternType:
//0:circle;
//1:chessboard;
bool iscircle = true;



int main()
{
    //structure_light为自定义类,参数可自行定义放入函数即可
    structure_light lineStructureLight(imageCount, patternSize, patternLength);

    double reprojectionError = 0.0;
    cameraCalib(lineStructureLight, reprojectionError);//摄像机标定
    cout << "Camera calibration reprojection error: " << reprojectionError << " pixels." << endl;
    return 0;
}

文章出处登录后可见!

已经登录?立即刷新

共计人评分,平均

到目前为止还没有投票!成为第一位评论此文章。

(0)
社会演员多的头像社会演员多普通用户
上一篇 2023年2月25日 下午4:06
下一篇 2023年2月25日 下午4:09

相关推荐