相机畸变校准

相机畸变校准详解及实例
这篇博客总结了相机的畸变模型,包括径向切向畸变、鱼眼畸变和FOV畸变,并提供了校准示例。文章讨论了针孔模型及其与畸变的关系,详细介绍了畸变模型的数学表达式,并给出了基于Matlab和C++的畸变矫正代码,以Euroc数据集为例进行了校准演示。

1. 前言

前言,关于相机的畸变模型及其推导,很多文章都已经写得很详细了,我这里只总结结论,把重点放在理解以及使用上。这篇文章总结了径向切向畸变,鱼眼畸变,FOV畸变;同时,还根据径向切向畸变的模型,以Euroc数据集为例,提供了畸变矫正的Matlab和C++代码示例。

2. 针孔模型(pinhole model)

针孔相机模型(即直线投影模型,是相机在理想情况下的投影模型)是消费类相机中最常见的相机模型。在此模型中,图像通过透视投影映射到平面上。当相机坐标系(原点在相机光心,向前为Z,向右为X,向下为Y)下的一个三维空间点 ( X c , Y c , Z c ) (X_c, Y_c, Z_c) (Xc,Yc,Zc)通过针孔相机模型投影得到图像坐标系(图像的左上角为坐标原点,向右为x,向下为y,单位为像素)下的一个像素点 ( u , v ) (u, v) (u,v)时,它们的对应关系如下:
{ x = f x X c Z c + c x = f x ⋅ x + c x y = f y Y c Z c + c y = f y ⋅ y + c y (1.1) \left\{ \begin{aligned} x = f_x \frac{X_c}{Z_c} + c_x = f_x \cdot x + c_x \\ y = f_y \frac{Y_c}{Z_c} + c_y = f_y \cdot y + c_y \end{aligned} \right. \tag{1.1} x=fxZcXc+cx=fxx+cxy=fyZcYc+cy=fyy+cy(1.1)
其中, x = X c Z c , y = Y c Z c x = \frac{X_c}{Z_c}, y = \frac{Y_c}{Z_c} x=ZcXc,y=ZcYc是归一化的坐标(没有单位),上式子也可写成齐次坐标的形式:
[ u v 1 ] = 1 Z c [ f x 0 c x 0 f y c y 0 0 1 ] [ X c Y c Z c ] (1.2) \left[\begin{matrix} u \\ v \\ 1 \end{matrix}\right] = \frac{1}{Z_c} \left[\begin{matrix} f_x & 0 & c_x \\0 & f_y & c_y \\ 0 & 0 & 1 \end{matrix}\right] \left[\begin{matrix} X_c \\ Y_c \\ Z_c \end{matrix}\right] \tag{1.2} uv1=Zc1fx000fy0cxcy1XcYcZc(1.2)
其中, f x , f y , c x , c y f_x, f_y, c_x, c_y fx,fy,cx,cy 为相机内参(单位:像素),上面模型的推导与相机内参的具体含义这里不过多展开,详情请见高博的《视觉SLAM十四讲》,或者链接:SLAM之相机标定,需要说明的是,该链接中讲的相机标定和畸变部分与高博的那本书一致,它同时还介绍了基于ROS的相机内参标定过程,以及校准的代码。但是,那里面只提到径向切向畸变,校准出来几个畸变参数也没有详细说明,而且代码写的有点小问题:(他写的显然是C++的代码,但是下面那两行却实matlab的语法,而且“^”在C++中的含义是异或操作)

double x_distorted = x*(1+k1*r^2+k2*r^4)+2*p1*x*y+p2*(r^2+2*x^2);
double y_distorted = y*(1+k1*r^2+k2*r^4)+2*p2*x*y+p1*(r^2+2*y^2);

因此,我想在此基础上做一点补充。

3. 针孔模型+径向切向畸变(rectilinear projection distortion)

{ x d i s t o r t e d = x ( 1 + k 1 r 2 + k 2 r 4 + k 3 r 6 ) + 2 p 1 x y + p 2 ( r 2 + 2 x 2 ) y d i s t o r t e d = y ( 1 + k 1 r 2 + k 2 r 4 + k 3 r 6 ) + 2 p 2 x y + p 1 ( r 2 + 2 y 2 ) (2.1) \left\{ \begin{aligned} x_{distorted} = x(1 + k_1 r^2 + k_2 r^4 + k_3 r^6) + 2p_1 xy + p_2(r^2 + 2x^2) \\ y_{distorted} = y(1 + k_1 r^2 + k_2 r^4 + k_3 r^6) + 2p_2 xy + p_1(r^2 + 2y^2) \end{aligned} \right. \tag{2.1} {xdistorted=x(1+k1r2+k2r4+k3r6)+2p1xy+p2(r2+2x2)ydistorted=y(1+k1r2+k2r4+k3r6)+2p2xy+p1(r2+2y2)(2.1)
其中,式(2.1)中的 x , y x, y x,y与式 ( 1.1 ) (1.1) (1.1)中的 x , y x, y x,y含义相同,都是归一化后的坐标,且 r = ( x 2 + y 2 ) r = \sqrt{(x^2 +y^2)} r=(x2+y2) 。可以得到校准后的像素位置 ( u d i s t o r t e d , v d i s t o r t e d ) (u_{distorted}, v_{distorted}) (udistorted,vdistorted)
{ u d i s t o r t e d = f x x d i s t o r t e d + c x v d i s t o r t e d = f y y d i s t o r t e d + c y (2.2) \left\{ \begin{aligned} u_{distorted} = f_x x_{distorted} + c_x \\ v_{distorted} = f_y y_{distorted} + c_y \end{aligned} \right.\tag{2.2} {udistorted=fxxdistorted+cxvdistorted=fyydistorted+cy(2.2)

写成矩阵的形式有:
[ u d i s t o r t e d v d i s t o r t e d 1 ] = [ f x 0 c x 0 f y c y 0 0 1 ] [ x d i s t o r t e d y d i s t o r t e d 1 ] (2.3) \left[\begin{matrix} u_{distorted} \\ v_{distorted} \\ 1 \end{matrix}\right] = \left[\begin{matrix} f_x & 0 & c_x \\0 & f_y & c_y \\ 0 & 0 & 1 \end{matrix}\right] \left[\begin{matrix} x_{distorted} \\ y_{distorted} \\ 1 \end{matrix}\right] \tag{2.3} udistortedvdistorted1=fx000fy0cxcy1xdistortedydistorted1(2.3)
利用ROS的 MonocularCalibration 校准得到两个参数文件,这是同一个相机参数的两种不同文件格式:一个ini格式和一个yaml格式的文件(yaml格式的文件是用于OpenCV校准程序),在参数文件中,它们的含义都比较明显:

image_width: 640
image_height: 480
camera_name: narrow_stereo
camera_matrix:
rows: 3
cols: 3
data: [634.706989, 0.000000, 323.354857, 0.000000, 634.489915, 213.107721, 0.000000, 0.000000, 1.000000]
distortion_model: plumb_bob
distortion_coefficients:
rows: 1
cols: 5
data: [-0.068953, 0.342004, -0.002181, 0.005728, 0.000000]
rectification_matrix:
rows: 3
cols: 3
data: [1.000000, 0.000000, 0.000000, 0.000000, 1.000000, 0.000000, 0.000000, 0.000000, 1.000000]
projection_matrix:
rows: 3
cols: 4
data: [650.866821, 0.000000, 325.423266, 0.000000, 0.000000, 650.506226, 212.983124, 0.000000, 0.000000, 0.000000, 1.000000, 0.000000]

上面的畸变模型参数值"plumb_bob"表示该相机的畸变模型是:径向和切向变形的5参数多项式逼近。而 distortion_coefficients 中的五个参数肯定是式(2.1)中 k 1 , k 2 , k 3 , p 1 , p 2 k_1, k_2, k_3, p_1, p_2 k1,k2,k3,p1,p2这五个,但它们的对应关系是怎么样的呢。根据参考 https://www.ros.org/reps/rep-0104.html#plumbbob 中的文献[2]
Plumb Bob
虽然其中的字符定义和我们习惯性用的不太一致,但是二者含义是一样的。从上面图中可以看出,畸变参数中,第1,2,5个参数对应的是式(2.1)中的 k 1 , k 2 , k 3 k_1,k_2,k_3 k1,k2,k3,第3,4个参数对应的是式(2.1)中的 p 1 , p 2 p_1, p_2 p1,p2。因此我们可以知道:distortion_coefficients中的五个参数从前到后分别对应的是: k 1 , k 2 , p 1 , p 2 , k 3 k_1, k_2, p_1, p_2, k_3 k1,k2,p1,p2,k3
说句题外话,将文献[2]中的字符翻译成和本文中的字符一致,可得到如式(2.3)所示的对应关系,其中 α \alpha α定义了x和y像素轴之间的角度,但很多文章貌似都没有提起这一项畸变,不知道是为什么。
[ u d i s t o r t e d v d i s t o r t e d 1 ] = [ f x α f x c x 0 f y c y 0 0 1 ] [ x d i s t o r t e d y d i s t o r t e d 1 ] (2.3) \left[\begin{matrix} u_{distorted} \\ v_{distorted} \\ 1 \end{matrix}\right] = \left[\begin{matrix} f_x & \alpha f_x & c_x \\0 & f_y & c_y \\ 0 & 0 & 1 \end{matrix}\right] \left[\begin{matrix} x_{distorted} \\ y_{distorted} \\ 1 \end{matrix}\right] \tag{2.3} udistortedvdistorted1=fx00αfxfy0cxcy1xdistortedydistorted1(2.3)

4. 径向切向畸变校准实例

根据上述畸变模型的含义,现在尝试根据它来校准畸变后的图像。我从Euroc数据集中,下载了示例图片数据以及对应的参数文件,其中参数文件内容如下(该数据集的校准文件格式与上面提到的ROS校准工具生成的格式不太一致,但该有的参数都有,其中的校准参数中没有 k 3 k_3 k3参数):

# General sensor definitions.
sensor_type: camera
comment: VI-Sensor cam0 (MT9M034)

# Sensor extrinsics wrt. the body-frame.
T_BS:
cols: 4
rows: 4
data: [0.0148655429818, -0.999880929698, 0.00414029679422, -0.0216401454975,
0.999557249008, 0.0149672133247, 0.025715529948, -0.064676986768,
-0.0257744366974, 0.00375618835797, 0.999660727178, 0.00981073058949,
0.0, 0.0, 0.0, 1.0]

# Camera specific definitions.
rate_hz: 20
resolution: [752, 480]
camera_model: pinhole
intrinsics: [458.654, 457.296, 367.215, 248.375] #fu, fv, cu, cv
distortion_model: radial-tangential
distortion_coefficients: [-0.28340811, 0.07395907, 0.00019359, 1.76187114e-05]

根据上述参数,对其中的畸变图像进行校准,基于matlab的校准代码如下:

close all; clear all; clc
img1 = imread('1403638225195097088.png');
intrinsic.fx = 458.654; intrinsic.fy = 457.296;
intrinsic.cx = 367.215; intrinsic.cy = 248.375;
intrinsic.k1 = -0.28340811; intrinsic.k2 = 0.07395907; intrinsic.k3 = 0;
intrinsic.p1 = 0.00019359; intrinsic.p2 = 1.76187114e-05;
img0 = calibration(img1, intrinsic, 1);

上面代码中calibration函数内容如下所示:

function [image_undistorted] = calibration(image_distorted, intrinsic, is_show)
%calibration 用于根据相机内参,对图像进行校准
%INPUT----------------------------------------
%   image_distorted : 输入(失真)图像矩阵;
%   intrinsic : 相机内参;
%   is_show : 是否显示校准前后的图像(默认为true);
%OUTPUT---------------------------------------   
%   image_undistorted : 输出的校准后的图像矩阵;

if nargin <= 2
    is_show = 1;
end

fx = intrinsic.fx; fy = intrinsic.fy;  % x<-->u; y<-->v
cx = intrinsic.cx; cy = intrinsic.cy;
k1 = intrinsic.k1; k2 = intrinsic.k2; k3 = intrinsic.k3;
p1 = intrinsic.p1; p2 = intrinsic.p2;
image_undistorted = image_distorted;

[h, w] = size(image_distorted);
for v = 1 : h
    for u = 1 : w
        x = (u - cx) / fx;
        y = (v - cy) / fy;
        r = sqrt(x*x + y*y);
        x_d = x*(1+k1*r*r + k2*r*r*r*r + k3*r*r*r*r*r*r)+ 2*p1*x*y + p2*(r*r + 2*x*x);
        y_d = y*(1+k1*r*r + k2*r*r*r*r + k3*r*r*r*r*r*r)+ 2*p2*x*y + p1*(r*r + 2*y*y);
        u_d = fx*x_d+cx;
        v_d = fy*y_d+cy;
        if u_d >=1 && u_d <= w && v_d >=1 && v_d <= h
            image_undistorted(v, u) = image_distorted(floor(v_d), floor(u_d));
        else
            image_undistorted(v, u) = 0;
        end
    end
end

if is_show
    figure('Name', '校准前')
    imshow(image_distorted);
    figure('Name', '校准后')
    imshow(image_undistorted);
end
end

基于C++的校准代码如下(只需将 SLAM之相机标定 中有问题的两行代码改过来就能实现校准的功能了):
使用方法:生成的可执行文件名 图片路径

#include <opencv2/opencv.hpp>

int main(int argc, char **argv) {

	if (argc < 2)
	{
		std::cout << "Usage: exe_name image_name \n";
		return -1;
	}
	std::string image_file(argv[1]); // 请确保路径正确

	double k1 = -0.28340811, k2 = 0.07395907, p1 = 0.00019359, p2 = 1.76187114e-05;  // 畸变参数
	double fx = 458.654, fy = 457.296, cx = 367.215, cy = 248.375;  // 内参

	cv::Mat image = cv::imread(image_file, 0);   // 图像是灰度图,CV_8UC1
	int rows = image.rows, cols = image.cols;
	cv::Mat image_undistort = cv::Mat(rows, cols, CV_8UC1);   // 去畸变以后的图

	// 计算去畸变后图像的内容
	for (int v = 0; v < rows; v++)
		for (int u = 0; u < cols; u++) {

			double u_distorted = 0, v_distorted = 0;
			//按照公式,计算点(u,v)对应到畸变图像中的坐标(u_distorted, v_distorted)
			double x = (u - cx) / fx;
			double y = (v - cy) / fy;
			double r = sqrt(x*x + y*y);

			double x_distorted = x * (1 + k1 * r*r + k2 * r*r*r*r) + 2 * p1*x*y + p2 * (r*r + 2*x*x);
			double y_distorted = y * (1 + k1 * r*r + k2 * r*r*r*r) + 2 * p2*x*y + p1 * (r*r + 2*y*y);
			u_distorted = fx * x_distorted + cx;
			v_distorted = fy * y_distorted + cy;

			// 赋值 (最近邻插值)
			if (u_distorted >= 0 && v_distorted >= 0 && u_distorted < cols && v_distorted < rows) 
			{
				image_undistort.at<uchar>(v, u) = image.at<uchar>((int)v_distorted, (int)u_distorted);
			}
			else 
			{
				image_undistort.at<uchar>(v, u) = 0;
			}
		}

	// 校准前图像:
	cv::imshow("图像矫正前", image);
	// 校准后图像
	cv::imshow("图像矫正后", image_undistort);
	cv::waitKey();

	return 0;
}

校准前后的效果如图所示:

5. 针孔模型+鱼眼畸变(fisheye distortion)

鱼眼相机模型是用于宽广视场相机的相机模型。因为当视野接近180度时,针孔相机模型无法对图像投影进行建模,所以需要该模型:
{ r = x 2 + y 2 θ = a t a n 2 ( ( X c 2 + Y c 2 ) , Z c ) = a t a n 2 ( r , 1 ) = a r c t a n ( r ) θ d = θ ( 1 + k 1 θ 2 + k 2 θ 4 + k 3 θ 6 + k 4 θ 8 ) x d = θ d r ⋅ x y d = θ d r ⋅ y (4.1) \left\{ \begin{aligned} r &= \sqrt{x^2 + y^2} \\ \theta &= atan2(\sqrt{(X_c^2 + Y_c^2)}, Z_c) = atan2(r, 1) = arctan(r) \\ \\ \theta_d &= \theta(1 + k_1 \theta^2 + k_2 \theta^4 + k_3 \theta^6 + k_4 \theta^8) \\ x_d &= \frac{\theta_d}{r} \cdot x \\ y_d &= \frac{\theta_d}{r} \cdot y \end{aligned} \right. \tag{4.1} rθθdxdyd=x2+y2 =atan2((Xc2+Yc2) ,Zc)=atan2(r,1)=arctan(r)=θ(1+k1θ2+k2θ4+k3θ6+k4θ8)=rθdx=rθdy(4.1)

将矫正后的归一化坐标转换回像素坐标:
{ u d = f x ( x d + α y d ) + c x v d = f y ⋅ y d + c y (4.2) \left\{ \begin{aligned} u_d &= f_x ( x_d + \alpha y_d) + c_x \\ v_d &= f_y \cdot y_d + c_y \end{aligned} \right. \tag{4.2} {udvd=fx(xd+αyd)+cx=fyyd+cy(4.2)

写成矩阵的形式有:
[ u d v d 1 ] = [ f x α f x c x 0 f y c y 0 0 1 ] [ x d y d 1 ] (4.3) \left[\begin{matrix} u_{d} \\ v_{d} \\ 1 \end{matrix}\right] = \left[\begin{matrix} f_x & \alpha f_x & c_x \\0 & f_y & c_y \\ 0 & 0 & 1 \end{matrix}\right] \left[\begin{matrix} x_{d} \\ y_{d} \\ 1 \end{matrix}\right] \tag{4.3} udvd1=fx00αfxfy0cxcy1xdyd1(4.3)

6. 针孔模型+FOV畸变(FOV distortion)

这是具有较大径向变形的相机模型(例如鱼眼相机)的替代表示,其中图像点和图像中心点(principal point)之间的距离大致与相机坐标系下的3维空间点和光轴之间的角度成比例。该模型首先在论文“Straight Lines Have to be Straight: Automatic Calibration and Removal of Distortion from Scenes of Structured Environments.”中提出。
FOV 畸变矫正模型:
{ r = ( x 2 + y 2 ) r d = 1 ω a r c t a n ( 2 r ⋅ t a n ( ω 2 ) ) x d = r d r ⋅ x c y d = r d r ⋅ y c (5.1) \left\{\begin{aligned} r &= \sqrt{(x^2 + y^2)} \\ r_d &= \frac{1}{\omega} arctan(2r \cdot tan(\frac{\omega}{2})) \\ x_d &= \frac{r_d}{r} \cdot x_c \\ y_d &= \frac{r_d}{r} \cdot y_c \end{aligned} \right. \tag{5.1} rrdxdyd=(x2+y2) =ω1arctan(2rtan(2ω))=rrdxc=rrdyc(5.1)

其中 ω \omega ω是FOV畸变系数,将矫正后的归一化坐标转换回像素坐标:
[ u d v d 1 ] = [ f x 0 c x 0 f y c y 0 0 1 ] [ x d y d 1 ] (5.2) \left[\begin{matrix} u_{d} \\ v_{d} \\ 1 \end{matrix}\right] = \left[\begin{matrix} f_x & 0 & c_x \\0 & f_y & c_y \\ 0 & 0 & 1 \end{matrix}\right] \left[\begin{matrix} x_{d} \\ y_{d} \\ 1 \end{matrix}\right] \tag{5.2} udvd1=fx000fy0cxcy1xdyd1(5.2)



参考:
https://blog.youkuaiyun.com/learning_tortosie/article/details/79901255
http://wiki.ros.org/camera_calibration/Tutorials/MonocularCalibration
https://docs.ros.org/api/sensor_msgs/html/distortion__models_8h_source.html#l00045
http://wiki.ros.org/camera_calibration_parsers

https://www.ros.org/reps/rep-0104.html#plumbbob
http://www.vision.caltech.edu/bouguetj/calib_doc/htmls/parameters.html
https://cggos.github.io/computervision/camera-models.html
https://zhuanlan.zhihu.com/p/93822726

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值