《视觉SLAM十四讲》读书笔记(四)

6.1 状态估计问题

6.1.1 批量状态估计与最大后验估计

考虑从 1 1 1 N N N的所有时刻,并假设有 M M M个路标点。定义所有时刻的机器人位姿和路标点坐标为
x = { x 1 , … , x N } , y = { y 1 , … , y M } \boldsymbol{x}=\left\{\boldsymbol{x}_{1}, \ldots, \boldsymbol{x}_{N}\right\}, \quad \boldsymbol{y}=\left\{\boldsymbol{y}_{1}, \ldots, \boldsymbol{y}_{M}\right\} x={x1,,xN},y={y1,,yM}
同样地, 用不带下标的 u \boldsymbol{u} u 表示所有时刻的输人, z \boldsymbol{z} z 表示所有时刻的观测数据。 对机 器人状态的估计, 就是已知输人数据 u \boldsymbol{u} u 和观测数据 z \boldsymbol{z} z 的条件下, 求状态 x , y \boldsymbol{x}, \boldsymbol{y} x,y 的条件概率分布:
P ( x , y ∣ z , u ) (6.4) P(\boldsymbol{x}, \boldsymbol{y} \mid \boldsymbol{z}, \boldsymbol{u})\tag{6.4} P(x,yz,u)(6.4)
特别地, 当我们不知道控制输人, 只有一张张的图像时, 即只考虑观测方程带来的数据时, 相当于估计 P ( x , y ∣ z ) P(\boldsymbol{x}, \boldsymbol{y} \mid \boldsymbol{z}) P(x,yz) 的条件概率分布, 此问题也称为 S f M \mathrm{SfM} SfM , 即如何从许多图像中重建三维空间结构。为了估计状态变量的条件分布, 利用贝叶斯法则, 有
P ( x , y ∣ z , u ) = P ( z , u ∣ x , y ) P ( x , y ) P ( z , u ) ∝ P ( z , u ∣ x , y ) ⏟ 似然  P ( x , y ) ⏟ 先验  . (6.5) P(\boldsymbol{x}, \boldsymbol{y} \mid \boldsymbol{z}, \boldsymbol{u})=\frac{P(\boldsymbol{z}, \boldsymbol{u} \mid \boldsymbol{x}, \boldsymbol{y}) P(\boldsymbol{x}, \boldsymbol{y})}{P(\boldsymbol{z}, \boldsymbol{u})} \propto \underbrace{P(\boldsymbol{z}, \boldsymbol{u} \mid \boldsymbol{x}, \boldsymbol{y})}_{\text {似然 }} \underbrace{P(\boldsymbol{x}, \boldsymbol{y})}_{\text {先验 }} .\tag{6.5} P(x,yz,u)=P(z,u)P(z,ux,y)P(x,y)似然  P(z,ux,y)先验  P(x,y).(6.5)
贝叶斯法则左侧称为后验概率,右侧的 P ( z ∣ x ) P(\boldsymbol{z} \mid \boldsymbol{x}) P(zx)称为似然 (Likehood),另一部分 P ( x ) P(\boldsymbol{x}) P(x)称为先验 (Prior)

注:式(6.5)的一个重要意义是,当先验概率确定时,后验概率与似然成正比。

直接求后验分布是困难的,但是求一个状态最有估计,使得在该状态下后验概率最大化,则是可行的:
( x , y ) MAP  ∗ = arg ⁡ max ⁡ P ( x , y ∣ z , u ) = arg ⁡ max ⁡ P ( z , u ∣ x , y ) P ( x , y ) (6.6) (\boldsymbol{x}, \boldsymbol{y})_{\text {MAP }}^{*}=\arg \max P(\boldsymbol{x}, \boldsymbol{y} \mid \boldsymbol{z}, \boldsymbol{u})=\arg \max P(\boldsymbol{z}, \boldsymbol{u} \mid \boldsymbol{x}, \boldsymbol{y}) P(\boldsymbol{x}, \boldsymbol{y}) \tag{6.6} (x,y)MAP =argmaxP(x,yz,u)=argmaxP(z,ux,y)P(x,y)(6.6)
贝叶斯法则的分母部分与待估计的状态 x , y \boldsymbol{x}, \boldsymbol{y} x,y无关,因而可以忽略。贝叶斯法则告诉我们,求解最大后验概率等价于最大化似然与先验的乘积。如果我们不知道机器人位姿或路标大概在在什么地方(预测),此时就没有了先验。那么,可以求解最大似然估计:
( x , y ) M L E ∗ = arg ⁡ max ⁡ P ( z , u ∣ x , y ) (6.7) (\boldsymbol{x}, \boldsymbol{y})_{\mathrm{MLE}}^{*}=\arg \max P(\boldsymbol{z}, \boldsymbol{u} \mid \boldsymbol{x}, \boldsymbol{y}) \tag{6.7} (x,y)MLE=argmaxP(z,ux,y)(6.7)
通过贝叶斯法则,我们的求解目标由“基于现在观测到的数据,机器人最可能处于什么样的状态”转换成“在什么样的状态下,最可能产生现在观测到的数据”。

6.1.2 最小二乘的引出

SLAM的观测模型:
z k , j = h ( y j , x k ) + v k , j \boldsymbol{z}_{k, j}=h\left(\boldsymbol{y}_{j}, \boldsymbol{x}_{k}\right)+\boldsymbol{v}_{k, j} zk,j=h(yj,xk)+vk,j
由于我们假设了噪声项 v k ∼ N ( 0 , Q k , j ) \boldsymbol{v}_{k} \sim \mathcal{N}\left(0, \boldsymbol{Q}_{k, j}\right) vkN(0,Qk,j) , 所以观测数据的条件概率为
P ( z j , k ∣ x k , y j ) = N ( h ( y j , x k ) , Q k , j ) P\left(\boldsymbol{z}_{j, k} \mid \boldsymbol{x}_{k}, \boldsymbol{y}_{j}\right)=N\left(h\left(\boldsymbol{y}_{j}, \boldsymbol{x}_{k}\right), \boldsymbol{Q}_{k, j}\right) P(zj,kxk,yj)=N(h(yj,xk),Qk,j)
它依然是一个高斯分布。考虑单次观测的最大似然估计, 可以使用最小化负对数来求一个高斯分布的最大似然。

