Kalman滤波器推导与实现(Python版本)

  • 卡尔曼滤波(Kalman Filter)

本文在参考文献的基础上添加了自己的理解,若有不当之处,敬请指正
卡尔曼滤波以前接触过,但是没有仔细推导,这次参考文献仔细推导实现,也是第一次完全的通过vscode+markdown来完成写作。
卡尔曼滤波,也被称为线性二次估计(Liner Quadratic Estimation, LQE),可以作为平滑数据、预测数据、滤波器;本人理解:是一个观察→预测的过程,我接触到的卡尔曼滤波主要有KF(卡尔曼滤波,线性),EKF(扩展卡尔曼滤波,非线性),UKF(无迹卡尔曼滤波,强非线性)。此处,只研究基本的KF。

在这里插入图片描述
     图1 来自老爷子的亲切凝视


  • 参考:

1. 导论

1960年, R.E. Kalman发表了他使用迭代方法解决离散线性滤波问题的文章(《A New Approach to Linear Filtering and Prediction Problems》),揭开了卡尔曼滤波的历史篇章,得益于数字计算能力的发展,卡尔曼滤波方法在得到了广泛应用,尤其是在自主/辅助导航方面。 现在该方法在计算机图像处理,多数据融合方面也得到广泛应用。
1)第一个问题:什么是卡尔曼滤波
  卡尔曼滤波是一种最优化自回归数据处理算法。其中最优化体现在:1)动态使用系统和测量设备的信息;2)该理论包含了系统噪声、测量误差、动态模型的不确定性;3)和感兴趣变量初始状态相关的各种可用信息。其中的自回归表现在:卡尔曼滤波器不需要使用/储存过去所有时刻的数据,使得卡尔曼滤波器计算性能好。
2)第二个问题:为什么用卡尔曼滤波
  卡尔曼滤波最初是为了解决控制问题。通常在做系统分析或者控制系统设计的时候,研究人员都会期望从变量的内在联系来建立一个理论模型,通过对理论模型的研究来研究问题。但是,理论模型并不完美,而且模型的结果也只是近似罢了,而观测到的数据也不是完整和完美的。基于这些存在的问题,所以研究人员探索能够解决这些问题的方法,也就有了卡尔曼滤波。


2. 卡尔曼滤波方法推导

本方法注重思路,详细过程请参考文章开始列出的文献。

2.1 lossfunction

前边提到,卡尔曼是一种线性二次估计,相当于一种优化算法,优化算法的目标就是针对损失函数,并使得损失函数最大或者最小。首先考虑一个最简单的线性模型:

  • yk=akxk+nky_k = a_kx_k + n_k
    其中xkx_k是系统的状态量,但是这个状态量一般我们建模不准确,会存在一个误差,量化这个误差就使用如下形式:
  • ek=xkxk^e_k = x_k - \hat{x_k}   --这个误差越小,建模状态量越准确,得到的输出值也就越准确
    但是eke_k有正有负不好优化,所以引入均方差(mean squared error, MSE), 可以只向一个方向优化:
  • lossfunciton=E(ek2)lossfunciton = E(e_k^2)

2.2. 极大似然估计

原理:从已知结果出发,反推参数值,这个参数值将使得结果出现的概率最大。
抽象一下问题:已知观测结果y,需要反推状态参数xk^\hat{x_k}。所以最大似然估计的目标就是使得y的条件概率最大:

  • max(P[yx^])max(P[y|\hat x])
    假设的随机噪声是符合标准偏差为$\sigma _k $的高斯分布(正态分布),那么条件概率可以写为:

  • P(ykx^k)=Kkexp((ykakx^k)22σk2)P(y_k|\hat x_k) = K_kexp-(\frac{(y_k -a_k\hat x_k)^2}{2\sigma _k^2})
    那么极大似然概率就是取得乘积:

  • P(ykx^k)=ΠkKkexp((ykakx^k)22σk2)P(y_k|\hat x_k) =\mathop{\Pi} \limits_{k} K_kexp-(\frac{(y_k -a_k\hat x_k)^2}{2\sigma _k^2})
    有exp, 可以通过取对数,将乘积转换为加和运算:

  • logP(ykx^k)=12k((ykakx^k)22σk2)+constantlogP(y_k|\hat x_k) = -\frac{1}{2}\sum \limits_{k} (\frac{(y_k -a_k\hat x_k)^2}{2\sigma _k^2}) + constant

    所以目标就是,找到x^k\hat x_k使得logP(ykx^k)logP(y_k|\hat x_k)最大,那么x^k\hat x_k就是最有可能的状态值。可以看到logP(ykx^k)logP(y_k|\hat x_k)的表达式其实就是2.1中描述的均方差的形式。我们可以通过最小化均方误差来求x^k\hat x_k

