使用opencv标定单目相机(张正友法)代码实现

一、前言

      使用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 张正友标定法标定相机

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Mega_Li

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值