我们知道高斯分布在负对数下有较好的数学形式。考虑任意高维高斯分布 x ∼ N ( μ , Σ ) x \sim \mathcal{N}(\mu, \Sigma) xN(μ,Σ) , 它 的概率密度函数展开形式为
P ( x ) = 1 ( 2 π ) N det ⁡ ( Σ ) exp ⁡ ( − 1 2 ( x − μ ) T Σ − 1 ( x − μ ) ) (6.8) P(\boldsymbol{x})=\frac{1}{\sqrt{(2 \pi)^{N} \operatorname{det}(\boldsymbol{\Sigma})}} \exp \left(-\frac{1}{2}(\boldsymbol{x}-\boldsymbol{\mu})^{\mathrm{T}} \boldsymbol{\Sigma}^{-1}(\boldsymbol{x}-\boldsymbol{\mu})\right)\tag{6.8} P(x)=(2π)Ndet(Σ) 1exp(21(xμ)TΣ1(xμ))(6.8)
对其取负对数, 则变为
− ln ⁡ ( P ( x ) ) = 1 2 ln ⁡ ( ( 2 π ) N det ⁡ ( Σ ) ) + 1 2 ( x − μ ) T Σ − 1 ( x − μ ) (6.9) -\ln (P(\boldsymbol{x}))=\frac{1}{2} \ln \left((2 \pi)^{N} \operatorname{det}(\boldsymbol{\Sigma})\right)+\frac{1}{2}(\boldsymbol{x}-\boldsymbol{\mu})^{\mathrm{T}} \boldsymbol{\Sigma}^{-1}(\boldsymbol{x}-\boldsymbol{\mu})\tag{6.9} ln(P(x))=21ln((2π)Ndet(Σ))+21(xμ)TΣ1(xμ)(6.9)
因为对数函数时单调递增的,所有对原函数求最大化相当于对负对数求最小化。在最小化上式的 x \boldsymbol{x} x时,第一项与 x \boldsymbol{x} x无关,可以略去。于是,只要最小化右侧的二次型项,就得到了对状态的最大似然估计。代入SLAM的观测模型,相当于在求:
( x k , y j ) ∗ = arg ⁡ max ⁡ N ( h ( y j , x k ) , Q k , j ) = arg ⁡ min ⁡ ( ( z k , j − h ( x k , y j ) ) T Q k , j − 1 ( z k , j − h ( x k , y j ) ) ) (6.10) \begin{aligned} \left(\boldsymbol{x}_{k}, \boldsymbol{y}_{j}\right)^{*} &=\arg \max \mathcal{N}\left(h\left(\boldsymbol{y}_{j}, \boldsymbol{x}_{k}\right), \boldsymbol{Q}_{k, j}\right) \\ &=\arg \min \left(\left(\boldsymbol{z}_{k, j}-h\left(\boldsymbol{x}_{k}, \boldsymbol{y}_{j}\right)\right)^{\mathrm{T}} \boldsymbol{Q}_{k, j}^{-1}\left(\boldsymbol{z}_{k, j}-h\left(\boldsymbol{x}_{k}, \boldsymbol{y}_{j}\right)\right)\right) \end{aligned}\tag{6.10} (xk,yj)=argmaxN(h(yj,xk),Qk,j)=argmin((zk,jh(xk,yj))TQk,j1(zk,jh(xk,yj)))(6.10)
该式等价于最小化噪声项(即误差)的马氏距离

现在我们考虑批量时刻的数据。通常假设各个时刻的输入和观测是相互独立的,这意味着各个输入之间的独立的,各个观测之间是独立的,并且输入和观测也是独立的。于是我们可以对联合分布进行因式分解:
P ( z , u ∣ x , y ) = ∏ k P ( u k ∣ x k − 1 , x k ) ∏ k , j P ( z k , j ∣ x k , y j ) (6.11) P(\boldsymbol{z}, \boldsymbol{u} \mid \boldsymbol{x}, \boldsymbol{y})=\prod_{k} P\left(\boldsymbol{u}_{k} \mid \boldsymbol{x}_{k-1}, \boldsymbol{x}_{k}\right) \prod_{k, j} P\left(\boldsymbol{z}_{k, j} \mid \boldsymbol{x}_{k}, \boldsymbol{y}_{j}\right)\tag{6.11} P(z,ux,y)=kP(ukxk1,xk)k,jP(zk,jxk,yj)(6.11)
这说明我们可以独立地处理各时刻的运动和观测。定义各次输人和观测数据与模型之间的误差:
e u , k = x k − f ( x k − 1 , u k ) e z , j , k = z k , j − h ( x k , y j ) (6.12) \begin{aligned} e_{\boldsymbol{u}, k} &=\boldsymbol{x}_{k}-f\left(\boldsymbol{x}_{k-1}, \boldsymbol{u}_{k}\right) \\ e_{\boldsymbol{z}, j, k} &=\boldsymbol{z}_{k, j}-h\left(\boldsymbol{x}_{k}, \boldsymbol{y}_{j}\right) \end{aligned}\tag{6.12} eu,kez,j,k=xkf(xk1,uk)=zk,jh(xk,yj)(6.12)
那么, 最小化所有时刻估计值与真实读数之间的马氏距离, 等价于求最大似然估计。负对数允许我们把乘积变成求和:
min ⁡ J ( x , y ) = ∑ k e u , k T R k − 1 e u , k + ∑ k ∑ j e z , k , j T Q k , j − 1 e z , k , j (6.13) \min J(\boldsymbol{x}, \boldsymbol{y})=\sum_{k} e_{\boldsymbol{u}, k}^{\mathrm{T}} \boldsymbol{R}_{k}^{-1} \boldsymbol{e}_{\boldsymbol{u}, k}+\sum_{k} \sum_{j} e_{\boldsymbol{z}, k, j}^{\mathrm{T}} \boldsymbol{Q}_{k, j}^{-1} e_{\boldsymbol{z}, k, j}\tag{6.13} minJ(x,y)=keu,kTRk1eu,k+kjez,k,jTQk,j1ez,k,j(6.13)
这样就得到了一个最小二乘问题,它的解等价于状态的最大似然估计。

6.1.3 最小二乘问题解析解

最小二乘的目标函数为
min ⁡ ∑ k = 1 3 e u , k T Q k − 1 e u , k + ∑ k = 1 3 e z , k T R k − 1 e z , k \min \sum_{k=1}^{3} e_{\boldsymbol{u}, k}^{\mathrm{T}} \boldsymbol{Q}_{k}^{-1} e_{\boldsymbol{u}, k}+\sum_{k=1}^{3} e_{\boldsymbol{z}, k}^{\mathrm{T}} \boldsymbol{R}_{k}^{-1} \boldsymbol{e}_{z, k} mink=13eu,kTQk1eu,k+k=13ez,kTRk1ez,k
如果系统是线性系统,那么我们可以很容易地将它写成向量形式,定义向量 y = [ u , z ] T \mathrm{y}=[\mathrm{u},\mathrm{z}]^T y=[u,z]T,那么可以写出矩阵 H \mathrm{H} H,使得
y − H x = e ∼ N ( 0 , Σ ) y-H x=e \sim \mathcal{N}(0, \Sigma) yHx=eN(0,Σ)
那么:
H = [ 1 − 1 0 0 0 1 − 1 0 0 0 1 − 1 0 1 0 0 0 0 1 0 0 0 0 1 ] \boldsymbol{H}=\left[\begin{array}{cccc} 1 & -1 & 0 & 0 \\ 0 & 1 & -1 & 0 \\ 0 & 0 & 1 & -1 \\ 0 & 1 & 0 & 0 \\ 0 & 0 & 1 & 0 \\ 0 & 0 & 0 & 1 \end{array}\right] H=100000110100011010001001
Σ = diag ⁡ ( Q 1 , Q 2 , Q 3 , R 1 , R 2 , R 3 ) \boldsymbol{\Sigma}=\operatorname{diag}\left(\boldsymbol{Q}_{1}, \boldsymbol{Q}_{2}, \boldsymbol{Q}_{3}, \boldsymbol{R}_{1}, \boldsymbol{R}_{2}, \boldsymbol{R}_{3}\right) Σ=diag(Q1,Q2,Q3,R1,R2,R3) 。整个问题可以写成
x m a p ∗ = arg ⁡ min ⁡ e T Σ − 1 e x_{\mathrm{map}}^{*}=\arg \min e^{\mathrm{T}} \Sigma^{-1} e xmap=argmineTΣ1e
之后我们将看到, 这个问题有唯一的解:
x m a p ∗ = ( H T Σ − 1 H ) − 1 H T Σ − 1 y \boldsymbol{x}_{\mathrm{map}}^{*}=\left(\boldsymbol{H}^{\mathrm{T}} \boldsymbol{\Sigma}^{-1} \boldsymbol{H}\right)^{-1} \boldsymbol{H}^{\mathrm{T}} \boldsymbol{\Sigma}^{-1} \boldsymbol{y} xmap=(HTΣ1H)1HTΣ1y

