相机标定(一)——内参标定与程序实现
相机标定(二)——图像坐标与世界坐标转换
相机标定(三)——手眼标定
备注:棋盘格黑白间距已知,可采用打印纸或者购买黑白棋盘标定板(精度要求高)
此处分两种情况
(1)标定畸变系数和相机内参,拍摄照片需要包含完整棋盘,同时需要不同距离,不同方位,同时需要有棋盘不同倾斜角度。
(2)标定畸变系数,相机内参和相机外参,图片包含上述要求,同时标定程序生成结果中每张照片会计算一个相机外参数因此根据实际需求,增加几张棋盘在工作位置的照片。(相机外参建议采用solvePnP函数获取)
角点检测
bool findChessboardCorners(InputArray image,
Size patternSize,
OutputArray corners,
int flags=CALIB_CB_ADAPTIVE_THRESH+CALIB_CB_NORMALIZE_IMAGE )
image,传入拍摄的棋盘图Mat图像,必须是8位的灰度或者彩色图像;
patternSize,每个棋盘图上内角点的行列数,一般情况下,行列数不要相同,便于后续标定程序识别标定板的方向;
corners,用于存储检测到的内角点图像坐标位置,一般用元素是Point2f的向量来表示:vector
flage:用于定义棋盘图上内角点查找的不同处理方式,有默认值。
提取亚像素角点信息
(1)cornerSubPix
void cornerSubPix(InputArray image, InputOutputArray corners, Size winSize, Size zeroZone, TermCriteria criteria)
img,输入的Mat矩阵,最好是8位灰度图像,检测效率更高;
corners
,初始的角点坐标向量,同时作为亚像素坐标位置的输出,所以需要是浮点型数据,一般用元素是Pointf2f/Point2d的向量来表示:vector
winSize
,搜索窗口边长的一半,例如如果winSize=Size(5,5),则一个大小为的搜索窗口将被使用。
zeroZone
,搜索区域中间的dead region边长的一半,有时用于避免自相关矩阵的奇异性。如果值设为(-1,-1)则表示没有这个区域。
criteria
,角点精准化迭代过程的终止条件。也就是当迭代次数超过criteria.maxCount,或者角点位置变化小于criteria.epsilon时,停止迭代过程。
该函数通过迭代法查找角点亚像素精度下的精确位置,函数实现流程如下:
(2) find4QuadCornerSubpix
bool find4QuadCornerSubpix(InputArray img, InputOutputArray corners, Size region_size);
img,输入的Mat矩阵,最好是8位灰度图像,检测效率更高;
corners,初始的角点坐标向量,同时作为亚像素坐标位置的输出,所以需要是浮点型数据,一般用元素是Pointf2f/Point2d的向量来表示:vector
region_size,角点搜索窗口的尺寸;
double calibrateCamera(InputArrayOfArrays objectPoints,
InputArrayOfArrays imagePoints,
Size imageSize,
InputOutputArray cameraMatrix,
InputOutputArray distCoeffs,
OutputArrayOfArrays rvecs,
OutputArrayOfArrays tvecs,
int flags=0,
TermCriteria criteria=TermCriteria( TermCriteria::COUNT+TermCriteria::EPS, 30, DBL_EPSILON) )
objectPoints,为世界坐标系中的三维点。在使用时,应该输入一个三维坐标点的向量的向量,即vector
imagePoints,为每一个内角点对应的图像坐标点。和objectPoints一样,应该输入vector
imageSize,为图像的像素尺寸大小,在计算相机的内参和畸变矩阵时需要使用到该参数;
cameraMatrix为相机的内参矩阵。输入一个Mat cameraMatrix即可,如Mat cameraMatrix=Mat(3,3,CV_32FC1,Scalar::all(0));
distCoeffs为畸变矩阵。输入一个Mat distCoeffs=Mat(1,5,CV_32FC1,Scalar::all(0))即可;
rvecs为旋转向量;应该输入一个Mat类型的vector,即vector
tvecs为位移向量,和rvecs一样,应该为vector
flags为标定时所采用的算法。有如下几个参数:
CV_CALIB_USE_INTRINSIC_GUESS:使用该参数时,在cameraMatrix矩阵中应该有fx,fy,u0,v0的估计值。否则的话,将初始化(u0,v0)图像的中心点,使用最小二乘估算出fx,fy。
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_RATIONAL_MODEL:计算k4,k5,k6三个畸变参数。如果没有设置,则只计算其它5个畸变参数。
criteria是最优迭代终止条件设定。
在使用该函数进行标定运算之前,需要对棋盘上每一个内角点的空间坐标系的位置坐标进行初始化,默认参数下生成的标定的结果为相机内参矩阵cameraMatrix、相机的5个畸变系数distCoeffs,另外每张图像都会生成属于自己的平移向量和旋转向量。
对标定结果进行评价的方法是通过得到的摄像机内外参数,对空间的三维点进行重新投影计算,得到空间三维点在图像上新的投影点的坐标,计算投影坐标和亚像素角点坐标之间的偏差,偏差越小,标定结果越好。
空间的三维点进行重新投影计算
void projectPoints( InputArray objectPoints,
InputArray rvec,
InputArray tvec,
InputArray cameraMatrix,
InputArray distCoeffs,
OutputArray imagePoints,
OutputArray jacobian=noArray(),
double aspectRatio=0 );
objectPoints,为相机坐标系中的三维点坐标;
rvec为旋转向量,每一张图像都有自己的选择向量;
tvec为位移向量,每一张图像都有自己的平移向量;
cameraMatrix为求得的相机的内参数矩阵;
distCoeffs为相机的畸变矩阵;
iamgePoints为每一个内角点对应的图像上的坐标点;
acobian是雅可比行列式;
aspectRatio是跟相机传感器的感光单元有关的可选参数,如果设置为非0,则函数默认感光单元的dx/dy是固定的,会依此对雅可比矩阵进行调整;
保存结果有两种格式。
txt用于查看结果。
//保存定标结果
cout << "开始保存定标结果………………" << endl;
Mat rotation_matrix = Mat(3, 3, CV_32FC1, Scalar::all(0)); /* 保存每幅图像的旋转矩阵 */
fout << "相机内参数矩阵:" << endl;
fout << cameraMatrix << endl << endl;
fout << "畸变系数:\n";
fout << distCoeffs << endl << endl << endl;
for (int i = 0; i
xml用于将标定结果应用于视觉处理。
//保存相机内参数矩阵和畸变系数和相机距离到xml
cout << "开始保存相机内参数矩阵和畸变系数………………" << endl;
string xmlResult = filePath + "\\calibrateImage\\caliberation_camera.xml";
FileStorage fs(xmlResult, FileStorage::WRITE); //创建XML文件
fs << "zConst" << 100.0;
fs << "cameraMatrix" << cameraMatrix << "distCoeffs" << distCoeffs;
fs.release();
运行程序后输入保存图片目录的绝对路径,标定结果默认在该目录的calibrateImage文件夹内。
#include "stdafx.h"
#include
#include
#include
#include
#include
#include
#include
using namespace std;
#include
using namespace cv;
//标定板方格边长,行角点,列角点
#define BOARD_SCALE 30
#define BOARD_HEIGHT 11
#define BOARD_WIDTH 8
//获取特定格式的文件名
void GetAllFormatFiles(string path, vector& files, string format)
{
//文件句柄
//long hFile = 0;//win7使用
intptr_t hFile = 0;//win10使用
//文件信息
struct _finddata_t fileinfo;
string p;
if ((hFile = _findfirst(p.assign(path).append("\\*" + format).c_str(), &fileinfo)) != -1)
{
do
{
if ((fileinfo.attrib & _A_SUBDIR))
{
if (strcmp(fileinfo.name, ".") != 0 && strcmp(fileinfo.name, "..") != 0)
{
//files.push_back(p.assign(path).append("\\").append(fileinfo.name) );
GetAllFormatFiles(p.assign(path).append("\\").append(fileinfo.name), files, format);
}
}
else
{
//files.push_back(p.assign(path).append("\\").append(fileinfo.name));//将文件路径保存
files.push_back(p.assign(fileinfo.name)); //只保存文件名:
}
} while (_findnext(hFile, &fileinfo) == 0);
_findclose(hFile);
}
}
void main()
{
vector imageFilesName;
vector files;
imageFilesName.clear(); files.clear();
string filePath;
cout << "请输入标定照片文件绝对目录路径" << endl;
cin >> filePath;
string format = ".jpg";
GetAllFormatFiles(filePath, imageFilesName, format);
cout << "找到的文件有" << endl;
for (int i = 0; i < imageFilesName.size(); i++)
{
files.push_back(filePath + "\\" + imageFilesName[i]);
cout << files[i] << endl;
}
string calibrateDir = filePath + "\\calibrateImage";
_mkdir(calibrateDir.c_str());
//读取每一幅图像,从中提取出角点,然后对角点进行亚像素精确化
cout << "开始提取角点………………" << endl;
int image_count = 0; /* 图像数量 */
Size image_size; /* 图像的尺寸 */
Size board_size = Size(BOARD_HEIGHT, BOARD_WIDTH); /* 标定板上每行、列的角点数 */
vector image_points_buf; /* 缓存每幅图像上检测到的角点 */
vector> image_points_seq; /* 保存检测到的所有角点 */
for (int i = 0; i> object_points; /* 保存标定板上角点的三维坐标 */
/*内外参数*/
Mat cameraMatrix = Mat(3, 3, CV_32FC1, Scalar::all(0)); /* 摄像机内参数矩阵 */
vector point_counts; // 每幅图像中角点的数量
Mat distCoeffs = Mat(1, 5, CV_32FC1, Scalar::all(0)); /* 摄像机的5个畸变系数:k1,k2,p1,p2,k3 */
vector tvecsMat; /* 每幅图像的旋转向量 */
vector rvecsMat; /* 每幅图像的平移向量 */
/* 初始化标定板上角点的三维坐标 */
int i, j, t;
for (t = 0; t tempPointSet;
for (i = 0; i image_points2; /* 保存重新计算得到的投影点 */
cout << "\t每幅图像的标定误差:\n";
fout << "每幅图像的标定误差:\n";
for (i = 0; i tempPointSet = object_points[i];
/* 通过得到的摄像机内外参数,对空间的三维点进行重新投影计算,得到新的投影点 */
projectPoints(tempPointSet, rvecsMat[i], tvecsMat[i], cameraMatrix, distCoeffs, image_points2);
/* 计算新的投影点和旧的投影点之间的误差*/
vector tempImagePoint = image_points_seq[i];
Mat tempImagePointMat = Mat(1, tempImagePoint.size(), CV_32FC2);
Mat image_points2Mat = Mat(1, image_points2.size(), CV_32FC2);
for (int j = 0; j < tempImagePoint.size(); j++)
{
image_points2Mat.at(0, j) = Vec2f(image_points2[j].x, image_points2[j].y);
tempImagePointMat.at(0, j) = Vec2f(tempImagePoint[j].x, tempImagePoint[j].y);
}
err = norm(image_points2Mat, tempImagePointMat, NORM_L2);
total_err += err /= point_counts[i];
cout << "第" << i + 1 << "幅图像的平均误差:" << err << "像素" << endl;
fout << "第" << i + 1 << "幅图像的平均误差:" << err << "像素" << endl;
}
cout << "总体平均误差:" << total_err / image_count << "像素" << endl;
fout << "总体平均误差:" << total_err / image_count << "像素" << endl << endl;
//保存定标结果
cout << "开始保存定标结果………………" << endl;
Mat rotation_matrix = Mat(3, 3, CV_32FC1, Scalar::all(0)); /* 保存每幅图像的旋转矩阵 */
fout << "相机内参数矩阵:" << endl;
fout << cameraMatrix << endl << endl;
fout << "畸变系数:\n";
fout << distCoeffs << endl << endl << endl;
for (int i = 0; i
参考
https://docs.opencv.org/2.4/modules/calib3d/doc/camera_calibration_and_3d_reconstruction.html
https://blog.csdn.net/u010128736/article/details/52860364
https://blog.csdn.net/dcrmg/article/details/52939318