欢迎访问我的博客首页。
针孔相机模型是像素坐标系、归一化平面坐标系、空间坐标系间的坐标变换的理论依据,在 2D-2D 和 PnP 问题中都会用到。
1. 针孔相机模型
我们都见过照片,照片可以看成真实景象的一个缩小,比如一个宽高都是几厘米的照片可以拍下一栋大楼。照片的产生原理可以用下图简单表示:
上图中有三个相互平行的平面和一个蜡烛:左边的平面是成像平面,中间的平面是光心平面,右边的平面是与左边平面对称的平面。按照小孔成像原理,像是在成像平面上的倒像。但我们一般都是正着看照片,所以我们可以把小孔成像模型看成红色框内的成像模型。下图是对红色框内成像模型的更详细呈现:
相机坐标系是图中原点为光心的红色坐标系,刻度是长度单位。像素坐标系是图中原点在成像平面右上角的黄色坐标系,刻度是像素个数。归一化成像平面是图中的绿色坐标系,它与相机坐标系只有一个倍数关系的差别,这个倍数就是相机坐标系的 Z 轴坐标。
2. 相机内参
假如相机坐标系中的一个点的坐标是 P = ( X , Y , Z ) P = (X, Y, Z) P=(X,Y,Z),它在归一化成像平面上的坐标是 n = ( x , y ) n = (x, y) n=(x,y),相机焦距是 f f f,单位都是米。那么它们的比例关系是:
Z f = X x = Y y . \frac{Z}{f} = \frac{X}{x} = \frac{Y}{y}. fZ=xX=yY.
于是:
x = f X Z , y = f Y Z . (2.1) x = f \frac{X}{Z}, \quad \quad y = f \frac{Y}{Z}. \tag{2.1} x=fZX,y=fZY.(2.1)
归一化成像平面和像素坐标系都是二维坐标系。因为它们的刻度不同,所以有比例关系;因为原点不同,所以还有一个平移关系。用单位为像素每米的 α x \alpha_x αx 、 α y \alpha_y αy 表示 x x x、 y y y 方向上的比例关系;用单位为像素的 c x c_x cx、 c y c_y cy 表示 x x x、 y y y 方向上的平移关系。则像素坐标 p = ( u , v ) p = (u, v) p=(u,v) 可以表示为
{ u = α x x + c x v = α y y + c y (2.2) \begin{cases} u = \alpha_x x + c_{x} \\ v = \alpha_y y + c_{y} \end{cases} \tag{2.2} {u=αxx+cxv=αyy+cy(2.2)
综合公式 (2.1) 和 (2.2) 得:
{ u = α x f X Z + c x ≜ f x X Z + c x v = α y f Y Z + c y ≜ f y Y Z + c y (2.3) \begin{cases} u = \alpha_x f \frac{X}{Z} + c_{x} \triangleq f_{x} \frac{X}{Z} + c_{x} \\\\ v = \alpha_y f \frac{Y}{Z} + c_{y} \triangleq f_{y} \frac{Y}{Z}+ c_{y} \end{cases} \tag{2.3} ⎩ ⎨ ⎧u=αxfZX+cx≜fxZX+cxv=αyfZY+cy≜fyZY+cy(2.3)
令 f x = α x f f_x = \alpha_x f fx=αxf, f y = α y f f_y = \alpha_y f fy=αyf,则公式 (2.3) 可以写成矩阵相乘的形式:
Z [ u v 1 ] = [ f x 0 c x 0 f y c y 0 0 1 ] [ X Y Z ] ≜ K P . (2.4) Z \left[ \begin{matrix} u \\ v \\ 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 \\ Y \\ Z \end{matrix} \right] \triangleq KP. \tag{2.4} Z uv1 = fx000fy0cxcy1 XYZ ≜KP.(2.4)
矩阵 K K K 包含了相机的焦距、横竖方向的缩放和平移系数,称为相机的内参矩阵。有了参数矩阵和距离 Z Z Z,就可以知道像素坐标系、归一化成像平面坐标系和相机坐标系之间的变换关系。
3. 坐标转换
相机坐标系到齐次像素坐标系的变换如公式 (2.4),它可以写成
( u , v , 1 ) T = K P / Z . (u, v, 1)^T = KP / Z. (u,v,1)T=KP/Z.
我们把它拆分成方程组,得到相机坐标系到像素坐标系的变换
{ u = ( X f x + Z c x ) / Z = X f x / Z + c x c = ( Y f y + Z c y ) / Z = Y f y / Z + c y . (3.1) \left\{\begin{aligned} u &= (X f_x + Z c_x) / Z &= X f_x / Z + c_x \\ c &= (Y f_y + Z c_y) / Z &= Y f_y / Z + c_y \end{aligned}\right.. \tag{3.1} {uc=(Xfx+Zcx)/Z=(Yfy+Zcy)/Z=Xfx/Z+cx=Yfy/Z+cy.(3.1)
下面的函数 cam2pix 实现了公式 (3.1)。
根据公式 (2.4) 还可以知道齐次归一化坐标和齐次像素坐标间的变换关系:
[ f x 0 c x 0 f y c y 0 0 1 ] [ x y 1 ] = [ u v 1 ] \left[ \begin{matrix} f_{x} & 0 & c_{x} \\ 0 & f_{y} & c_{y} \\ 0 & 0 & 1 \end{matrix} \right] \left[ \begin{matrix} x \\ y \\ 1 \end{matrix} \right] = \left[ \begin{matrix} u \\ v \\ 1 \end{matrix} \right] fx000fy0cxcy1 xy1 = uv1
我们把它拆分成方程组,得到归一化坐标到像素坐标的变换
{ u = x f x + c x v = x f y + c y (3.2) \begin{cases} u = x f_x + c_{x} \\ v = x f_y + c_{y} \end{cases} \tag{3.2} {u=xfx+cxv=xfy+cy(3.2)
由公式 (3.2) 还可以得到像素坐标到归一化坐标的变换
{ x = ( u − c x ) / f x y = ( v − c y ) / f y (3.3) \begin{cases} x = (u - c_x) / f_x \\ y = (v - c_y) / f_y \end{cases} \tag{3.3} {x=(u−cx)/fxy=(v−cy)/fy(3.3)
下面的函数 pix2nor 实现了公式 (3.3)。
下面是相机坐标、归一化成像平面坐标、像素坐标间的坐标变换。由于坐标的单位是米和像素,相除时需要较高精度,所以下面使用 double 实例化模板参数,用 float 可能造成精度不够而使结果误差很大。
// 相机坐标系到像素坐标系的变换。
cv::Point2f cam2pix(const cv::Point3f &p, const cv::Mat &K) {
return cv::Point2f(
(p.x * K.at<double>(0, 0)) / p.z + K.at<double>(0, 2),
(p.y * K.at<double>(1, 1)) / p.z + K.at<double>(1, 2));
}
// 像素坐标系到归一化成像平面的变换。
cv::Point2f pix2nor(const cv::Point2f &p, const cv::Mat &K) {
return cv::Point2f(
(p.x - K.at<double>(0, 2)) / K.at<double>(0, 0),
(p.y - K.at<double>(1, 2)) / K.at<double>(1, 1));
}
// 相机坐标系到归一化成像平面的变换。
cv::Point2f cam2nor(const cv::Point3f &p, const cv::Mat &K) {
return pix2nor(cam2pix(p, K), K);
}
// 相机坐标系到归一化成像平面的变换。
cv::Point2f cam2nor(const cv::Point3f &p) {
return cv::Point2f(p.x / p.z, p.y / p.z);
}
4. 畸变
4.1 畸变过程
透镜形状引起的畸变称为径向畸变,表现为桶形失真或枕形失真。这种失真离透镜中心越远越严重,因此可以用该距离的多项式表示失真过程
{ x d i s t o r t e d = x ( 1 + k 1 r 2 + k 2 r 4 + k 3 r 6 ) y d i s t o r t e d = y ( 1 + k 1 r 2 + k 2 r 4 + k 3 r 6 ) \left\{\begin{aligned} x_{distorted} &= x (1 + k_1 r^2 + k_2 r^4 + k_3 r^6) \\ y_{distorted} &= y (1 + k_1 r^2 + k_2 r^4 + k_3 r^6) \end{aligned}\right. {xdistortedydistorted=x(1+k1r2+k2r4+k3r6)=y(1+k1r2+k2r4+k3r6)
其中, k 1 k_1 k1, k 2 k_2 k2, k 3 k_3 k3 称为径向畸变参数。
透镜安装倾斜引起的畸变称为切向畸变,畸变过程为
{ x d i s t o r t e d = x + 2 p 1 x y + p 2 ( r 2 + 2 x 2 ) x d i s t o r t e d = y + p 1 ( r 2 + 2 y 2 ) + 2 p 2 x y \left\{\begin{aligned} x_{distorted} &= x + 2 p_1 x y + p2 (r^2 + 2 x^2) \\ x_{distorted} &= y + p1 (r^2 + 2 y^2) + 2 p_2 x y \end{aligned}\right. {xdistortedxdistorted=x+2p1xy+p2(r2+2x2)=y+p1(r2+2y2)+2p2xy
其中, p 1 p_1 p1, p 2 p_2 p2 称为切向畸变参数。
综上所述,畸变过程为
{ 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 ) + p 1 ( r 2 + 2 y 2 ) + 2 p 2 x y (4.1) \left\{\begin{aligned} x_{distorted} &= x (1 + k_1 r^2 + k_2 r^4 + k_3 r^6) + 2 p_1 x y + p2 (r^2 + 2 x^2) \\ y_{distorted} &= y (1 + k_1 r^2 + k_2 r^4 + k_3 r^6) + p1 (r^2 + 2 y^2) + 2 p_2 x y \end{aligned}\right. \tag{4.1} {xdistortedydistorted=x(1+k1r2+k2r4+k3r6)+2p1xy+p2(r2+2x2)=y(1+k1r2+k2r4+k3r6)+p1(r2+2y2)+2p2xy(4.1)
4.2 去畸变
公式 (4.1) 是无畸变归一化坐标 [ x , y ] T [x, y]^T [x,y]T 到有畸变归一化坐标 [ x d i s t o r t e d , y d i s t o r t e d ] T [x_{distorted}, y_{distorted}]^T [xdistorted,ydistorted]T 的过程。我们把它写成下面的形式
{ x d i s t o r t e d = x + Δ x y d i s t o r t e d = y + Δ y \left\{\begin{aligned} x_{distorted} &= x + \Delta x \\ y_{distorted} &= y + \Delta y \end{aligned}\right. {xdistortedydistorted=x+Δx=y+Δy
于是去畸变的过程可以描述为
{ x = x d i s t o r t e d − Δ x y = y d i s t o r t e d − Δ y (4.2) \left\{\begin{aligned} x &= x_{distorted} - \Delta x \\ y &= y_{distorted} - \Delta y \end{aligned}\right. \tag{4.2} {xy=xdistorted−Δx=ydistorted−Δy(4.2)
由于径向畸变的多项式只取了 3 项,切向畸变也同理。所以公式 (4.2) 并没有完全去除畸变,只是让畸变的程度减轻:像素点像透镜中心移动。所以可以通过多次迭代实现更精确的去畸变。下面是 vins-mono 去畸变的代码。
void PinholeCamera::liftProjective(const Eigen::Vector2d &p, // 有畸变的像素坐标。
Eigen::Vector3d &P) const { // 无畸变的归一化坐标。
double mx_d, my_d, mx2_d, mxy_d, my2_d, mx_u, my_u;
double rho2_d, rho4_d, radDist_d, Dx_d, Dy_d, inv_denom_d;
// double lambda;
// 1.有畸变的像素坐标到有畸变的归一化坐标。Lift points to normalised plane
mx_d = m_inv_K11 * p(0) + m_inv_K13; // (u - cx) / fx。
my_d = m_inv_K22 * p(1) + m_inv_K23; // (v - cy) / fy。
// 2.有畸变的归一化坐标到无畸变的归一化坐标。
if (m_noDistortion) {
// 无需畸变矫正。
mx_u = mx_d;
my_u = my_d;
} else {
// 迭代去畸变模型。Recursive distortion model
int n = 8;
Eigen::Vector2d d_u;
distortion(Eigen::Vector2d(mx_d, my_d), d_u);
// Approximate value
mx_u = mx_d - d_u(0);
my_u = my_d - d_u(1);
for (int i = 1; i < n; ++i) {
distortion(Eigen::Vector2d(mx_u, my_u), d_u);
mx_u = mx_d - d_u(0);
my_u = my_d - d_u(1);
}
}
// Obtain a projective ray
P << mx_u, my_u, 1.0;
}
void PinholeCamera::distortion(const Eigen::Vector2d &p_u, // 归一化成像平面上未纠正的点坐标 (x, y)。
Eigen::Vector2d &d_u) const { // 归一化成像平面上已纠正的点坐标 = p_u + d_u。
// 径向畸变(透镜形状引起)参数。
double k1 = mParameters.k1();
double k2 = mParameters.k2();
// 切向畸变(透镜安装引起)参数。
double p1 = mParameters.p1();
double p2 = mParameters.p2();
double mx2_u, my2_u, mxy_u, rho2_u, rad_dist_u;
mx2_u = p_u(0) * p_u(0); // x^2。
my2_u = p_u(1) * p_u(1); // y^2。
mxy_u = p_u(0) * p_u(1); // xy。
rho2_u = mx2_u + my2_u; // r^2 = x^2 + y^2。
// 注意:rad_dist_u 少加了个 1(见十四讲公式 5.13)。因此去畸变后的坐标不是 d_u 而是 p_u + d_u。
rad_dist_u = k1 * rho2_u + k2 * rho2_u * rho2_u; // k1 * r^2 + k2 * r^4。
double x_c =
p_u(0) * rad_dist_u + 2.0 * p1 * mxy_u + p2 * (rho2_u + 2.0 * mx2_u); // x * (k1 * r^2 + k2 * r^4) + 2 * p1 * xy + p2 * (r^2 + 2 * x^2)。
double y_c =
p_u(1) * rad_dist_u + 2.0 * p2 * mxy_u + p1 * (rho2_u + 2.0 * my2_u); // y * (k1 * r^2 + k2 * r^4) + 2 * p2 * xy + p1 * (r^2 + 2 * y^2)。
d_u << x_c, y_c;
}
5. 视差公式
双目相机中经常用到视差公式。同一个物方点在两个像素坐标系上的投影坐标的差叫做视差。下图是双目相机的视差模型,双目相机水平放置,
O
1
O_1
O1 和
O
2
O_2
O2 是两个相机的光心,也是相机坐标系的原点,
x
x
x 轴方向从
O
1
O_1
O1 到
O
2
O_2
O2,
y
y
y 轴向下,
z
z
z 轴向前。
上图中,蓝线代表像素平面,每个平面在 x x x 方向上的长度是 c x c_x cx 像素。由于我们只关系 x x x 方向上的视差,所以用线代表平面。空间点 P P P 在左右像素坐标系上的投影 p 1 p_1 p1、 p 2 p_2 p2 的像素横坐标是 u 1 u_1 u1、 u 2 u_2 u2。 p 1 p_1 p1 和 p 2 p_2 p2 间的距离是 x x x,它们到各自相机坐标系 z z z 轴的距离是 x 1 x_1 x1 和 x 2 x_2 x2。 P P P 在相机坐标系中的 z z z 坐标是 Z Z Z,基线长度是 b b b,焦距是 f f f。图中绿色的字母单位是米,蓝色的字母单位是像素。我们求的视差
d = u 1 − u 2 . (5.1) d = u_1 - u_2. \tag{5.1} d=u1−u2.(5.1)
首先求
{ x 1 = u 1 − c x α x x 2 = c x − u 2 α x . (5.2) \left\{\begin{aligned} x_1 = \frac{u_1 - c_x}{\alpha_x} \\ x_2 = \frac{c_x - u_2}{\alpha_x} \end{aligned}\right.. \tag{5.2} ⎩ ⎨ ⎧x1=αxu1−cxx2=αxcx−u2.(5.2)
然后
x = b − x 1 − x 2 = b − u 1 − u 2 α x = b − d α x . (5.3) x = b - x_1 - x_2 = b - \frac{u_1 - u_2}{\alpha_x} = b - \frac{d}{\alpha_x}. \tag{5.3} x=b−x1−x2=b−αxu1−u2=b−αxd.(5.3)
由三角形 P p 1 p 2 Pp_1p_2 Pp1p2 与三角形 P O 1 O 2 PO_1O_2 PO1O2 相似,知
x b = Z − f Z . (5.4) \frac{x}{b} = \frac{Z - f}{Z}. \tag{5.4} bx=ZZ−f.(5.4)
从而知
x = b − b f Z . (5.5) x = b - \frac{bf}{Z}. \tag{5.5} x=b−Zbf.(5.5)
由公式 (5.3) 和公式 (5.5) 知
d = b α x f Z = b f x Z . (5.6) d = \frac{b \alpha_x f}{Z} = \frac{b f_x}{Z}. \tag{5.6} d=Zbαxf=Zbfx.(5.6)
上面我们只考虑了三维点在两相机之间的情况,对于三维点在双目相机左侧和右侧的情况,可以同理推出公式 (5.6)。
6. 参考
- Vins 前端中高效的去畸变的方式解析,博客园,2023。