6.2 非线性最小二乘

最小二乘问题的基本形式:
min ⁡ x F ( x ) = 1 2 ∥ f ( x ) ∥ 2 2 (6.24) \min _{\boldsymbol{x}} F(\boldsymbol{x})=\frac{1}{2}\|f(\boldsymbol{x})\|_{2}^{2} \tag{6.24} xminF(x)=21f(x)22(6.24)
迭代求解过程:

  1. 给定某个初始值 x 0 x_0 x0
  2. 给定第 k k k次迭代,寻找一个增量 Δ x k \Delta x_k Δxk,使得 ∣ ∣ f ( x k + Δ x k ) ∣ ∣ 2 2 ||f(x_k+\Delta x_k)||^2_2 f(xk+Δxk)22达到极小值
  3. Δ x k \Delta x_k Δxk足够小,则停止
  4. 否则,令 x k + 1 = x k + Δ x k x_{k+1}=x_k+\Delta x_k xk+1=xk+Δxk,返回第2步

6.2.1 一阶和二阶梯度法

将目标方程在 x k x_k xk附近进行泰勒展开:
F ( x k + Δ x k ) ≈ F ( x k ) + J ( x k ) T Δ x k + 1 2 Δ x k T H ( x k ) Δ x k (6.26) F\left(\boldsymbol{x}_{k}+\Delta \boldsymbol{x}_{k}\right) \approx F\left(\boldsymbol{x}_{k}\right)+\boldsymbol{J}\left(\boldsymbol{x}_{k}\right)^{\mathrm{T}} \Delta \boldsymbol{x}_{k}+\frac{1}{2} \Delta \boldsymbol{x}_{k}^{\mathrm{T}} \boldsymbol{H}\left(\boldsymbol{x}_{k}\right) \Delta \boldsymbol{x}_{k}\tag{6.26} F(xk+Δxk)F(xk)+J(xk)TΔxk+21ΔxkTH(xk)Δxk(6.26)
其中 J ( x k ) \boldsymbol{J}\left(\boldsymbol{x}_{k}\right) J(xk) F ( x ) F(\boldsymbol{x}) F(x) 关于 x \boldsymbol{x} x 的一阶导数 [也叫梯度、雅可比 ( Jacobian ) 矩阵], H \boldsymbol{H} H 则是二阶 导数 [海塞 (Hessian ) 矩阵],它们都在 x k x_{k} xk 处取值。

(1)最速下降法

如果保留一阶梯度,那么取增量为反向的梯度,即可保证函数下降:
Δ x ∗ = − J ( x k ) (6.27) \Delta x^{*}=-J\left(x_{k}\right)\tag{6.27} Δx=J(xk)(6.27)
当然这只是个方向,通常我们还要再指定一个步长 λ \lambda λ

(2)牛顿法

我们可以对式(6.26)保留二阶梯度信息,此时增量方程味:
Δ x ∗ = arg ⁡ min ⁡ ( F ( x ) + J ( x ) T Δ x + 1 2 Δ x T H Δ x ) (6.28) \Delta \boldsymbol{x}^{*}=\arg \min \left(F(\boldsymbol{x})+\boldsymbol{J}(\boldsymbol{x})^{\mathrm{T}} \Delta \boldsymbol{x}+\frac{1}{2} \Delta \boldsymbol{x}^{\mathrm{T}} \boldsymbol{H} \Delta \boldsymbol{x}\right)\tag{6.28} Δx=argmin(F(x)+J(x)TΔx+21ΔxTHΔx)(6.28)
右侧只含 Δ x \Delta x Δx 的零次、一次和二次项。求右侧等式关于 Δ x \Delta x Δx 的导数并令它为零,得到
J + H Δ x = 0 ⇒ H Δ x = − J (6.29) J+H \Delta x=0 \Rightarrow H \Delta x=-J\tag{6.29} J+HΔx=0HΔx=J(6.29)
求解这个 Δ x = − H − 1 J \Delta x=-H^{-1}J Δx=H1J即可得到增量。

(3)小结 1

  1. 最速下降法过于贪心,容易走出锯齿路线,反而增加了迭代次数。
  2. 牛顿法需要计算目标函数的 H H H矩阵,这在问题规模较大时非常困难,我们通常倾向于避免 H H H的计算。

(4)补充:矩阵求导公式

f ( X ) 和 g ( X ) f(\mathbf{X}) 和 g(\mathbf{X}) f(X)g(X) 分别是矩阵 X \mathbf{X} X 的实值函数, c 1 , c 2 c_{1}, c_{2} c1,c2 为实常数, 则

  • 线性法则 :
    ∂ [ c 1 f ( X ) + c 2 g ( X ) ] ∂ X = c 1 ∂ f ∂ X + c 2 ∂ g ∂ X \frac{\partial\left[c_{1} f(\mathbf{X})+c_{2} g(\mathbf{X})\right]}{\partial \mathbf{X}}=c_{1} \frac{\partial f}{\partial \mathbf{X}}+c_{2} \frac{\partial g}{\partial \mathbf{X}} X[c1f(X)+c2g(X)]=c1Xf+c2Xg .
  • 乘积法则: ∂ [ f ( X ) g ( X ) ] ∂ X = g ( X ) ∂ f ∂ X + f ( X ) ∂ g ∂ X \frac{\partial[f(\mathbf{X}) g(\mathbf{X})]}{\partial \mathbf{X}}=g(\mathbf{X}) \frac{\partial f}{\partial \mathbf{X}}+f(\mathbf{X}) \frac{\partial g}{\partial \mathbf{X}} X[f(X)g(X)]=g(X)Xf+f(X)Xg .
  • 商法则 : ∂ [ f ( X ) / g ( X ) ] ∂ X = 1 g 2 ( X ) [ g ( X ) ∂ f ∂ X − f ( X ) ∂ g ∂ X ] \frac{\partial[f(\mathbf{X}) / g(\mathbf{X})]}{\partial \mathbf{X}}=\frac{1}{g^{2}(\mathbf{X})}\left[g(\mathbf{X}) \frac{\partial f}{\partial \mathbf{X}}-f(\mathbf{X}) \frac{\partial g}{\partial \mathbf{X}}\right] X[f(X)/g(X)]=g2(X)1[g(X)Xff(X)Xg].
  • 链式法则: 若 y = f ( X ) , g ( y ) y=f(\mathbf{X}), g(y) y=f(X),g(y) 分别为矩阵 X \mathbf{X} X 和标量 y y y 的实值函数, 则
    ∂ g ( f ( X ) ) ∂ X = d g ( y ) d y ∂ f ( X ) ∂ X \frac{\partial g(f(\mathbf{X}))}{\partial \mathbf{X}}=\frac{\mathrm{d} g(y)}{\mathrm{d} y} \frac{\partial f(\mathbf{X})}{\partial \mathbf{X}} Xg(f(X))=dydg(y)Xf(X)

