单目相机的标定原理大致如下:
世界坐标到像素坐标的转换。
期间的参数有S尺度因子,内参矩阵K,旋转矩阵R,平移矩阵T,一共八个未知数。
在Opencv中我们可以方便的根据相机拍摄不同位姿的标定板图片来标定相机,从而求得这些参数,从而可以将相机下的坐标和实际的世界坐标联系起来,求取单应性矩阵H
参考:
单目相机综述:写的特别好
https://www.cnblogs.com/zyly/p/9366080.html
https://blog.csdn.net/qq_37791134/article/details/80942171
张正有标定原理:
接下来我具体的说明一下Opencv中的单目相机的标定:
- 准备20-30张拍摄的标定板图像,以20张图片为例。
- 提取20张图片的的角点信息。
- 在上一步的基础上,提取精度更高的角点坐标。
- 把找到的角点在图上画出来,显得更加直观。
- 相机标定计算,根据上面提取到的角点坐标信息和世界坐标系中的三维点进行计算。
- 对标定结果进行评价
- 查看标定效果——利用标定结果对棋盘图进行矫正 (两种方法)
输入:标定图像上所有内角点的图像坐标,标定板图像上所有内角点的空间三维坐标
输出:摄像机的内参、外参系数。
31张棋盘图下载地址:
main.cpp
#include"11.h"
int main()
{
//初始化棋盘数据,载入图片
calibrator calib(IMAGE_FOLDER, 25.f, 5, 4);
//把所有的图片的角点坐标找到
calib.calc_image_points(true);
//回车切换下张棋盘图
cout << "计算相机参数中......请等待" << endl;
//相机标定计算
calib.calibrate();
cout << "--------标定完成--------" << endl;
//对标定结果进行评价
//利用相机标定计算得到的每张图片的转换关系,将其三维点转换成二维点
//和之前得到的角点二维坐标进行对比 三维--》二维
calib.get_error();
//查看标定效果——利用标定结果对棋盘图进行矫正
//利用内参和畸变系数求出 无畸变和修正转换映射
//去除镜头畸变的图像拉伸
calib.Correct_image();
string filename = DATA_FOLDER + string("cam_calib.xml");
FileStorage fs(filename, FileStorage::WRITE);
fs << "cameraMatrix" << calib.get_cameraMatrix();
fs << "distCoeffs" << calib.get_distCoeffs();
fs.release();
cout << "保存相机参数到:" << filename << endl;
system("pause");
return 0;
}
11.h
#include <opencv2/opencv.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/calib3d/calib3d.hpp>
#define DATA_FOLDER "./date/"
#define IMAGE_FOLDER "./img/"
using namespace cv;
using namespace std;
//using namespace boost::filesystem;
class calibrator
{
public:
calibrator(string, float, int, int); //构造函数,读取图像的信息
void calc_image_points(bool); //找到图像的内角点
void calibrate(); //计算相机参数
Mat get_cameraMatrix(); //得到相机的内参矩阵
Mat get_distCoeffs(); //得到相机的畸变系数
void get_error(); //对标定结果进行评价
void Correct_image(); //查看标定效果——利用标定结果对棋盘图进行矫正
private:
string pattern; //30张标定图片的位置
vector<Mat> images; //用于保存标定图片
Mat cameraMatrix, distCoeffs; //定义的相机内参矩阵、畸变系数
bool show_chess_corners;
float side_length; //棋盘单个正方形方块的长度
int width, height; //内角点在长和宽方向上的数量
vector<vector<Point2f> > image_points; //保存2D点
vector<vector<Point3f> > object_points; //保存3D点
vector<Mat> rvecs, tvecs; //保存每张图片R,T的转换矩阵
};
11.cpp
#include"11.h"
calibrator::calibrator(string _path, float _side_length, int _width, int _height)
{
side_length = _side_length;
width = _width;
height = _height;
pattern = _path;
cout <<"图片的位置:" <<pattern << endl;
//std::string pattern = "./img/*.jpg";
std::vector<cv::String>image_file;
glob(pattern, image_file);
for(int i=0;i<image_file.size();i++)
{
images.push_back(imread(image_file[i]));
}
}
void calibrator::calc_image_points(bool show) //计算内角点
{
//计算物体坐标系中的物体点(左上角原点)
vector<Point3f> ob_p; //定义内角点的容器,20个点
for (int i = 0; i < height; i++) //4
{
for (int j = 0; j < width; j++) //5
{
ob_p.push_back(Point3f(j * side_length, i * side_length, 0.f));
}
}
if (show) namedWindow("Chessboard corners");
for (int i = 0; i < images.size(); i++)
{
Mat im = images[i];
vector<Point2f> im_p; //在棋盘图像中找到内角点坐标,初始的二维点im_p
bool pattern_found = findChessboardCorners(im, Size(width, height), im_p, CALIB_CB_ADAPTIVE_THRESH + CALIB_CB_NORMALIZE_IMAGE + CALIB_CB_FAST_CHECK);
if (pattern_found)
{
object_points.push_back(ob_p);//存放20个vector<Point3f>,每张图的20个点现在用不到
//现在用不到,是物体的坐标,不是图像
Mat gray;
cvtColor(im, gray, CV_BGR2GRAY);//亚像素级角点检测,提高精度im_p,输出im_p
cornerSubPix(gray, im_p, Size(5, 5), Size(-1, -1), TermCriteria(CV_TERMCRIT_EPS + CV_TERMCRIT_ITER, 30, 0.1));
image_points.push_back(im_p);
if (show)
{
Mat im_show = im.clone(); //第二个参数patternSize,每张标定棋盘上内角点的行列数
drawChessboardCorners(im_show, Size(width, height), im_p, true);
imshow("Chessboard corners", im_show);
while (char(waitKey(1)) != '\r') //按回车跳出,进行下一张图片
{
}
}
}
else images.erase(images.begin() + i);//如果图片无效找不到角点,删除vector中这个图片的接口
}
}
void calibrator::calibrate()
{
//vector<Mat> rvecs, tvecs;
float rms_error = calibrateCamera(object_points, image_points, images[0].size(),
cameraMatrix, distCoeffs, rvecs, tvecs);
//第一个参数:objectPoints,为世界坐标系中的三维点:vector<vector<Point3f>> object_points,
//需要依据棋盘上单个黑白矩阵的大小,计算出(初始化)每一个内角点的世界坐标。
//长100*宽75
//第二个参数:imagePoints,为每一个内角点对应的图像坐标点:vector<vector<Point2f>> image_points
//第三个参数:imageSize,为图像的像素尺寸大小
//第四个参数:cameraMatrix为相机的内参矩阵:Mat cameraMatrix=Mat(3,3,CV_32FC1,Scalar::all(0));
//第五个参数:distCoeffs为畸变矩阵Mat distCoeffs=Mat(1,5,CV_32FC1,Scalar::all(0));
//内参数矩阵 M=[fx γ u0,0 fy v0,0 0 1]
//外参矩阵 5个畸变系数k1,k2,p1,p2,k3
//第六个参数:rvecs旋转向量R,vector<Mat> tvecs;
//第七个参数:tvecs位移向量T,和rvecs一样,应该为vector<Mat> tvecs;
//第八个参数:flags为标定时所采用的算法 第九个参数:criteria是最优迭代终止条件设定。
//return:重投影的总的均方根误差。
//总结:得到相机内参矩阵K、相机的5个畸变系数、每张图片属于自己的平移向量T、旋转向量R
cout << "重投影的总的均方根误差:" << rms_error << endl;
}
Mat calibrator::get_cameraMatrix()
{
return cameraMatrix;
}
Mat calibrator::get_distCoeffs()
{
return distCoeffs;
}
void calibrator::get_error()
{
double total_err = 0.0; //所有图像的平均误差的总和
double err = 0.0; //每幅图像的平均误差
vector<Point2f> image_points2; // 保存重新计算得到的投影点
cout << "\t每幅图像的标定误差:\n" << endl;
for(int i=0;i< images.size(); i++)
{
vector<Point3f> tempPointSet = object_points[i];
//通过得到的摄像机内外参数,对空间的三维点进行重新投影计算,得到新的投影点
projectPoints(tempPointSet, rvecs[i], tvecs[i], cameraMatrix, distCoeffs, image_points2);
// 计算新的投影点和旧的投影点之间的误差
vector<Point2f> tempImagePoint = image_points[i];
Mat tempImagePointMat = Mat(1, tempImagePoint.size(), CV_32FC2); //cornerSubPix
Mat image_points2Mat = Mat(1, image_points2.size(), CV_32FC2); //projectPoints
//对标定结果进行评价
for (int j = 0; j < tempImagePoint.size(); j++)
{ // //分别给两个角点坐标赋值他x,y坐标
image_points2Mat.at<Vec2f>(0, j) = Vec2f(image_points2[j].x, image_points2[j].y);
tempImagePointMat.at<Vec2f>(0, j) = Vec2f(tempImagePoint[j].x, tempImagePoint[j].y);
}
//norm计算数组src1和src2的相对差范数
err = norm(image_points2Mat, tempImagePointMat, NORM_L2);
total_err += err ;
std::cout << "第" << i + 1 << "幅图像的平均误差:" << err << "像素" << endl;
}
std::cout << "总体的平均误差:" << total_err/ images.size() << "像素" << endl;
}
//查看标定效果——利用标定结果对棋盘图进行矫正
void calibrator::Correct_image()
{
Mat mapx = Mat(images[0].size(), CV_32FC1);
Mat mapy = Mat(images[0].size(), CV_32FC1);
Mat R = Mat::eye(3, 3, CV_32F); //单位矩阵
//计算出对应的映射表
initUndistortRectifyMap(cameraMatrix, distCoeffs, R, cameraMatrix, images[0].size(), CV_32FC1, mapx, mapy);
//第三个参数R,可选的输入,是第一和第二相机坐标之间的旋转矩阵;
//第四个参数newCameraMatrix,输入的校正后的3X3摄像机矩阵;
//第五个参数size,摄像机采集的无失真的图像尺寸;
//第六个参数m1type,定义map1的数据类型,可以是CV_32FC1或者CV_16SC2;
//第七个参数map1和第八个参数map2,输出的X / Y坐标重映射参数;
for (int i = 0; i!= images.size(); i++)
{
Mat imageSource = images[i];
Mat newimage = images[i].clone();
Mat newimage1 = images[i].clone();
//方法一:使用initUndistortRectifyMap和remap两个函数配合实现。
//initUndistortRectifyMap用来计算畸变映射,remap把求得的映射应用到图像上。
remap(imageSource, newimage, mapx, mapy, INTER_LINEAR);
//第五个参数interpolation,定义图像的插值方式;
//第六个参数borderMode,定义边界填充方式;
//方法二:使用undistort函数实现
undistort(imageSource, newimage1, cameraMatrix, distCoeffs);
//第五个参数newCameraMatrix,默认跟cameraMatrix保持一致;
//输出图像
string str = "./img_remap/" + to_string(i + 1) + ".jpg";
string str1 = "./img_remap_undistort/" + to_string(i + 1) + ".jpg";
//string str = "d:\\" + to_string(i + 1) + ".jpg";
imwrite(str, newimage);
imwrite(str1, newimage1);
}
}
最后效果:相机内外参数,矫正前后图片
单目标定我们得到:
- cameraMatrix-----相机的内参矩阵
- distCoeffs-----相机镜头的畸变矩阵
- rvecs-----旋转向量R-----用于三维初始点转换为二维投影点
- tvecs-----位移向量T-----用于三维初始点转换为二维投影点
图像去畸变:
第一种:
Mat mapx = Mat(images[0].size(), CV_32FC1);
Mat mapy = Mat(images[0].size(), CV_32FC1);
initUndistortRectifyMap(cameraMatrix, distCoeffs, R, cameraMatrix,
images[0].size(), CV_32FC1, mapx, mapy);
remap(imageSource, newimage, mapx, mapy, INTER_LINEAR);
第二种:
undistort(imageSource, newimage1, cameraMatrix, distCoeffs);
mapx、mapy用于二维图像的畸变矫正,也可以直接使用undistort()矫正图像。