一、前言
使用opencv标定相机的方法和实现已经十分成熟了,自己参考了书籍《opencv2计算编程手册》和网上大神的代码后实现了该功能。
把标定相关操作简单封装为类并提供了调用demo,这里把完整程序上传上来,以供其他网友参考。
二、代码
CameraCalibrator.h 类声明与定义
#ifndef _CAMERACALIBRATOR_H_
#define _CAMERACALIBRATOR_H_
#include <vector>
#include <iostream>
#include <opencv2/core/core.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/calib3d/calib3d.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/opencv.hpp>
class CameraCalibrator
{
private:
std::vector<std::vector<cv::Point3f>> objectPoints;// 角点的世界坐标系坐标
std::vector<std::vector<cv::Point2f>> imagePoints;// 角点的像素坐标系坐标
cv::Mat cameraMatrix;// 内参矩阵
cv::Mat distCoeffs;// 畸变矩阵
std::vector<cv::Mat> rvecs, tvecs;// 旋转矩阵队列和平移矩阵队列,每一幅标定图像都有对应的一个旋转矩阵和平移矩阵
std::vector<double> calibrateErrs;// 保存矫正偏差
int flag;
cv::Mat map1, map2;
bool mustInitUndistort;
public:
CameraCalibrator(): flag(0), mustInitUndistort(true){};
int addChessboardPoints(const std::vector<std::string>& filelist, cv::Size& boardSize);
void addPoints(const std::vector<cv::Point2f>& imageCorners, const std::vector<cv::Point3f>& objectCorners);
double calibrate(cv::Size &imageSize);
void setCalibrationFlag(bool radial8CoeffEnabled = false, bool tangentialParamEnabled = false);
void computeCalibrateError();
cv::Mat remap(const cv::Mat& image);
cv::Mat getCameraMatrix() {return cameraMatrix;}
cv::Mat getDistCoeffs() {return distCoeffs;}
std::vector<double>getCalibrateErrs(){return calibrateErrs;}
};
#endif
CameraCalibrator.cpp 类实现
#include "CameraCalibrator.h"
int CameraCalibrator::addChessboardPoints(const std::vector<std::string>& filelist, cv::Size& boardSize)
{
std::vector<cv::Point2f> imageCorners;
std::vector<cv::Point3f> objectCorners;
for(int i = 0; i < boardSize.height; i++)
{
for(int j = 0; j < boardSize.width; j++)
{
objectCorners.push_back(cv::Point3f(i, j, 0.0f));// 保存世界坐标系坐标
}
}
cv::Mat image;
int success = 0;
for(int i = 0; i < filelist.size(); i++)
{
image = cv::imread(filelist[i], 0);
bool found = cv::findChessboardCorners(image, boardSize, imageCorners);// 计算角点信息
cv::cornerSubPix(image, imageCorners, cv::Size(5,5), cv::Size(-1,-1),
cv::TermCriteria(cv::TermCriteria::MAX_ITER + cv::TermCriteria::EPS, 30, 0.1));// 计算亚像素级别角点信息
if(imageCorners.size() == boardSize.area())
{
addPoints(imageCorners, objectCorners);// 保存像素坐标系坐标
success++;
}
cv::drawChessboardCorners(image, boardSize, imageCorners, found);
cv::imshow("Corners on Chessboard", image);// 显示角点信息
cv::waitKey(100);
}
return success;
}
void CameraCalibrator::addPoints(const std::vector<cv::Point2f>& imageCorners, const std::vector<cv::Point3f>& objectCorners)
{
imagePoints.push_back(imageCorners);
objectPoints.push_back(objectCorners);
}
double CameraCalibrator::calibrate(cv::Size &imageSize)
{
mustInitUndistort = true;
double ret = cv::calibrateCamera(objectPoints, imagePoints, imageSize,
cameraMatrix, distCoeffs, rvecs, tvecs, flag);// 计算相机参数
return ret;
}
cv::Mat CameraCalibrator::remap(const cv::Mat& image)
{
cv::Mat undistorted;
if(mustInitUndistort)
{
cv::initUndistortRectifyMap(cameraMatrix,
distCoeffs, cv::Mat(), cv::Mat(), image.size(), CV_32FC1, map1, map2);// 首先计算得到用于矫正计算的两个矩阵map1和map2
mustInitUndistort = false;
}
cv::remap(image, undistorted, map1, map2,cv::INTER_LINEAR);// 矫正运算
return undistorted;
}
void CameraCalibrator::setCalibrationFlag(bool radial8CoeffEnabled, bool tangentialParamEnabled)
{
flag = 0;
if(!tangentialParamEnabled)
flag += CV_CALIB_ZERO_TANGENT_DIST;
if(radial8CoeffEnabled)
flag += CV_CALIB_RATIONAL_MODEL;
}
void CameraCalibrator::computeCalibrateError()
{
int image_count = rvecs.size();
for(int i = 0; i < image_count; i++)
{
std::vector<cv::Point2f> result_image_points;// 保存反向映射后的像素坐标系坐标
cv::projectPoints(objectPoints[i], rvecs[i], tvecs[i],
cameraMatrix, distCoeffs, result_image_points);// 将世界坐标系坐标反向投影到像素坐标系中
cv::Mat original_image_points_Mat = cv::Mat(1, objectPoints[i].size(), CV_32FC2);
cv::Mat result_image_points_Mat = cv::Mat(1, result_image_points.size(), CV_32FC2);
/* 使用二阶笵数计算矩阵间的差异作为矫正偏差 */
for(int j = 0; j < objectPoints[i].size(); j++)
{
original_image_points_Mat.at<cv::Vec2f>(0,j) = cv::Vec2f(imagePoints[i][j].x, imagePoints[i][j].y);
result_image_points_Mat.at<cv::Vec2f>(0,j) = cv::Vec2f(result_image_points[j].x, result_image_points[j].y);
}
double err = cv::norm(original_image_points_Mat, result_image_points_Mat, cv::NORM_L2);
err /= result_image_points.size();
calibrateErrs.push_back(err);
}
}
calibrate.cpp 提供调用范例
#include <io.h>
#include <windows.h>
#include "CameraCalibrator.h"
/* 获取指令路径下所有文件的绝对路径 */
void getFiles(std::string path, std::vector<std::string>& files)
{
long hFile = 0;
struct _finddata_t fileinfo;
std::string p;
if((hFile = _findfirst(p.assign(path).append("\\*").c_str(), &fileinfo)) != -1)
{
do
{
if((fileinfo.attrib & _A_SUBDIR))
{
if(strcmp(fileinfo.name, ".") != 0 && strcmp(fileinfo.name, "..") != 0)
getFiles(p.assign(path).append("\\").append(fileinfo.name), files);
}
else
{
files.push_back(p.assign(path).append("\\").append(fileinfo.name));
}
}while(_findnext(hFile, &fileinfo) == 0);
_findclose(hFile);
}
}
// 存放用于标定的图像文件夹路径
std::string path = "C:\\Users\\Wiley\\Documents\\Visual Studio 2012\\Projects\\opencv_camera\\opencv_camera\\saveImage";
int main()
{
/* 获取图像路径信息 */
std::vector<std::string> files;
getFiles(path, files);
cv::Mat image;
CameraCalibrator cc;
cv::Size boardSize(6, 4);// 标定图像中每行、列中内角点的数量
image = cv::imread(files[7], 0);
cc.addChessboardPoints(files, boardSize);// 计算并保存角点信息
cc.calibrate(image.size());// 标定
cv::Mat uImage = cc.remap(image);// 对图像进行矫正操作
cv::imshow("Original Image", image);
cv::imshow("Undistorted Image", uImage);
/* 打印内参矩阵 */
std::cout<<"相机内参矩阵:"<<std::endl;
cv::Mat cameraMatrix = cc.getCameraMatrix();
for(int i = 0; i < cameraMatrix.rows; i++)
{
for(int j = 0; j < cameraMatrix.cols; j++)
{
std::cout<<cameraMatrix.at<double>(i,j)<<" ";
}
std::cout<<std::endl;
}
std::cout<<std::endl;
/* 验证标定结果 */
cc.computeCalibrateError();
std::vector<double> errs = cc.getCalibrateErrs();
for(int i = 0; i < errs.size(); i++)
{
std::cout<<"第"<<i+1<<"幅图像的平均矫正偏差: "<<errs[i]<<"像素大小"<<std::endl;
}
/* 保存标定结果 */
cv::FileStorage fs("intrinsics.yml", cv::FileStorage::WRITE);
if(fs.isOpened())
{
fs<<"M"<<cc.getCameraMatrix()<<"D"<<cc.getDistCoeffs();
fs.release();
}
cv::waitKey();
return 0;
}
控制台输出相机内参矩阵和矫正偏差
矫正前图像:
矫正后图像:
完整代码和标定原始图片下载链接:http://download.csdn.net/download/lwx309025167/9994462
三、参考文献
1.《opencv2计算机视觉编程手册》
2. http://download.csdn.net/download/lwx309025167/9994462 张正友标定法标定相机