以下设 a 、 A \mathbf{a} 、 \mathbf{A} aA 为与变量 x \mathbf{x} x 无关的向量或者矩阵。则
∂ a ⊤ x ∂ x = ∂ x ⊤ a ∂ x = a \frac{\partial \mathbf{a}^{\top} \mathbf{x}}{\partial \mathbf{x}}=\frac{\partial \mathbf{x}^{\top} \mathbf{a}}{\partial \mathbf{x}}=\mathbf{a} xax=xxa=a
∂ x ⊤ A y ∂ x = A y , ∂ y ⊤ A x ∂ x = A ⊤ y \frac{\partial \mathbf{x}^{\top} \mathbf{A} \mathbf{y}}{\partial \mathbf{x}}=\mathbf{A} \mathbf{y}, \quad \frac{\partial \mathbf{y}^{\top} \mathbf{A x}}{\partial \mathbf{x}}=\mathbf{A}^{\top} \mathbf{y} xxAy=Ay,xyAx=Ay
∂ x ⊤ A ∂ x = A , ∂ x ⊤ A x ∂ x = ( A + A ⊤ ) x \frac{\partial \mathbf{x}^{\top} \mathbf{A}}{\partial \mathbf{x}}=\mathbf{A}, \quad \frac{\partial \mathbf{x}^{\top} \mathbf{A} \mathbf{x}}{\partial \mathbf{x}}=\left(\mathbf{A}+\mathbf{A}^{\top}\right) \mathbf{x} xxA=A,xxAx=(A+A)x
f = 1 2 x ⊤ Q x f=\frac{1}{2} \mathbf{x}^{\top} \mathbf{Q} \mathbf{x} f=21xQx, 其中 Q \mathbf{Q} Q 为对称矩阵。则 ∇ f = Q x , ∇ 2 f = Q \nabla f=\mathbf{Q} \mathbf{x}, \nabla^{2} f=\mathbf{Q} f=Qx,2f=Q


6.2.2 高斯牛顿法

它的思想是将 f ( x ) f(x) f(x)进行一阶的泰勒展开。注意这里不是目标函数 F ( x ) F(x) F(x),而是 f ( x ) f(x) f(x),否则就变成牛顿法了。
f ( x + Δ x ) ≈ f ( x ) + J ( x ) Δ x (6.30) f(\boldsymbol{x}+\Delta \boldsymbol{x}) \approx f(\boldsymbol{x})+\boldsymbol{J}(\boldsymbol{x}) \Delta \boldsymbol{x}\tag{6.30} f(x+Δx)f(x)+J(x)Δx(6.30)
这里 J ( x ) T \boldsymbol{J}(\boldsymbol{x})^{\mathrm{T}} J(x)T f ( x ) f(\boldsymbol{x}) f(x) 关于 x \boldsymbol{x} x 的导数, 为 n × 1 n \times 1 n×1 的列向量。根据前面的框架, 当前的目标是寻找增 量 Δ x \Delta x Δx , 使得 ∥ f ( x + Δ x ) ∥ 2 \|f(x+\Delta x)\|^{2} f(x+Δx)2 达到最小。为了求 Δ x \Delta x Δx , 我们需要解一个线性的最小二乘问题:
Δ x ∗ = arg ⁡ min ⁡ Δ x 1 2 ∥ f ( x ) + J ( x ) Δ x ∥ 2 (6.31) \Delta x^{*}=\arg \min _{\Delta x} \frac{1}{2}\left\|f(x)+J(x) \Delta x\right\|^{2}\tag{6.31} Δx=argΔxmin21f(x)+J(x)Δx2(6.31)
注:必须说明的是,《视觉SLAM十四讲》里的式(6.30)泰勒展开有误,展开后的雅克比矩阵是不需要转置的
1 2 ∥ f ( x ) + J ( x ) Δ x ∥ 2 = 1 2 ( f ( x ) + J ( x ) Δ x ) T ( f ( x ) + J ( x ) Δ x ) = 1 2 ( f ( x ) T + Δ x T J ( x ) T ) ( f ( x ) + J ( x ) Δ x ) = 1 2 ( f ( x ) T f ( x ) + f ( x ) T J ( x ) Δ x + Δ x T J ( x ) T f ( x ) + Δ x T J ( x ) T J ( x ) Δ x ) (6.32) \begin{aligned} \frac{1}{2}\left\|f(\boldsymbol{x})+\boldsymbol{J}(\boldsymbol{x})\Delta \boldsymbol{x}\right\|^{2} &=\frac{1}{2}\left(f(\boldsymbol{x})+\boldsymbol{J}(\boldsymbol{x}) \Delta \boldsymbol{x}\right)^{\mathrm{T}}\left(f(\boldsymbol{x})+\boldsymbol{J}(\boldsymbol{x})\Delta \boldsymbol{x}\right) \\ &= \frac{1}{2}\left(f(\boldsymbol{x})^{\mathrm{T}}+\Delta \boldsymbol{x}^{\mathrm{T}}\boldsymbol{J}(\boldsymbol{x})^{\mathrm{T}} \right) \left(f(\boldsymbol{x})+\boldsymbol{J}(\boldsymbol{x}) \Delta \boldsymbol{x}\right)\\ &= \frac{1}{2}\left(f(\boldsymbol{x})^{\mathrm{T}} f(\boldsymbol{x})+ f(\boldsymbol{x})^\mathrm{T} \boldsymbol{J}(\boldsymbol{x}) \Delta \boldsymbol{x} + \Delta \boldsymbol{x}^{\mathrm{T}} \boldsymbol{J}(\boldsymbol{x})^{\mathrm{T}}f(\boldsymbol{x})+\Delta \boldsymbol{x}^{\mathrm{T}} \boldsymbol{J}(\boldsymbol{x})^{\mathrm{T}} \boldsymbol{J}(\boldsymbol{x}) \Delta \boldsymbol{x}\right)\\ \end{aligned}\tag{6.32} 21f(x)+J(x)Δx2=21(f(x)+J(x)Δx)T(f(x)+J(x)Δx)=21(f(x)T+ΔxTJ(x)T)(f(x)+J(x)Δx)=21(f(x)Tf(x)+f(x)TJ(x)Δx+ΔxTJ(x)Tf(x)+ΔxTJ(x)TJ(x)Δx)(6.32)
求上式关于 Δ x \Delta x Δx 的导数, 并令其为零:
J ( x ) T f ( x ) + J ( x ) T J ( x ) Δ x = 0 (6.33) \boldsymbol{J}(\boldsymbol{x})^{\mathrm{T}} f(\boldsymbol{x})+\boldsymbol{J}(\boldsymbol{x})^{\mathrm{T}} \boldsymbol{J}(\boldsymbol{x}) \Delta \boldsymbol{x}=\mathbf{0}\tag{6.33} J(x)Tf(x)+J(x)TJ(x)Δx=0(6.33)
可以得到如下方程组:
J T ( x ) J ⏟ H ( x ) ( x ) Δ x = − J T ( x ) f ( x ) ⏟ g ( x ) . (6.34) \underbrace{\boldsymbol{J}^{\mathrm{T}}(\boldsymbol{x}) \boldsymbol{J}}_{\boldsymbol{H}(\boldsymbol{x})}(\boldsymbol{x}) \Delta \boldsymbol{x}=\underbrace{-\boldsymbol{J}^{\mathrm{T}}(\boldsymbol{x}) f(\boldsymbol{x})}_{\boldsymbol{g}(\boldsymbol{x})} .\tag{6.34} H(x) JT(x)J(x)Δx=g(x) JT(x)f(x).(6.34)
这个方程是关于变量 Δ x \Delta x Δx 的线性方程组, 我们称它为增量方程, 也可以称为高斯牛顿方程 ( Gauss-Newton equation) 或者正规方程 (Normal equation )。我们把左边的系数定义为 H \boldsymbol{H} H , 右边 定义为 g g g , 那么上式变为
H Δ x = g (6.35) \boldsymbol{H} \Delta \boldsymbol{x}=\boldsymbol{g}\tag{6.35} HΔx=g(6.35)

