目录
在相机内参标定中,采用二维靶标标定主要分为两种方式:棋盘格标定、实心圆点标定。注意棋盘格和实心圆点在标定过程中注意保持清晰,且与水平(竖直)成一定角度15-30°,实验所得,仅供参考。
棋盘格标定
棋盘格标定相机内参主要采用两种方法:Matlab的相机工具包、OpenCV函数findChessboardCorner函数调用。
Matlab相机工具包计算相机内参:详情参考链接
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
相机标定
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;
}
文章出处登录后可见!