1.简介
相机标定(Camera Calibration)是计算机视觉中的一项重要技术,其目的就是确定相机的内参和外参。它可以修正镜头畸变,如桶形畸变或枕形畸变,以获得更接近真实世界的图像;在机器人视觉系统中,准确的相机标定可以提高导航和物体识别的精度。
以下都只是简单介绍一下内参和外参,数学推导有些复杂,可以参考c++opencv相机标定(calibrateCamera)详解 - 知乎
2.内参和外参
2.1 内参
内参(Intrinsic Parameters)是指相机的内在特性,包括相机的焦距(focal length)、主点(principal point,通常是相机感光元件的中心点)以及径向和切向畸变系数。这些参数定义了3D世界坐标到2D图像坐标的映射关系。内参可以通过相机标定过程获得,并用于图像的畸变校正。概括:内参描述了相机坐标系和像素坐标系之间的映射关系。数学形式如下:
其中,分别是方向上的焦距,是图像坐标原点到像素坐标原点平移的距离。其实从图像坐标到像素坐标也就是一个平移和缩放的事。
2.2 外参
外参(Extrinsic Parameters)描述了相机在世界坐标系中的位置和方向,即确定了世界坐标系与相机坐标系之间的旋转和平移关系。外参可以用来将3D点的坐标转换为相机坐标系中的坐标。
即点从世界坐标到相机坐标的变换经历了旋转和平移,齐次表达式如下
其中是33的旋转矩阵,是的平移矩阵。
3.张正友标定法
张正友标定法是一种广泛使用的相机标定技术,这种标定方式主要目的是确定相机的内参和畸变参数,以便能够将图像中的点准确地映射到现实世界中的点。
3.1基本原理
张正友标定法利用一个具有已知几何图案的标定版(通常是棋盘格),通过拍摄标定板从不同角度得到多组图像。在每组图像中,使用计算机视觉算法检测并提取标定板上的角点的像素坐标。同时,根据标定板的物理尺寸和格子大小,计算出这些角点在世界坐标系中的理论位置。
3.2 标定步骤
- 拍摄标定板图像:准备一个棋盘格标定板,并从不同的角度拍摄得到一组图像。
- 角点检测:对每张图像进行处理,检测出棋盘格的角点,并提取其像素坐标。
- 求解内参和外参:使用检测到的角点像素坐标和对应的世界坐标,通过数学优化方法求解相机的内参矩阵和畸变系数。
- 畸变系数标定:计算径向畸变系数,这些参数描述了图像中的畸变效果。
- 参数优化:使用Levenberg-Marquardt等算法对求解得到的参数进行优化,以提高标定的准确性。
标定过程可以使用如OpenCV这样的计算机视觉库来简化。OpenCV提供了cv::calibrateCamera
函数,它可以根据提供的角点信息自动计算相机的内参和畸变系数。
4.C++实现
以下代码均在ubuntu20.04上验证通过,使用的OpenCV C++版本为3.4.15。
首先需要创建如下图所示的文件目录
parameters.txt文件是最后写入的内参和畸变系数,现在可以不必理会,具体其他文件夹的作用可以参考我的另一篇博客ORB算法及C++实现-CSDN博客,calibration.cpp中源代码如下:
#include<opencv2/opencv.hpp>
#include<iostream>
#include<fstream>
#include<string>
using namespace std;
int main(int argc, char **argv)
{
// 定义棋盘格的尺寸,这里假设棋盘格是 8x6
int boardWidth = 8;
int boardHeight = 6;
// 定义棋盘格每个方格的大小,单位是米
float squareSize = 1.f;
// 创建棋盘格尺寸的 cv::Size 对象
cv::Size boardSize(boardWidth, boardHeight);
// 存储棋盘格角点对应的 3D 点
vector<vector<cv::Point3f>> objectPoints;
// 存储图像中检测到的棋盘格角点的 2D 点
vector<vector<cv::Point2f>> imagePoints;
// 临时存储当前图像中检测到的棋盘格角点
vector<cv::Point2f> corners;
// 声明图像的 cv::Mat 对象
cv::Mat image, gray;
// 创建一个窗口来显示图像
cv::namedWindow("image", cv::WINDOW_NORMAL);
// 存储图像文件名的 vector
vector<cv::String> filNames;
// 使用 glob 函数获取指定格式的图像文件名
glob("./../images/left*.jpg", filNames);
// 遍历所有图像文件
for(size_t i = 0; i < filNames.size(); i++)
{
// 读取当前图像文件
image = cv::imread(filNames[i], cv::IMREAD_COLOR);
// 将图像从 BGR 颜色空间转换为灰度空间
cvtColor(image, gray, cv::COLOR_BGR2GRAY);
// 使用 findChessboardCorners 函数检测棋盘格角点
// 第二个参数是棋盘格的尺寸
// 第三个参数是输出的角点集
// 第四个参数是检测棋盘格时使用的参数
bool found = cv::findChessboardCorners(image, boardSize, corners,
CV_CALIB_CB_ADAPTIVE_THRESH + CV_CALIB_CB_NORMALIZE_IMAGE + CV_CALIB_CB_FAST_CHECK);
// 如果角点被成功检测到
if(found)
{
// 使用 cornerSubPix 对角点位置进行精确化处理
cv::cornerSubPix(gray, corners, cv::Size(11,11), cv::Size(-1,-1), cv::TermCriteria(cv::TermCriteria::EPS + cv::TermCriteria::COUNT, 30, 0.1));
// 在图像上绘制棋盘格角点
cv::drawChessboardCorners(image, boardSize, corners, found);
// 显示处理后的图像
cv::imshow("image",image);
// 等待用户按键操作
cv::waitKey(0);
// 为每个 3D 点生成棋盘格角点的 vector
vector<cv::Point3f> objectCorners;
// 填充 objectCorners,每个棋盘格角点的 Z 坐标为 0.0f
for(int j = 0; j < boardHeight; j++)
{
for(int k = 0; k < boardWidth; k++)
{
objectCorners.push_back(cv::Point3f(k * squareSize, j * squareSize, 0.0f));
}
}
// 将 objectCorners 添加到 objectPoints 中
objectPoints.push_back(objectCorners);
// 将检测到的 2D 角点 corners 添加到 imagePoints 中
imagePoints.push_back(corners);
}
}
// 声明相机内参矩阵和畸变系数向量
cv::Mat cameraMatrix, distCoeffs;
// 声明存储每个图像的旋转向量和平移向量的 vector
vector<cv::Mat> rvecs, tvecs;
// 使用 calibrateCamera 函数计算相机的内参和畸变系数
// objectPoints 和 imagePoints 是之前存储的 3D 和 2D 角点
// image.size() 是图像的尺寸
// rvecs 和 tvecs 存储每个图像的旋转和平移向量
cv::calibrateCamera(objectPoints, imagePoints, image.size(), cameraMatrix, distCoeffs, rvecs, tvecs);
// 输出相机内参矩阵
cout<<"camera matrix:"<<endl<<cameraMatrix<<endl;
// 输出畸变系数
cout<<"Distortion coefficients:"<<endl<<distCoeffs<<endl;
//将参数写入parameters.txt文件中
ofstream outfile("./../parameters.txt"); //打开一个文件流,准备写入
if(!outfile.is_open()) //检查文件是否成功打开
{
cerr<<"Failed to open the output file!"<<endl;
return -1;
}
try{
//写入相机内参矩阵
outfile<<"Camera Matrix:"<<endl<<cameraMatrix<<endl;
//写入畸变系数
outfile<<"Distortion Coefficients:"<<endl<<distCoeffs<<endl;
}
catch(exception& e)
{
cerr<<"An exception occurred!"<<endl;
cerr<<e.what()<<endl;
return -1;
}
outfile.close(); //关闭文件流
return 0;
}
编写CMakeLists.txt文件,代码如下:
#设置当前cmake运行的最低版本
cmake_minimum_required(VERSION 3.2)
# 设置项目名称
project(calibration)
# 设置C++标准为11
set(CMAKE_CXX_STANDARD 11)
# 查找OpenCV库
find_package(OpenCV REQUIRED)
#包含OpenCV的头文件
include_directories(${OpenCV_INCLUDE_DIRS})
# 添加可执行文件
add_executable(calibration src/calibration.cpp)
# 链接OpenCV库
target_link_libraries(calibration ${OpenCV_LIBS})
在build文件夹下通过终端运行以下命令:
cmake ..
make
编译结果如下:
运行结果:
更多详细的信息可以参考(1.简介)里面的知乎链接。