这里把左侧记作 H \boldsymbol{H} H是有意义的。对比牛顿法可见,高斯牛顿法用 J ( x ) J T \boldsymbol{J}(\boldsymbol{x}) \boldsymbol{J}^{\mathrm{T}} J(x)JT作为牛顿法中二阶海森矩阵的近似,从而省略了计算 H \boldsymbol{H} H的过程。

高斯牛顿法的缺陷

为了求解增量方程,我们需要求解 H − 1 \boldsymbol{H}^{-1} H1,但实际数据中计算得到的 J ( x ) J T \boldsymbol{J}(\boldsymbol{x}) \boldsymbol{J}^{\mathrm{T}} J(x)JT只有半正定性。也就是说,求 H − 1 \boldsymbol{H}^{-1} H1时可能无解,导致算法不收敛。直观地说,原函数在这个点的局部近似不像一个二次函数。因此,每次求逆都需要判断判断是否求逆成功。

6.2.3 列文伯格——马夸尔特方法

高斯牛顿法中采用的近似二阶泰勒展开只能在展开点附近有较好的近似效果,所以我们应该给 Δ x \Delta \boldsymbol{x} Δx添加一个范围,称为信赖区域。这个范围定义了在什么情况下二阶近似是有效的。

那么,如何确定这个信赖区域的范围呢?一个比较好的方法是根据我们的近似模型跟实际函数之间的差异来确定:如果差异小,说明近似效果好,我们扩大近似的范围;反之,如果差异搭,就缩小近似的范围。我们定义一个指标 ρ \rho ρ来刻画近似的好坏程度:
ρ = f ( x + Δ x ) − f ( x ) J ( x ) T Δ x (6.34) \rho=\frac{f(\boldsymbol{x}+\Delta \boldsymbol{x})-f(\boldsymbol{x})}{\boldsymbol{J}(\boldsymbol{x})^{\mathrm{T}} \Delta \boldsymbol{x}}\tag{6.34} ρ=J(x)TΔxf(x+Δx)f(x)(6.34)
ρ \rho ρ的分子式实际函数下降的值,分母式近似模型下降的值。

  1. 如果 ρ \rho ρ接近于1,则近似是好的。
  2. 如果 ρ \rho ρ太小,说明实际减小的值远少于近似减小的值,则认为近似比较差。需要缩小近似范围。
  3. 如果 ρ \rho ρ比较大,则说明实际下降的比预计的更大,我们可以放大近似范围。

算法流程:

  1. 给定初始值 x 0 \boldsymbol{x}_0 x0,以及初始优化半径 μ \mu μ
  2. 对于第 k k k次迭代,在高斯牛顿法的基础上加上信赖区域,求解:
    min ⁡ Δ x k 1 2 ∥ f ( x k ) + J ( x k ) T Δ x k ∥ 2 ,  s.t.  ∥ D Δ x k ∥ 2 ⩽ μ (6.35) \min _{\Delta \boldsymbol{x}_{k}} \frac{1}{2}\left\|f\left(\boldsymbol{x}_{k}\right)+\boldsymbol{J}\left(\boldsymbol{x}_{k}\right)^{\mathrm{T}} \Delta \boldsymbol{x}_{k}\right\|^{2}, \quad \text { s.t. } \quad\left\|\boldsymbol{D} \Delta \boldsymbol{x}_{k}\right\|^{2} \leqslant \mu\tag{6.35} Δxkmin21f(xk)+J(xk)TΔxk2, s.t. DΔxk2μ(6.35)
    其中, μ \mu μ是信赖区域的半径, D \boldsymbol{D} D为系数矩阵
  3. 按照式(6.34)计算 ρ \rho ρ
  4. ρ > 3 4 \rho > \frac{3}{4} ρ>43,则设置 μ = 2 μ \mu=2\mu μ=2μ
  5. ρ < 1 4 \rho < \frac{1}{4} ρ<41,则设置 μ = 0.5 μ \mu=0.5\mu μ=0.5μ
  6. 如果 ρ \rho ρ大于某阈值,则认为近似可行。令 x k + 1 = x k + Δ x k \boldsymbol{x}_{k+1}=\boldsymbol{x}_k+\Delta\boldsymbol{x}_k xk+1=xk+Δxk
  7. 判断算法是否收敛。如不收敛则返回第2步,否则结束。

这里近似范围扩大的倍数和阈值都是经验值,可以替换成别的数值。在式(6.35)中,我们把增量限定于一个半径为 μ \mu μ的球中,认为只在这个球内才是有效的。带上 D \boldsymbol{D} D之后,这个球可以看成一个椭球。在列文伯格提出的优化方法中,把 D \boldsymbol{D} D取成单位阵 I \boldsymbol{I} I,相当于直接把 Δ x k \Delta \boldsymbol{x}_k Δxk约束在一个球中。随后,马夸尔特提出将 D \boldsymbol{D} D取成非负数对角阵——实际中通常用 J T J \boldsymbol{J}^\boldsymbol{T}\boldsymbol{J} JTJ的对角元素平方根,使得在梯度小的维度上的维度上约束范围更大一些。

