相机模型与畸变处理

针孔相机

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=[xyz]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. xy=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] z1uv1=K fx000fy0cxcy1xyz
矩阵 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=riyxy=rcfθXnc=rcfθYncxy=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=θkf(θ)f(θ)
  • 2
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值