针孔相机
SLAM中采用的相机模型大多基于针孔相机模型,在说明针孔相机模型的投影关系之前,先明确需要用到的几个坐标系下用一个空间点的不同坐标表示:
-世界坐标系 w w w:空间点在世界坐标系下的坐标表示为 P w = [ X Y Z ] T \bm{P}^w=\left[\begin{array}{ccc}X & Y & Z \end{array}\right]^T Pw=[XYZ]T ;
- 相机坐标系 c c c:空间点在相机坐标系下的坐标表示为 p c = [ x y z ] T \bm{p}^c=\left[\begin{array}{ccc}x & y & z \end{array}\right]^T pc=[xyz]T ;
- 像素坐标系 p p p:空间点在像素坐标系下的坐标表示为 u p = [ u v ] T \bm{u}^p=\left[\begin{array}{cc}u & v \end{array}\right]^T up=[uv]T ;
空间点在世界坐标系和相机坐标系下的关系由外参矩阵
T
\bm{T}
T决定,注意由于
T
\bm{T}
T是4维矩阵,这里所有坐标需采用齐次表示:
p
=
T
P
\bm{p}=\bm{T}\bm{P}
p=TP
记空间点在成像平面上的坐标为
p
c
′
=
[
x
′
y
′
z
′
]
T
{\bm{p}^c}'=\left[\begin{array}{ccc}x' & y' & z' \end{array}\right]^T
pc′=[x′y′z′]T,则有:
{
x
′
=
f
z
x
y
′
=
f
z
y
\left\{\begin{aligned} x'&=\frac{f}{z}x\\ y'&=\frac{f}{z}y \end{aligned} \right.
⎩⎪⎪⎨⎪⎪⎧x′y′=zfx=zfy
由于像素坐标系和成像坐标系原点不一致(成像坐标系原点位于光心延长线,像素坐标系原点位于图像左上角),记两者之间的坐标平移量为
[
c
x
c
y
]
\left[\begin{array}{cc}c_x & c_y\end{array}\right]
[cxcy];同时,成像坐标系单位为
m
m
m,像素坐标系单位为像素
p
i
x
e
l
pixel
pixel,记两者之间的转换比例为
[
α
β
]
\left[\begin{array}{cc}\alpha & \beta\end{array}\right]
[αβ],则像素坐标和相机坐标之间的关系可以表示为:
{
u
=
α
f
z
x
+
c
x
v
=
β
f
z
y
+
c
y
\left\{\begin{aligned} u&=\alpha\frac{f}{z}x+c_x\\ v&=\beta\frac{f}{z}y+c_y \end{aligned} \right.
⎩⎪⎪⎨⎪⎪⎧uv=αzfx+cx=βzfy+cy
令
α
f
=
f
x
\alpha f=f_x
αf=fx、
β
f
=
f
y
\beta f=f_y
βf=fy,同时将上式写成齐次矩阵形式为:
1
z
[
u
v
1
]
=
[
f
x
0
c
x
0
f
y
c
y
0
0
1
]
⏟
K
[
x
y
z
]
\frac{1}{z}\left[\begin{array}{c}u\\v\\1\end{array}\right]=\underbrace{\left[\begin{matrix} f_x & 0 & c_x\\ 0 & f_y & c_y\\ 0 & 0 & 1 \end{matrix}\right]}_{\bm{K}}\left[\begin{array}{c}x\\y\\z\end{array}\right]
z1⎣⎡uv1⎦⎤=K
⎣⎡fx000fy0cxcy1⎦⎤⎣⎡xyz⎦⎤
矩阵
K
\bm{K}
K称为相机的内参矩阵。
畸变模型
径向畸变模型
上式的空间点投影实在理想状态下给出的,实际情况下的成像存在由各种原因引起的畸变,需要我们对其进行进一步矫正。所有畸变中,最常见且影响最大的为径向畸变和切向畸变。径向畸变采用多项式模型描述,通常我们只取其前两项,即:
{
x
d
i
s
t
o
r
t
e
d
=
x
(
1
+
k
1
r
2
+
k
2
r
4
)
y
d
i
s
t
o
r
t
e
d
=
y
(
1
+
k
1
r
2
+
k
2
r
4
)
r
2
=
x
2
+
y
2
\left\{\begin{aligned} x_{distorted}&=x\left(1+k_1r^2+k_2r^4\right)\\ y_{distorted}&=y\left(1+k_1r^2+k_2r^4\right)\\ r^2&=x^2+y^2 \end{aligned} \right.
⎩⎪⎨⎪⎧xdistortedydistortedr2=x(1+k1r2+k2r4)=y(1+k1r2+k2r4)=x2+y2
切向畸变模型
相比于径向畸变模型,切向畸变模型则更为复杂:
{
x
d
i
s
t
o
r
t
e
d
=
x
+
[
2
p
1
x
y
+
p
2
(
r
2
+
2
x
2
)
]
y
d
i
s
t
o
r
t
e
d
=
y
+
[
p
1
(
r
2
+
2
y
2
)
+
2
p
2
x
y
]
r
2
=
x
2
+
y
2
\left\{\begin{aligned} x_{distorted}&=x+\left[2p_1xy+p_2\left(r^2+2x^2\right)\right]\\ y_{distorted}&=y+\left[p_1\left(r^2+2y^2\right)+2p_2xy\right]\\ r^2&=x^2+y^2 \end{aligned} \right.
⎩⎪⎨⎪⎧xdistortedydistortedr2=x+[2p1xy+p2(r2+2x2)]=y+[p1(r2+2y2)+2p2xy]=x2+y2
联合畸变模型
综上,我们可以得到同时存在径向和切向畸变的相机模型为:
{
x
d
i
s
t
o
r
t
e
d
=
x
(
1
+
k
1
r
2
+
k
2
r
4
)
+
[
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
)
+
[
p
1
(
r
2
+
2
y
2
)
+
2
p
2
x
y
]
r
2
=
x
2
+
y
2
\left\{\begin{aligned} x_{distorted}&=x\left(1+k_1r^2+k_2r^4\right)+\left[2p_1xy+p_2\left(r^2+2x^2\right)\right]\\ y_{distorted}&=y\left(1+k_1r^2+k_2r^4\right)+\left[p_1\left(r^2+2y^2\right)+2p_2xy\right]\\ r^2&=x^2+y^2 \end{aligned} \right.
⎩⎪⎨⎪⎧xdistortedydistortedr2=x(1+k1r2+k2r4)+[2p1xy+p2(r2+2x2)]=y(1+k1r2+k2r4)+[p1(r2+2y2)+2p2xy]=x2+y2
畸变矫正方法
在相机各项畸变参数已知的情况下,由于
r
2
r^2
r2的存在,想要直接求解上式也是困难的,通常我们采用迭代方式求解,另畸变方程左边等于零有
{
x
(
1
+
k
1
r
2
+
k
2
r
4
)
=
x
d
i
s
t
o
r
t
e
d
−
[
2
p
1
x
y
+
p
2
(
r
2
+
2
x
2
)
]
y
(
1
+
k
1
r
2
+
k
2
r
4
)
=
y
d
i
s
t
o
r
t
e
d
−
[
p
1
(
r
2
+
2
y
2
)
+
2
p
2
x
y
]
⇒
{
x
=
x
d
i
s
t
o
r
t
e
d
−
[
2
p
1
x
y
+
p
2
(
r
2
+
2
x
2
)
]
(
1
+
k
1
r
2
+
k
2
r
4
)
y
=
y
d
i
s
t
o
r
t
e
d
−
[
p
1
(
r
2
+
2
y
2
)
+
2
p
2
x
y
]
(
1
+
k
1
r
2
+
k
2
r
4
)
\begin{aligned} &\left\{\begin{aligned} &x\left(1+k_1r^2+k_2r^4\right)=x_{distorted}-\left[2p_1xy+p_2\left(r^2+2x^2\right)\right]\\ &y\left(1+k_1r^2+k_2r^4\right)=y_{distorted}-\left[p_1\left(r^2+2y^2\right)+2p_2xy\right]\\ \end{aligned}\right.\\ &\Rightarrow\left\{\begin{aligned} x&=\frac{x_{distorted}-\left[2p_1xy+p_2\left(r^2+2x^2\right)\right]}{\left(1+k_1r^2+k_2r^4\right)}\\ y&=\frac{y_{distorted}-\left[p_1\left(r^2+2y^2\right)+2p_2xy\right]}{\left(1+k_1r^2+k_2r^4\right)}\\ \end{aligned}\right.\\ \end{aligned}
{x(1+k1r2+k2r4)=xdistorted−[2p1xy+p2(r2+2x2)]y(1+k1r2+k2r4)=ydistorted−[p1(r2+2y2)+2p2xy]⇒⎩⎪⎪⎪⎨⎪⎪⎪⎧xy=(1+k1r2+k2r4)xdistorted−[2p1xy+p2(r2+2x2)]=(1+k1r2+k2r4)ydistorted−[p1(r2+2y2)+2p2xy]
注意公式中的
x
d
i
s
t
o
r
t
e
d
x_{distorted}
xdistorted和
y
d
i
s
t
o
r
t
e
d
y_{distorted}
ydistorted为迭代初值,即带畸变的点坐标;
x
x
x和
y
y
y为当前迭代的去畸变点坐标。使用上式不断迭代,直至两步之间的迭代误差小于一定阈值即可,一个简单的demo如下:
/*
* @Description:
* @Author: Yuntian Li
* @Github: https://github.com/yuntinali91
* @Date: 2019-09-24 14:28:08
* @LastEditors: Yuntian Li
* @LastEditTime: 2019-09-25 15:13:20
*/
#include <iostream>
#include <opencv2/opencv.hpp>
#include <Eigen/Dense>
using namespace std;
void distortion(const Eigen::Vector2d& u_u, Eigen::Vector2d& u_d,
const Eigen::Matrix3d& K, const Eigen::Vector4d& D);
void undistortion(const Eigen::Vector2d& u_u, Eigen::Vector2d& u_d,
const Eigen::Matrix3d& K, const Eigen::Vector4d& D, const double tolerance);
int main(int argc, char** argv){
// distortion coefficients
Eigen::Vector4d D{-2.917e-1, 8.228e-2, 5.333e-5, -1.578e-4};
// intrinsic matrix
Eigen::Matrix3d K;
K << 4.616e2, 0, 3.63e2,
0, 4.603e2, 2.481e2,
0, 0, 1;
Eigen::Vector2d u{40., 50.};
Eigen::Vector2d u_u, u_d;
distortion(u, u_d, K, D);
cout << "Distorted point coordinates are:" << endl << u_d << endl;
double tolerance(1e-4);
undistortion(u_d, u_u, K, D, tolerance);
cout << "Undistorted point coordinates are: " << endl << u_u << endl;
return 0;
}
void distortion(const Eigen::Vector2d& u_u, Eigen::Vector2d& u_d,
const Eigen::Matrix3d& K, const Eigen::Vector4d& D){
// lift from pixel plane to normalized plane
Eigen::Vector3d p_u;
Eigen::Vector3d u_u_homo{0., 0., 1.};
u_u_homo.head(2) = u_u;
p_u = K.inverse() * u_u_homo;
// apply distortion
double x_u, y_u;
x_u = p_u[0]; y_u = p_u[1];
double k1, k2; // radial distortion coefficients
double p1, p2; // tangent distortion coefficients
k1 = D[0]; k2 = D[1];
p1 = D[2]; p2 = D[3];
double x_u2, y_u2, xy_u, r2;
x_u2 = x_u * x_u;
y_u2 = y_u * y_u;
xy_u = x_u * y_u;
r2 = x_u2 + y_u2;
Eigen::Vector3d p_d;
p_d[0] = x_u * (1 + k1 * r2 + k2 * r2 * r2) + (2 * p1 * xy_u + p2 * (r2 + 2 * x_u2));
p_d[1] = y_u * (1 + k1 * r2 + k2 * r2 * r2) + (2 * p2 * xy_u + p1 * (r2 + 2 * y_u2));
p_d[2] = 1.;
// project from normalized plane to normalized plane
u_d = (K * p_d).head(2);
}
void undistortion(const Eigen::Vector2d& u_d, Eigen::Vector2d& u_u,
const Eigen::Matrix3d& K, const Eigen::Vector4d& D, const double tolerance){
// lift from pixel plane to normalized plane
Eigen::Vector3d p_d;
Eigen::Vector3d u_d_homo{0., 0., 1.};
u_d_homo.head(2)=u_d;
p_d = K.inverse() * u_d_homo;
// recursive undistortion
double k1, k2; // radial distortion coefficients
double p1, p2; // tangent distortion coefficients
k1 = D[0]; k2 = D[1];
p1 = D[2]; p2 = D[3];
double x_d, y_d;
x_d = p_d[0]; y_d = p_d[1];
Eigen::Vector3d p_u;
p_u[2] = 1.0;
int count(0), conv_count(0);
while (1){
count++;
double x_d2, y_d2, xy_d, r2;
x_d2 = x_d * x_d;
y_d2 = y_d * y_d;
xy_d = x_d * y_d;
r2 = x_d2 + y_d2;
p_u[0] = (p_d[0] - (2 * p1 * xy_d + p2 * (r2 + 2 * x_d2))) / (1 + k1 * r2 + k2 * r2 * r2);
p_u[1] = (p_d[1] - (2 * p2 * xy_d + p2 * (r2 + 2 * y_d2))) / (1 + k1 * r2 + k2 * r2 * r2);
// project from normalized plane to pixel plane
u_u = (K * p_u).head(2);
cout << "Iterative times: " << count << " Estimation: " << u_u.transpose() << endl;
// termination criteria
double u_err, v_err;
u_err = abs(K(0, 0) * (p_u[0] - x_d));
v_err = abs(K(1, 1) * (p_u[1] - y_d));
if ( u_err < tolerance && v_err < tolerance) conv_count++;
if (conv_count > 3){
cout << "Total Iterative times: " << count << endl
<< "Final error: " << u_err << " pixels in x and " << v_err << " pixels in y." << endl
<< "Final estimation: " << u_u.transpose() << endl;
break;
}
x_d = p_u[0]; y_d = p_u[1];
}
}
鱼眼相机模型
Kannala畸变模型
鱼眼相机的成像原理并不同于普通的真空相机模型,而是一个半球面映射过程,包括透射映射、等距映射、等立体角映射、正交投影、球极投影等。这里仅介绍OpenCV中使用的等距投影模型,Kannala畸变投影模型。
上图给出了鱼眼相机的等距成像模型,所谓等距模型,即空间点在像平面的像高
r
r
r仅与入射角
θ
\theta
θ有关,且两者之间为等比关系,即
r
=
f
θ
r = f\theta
r=fθ。
上图进一步给出了鱼眼相机在等距模型下的完整成像关系。假设空间点在经过外参变换后在相机坐标系下的坐标为,
P
=
[
X
c
,
Y
c
,
Z
c
]
T
\bm{P}=[X^c,Y^c,Z^c]^T
P=[Xc,Yc,Zc]T,将其缩放至归一化平面有:
X
n
c
=
X
c
/
Z
c
Y
n
c
=
Y
c
/
Z
c
(1)
\begin{aligned} X_n^c &= X^c / Z^c\\ Y_n^c &= Y^c / Z^c \end{aligned} \tag{1}
XncYnc=Xc/Zc=Yc/Zc(1)
则入射角
θ
\theta
θ可以表示为:
r
c
=
X
n
c
2
+
X
n
c
2
θ
=
a
t
a
n
(
r
c
/
1
)
=
a
t
a
n
(
r
c
)
(2)
\begin{aligned} r^c& = \sqrt{{X_n^c}^2+{X_n^c}^2}\\ \theta&=atan(r^c/1)=atan(r^c) \end{aligned} \tag{2}
rcθ=Xnc2+Xnc2=atan(rc/1)=atan(rc)(2)
进而根据等距投影模型可以得到像点的像高为:
r
i
=
f
θ
r^i=f\theta
ri=fθ,利用相似三角形关系,同时考虑成像面为归一化成像面(
f
=
1
f=1
f=1)可得:
{
X
n
c
r
c
=
x
r
i
Y
n
c
r
c
=
y
r
i
⇒
{
x
=
f
θ
r
c
X
n
c
y
=
f
θ
r
c
Y
n
c
⇒
{
x
=
θ
r
c
X
n
c
y
=
θ
r
c
Y
n
c
(3)
\left\{ \begin{aligned} \frac{X_n^c}{r^c}&=\frac{x}{r^i}\\ \frac{Y_n^c}{r^c}&=\frac{y}{r^i} \end{aligned} \right. \Rightarrow \left\{ \begin{aligned} x &= \frac{f\theta}{r^c}X_n^c\\ y &= \frac{f\theta}{r^c}Y_n^c \end{aligned} \right. \Rightarrow \left\{ \begin{aligned} x &= \frac{\theta}{r^c}X_n^c\\ y &= \frac{\theta}{r^c}Y_n^c \end{aligned} \right. \tag{3}
⎩⎪⎪⎨⎪⎪⎧rcXncrcYnc=rix=riy⇒⎩⎪⎪⎨⎪⎪⎧xy=rcfθXnc=rcfθYnc⇒⎩⎪⎪⎨⎪⎪⎧xy=rcθXnc=rcθYnc(3)
式(1)-(3)即为理想情况下鱼眼相机的等距投影模型,实际成像中,鱼眼相机的畸变会引起入射角的变化,Kannala给出的鱼眼畸变模型如下:
θ
d
=
θ
+
k
1
θ
3
+
k
2
θ
5
+
k
3
θ
7
+
k
4
θ
9
(4)
\theta_d=\theta+k_1\theta^3+k_2\theta^5+k_3\theta^7+k_4\theta^9 \tag{4}
θd=θ+k1θ3+k2θ5+k3θ7+k4θ9(4)
将式(4)计算得到的
θ
d
\theta_d
θd带入式(3)中替换
θ
\theta
θ即可得到存在畸变的鱼眼相机等距投影模型。
在获取像平面坐标
p
=
[
x
,
y
,
z
]
T
\bm{p}=[x,y,z]^T
p=[x,y,z]T后,基于相机内参矩阵
K
\bm{K}
K获得像点在像素平面上的坐标,由于这一过程与针孔相机模型完全一致,这里略过不谈。
鱼眼相机畸变校正
在上述鱼眼相机等距投影模型中,可以记空间点
P
\bm{P}
P与鱼眼透镜光心
O
\bm{O}
O连线与透镜的交点为
P
1
=
[
X
1
c
,
Y
1
c
,
Z
1
c
]
T
\bm{P}_1=[X_1^c,Y_1^c, Z_1^c]^T
P1=[X1c,Y1c,Z1c]T,将
P
1
\bm{P}_1
P1表示为极坐标形式有:
X
1
c
=
R
sin
(
θ
)
cos
(
φ
)
Y
1
c
=
R
sin
(
θ
)
sin
(
φ
)
Z
1
c
=
R
cos
(
θ
)
\begin{aligned} X_1^c&=R\sin(\theta)\cos(\varphi)\\ Y_1^c&=R\sin(\theta)\sin(\varphi)\\ Z_1^c&=R\cos(\theta) \end{aligned}
X1cY1cZ1c=Rsin(θ)cos(φ)=Rsin(θ)sin(φ)=Rcos(θ)
由于通常我们在归一化球面(
R
=
1
R=1
R=1)上进行投影,鱼眼相机畸变校正等效于已知像点坐标
p
=
[
x
,
y
,
z
]
T
\bm{p}=[x,y,z]^T
p=[x,y,z]T和畸变参数
k
1
,
k
2
,
k
3
,
k
4
k_1,k_2,k_3,k_4
k1,k2,k3,k4,获得无畸变情况下的极坐标参数
θ
\theta
θ和
φ
\varphi
φ。
-
φ
\varphi
φ求解
成像面上夹角 φ 1 \varphi_1 φ1可以用像点坐标表示为: tan ( φ 1 ) = y / x \tan(\varphi_1)=y/x tan(φ1)=y/x。由于入射光线和出射光线必定在同一个入射面内,有 φ = φ 1 \varphi=\varphi_1 φ=φ1,因此未畸变的极坐标参数 φ \varphi φ为:
φ = tan ( y / x ) \varphi=\tan(y/x) φ=tan(y/x) -
θ
\theta
θ求解
畸变后的入射角 θ d \theta_d θd可以用像点坐标表示为 θ = ( x 2 + y 2 ) \theta=\sqrt{(x^2+y^2)} θ=(x2+y2), θ \theta θ角可通过牛顿迭代求解式(4)所表示的多项式方程获得,令:
f ( θ ) = θ + k 1 θ 3 + k 2 θ 5 + k 3 θ 7 + k 4 θ 9 − θ d f(\theta)=\theta+k_1\theta^3+k_2\theta^5+k_3\theta^7+k_4\theta^9 - \theta_d f(θ)=θ+k1θ3+k2θ5+k3θ7+k4θ9−θd
则根据牛顿迭代有:
θ k + 1 = θ k − f ( θ ) f ′ ( θ ) \theta_{k+1}=\theta_k-\frac{f(\theta)}{f'(\theta)} θk+1=θk−f′(θ)f(θ)