这个子问题是带不等式约束的优化问题,我们用拉格朗日乘子把约束项放到目标函数中,构成拉格朗日函数:
L ( Δ x k , λ ) = 1 2 ∥ f ( x k ) + J ( x k ) T Δ x k ∥ 2 + λ 2 ( ∥ D Δ x k ∥ 2 − μ ) (6.36) \mathcal{L}\left(\Delta \boldsymbol{x}_{k}, \lambda\right)=\frac{1}{2}\left\|f\left(\boldsymbol{x}_{k}\right)+\boldsymbol{J}\left(\boldsymbol{x}_{k}\right)^{\mathrm{T}} \Delta \boldsymbol{x}_{k}\right\|^{2}+\frac{\lambda}{2}\left(\left\|\boldsymbol{D} \Delta \boldsymbol{x}_{k}\right\|^{2}-\mu\right)\tag{6.36} L(Δxk,λ)=21f(xk)+J(xk)TΔxk2+2λ(DΔxk2μ)(6.36)
这里 λ \lambda λ 为拉格朗日乘子。类似于高斯牛顿法中的做法, 令该拉格朗日函数关于 Δ x \Delta x Δx 的导数为零, 它的核心仍是计算增量的线性方程:
( H + λ D T D ) Δ x k = g (6.37) \left(\boldsymbol{H}+\lambda \boldsymbol{D}^{\mathrm{T}} \boldsymbol{D}\right) \Delta \boldsymbol{x}_{k}=\boldsymbol{g}\tag{6.37} (H+λDTD)Δxk=g(6.37)
可以看到, 相比于高斯牛顿法, 增量方程多了一项 λ D T D \lambda \boldsymbol{D}^{T} \boldsymbol{D} λDTD 。如果考虑它的简化形式(列文伯格提出), 即 D = I \boldsymbol{D}=\boldsymbol{I} D=I , 那么相当于求解 :
( H + λ I ) Δ x k = g (6.37-1) (\boldsymbol{H}+\lambda \boldsymbol{I}) \Delta \boldsymbol{x}_{k}=\boldsymbol{g}\tag{6.37-1} (H+λI)Δxk=g(6.37-1)

  • 当参数 λ \lambda λ比较小时, H \boldsymbol{H} H占主要地位,这说明二次近似模型在该范围内是比较好的,列文伯格-马夸尔特方法更接近于高斯牛顿法
  • 当参数 λ \lambda λ比较大时, λ I \lambda \boldsymbol{I} λI占据主要地位,列文伯格-马夸尔特方法更接近于最速下降法,这说明附近的二次近似不够好。

进一步地,马夸尔特提出了通过对角线元素增加比例系数来提供更快的收敛速度:
( J ( x ) T J ( x ) + λ diag ( J ( x ) J ( x ) T ) ) Δ x k = − J ( x ) f ( x ) (6.37-2) \left(\boldsymbol{J}(\boldsymbol{x})^{\mathrm{T}} \boldsymbol{J}(\boldsymbol{x}) + \lambda \text{diag}\left(\boldsymbol{J}(\boldsymbol{x})\boldsymbol{J}(\boldsymbol{x})^{\mathrm{T}} \right)\right)\Delta \boldsymbol{x}_{k}=-\boldsymbol{J}(\boldsymbol{x}) f(\boldsymbol{x})\tag{6.37-2} (J(x)TJ(x)+λdiag(J(x)J(x)T))Δxk=J(x)f(x)(6.37-2)

  • 如果梯度值很小(目标函数几乎是平面的区域),对角线元素的倒数将会变得很大,那么这个修改就会导致在梯度方向上产生较大的步长。
  • 反之,在目标函数的梯度方向上,算法将变得更加保守,并采取更小的步长。

式(6.37-1)和式(6.37-2)对正规方程 H Δ x = g \boldsymbol{H} \Delta \boldsymbol{x}=\boldsymbol{g} HΔx=g的修改都可以从贝叶斯的角度理解为给系统增加了一个零均值的先验信息。

列文伯格-马夸尔特算法伪代码

在这里插入图片描述
高斯牛顿法和列文伯格-马夸尔特算法之间最关键的区别在于,后者会拒绝可能导致更大的残差平方和的更新,而前者不会。一个更新被拒绝,说明非线性函数在局部附近表现不好,所以需要更小的更新步长。缩小更新步长可以通过启发式地增大 λ \lambda λ的值来实现,例如,可以将现有的 λ \lambda λ值乘以因子10,并且求解修改后的正规方程。另一方面,如果一个步长导致了残差平方和的减小,这个步长就会被接受,并且状态估计值也会相应地被更新。在这种情况下,减小 λ \lambda λ(通过将 λ \lambda λ除以10),算法在一个新的线性化点处继续迭代,直到收敛为止。

6.2.4 Dogleg最小化法(占坑)

待续。。。

6.3 实践:曲线拟合问题

考虑一条满足以下方程的曲线:
y = exp ⁡ ( a x 2 + b x + c ) + w , y=\exp \left(a x^{2}+b x+c\right)+w, y=exp(ax2+bx+c)+w,
其中, a , b , c a, b, c a,b,c 为曲线的参数, w w w 为高斯噪声, 满足 w ∼ ( 0 , σ 2 ) w \sim\left(0, \sigma^{2}\right) w(0,σ2) 。现在, 假设我们有 N N N 个关于 x , y x, y x,y 的观测数据点, 想根据这些数据点求出曲线的参数。那么, 可以求解下面的最小二乘问题以估计曲线参数:
min ⁡ a , b , c 1 2 ∑ i = 1 N ∥ y i − exp ⁡ ( a x i 2 + b x i + c ) ∥ 2 \min _{a, b, c} \frac{1}{2} \sum_{i=1}^{N}\left\|y_{i}-\exp \left(a x_{i}^{2}+b x_{i}+c\right)\right\|^{2} a,b,cmin21i=1Nyiexp(axi2+bxi+c)2
请注意, 在这个问题中, 待估计的变量是 a , b , c a, b, c a,b,c , 而不是 x x x 。我们的程序里先根据模型生成 x , y x, y x,y 的真值, 然后在真值中添加高斯分布的噪声。随后, 使用高斯牛顿法从带噪声的数据拟合参数模型。定义误差为
e i = y i − exp ⁡ ( a x i 2 + b x i + c ) , e_{i}=y_{i}-\exp \left(a x_{i}^{2}+b x_{i}+c\right), ei=yiexp(axi2+bxi+c),
那么, 可以求出每个误差项对于状态变量的导数:
∂ e i ∂ a = − x i 2 exp ⁡ ( a x i 2 + b x i + c ) ∂ e i ∂ b = − x i exp ⁡ ( a x i 2 + b x i + c ) ∂ e i ∂ c = − exp ⁡ ( a x i 2 + b x i + c ) \begin{array}{l} \frac{\partial e_{i}}{\partial a}=-x_{i}^{2} \exp \left(a x_{i}^{2}+b x_{i}+c\right) \\ \frac{\partial e_{i}}{\partial b}=-x_{i} \exp \left(a x_{i}^{2}+b x_{i}+c\right) \\ \frac{\partial e_{i}}{\partial c}=-\exp \left(a x_{i}^{2}+b x_{i}+c\right) \end{array} aei=xi2exp(axi2+bxi+c)bei=xiexp(axi2+bxi+c)cei=exp(axi2+bxi+c)
于是 J i = [ ∂ e i ∂ a , ∂ e i ∂ b , ∂ e i ∂ c ] T \boldsymbol{J}_{i}=\left[\frac{\partial e_{i}}{\partial a}, \frac{\partial e_{i}}{\partial b}, \frac{\partial e_{i}}{\partial c}\right]^{\mathrm{T}} Ji=[aei,bei,cei]T , 高斯牛顿法的增量方程为
( ∑ i = 1 100 J i ( σ 2 ) − 1 J i ⊤ ) Δ x k = ∑ i = 1 100 − J i ( σ 2 ) − 1 e i , \left(\sum_{i=1}^{100} J_{i}\left(\sigma^{2}\right)^{-1} J_{i}{ }^{\top}\right) \Delta x_{k}=\sum_{i=1}^{100}-J_{i}\left(\sigma^{2}\right)^{-1} e_{i,} (i=1100Ji(σ2)1Ji)Δxk=i=1100Ji(σ2)1ei,
为了让求解过程更加清晰,我们选择把所有的 J i \boldsymbol{J}_{i} Ji排成一列,将这个方程写成矩阵形式。不过这样会消耗较多的内存。

