理论
- 对于失真,OpenCV考虑了径向和切向因子。 对于径向因子,使用以下公式:
- 因此,对于(x,y)坐标处的未失真像素点,其在失真图像上的位置将是(x distorted y distorted)。 径向变形的存在表现为“桶状”或“鱼眼”效应。
- 由于摄像镜头与成像平面不完全平行,因此发生切向畸变。 它可以通过公式表示:
- 所以我们有五个失真参数,在OpenCV中呈现为一行矩阵,有5列:
- 现在,对于单位转换,我们使用以下公式:
- 这里通过使用单应性坐标系(和w = Z)来解释w的存在。 未知参数是fx和fy(相机焦距)和(cx,cy),它们是以像素坐标表示的光学中心。 如果对于两个轴,使用具有给定纵横比(通常为1)的共同焦距,则fy = fx * a,并且在上部公式中,我们将具有单个焦距f。 包含这四个参数的矩阵称为相机矩阵。 虽然无论使用何种相机分辨率,失真系数都是相同的,但应根据校准分辨率的当前分辨率进行缩放。
- 确定这两个矩阵的过程是校准。 通过基本几何方程计算这些参数。 使用的等式取决于所选择的校准对象。 目前,OpenCV支持三种类型的校准对象:
- 古典黑白棋盘
- 对称的圆圈图案
- 不对称的圆圈图案
- 基本上,您需要使用相机拍摄这些图案的快照,然后让OpenCV找到它们。 每个找到的模式都会产生一个新的等式。 要求解方程,您需要至少预定数量的模式快照,以形成一个良好的方程系统。 棋盘图案的数字较高,而圆形图案的数字较少。 例如,理论上棋盘图案需要至少两个快照。 然而,在实践中,我们的输入图像中存在大量噪声,因此为了获得良好的结果,您可能需要在不同位置至少10个输入模式的良好快照。
代码
#include <iostream>
#include <sstream>
#include <string>
#include <ctime>
#include <cstdio>
#include <opencv2/core.hpp>
#include <opencv2/core/utility.hpp>
#include <opencv2/imgproc.hpp>
#include <opencv2/calib3d.hpp>
#include <opencv2/imgcodecs.hpp>
#include <opencv2/videoio.hpp>
#include <opencv2/highgui.hpp>
using namespace cv;
using namespace std;
static void help()
{
cout << "This is a camera calibration sample." << endl
<< "Usage: camera_calibration [configuration_file -- default ./default.xml]" << endl
<< "Near the sample file you'll find the configuration file, which has detailed help of "
"how to edit it. It may be any OpenCV supported file format XML/YAML." << endl;
}
class Settings
{
public:
Settings() : goodInput(false) {}
enum Pattern { NOT_EXISTING, CHESSBOARD, CIRCLES_GRID, ASYMMETRIC_CIRCLES_GRID };
enum InputType { INVALID, CAMERA, VIDEO_FILE, IMAGE_LIST };
void write(FileStorage& fs) const //Write serialization for this class
{
fs << "{"
<< "BoardSize_Width" << boardSize.width
<< "BoardSize_Height" << boardSize.height
<< "Square_Size" << squareSize
<< "Calibrate_Pattern" << patternToUse
<< "Calibrate_NrOfFrameToUse" << nrFrames
<< "Calibrate_FixAspectRatio" << aspectRatio
<< "Calibrate_AssumeZeroTangentialDistortion" << calibZeroTangentDist
<< "Calibrate_FixPrincipalPointAtTheCenter" << calibFixPrincipalPoint
<< "Write_DetectedFeaturePoints" << writePoints
<< "Write_extrinsicParameters" << writeExtrinsics
<< "Write_outputFileName" << outputFileName
<< "Show_UndistortedImage" << showUndistorsed
<< "Input_FlipAroundHorizontalAxis" << flipVertical
<< "Input_Delay" << delay
<< "Input" << input
<< "}";
}
void read(const FileNode& node) //Read serialization for this class
{
node["BoardSize_Width" ] >> boardSize.width;
node["BoardSize_Height"] >> boardSize.height;
node["Calibrate_Pattern"] >> patternToUse;
node["Square_Size"] >> squareSize;
node["Calibrate_NrOfFrameToUse"] >> nrFrames;
node["Calibrate_FixAspectRatio"] >> aspectRatio;
node["Write_DetectedFeaturePoints"] >> writePoints;
node["Write_extrinsicParameters"] >> writeExtrinsics;
node["Write_outputFileName"] >> outputFileName;
node["Calibrate_AssumeZeroTangentialDistortion"] >> calibZeroTangentDist;
node["Calibrate_FixPrincipalPointAtTheCenter"] >> calibFixPrincipalPoint;
node["Calibrate_UseFisheyeModel"] >> useFisheye;
node["Input_FlipAroundHorizontalAxis"] >> flipVertical;
node["Show_UndistortedImage"] >> showUndistorsed;
node["Input"] >> input;
node["Input_Delay"] >> delay;
node["Fix_K1"] >> fixK1;
node["Fix_K2"] >> fixK2;
node["Fix_K3"] >> fixK3;
node["Fix_K4"] >> fixK4;
node["Fix_K5"] >> fixK5;
validate();
}
void validate()
{
goodInput = true;
if (boardSize.width <= 0 || boardSize.height <= 0)
{
cerr << "Invalid Board size: " << boardSize.width << " " << boardSize.height << endl;
goodInput = false;
}
if (squareSize <= 10e-6)
{
cerr << "Invalid square size " << squareSize << endl;
goodInput = false;
}
if (nrFrames <= 0)
{
cerr << "Invalid number of frames " << nrFrames << endl;
goodInput = false;
}
if (input.empty()) // Check for valid input
inputType = INVALID;
else
{
if (input[0] >= '0' && input[0] <= '9')
{
stringstream ss(input);
ss >> cameraID;
inputType = CAMERA;
}
else
{
if (isListOfImages(input) && readStringList(input, imageList))
{
inputType = IMAGE_LIST;
nrFrames = (nrFrames < (int)imageList.size()) ? nrFrames : (int)imageList.size();
}
else
inputType = VIDEO_FILE;
}
if (inputType == CAMERA)
inputCapture.open(cameraID);
if (inputType == VIDEO_FILE)
inputCapture.ope