2.3 状态方程推导

总体思路还是比较简单:由状态构建一个误差表达式,并将该表达式构建为MSE形式,根据已知条件推导,将MES改写称为一个包含已知量的表达式,求导,令导数等于零求得极小点,将该点回代至表达式,结束!整个推导过程的步骤也比较简单,只是涉及的量比较多,以及矩阵表达的形式,看起来比较繁杂而已。


已知条件,6个
一般情况下,我们将一个系统描述为状态方程和观测方程:

  • 状态方程: xk+1=Φxk+wkx_{k+1} = \Phi x_k + w_k
  • 观测方程: zk=Hxk+vkz_k = Hx_k + v_k

wkw_k为已知协方差的过程白噪声,vkv_k为已知协方差的测量白噪声,且二者不相关。
所以,以下两个值已知:

  • Q=E(wkwkT)Q = E(w_kw_k^T)
  • R=E(vkvkT)R = E(v_kv_k^T)
  • ek=xkx^ke_k = x_k - \hat x_k
  • x^k=x^k+Kk(zkHx^k)\hat x_k =\hat x_k^{'} + K_k(z_k - H\hat x_k^{'}) x^k\ldots \hat x_k^{'}表示直接估计的值,该式将在下一节的极大似然估计中推导

求解问题
优化的目标根据2.1就是:

lossfunciton=E(ek2)=E(ekekT)=Pklossfunciton = E(e_k^2) = E(e_ke_k^T) = P_k 一切都是从这个方程出发


推导过程

先处理已知条件:

  • zkz_k 代入表达式:x^k=x^k+Kk(zkHx^k)=x^k+Kk(Hxk+vkHx^k)=KkHxk+(IKkH)xk+Kkvk\hat x_k =\hat x_k^{'} + K_k(z_k - H\hat x_k) = \hat x_k^{'} + K_k(Hx_k + v_k - H\hat x_k^{'}) =K_kHx_k + (I - K_kH)x_k^{'} +K_kv_k
  • x^k\hat x_k 代入表达式:ek=xkx^k=xk(KkHxk+(IKkH)xk+Kkvk)=(IKkH)(xkxk)Kkvke_k = x_k - \hat x_k =x_k -(K_kHx_k + (I - K_kH)x_k^{'} +K_kv_k) = (I - K_kH)(x_k - x_k^{'}) - K_kv_k
  • eke_k 代入
    Pk=E(ekekT)=E[[(IKkH)(xkxk)Kkvk][(IKkH)(xkxk)Kkvk]T]=(IKkH)E[(xkxk)(xkxk)T](IKkH)TKkE[vkvkT]KkT \begin{array}{l} P_k = E(e_ke_k^T) \\ = E[[(I - K_kH)(x_k - x_k^{'}) - K_kv_k][(I - K_kH)(x_k - x_k^{'}) - K_kv_k]^T] \\ =(I - K_kH)E[(x_k - x_k^{'})(x_k - x_k^{'})^T](I - K_kH)^T - K_kE[v_kv_k^T]K_k^T \end{array}
    根据ek,Pke_k, P_k的定义,可以将上式中与期望有关的项改写成新的形式,可以得到:
  • Pk=(IKkH)Pk(IKkH)TKkRKkTP_k =(I - K_kH)P_k^{'}(I - K_kH)^T - K_kRK_k^T
    PkP_k^{'}称为先验估计,这里就得到协方差关系式,由于直接处理矩阵不好处理,所以,通过展开,再求迹(tr)然后求导,令导数等于零,求得极小值点,再回代入原方程。
    展开:
  • Pk=(IKkH)Pk(IKkH)TKkRKkT=PkKkHPkPkHTKkT+Kk(HPkHT+R)KkT\begin{array}{l} P_k =(I - K_kH)P_k^{'}(I - K_kH)^T - K_kRK_k^T \\ =P_k^{'}-K_kHP_k^{'}-P_k^{'}H^TK_k^T+K_k(HP_k^{'}H^T+R)K_k^{T} \end{array}
    求迹:
  • T[Pk]=T[Pk]2T[KkHPk]+T[Kk(HPKHT+R)KkT]T[P_k] = T[P_k^{'}] - 2T[K_kHP_k^{'}] + T[K_k(HP_K^{'}H^T+R)K_k^{T}]
    求导:
  • dT[Pk]dKk=2(HPk)T+2Kk(HPkHT+R)=0\frac{dT[P_k]}{dK_k} = - 2(HP_k^{'})^T + 2K_k(HP_k^{'}H^T+R) = 0
    2Kk(HPKHT+R)=2HPk\Rightarrow 2K_k(HP_K^{'}H^T+R) = 2HP_k^{'}
    Kk=(HPk)T(HPkHT+R)1\Rightarrow K_k = (HP_k^{'})^T(HP_k^{'}H^T+R)^{-1} {方程.1,卡尔曼增益方程}
  • KkK_k回代到PkP_k展开的关系式中,替换掉最后一项中的第一个KkK_k
    Pk=PkKkHPk2(HPk)TKkT\Rightarrow P_k = P_k^{'}-K_kHP_k^{'}-2(HP_k^{'})^TK_k^{T}
    Pk=(IKkH)Pk\Rightarrow P_k = (I -K_kH )P_k^{'} {方程.2,协方差更新方程}

推到这里,我们已经得到卡尔曼滤波方程中的两个关系式,但是PkP_k^{'}还是未知的
\because 直接的估计值没有办法估计噪声,用上个状态的最优值根据模型来直接估计:
\therefore xk+1=Φx^kx_{k+1}^{'} =\Phi \hat x_k {方程3.状态估计方程}
Pk+1=E[ek+1ek+1T]\because P_{k+1}^{'} = E[e_{k+1}^{'}e_{k+1}^{T'}]
又,
ek+1=xk+1xk+1=Φxk+wkΦx^k=Φek+wk\because e_{k+1}^{'} = x_{k+1} - x_{k+1}^{'} = \Phi x_k + w_k - \Phi \hat x_k = \Phi e_k + w_k
Pk+1=E[(Φek+wk)(Φek+wk)T]=ΦE[ekekT]ΦT+E(wkwkT)=ΦPkΦT+Q\begin{array}{l} \therefore P_{k+1}^{'} = E[(\Phi e_k + w_k )(\Phi e_k + w_k )^T] \\ = \Phi E[e_ke_k^T]\Phi ^T + E(w_kw_k^T) \\ = \Phi P_k \Phi ^T + Q \end{array}
Pk+1=ΦPkΦT+Q\Rightarrow P_{k+1}^{'} = \Phi P_k \Phi ^T + Q {方程4.协方差估方程}
PkP_k^{'}采用该关系式,由上一步数据计算得到。


至此,推导出了卡尔曼滤波方程当中的四个,还缺少一个最优估计方程,也就是已知条件的最后一个。

2.4 极大似然估计的推导

为什么有这部分?已知条件的最后一个是直接给出的,这里解释关系式是怎么来的。
从2.2知道,极大似然估计就是在观测值
卡尔曼滤波寻求最小化均方差的目标和卡方分布(chi-square)有很多相似之处,卡方分布的观察频数与期望频数越接近,两者之间的差异越小,也就是观测值会更加接近真实值。这里直接推导chi-square,最后从结论得到最优估计。
抛开前边推导的一切,重新开始,假设一个卡方分布:
χ2=i=1k((zkh(a,x)2σi2)\chi^2 = \sum \limits_{i=1}^{k} (\frac{(z_k -h(a,x)^2}{\sigma _i^2})
zizkh(a,x)xh(),σi(vk)z_i 测量值(相当于z_k),h(a, x) 表示参数为x的模型h(相当于观测方程模型),在\sigma _i是和测量值有关的误差(相当于v_k)
通过改写形式:
χ2=i=1k(1σiσi(zkh(ai,x)2)\chi^2 = \sum \limits_{i=1}^{k} (\frac{1}{\sigma _i\sigma _i}(z_k -h(a_i,x)^2)
将标量形式转化为向量形式:(注此处的RR与前边的RR没有直接联系,但是含义相似)
χk2=[zkh(a,xk)]R1[zkh(ai,x]T\chi_k^2 = [z_k -h(a,x_k)]R^{-1}[z_k -h(a_i,x]^T
上式只使用了k时刻的测量值,为了能够前边所有的测量值,需要:
χk12=kκ(zkh(a,x)R1(zkh(a,x)Tχmin2+(xk1x^k1)R1(xk1x^k1)T\begin{array}{cc} \chi_{k-1}^2 = \sum \limits_{k}^{\kappa} (z_k -h(a,x)R^{-1}(z_k -h(a,x)^T \\ \approx \chi_{min}^2 + (x_{k-1} -\hat x_{k-1})R^{-1}(x_{k-1} -\hat x_{k-1})^T \end{array}
考虑前边的所有数据:
χ2=χk2+χk12=[zkh(a,xk)]R1[zkh(ai,x)]T+χmin2+(xk1x^k1)R1(xk1x^k1)T\begin{array}{cc} \chi^2 = \chi_k^2 + \chi_{k-1}^2 =[z_k -h(a,x_k)]R^{-1}[z_k -h(a_i,x)]^T + \chi_{min}^2 + (x_{k-1} -\hat x_{k-1})R^{-1}(x_{k-1} -\hat x_{k-1})^T \end{array}
考虑
h(a,xk)=h(a,(x^k+Δx))wherexk=x^k+Δxh(a,x_k) = h(a,(\hat x_k + \Delta x)) where x_k = \hat x_k + \Delta x
使用泰勒展开:
h(a,(x^k+Δx))=h(x^)+Δxxh(x^)\Rightarrow h(a,(\hat x_k + \Delta x)) = h(\hat x) + \Delta x \nabla _x h(\hat x)
对卡方求导,并将hΔxkh和\Delta x_k的表达式是代入:
d(χ2)dx=2Pk11(xk1x^k1)2xh(a,xk)TR1[zkh(a,xk)]=2Pk11(xk1x^k1)2xh(a,xk)TR1[zkh(a,x^)+(xkx^k)xh(a,x^k)]\begin{array}{cc} \frac {d(\chi ^2)}{dx} = 2P_{k-1}^{'-1}(x_{k-1}-\hat x_{k-1}) - 2\nabla _xh(a,x_k)^TR^{-1}[z_k-h(a,x_k) ] \\ =2P_{k-1}^{'-1}(x_{k-1}-\hat x_{k-1}) - 2\nabla xh(a,x_k)^TR^{-1}[z_k-h(a,\hat x) + (x_k- \hat x_k) \nabla _x h(a,\hat x_k) ] \end{array}
假设估计模型的参数和实际模型的参数十分接近,那么他们的梯度相等:
xh(a,xk)=xh(a,x^k)=H\nabla _x h(a,x_k) = \nabla _x h(a,\hat x_k) = H
代入求导后的等式,并令该导数等于零,求得极值点:
d(χ2)dx=0=2Pk11(xk1x^k1)2HTR1[zkh(a,x^)+(xkx^k)H]=2(Pk11HTR1H)(xkx^k)2HTR1[zkh(a,x^)]\begin{array}{cc} \frac {d(\chi ^2)}{dx} =0 = 2P_{k-1}^{'-1}(x_{k-1}-\hat x_{k-1}) - 2H^TR^{-1}[z_k-h(a,\hat x) + (x_k- \hat x_k) H ] \\ =2(P_{k-1}^{'-1} - H^TR^{-1}H)(x_k- \hat x_k) - 2H^TR^{-1}[z_k-h(a,\hat x)] \end{array}
求解方程:
(xkx^k)=x=(Pk11HTR1H)(xkx^k)1HTR1[zkh(a,x^)](x_k- \hat x_k) = \nabla x =(P_{k-1}^{'-1} - H^TR^{-1}H)(x_k- \hat x_k)^{-1}H^TR^{-1}[z_k-h(a,\hat x)]
令:Kk=(Pk11HTR1H)HTR1 K_k =(P_{k-1}^{'-1} - H^TR^{-1}H)H^TR^{-1}
所以:
xk=x^k+x=x^k+Kk[zkh(a,x^)]x_k = \hat x_k + \nabla x = \hat x_k + K_k[z_k-h(a,\hat x)]
已知,我们推出了一个线性问题可以表达为y=ax+by = ax + b,那么我们遇到一个问题,判断为线性问题,就可以直接假设其方程为y=ax+by = ax + b,再求解其参数。同样的道理,我们已经推导出卡方问题的形式可以表示为xk=x^k+Kk[zkh(a,x^)]x_k = \hat x_k + K_k[z_k-h(a,\hat x)],那么对于卡方问题的卡尔曼滤波形式,我们可以直接假设:
x^k=x^k+Kk(zkHx^k)\hat x_k =\hat x_k^{'} + K_k(z_k - H\hat x_k^{'})
再通过已知的其他条件来求解对应参数。
注意:这里的KkK_k只是使用了卡方的形式,并不是卡方推导公式里的KkK_k值,这里KkK_k的值由2.3中的推导求出的。

3.卡尔曼滤波的基本计算过程

卡尔曼滤波的核心点就是状态、协方差,一共分为5步,反复迭代进行。
1. 由上一步最优状态估计当前状态;
2. 由上一步最优协方差估计当前协方差;
3. 计算卡尔曼增益;
4. 由状态测量值和状态估计值,预测当前状态最优值;
5. 由卡尔曼增益、估计协方差计算当前协方差最优值;

五步方程:

  • (1) X^kk1=Fk1X^k1+Gk1uk1\hat{X}_{k|k-1} = F_{k-1}\hat{X}_{k-1} + G_{k-1}u_{k-1}
  • (2) Pkk1=Fk1Pk1Fk1T+Gk1uk1P_{k|k-1} = F_{k-1}P_{k-1}F_{k-1}^T + G_{k-1}u_{k-1}
  • (3) Kk=Pkk1HkT(HkPkk1HkT+Rk)1K_k = P_{k|k-1}H_{k}^T(H_{k}P_{k|k-1}H_{k}^T+R_{k})^{-1}
  • (4) X^k=X^xk1+Kk(zkHkX^xk1)\hat{X}_{k} = \hat{X}_{x|k-1} + K_{k}(z_{k}-H_{k}\hat{X}_{x|k-1})
  • (5) Pk=(IKkHk)Pkk1P_{k} = (I-K_kH_k) P_{k|k-1}
初状态
估计
预测
更新

4.卡尔曼滤波方法的实例

为了更好地理解卡尔曼滤波,下面引入一个简单的实例来进行说明,牛顿力学模型:

  • 假设:小球做自由落体运动,忽略阻力,
  • 目标:滤波器KF由初始位置的不确定性来估计小球的位置
  • 符号说明:
    符号 hh gg tt Δt\Delta t kk k1k-1
    说明 高度 重力加速度 时刻 微小时间长度 当前计数 上一个计数

根据基本的牛顿力学定律,经过Δt\Delta t时间后:
* 速度关系:h˙(t)=h˙(tΔt)gΔt\dot h(t) = \dot h(t - \Delta t)-g\Delta t
* 距离关系:h(t)=h(tΔt)+h˙(tΔt)Δt12g(Δt)2h(t)=h(t-\Delta t)+\dot h(t-\Delta t)\Delta t-\frac{1}{2}g(\Delta t)^2

以上是该问题的连续性表述,将该问题离散化(这个离散化的思路有点意思),假设t=kΔtt=k\Delta t,将上述公式重写为:
* 速度关系:h˙(kΔt)=h˙(kΔtΔt)gΔt\dot h(k\Delta t) = \dot h(k\Delta t - \Delta t)-g\Delta t
* 距离关系:h(kΔt)=h(kΔtΔt)+h˙(kΔtΔt)Δt12g(Δt)2h(k\Delta t)=h(k\Delta t-\Delta t)+\dot h(k\Delta t-\Delta t)\Delta t-\frac{1}{2}g(\Delta t)^2

变量形式的替换不一一说明,按照位置对应即可。
更改一下表述形式:
* 速度关系:h˙k=h˙k1gΔt\dot h_k = \dot h_{k-1}-g\Delta t
* 距离关系:hk=hk1+h˙k1Δt12g(Δt)2h_k=h_{k-1}+\dot h_{k-1}\Delta t-\frac{1}{2}g(\Delta t)^2

转换成矩阵表示:
[hkh˙k]=[1Δt01][hkh˙k]+[12(Δt)2Δt]g\left[\begin{matrix}h_k \\\dot h_k \\\end{matrix} \right]= \left[\begin{matrix}1 & \Delta t \\0 &1 \\\end{matrix} \right]\left[\begin{matrix}h_k \\\dot h_k \\\end{matrix} \right] + \left[\begin{matrix} -\frac{1}{2}(\Delta t)^2 \\ -\Delta t \\\end{matrix} \right]g

将对应位置的变量换一种表达形式:
Xk=Fk1Xk1+Gk1uk1X_k = F_{k-1}X_{k_1} + G_{k-1}u_{k-1}(状态方程)

此外,还需建立一个观测方程: 观测值为输出状态值加上观测噪声
yk=[10]hk+vky_k = \left[\begin{matrix}1 & 0 \\\end{matrix}\right] h_k + v_k
写成矩阵的形式:
yk=HkXk+vky_k = H_kX_k + v_k

这个时候,回到卡尔曼滤波问题,思考一下想要使用卡尔曼滤波需要哪些变量?


5.卡尔曼滤波方法的Python实现

图1是估计值与真实值之间的差值变化情况,随着时间推移,估计值越来越接近真实值,最终在真实值附近波动。图2是测量值、真实值、估计值的一个情况对比。Python代码部分将4中的实例执行了一次。
在这里插入图片描述在这里插入图片描述

#-*- coding: utf-8 -*-
import numpy as np
import matplotlib.pyplot as plt

#定义步长和迭代次数
iterations = 1000
delta_t = 0.001
#生成时间序列
time = np.linspace(0,1,iterations)
time = np.mat(time)

#定义g值
g = 9.80665 
# 系统真值
z = [100-0.5*g*(delta_t*i)**2 for i in range(iterations)]
z_watch = np.mat(z)
# 创建一个方差为1的高斯噪声,精确到小数点后两位
noise = np.round(np.random.normal(0, 1, iterations), 2)
noise_mat = np.mat(noise) 
# 将z的观测值和噪声相加
z_mat = z_watch + noise_mat
# 定义最优估计的输出
y = []

# 定义x的初始状态
x_mat = np.mat([[105,], [0,]])
# 定义初始状态协方差矩阵
p_mat = np.mat([[10, 0], [0, 0.01]])
# 定义状态转移矩阵,因为每秒钟采样1000次,所以delta_t = 0.001
f_mat = np.mat([[1, delta_t], [0, 1]])
# 定义输入矩阵
g_mat = np.mat([[-0.5*delta_t**2],[-delta_t]])
# 定义状态转移协方差矩阵,这里我们把协方差设置的很小,因为觉得状态转移矩阵准确度高
q_mat = np.mat([[0.0, 0], [0, 0.0]])
# 定义观测矩阵
h_mat = np.mat([1, 0])
# 定义观测噪声协方差
r_mat = np.mat([4])

#卡尔曼滤波器的5个公式
for i in range(iterations):
    x_predict = f_mat * x_mat + g_mat * g
    p_predict = f_mat * p_mat * f_mat.T + q_mat
    kalman = p_predict * h_mat.T / (h_mat * p_predict * h_mat.T + r_mat)
    x_mat = x_predict + kalman *(z_mat[0, i] - h_mat * x_predict)
    p_mat = (np.eye(2) - kalman * h_mat) * p_predict
    
    #将每步计算结果添加到序列中
    y.append(x_mat[0].tolist()[0][0])

#数据格式转化
y = np.mat(y)
error = y - z_watch
y = y.A
error = error.A
time = time.A
z_mat = z_mat.A
z_watch = z_watch.A

#绘图
plt.plot(time[0,:], z_mat[0,:],  label = 'Measured')  
plt.plot(time[0,:], z_watch[0,:],'g', label='True')  
plt.plot(time[0,:], y[0,:], 'r',label = 'Estimated')   
plt.xlabel('h(m)')
plt.ylabel('time(s)')
plt.legend(loc='lower right')

plt.figure(2)
plt.plot(time[0,:], error[0,:], label ='Errors')
plt.ylabel('error(m)')
plt.xlabel('time(s)')
plt.legend(loc='lower right')
plt.show()

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章