6.3.2 手写高斯牛顿法

#include <iostream>
#include <chrono>
#include <Eigen/Core>
#include <Eigen/Dense>
#include <opencv2/opencv.hpp>

using namespace Eigen;

int main(int argc, char** argv) {
    // 配置真实值
    double ar = 1.0, br = 2.0, cr = 1.0;
    // 配置噪声值
    double w_sigma = 1.0;
    double inv_w_sigma = 1.0/w_sigma;
    // 生成数据点
    int N = 100;
    // OpenCV随机数生成器
    cv::RNG rng; 

    // 雅克比矩阵
    Eigen::Matrix<double, 100, 3> J;
    J.setZero();
    // 正规矩阵H = J^T*J
    Eigen::Matrix<double, 3, 3> H;
    H.setZero();
    // J_n
    Eigen::Matrix<double, 1, 3> Jn;
    Jn.setZero();
    // f(x)
    Eigen::Matrix<double, 100, 1> f;
    f.setZero();
    // g(x)
    Eigen::Matrix<double, 3, 1> g;
    g.setZero();
    // /Delta x
    Eigen::Matrix<double, 3, 1> Delta_x;
    Delta_x.setZero();

    std::vector<double> x_data, y_data;
    for(size_t i=0; i<N; i++)
    {
        double x = i/100.0;
        x_data.push_back(x);
        y_data.push_back(std::exp(ar*x*x+br*x+cr)+rng.gaussian(w_sigma*w_sigma));
    }

    // 设置初始值
    double ae = 2.0, be = -1.0, ce = 5.0;

    // 开始最速下降法迭代
        // 设置迭代次数
        // 设置结束条件
    size_t numIter = 100;
    double cost = 0.0;
    double last_cost = 0.0;
    size_t count = 0;
    std::chrono::steady_clock::time_point t1 = std::chrono::steady_clock::now();
    for(size_t i=0; i<numIter; i++)
    {
        for(size_t j=0; j<N; j++)
        {
            Jn(0, 0) = x_data[j]*x_data[j]*std::exp(ae*x_data[j]*x_data[j]+be*x_data[j]+ce);
            Jn(0, 1) = x_data[j]*std::exp(ae*x_data[j]*x_data[j]+be*x_data[j]+ce);
            Jn(0, 2) = 1*std::exp(ae*x_data[j]*x_data[j]+be*x_data[j]+ce);
            J.block(j, 0, 1, 3) = inv_w_sigma*Jn; // 记得配置上噪声权值
            f(j, 0) = inv_w_sigma*std::exp(ae*x_data[j]*x_data[j]+be*x_data[j]+ce) - y_data[j]; // 记得配置上噪声权值
        }
        cost = f.norm();
        H = J.transpose()*J;
        g = -J.transpose()*f;
        // 直接求逆
        // Delta_x = H.inverse()*g;

        // 使用cholesky分解来解方程
        // Delta_x = H.ldlt().solve(g);

        // 使用QR分解来解方程,速度比较快
        Delta_x = H.colPivHouseholderQr().solve(g);
        

        if(isnan(Delta_x[0]))
        {
            std::cout<<"result is nan!"<<std::endl;
            break;
        }

        if(cost>=last_cost && i>0)
        {
            std::cout<<"cost: "<<cost<<" >= last cost: "<<last_cost<<", break."<<std::endl;
            break;
        }

        last_cost = cost;

        ae = ae + Delta_x(0, 0);
        be = be + Delta_x(1, 0);
        ce = ce + Delta_x(2, 0);

        count = i;
    }

    std::chrono::steady_clock::time_point t2 = std::chrono::steady_clock::now();
    std::chrono::duration<double> time_used = std::chrono::duration_cast<std::chrono::duration<double>>(t2 - t1);
    std::cout << "solve time cost = " << time_used.count() << " seconds. " << std::endl;
    std::cout<<"estimated abc = "<<ae<<", "<<be<<", "<<ce<<std::endl;
    std::cout<<"number of iterations = "<<count<<std::endl;
    return 0;
    
}

6.3.1 手写列文伯格-马夸尔特方法

#include <iostream>
#include <chrono>
#include <Eigen/Core>
#include <Eigen/Dense>
#include <opencv2/opencv.hpp>

using namespace Eigen;

int main(int argc, char** argv) {
    // 配置真实值
    double ar = 1.0, br = 2.0, cr = 1.0;
    // 配置噪声值
    double w_sigma = 1.0;
    double inv_w_sigma = 1.0/w_sigma;
    // 生成数据点
    int N = 100;
    // OpenCV随机数生成器
    cv::RNG rng; 

    double lambda = 1e-4;

    // 雅克比矩阵
    Eigen::Matrix<double, 100, 3> J;
    J.setZero();
    // 正规矩阵H = J^T*J
    Eigen::Matrix<double, 3, 3> H;
    H.setZero();
    // J_n
    Eigen::Matrix<double, 1, 3> Jn;
    Jn.setZero();
    // f(x)
    Eigen::Matrix<double, 100, 1> f;
    f.setZero();
    // g(x)
    Eigen::Matrix<double, 3, 1> g;
    g.setZero();
    // /Delta x
    Eigen::Matrix<double, 3, 1> Delta_x;
    Delta_x.setZero();

    Eigen::Matrix3d DeltaMatrix;
    DeltaMatrix.setZero();

    std::vector<double> x_data, y_data;
    for(size_t i=0; i<N; i++)
    {
        double x = i/100.0;
        x_data.push_back(x);
        y_data.push_back(std::exp(ar*x*x+br*x+cr)+rng.gaussian(w_sigma*w_sigma));
    }

    // 设置初始值
    double ae = 2.0, be = -1.0, ce = 5.0;

    // 开始最速下降法迭代
        // 设置迭代次数
        // 设置结束条件
    size_t numIter = 100;
    double cost = 0.0;
    double last_cost = 0.0;
    size_t count = 0;
    std::chrono::steady_clock::time_point t1 = std::chrono::steady_clock::now();
    for(size_t i=0; i<numIter; i++)
    {
        for(size_t j=0; j<N; j++)
        {
            Jn(0, 0) = x_data[j]*x_data[j]*std::exp(ae*x_data[j]*x_data[j]+be*x_data[j]+ce);
            Jn(0, 1) = x_data[j]*std::exp(ae*x_data[j]*x_data[j]+be*x_data[j]+ce);
            Jn(0, 2) = 1*std::exp(ae*x_data[j]*x_data[j]+be*x_data[j]+ce);
            J.block(j, 0, 1, 3) = inv_w_sigma*Jn; // 记得配置上噪声权值
            f(j, 0) = inv_w_sigma*std::exp(ae*x_data[j]*x_data[j]+be*x_data[j]+ce) - y_data[j]; // 记得配置上噪声权值
        }
        cost = f.norm();
        H = J.transpose()*J;
        Eigen::Matrix3d diagMatrix = Eigen::Matrix3d(H.diagonal().asDiagonal());
        DeltaMatrix = H + lambda*diagMatrix;
        g = -J.transpose()*f;

        // 直接求逆
        // Delta_x = DeltaMatrix.inverse()*g;

        // 使用cholesky分解来解方程
        // Delta_x = DeltaMatrix.ldlt().solve(g);

        // 使用QR分解来解方程,速度比较快
        Delta_x = DeltaMatrix.colPivHouseholderQr().solve(g);
        

        if(isnan(Delta_x[0]))
        {
            std::cout<<"result is nan!"<<std::endl;
            break;
        }

        if(Delta_x.norm()<0.001)
        {
            std::cout<<"result is converge!"<<std::endl;
            break;
        }

        // 接受更新
        if((cost<last_cost && i>0) || i==0)
        {
            ae = ae + Delta_x(0, 0);
            be = be + Delta_x(1, 0);
            ce = ce + Delta_x(2, 0);

            if(i>0) lambda*=0.1;
        }
        // 发散了,拒绝更新
        else
        {
            lambda*=10.0;
        }
        
        last_cost = cost;

        count = i;
    }

    std::chrono::steady_clock::time_point t2 = std::chrono::steady_clock::now();
    std::chrono::duration<double> time_used = std::chrono::duration_cast<std::chrono::duration<double>>(t2 - t1);
    std::cout << "solve time cost = " << time_used.count() << " seconds. " << std::endl;
    std::cout<<"estimated abc = "<<ae<<", "<<be<<", "<<ce<<std::endl;
    std::cout<<"number of iterations = "<<count<<std::endl;
    return 0;
    
}

6.3.3 使用Ceres进行曲线拟合(占坑)

学习Ceres-solver最全的文档:Ceres Solver

代码以后再补…

6.3.4 使用g2o进行曲线拟合(占坑)

在图优化库里,g2o和GTSAM的功能类似,而我自己更喜欢用GTSAM,因为它集成了ISAM2。因此这里暂时搁置g2o。

6.3.5 使用GTSAM进行曲线拟合(占坑)

学习GTSAM最全的文档:GTSAM

代码以后再补…

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
基于C++&OPENCV 的全景图像拼接 C++是一种广泛使用的编程语言,它是由Bjarne Stroustrup于1979年在新泽西州美利山贝尔实验室开始设计开发的。C++是C语言的扩展,旨在提供更强大的编程能力,包括面向对象编程和泛型编程的支持。C++支持数据封装、继承和多态等面向对象编程的特性和泛型编程的模板,以及丰富的标准库,提供了大量的数据结构和算法,极大地提高了开发效率。12 C++是一种静态类型的、编译式的、通用的、大小写敏感的编程语言,它综合了高级语言和低级语言的特点。C++的语法与C语言非常相似,但增加了许多面向对象编程的特性,如类、对象、封装、继承和多态等。这使得C++既保持了C语言的低级特性,如直接访问硬件的能力,又提供了高级语言的特性,如数据封装和代码重用。13 C++的应用领域非常广泛,包括但不限于教育、系统开发、游戏开发、嵌入式系统、工业和商业应用、科研和高性能计算等领域。在教育领域,C++因其结构化和面向对象的特性,常被选为计算机科学和工程专业的入门编程语言。在系统开发领域,C++因其高效性和灵活性,经常被作为开发语言。游戏开发领域中,C++由于其高效性和广泛应用,在开发高性能游戏和游戏引擎中扮演着重要角色。在嵌入式系统领域,C++的高效和灵活性使其成为理想选择。此外,C++还广泛应用于桌面应用、Web浏览器、操作系统、编译器、媒体应用程序、数据库引擎、医疗工程和机器人等领域。16 学习C++的关键是理解其核心概念和编程风格,而不是过于深入技术细节。C++支持多种编程风格,每种风格都能有效地保证运行时间效率和空间效率。因此,无论是初学者还是经验丰富的程序员,都可以通过C++来设计和实现新系统或维护旧系统。3
在《视觉SLAM十四》中,章节安排如下: 1. 数学基础部分:介绍这本书的基本信息,包括自测题。概述SLAM系统的组成和各模块的工作。介绍三维空间运动、李群和李代数、针孔相机模型以及非线性优化。完成一个曲线拟合的实验。 2. SLAM技术部分:解特征点法的视觉里程计,包括特征点的提取与匹配、对极几何约束的计算、PnP和ICP等方法。学习直接法的视觉里程计,包括光流和直接法的原理,并使用g2o实现一个简单的RGB-D直接法。构建一个视觉里程计框架,解决优化和关键帧选择的问题。深入讨论后端优化,包括Bundle Adjustment和位姿图的优化。介绍回环检测和地图构建的方法。最后,介绍当前的开源SLAM项目和未来的发展方向。 另外,对于元数的学习,可以先了解复平面的概念。复平面是一个用来描述复数的平面,其中实部和虚部分别对应平面的横坐标和纵坐标。了解复平面后,可以开始学习元数的概念和应用。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [视觉SLAM十四笔记](https://blog.csdn.net/dada19980122/article/details/111404967)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT0_1"}}] [.reference_item style="max-width: 50%"] - *2* *3* [【视觉SLAM十四】笔记【逐行代码带你解析】【适合纯小白 ps:因为我就是】(持续更新中)](https://blog.csdn.net/R_ichun/article/details/131964588)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT0_1"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值