卡爾曼濾波的簡(jiǎn)單實(shí)現(xiàn)(Matlab & OC)

本文將不解釋卡爾曼濾波具體的數(shù)學(xué)原理,不作為小白晉升高端玩家的臺(tái)階,只說(shuō)卡爾曼濾波在簡(jiǎn)單場(chǎng)景下(處理溫度、加速度計(jì)、RSSI等數(shù)據(jù))怎樣只用三行代碼實(shí)現(xiàn),及相關(guān)參數(shù)的設(shè)置。代碼提供Matlab和Objective-C版本。

最近做的項(xiàng)目用到iPhone收集的藍(lán)牙信號(hào)強(qiáng)度,即RSSI值。發(fā)現(xiàn)果然如文獻(xiàn)中所說(shuō),即使手機(jī)保持靜止,檢測(cè)到的來(lái)自同一藍(lán)牙信標(biāo)的信號(hào)強(qiáng)度也會(huì)不停地上下波動(dòng),且數(shù)據(jù)(濾除粗大誤差后)呈高斯分布。許多文獻(xiàn)中也提到,對(duì)付這樣的噪聲可以用卡爾曼濾波器。同樣地,做自平衡車、四旋翼等用到的陀螺儀、加速度數(shù)據(jù)也常常用到卡爾曼濾波。

圖一:iPhone以10Hz的頻率掃描藍(lán)牙,記錄下的RSSI值,大致呈高斯分布。
圖二:藍(lán)線為RSSI原始數(shù)據(jù),大致圍繞-67上下浮動(dòng);紅線為卡爾曼濾波后平緩變化的數(shù)據(jù)。

百度百科對(duì)卡爾曼濾波的簡(jiǎn)介:

卡爾曼濾波(Kalman filtering)一種利用線性系統(tǒng)狀態(tài)方程,通過(guò)系統(tǒng)輸入輸出觀測(cè)數(shù)據(jù),對(duì)系統(tǒng)狀態(tài)進(jìn)行最優(yōu)估計(jì)的算法。由于觀測(cè)數(shù)據(jù)中包括系統(tǒng)中的噪聲和干擾的影響,所以最優(yōu)估計(jì)也可看作是濾波過(guò)程。

卡爾曼濾波的數(shù)學(xué)原理當(dāng)然是它最美妙的部分,不過(guò)據(jù)說(shuō)挺難看懂的,我也就知難而退了,畢竟在程序中實(shí)現(xiàn)好像不需要非常理解其數(shù)學(xué)原理。有兩篇英文博客寫得不錯(cuò),比較生動(dòng)形象地描述了這個(gè)濾波器的工作過(guò)程,建議閱讀:

一、最后一點(diǎn)點(diǎn)數(shù)學(xué)

為了不讓實(shí)現(xiàn)濾波器的代碼顯得突兀,這里還是留下幾個(gè)最最不可或缺的方程。實(shí)在心急,可以直接跳過(guò)這一部分。
(要找OC代碼的朋友請(qǐng)不要一并跳過(guò)第二部分,那里講到了參數(shù)設(shè)置)

  • 被觀測(cè)的系統(tǒng)

首先我們需要用方程來(lái)描述被觀測(cè)的系統(tǒng),因?yàn)楹竺鏋V波器的方程要用到這里的一些參數(shù)??柭鼮V波器適用于線性系統(tǒng),設(shè)該系統(tǒng)的狀態(tài)方程和觀測(cè)方程為:

x(k) = A · x(k-1) + B · u(k) + w(k)
z(k) = H · x(k) + y(k)

x(k) —— k時(shí)刻系統(tǒng)的狀態(tài)
u(k) —— 控制量
w(k) —— 符合高斯分布的過(guò)程噪聲,其協(xié)方差在下文中為Q
z(k) —— k時(shí)刻系統(tǒng)的觀測(cè)值
y(k) —— 符合高斯分布的測(cè)量噪聲,其協(xié)方差在下文中為R
A、B、H —— 系統(tǒng)參數(shù),多輸入多輸出時(shí)為矩陣,單輸入單輸出時(shí)就是幾個(gè)常數(shù)

注意在后面濾波器的方程中我們將不會(huì)再直接面對(duì)兩個(gè)噪聲w(k)和y(k),而是用到他們的協(xié)方差Q和R。至此,A、B、H、Q、R這幾個(gè)參數(shù)都由被觀測(cè)的系統(tǒng)本身測(cè)量過(guò)程中的噪聲確定了。

  • 濾波器

工程中用到的卡爾曼濾波是一個(gè)迭代的過(guò)程,每得到一個(gè)新的觀測(cè)值迭代一次,來(lái)回來(lái)去地更新兩個(gè)東西:“系統(tǒng)狀態(tài)”(x)“誤差協(xié)方差”(P)。由于每次迭代只用到上一次迭代的結(jié)果和新的測(cè)量值,這樣濾波對(duì)計(jì)算資源的占用是很小的。

每一次迭代,兩個(gè)步驟:預(yù)測(cè)修正。預(yù)測(cè)是根據(jù)前一時(shí)刻迭代的結(jié)果,即x(k-1|k-1)和P(k-1|k-1),來(lái)預(yù)測(cè)這一時(shí)刻的系統(tǒng)狀態(tài)和誤差協(xié)方差,得到x(k|k-1)和P(k|k-1):

x(k|k-1) = A · x(k-1|k-1) + B · u(k)
P(k|k-1) = A · P(k-1|k-1) · AT + Q

(這里用到的A、B、H、Q、R就是從前面的狀態(tài)/觀測(cè)方程中拿來(lái)的)
然后計(jì)算卡爾曼增益K(k),和這一次的實(shí)際測(cè)量結(jié)果z(k)一起,用于修正系統(tǒng)狀態(tài)x(k|k-1)及誤差協(xié)方差P(k|k-1),得到最新的x(k|k)和P(k|k):

K(k) = P(k|k-1) · HT · (H · P(k|k-1) · HT + R)-1
x(k|k) = x(k|k-1) + K(k) · (z(k) - H · x(k|k-1))
P(k|k) = (I - K(k) · H) · P(k|k-1)

好了好了不會(huì)有新的公式了。至此這次迭代就算結(jié)束了,x(k|k)就是我們要的濾波后的值,它和P(k|k)將會(huì)作為x(k-1|k-1)和P(k-1|k-1)用在下一時(shí)刻的迭代里。別看現(xiàn)在公式還是一大堆,在我們所謂的“簡(jiǎn)單場(chǎng)景”下,系統(tǒng)參數(shù)一定下來(lái),就能簡(jiǎn)化很多很多。

先看狀態(tài)方程和觀測(cè)方程。假設(shè)我們是在測(cè)溫度、加速度或者記錄藍(lán)牙RSSI,此時(shí)控制量是沒(méi)有的,即:

B · u(k) ≡ 0

另外,參數(shù)A和H也簡(jiǎn)單地取1?,F(xiàn)在濾波器的預(yù)測(cè)方程簡(jiǎn)化為:

① x(k|k-1) = x(k-1|k-1)
② P(k|k-1) = P(k-1|k-1) + Q

同時(shí)修正方程變成:

③ K(k) = P(k|k-1) / (P(k|k-1) + R)
④ x(k|k) = x(k|k-1) + K(k) · (z(k) - x(k|k-1))
⑤ P(k|k) = (1 - K(k)) · P(k|k-1)

①對(duì)②、③無(wú)影響,于是④可以寫成:

x(k|k) = x(k-1|k-1) + K(k) · (z(k) - x(k-1|k-1))

在程序中,該式寫起來(lái)更簡(jiǎn)單:

x = x + K * (新觀測(cè)值 - x);

觀察②,對(duì)這一時(shí)刻的預(yù)測(cè)值不就是上一時(shí)刻的修正值+Q嘛,不妨把它合并到上一次迭代中,即⑤改寫成:

P(k+1|k) = (1 - K(k)) · P(k|k-1) + Q

這一時(shí)刻的P(k+1|k),會(huì)作為下一時(shí)刻的P(k|k-1),剛好是③需要的。于是整個(gè)濾波過(guò)程只用這三個(gè)式子來(lái)迭代即可:

K(k) = P(k|k-1) / (P(k|k-1) + R)
x(k|k) = x(k-1|k-1) + K(k) · (z(k) - x(k-1|k-1))
P(k+1|k) = (1 - K(k)) · P(k|k-1) + Q

二、Matlab實(shí)現(xiàn) & 參數(shù)設(shè)置

經(jīng)過(guò)前面的推導(dǎo),我們發(fā)現(xiàn)在程序中只需要不斷重復(fù)這一小段即可實(shí)現(xiàn)卡爾曼濾波:

while(新觀測(cè)值)
{
    K = P / (P + R);
    x = x + K * (新觀測(cè)值 - x);
    P = (1 - K) · P + Q;
}

沒(méi)看前面推導(dǎo)的朋友們別擔(dān)心?,F(xiàn)在我們只從程序的角度來(lái)看,不管物理意義。只要定好參數(shù)、賦好初值,它就能好好迭代下去。我們發(fā)現(xiàn)這一堆字母中,x和P只需要賦初值,每次迭代會(huì)產(chǎn)生新值;K用不著賦初值;Q和R賦值以后在之后的迭代中也可以改。

好消息是,x和P的初值是可以隨便設(shè)的,強(qiáng)大的卡爾曼濾波器馬上就能抹除不合理之處。但需注意,P的初值不能為0,否則濾波器會(huì)認(rèn)為已經(jīng)沒(méi)有誤差了,不玩兒了,看下圖——

只要P(0) ≠ 0,一切都好。

剩下的也就是Q和R了。他倆的物理意義是噪聲的協(xié)方差,他們的值是需要我們?cè)嚦鰜?lái)的。舉個(gè)例子,我們?nèi)杂蒙蠄D的數(shù)據(jù)——

減小R值以后,濾波效果就沒(méi)有這么明顯了。但這并不意味著R值越大越好,因?yàn)槲覀兊臄?shù)據(jù)也可能是這樣:

右圖中增大了R值以后,雖然濾波結(jié)果更平滑,但濾波器會(huì)變得不敏感,會(huì)有滯后。因此應(yīng)該根據(jù)具體的使用場(chǎng)景收集到的數(shù)據(jù)來(lái)決定Q和R的取值。他倆的取值當(dāng)然也可以是時(shí)變的,可以識(shí)別跳變,可以自適應(yīng)……這些就是高端玩法了,這里不做討論。

至此,所有準(zhǔn)備工作就做完了。Matlab代碼也很簡(jiǎn)單:

% kalman_filter.m

function X = kalman_filter(data,Q,R,x0,P0)
N = length(data);

K = zeros(N,1);
X = zeros(N,1);
P = zeros(N,1);

X(1) = x0;
P(1) = P0;

for i = 2:N
    K(i) = P(i-1) / (P(i-1) + R);
    X(i) = X(i-1) + K(i) * (data(i) - X(i-1));
    P(i) = P(i-1) - K(i) * P(i-1) + Q;
end

調(diào)用及作圖:

% test.m

clear;

data = [-59,-60,-55,-56,-56,-56,-57,-57,-59,-61,-57,-57,-57,-57,-57,-58,-57,-61,-57,-61,-59,-61,-60,-60,-59,-57,-56,-58,-58,-57,-57,-57,-57,-57,-60,-58,-61,-60,-60,-60,-57,-57,-57,-57,-58,-57,-58,-57,-58,-57,-58,-61,-58,-61,-62,-61,-61,-57,-57,-57,-57,-58,-57,-58,-57,-58,-61,-61,-60,-60,-58,-57,-57,-57,-57,-58,-58,-58,-58,-60,-61,-60,-57,-57,-57,-57,-57,-58,-58,-57,-57,-57,-60,-57,-60,-60,-59,-60,-57,-56]';
result = kalman_filter(data,1e-6,4e-4,-60,1);
i = 1:length(data);
plot(i,result,'r',i,data,'b');

做平面定位的時(shí)候可能需要濾x軸和y軸的數(shù)據(jù),加速度計(jì)還可能有z軸數(shù)據(jù)。Matlab里面可以很方便地把A、B、H、Q、R變成矩陣,一次性把幾個(gè)數(shù)據(jù)同時(shí)濾了;但實(shí)質(zhì)上當(dāng)這些數(shù)據(jù)互相之間沒(méi)什么影響時(shí),同時(shí)濾和分開(kāi)濾是一毛一樣的,所以上面的代碼都可以不改,幾個(gè)軸的數(shù)據(jù)分別調(diào)用濾波函數(shù)就是了。

三、Objective-C實(shí)現(xiàn)

雖然代碼不多,但還是單開(kāi)一個(gè)類好了。代碼借鑒了MGKalman庫(kù)。

//  KalmanFilter.h

#import <Foundation/Foundation.h>

@interface KalmanFilter : NSObject

/*-----------------------------------------------*\
|  Kalman Filter equations                        |
|                                                 |
|  state equation                                 |
|  x(k) = A·x(k-1) + B·u(k) + w(k-1)              |
|                                                 |
|  observations equation                          |
|  z(k) = H·x(k) + y(k)                           |
|                                                 |
|  prediction equations                           |
|  x(k|k-1) = A·x(k-1|k-1) + B·u(k)               |
|  P(k|k-1) = A·P(k-1|k-1)·A^T + Q                |
|                                                 |
|  correction equations                           |
|  K(k) = P(k|k-1)·H^T·(H·P(k|k-1)·H^T + R)^(-1)  |
|  x(k|k) = x(k|k-1) + K(k)·(z(k) - H·x(k|k-1))   |
|  P(k|k) = (I - K(k)·H)·P(k|k-1)                 |
\*-----------------------------------------------*/

@property (assign, nonatomic) UInt64 timestamp;
@property (assign, nonatomic) float Q;              // Process noise covariance
@property (assign, nonatomic) float R;              // Observation noise covariance
@property (assign, nonatomic) float K;              // Kalman gain
@property (assign, nonatomic) float X;              // State
@property (assign, nonatomic) float P;              // Covariance

- (KalmanFilter *)initWithQ:(float)q R:(float)r X0:(float)x0 P0:(float)p0;
- (float)filterWithObservation:(float)observation;

@end
//  KalmanFilter.m

#import "KalmanFilter.h"

@implementation KalmanFilter

- (KalmanFilter *)initWithQ:(float)q R:(float)r X0:(float)x0 P0:(float)p0 {
    if (self = [super init]) {
        _timestamp = [[NSDate date] timeIntervalSince1970] * 1000;
        _Q = q;
        _R = r;
        _K = 0;
        _X = x0;
        _P = p0;
    }
    
    return self;
}

- (float)filterWithObservation:(float)observation {
    /*-----------------------------------------------*\
    |  Simplified Kalman Filter equations             |
    |                                                 |
    |  state equation                                 |
    |  x(k) = x(k-1) + w(k)                           |
    |                                                 |
    |  observations equation                          |
    |  z(k) = x(k) + y(k)                             |
    |                                                 |
    |  prediction equations                           |
    |  x(k|k-1) = x(k-1|k-1)                          |
    |  P(k|k-1) = P(k-1|k-1) + Q                      |
    |                                                 |
    |  correction equations                           |
    |  K(k) = P(k|k-1)·(P(k|k-1) + R)^(-1)            |
    |  x(k|k) = x(k|k-1) + K(k)·(z(k) - x(k|k-1))     |
    |  P(k|k) = (I - K(k))·P(k|k-1)                   |
    |                                                 |
    |  Iteration equations                            |
    |  K(k) = P(k|k-1)/(P(k|k-1) + R)                 |
    |  x(k|k) = x(k-1|k-1) + K(k)·(z(k) - x(k-1|k-1)) |
    |  P(k+1|k) = (1 - K(k))·P(k|k-1) + Q             |
    \*-----------------------------------------------*/
    
    _timestamp = [[NSDate date] timeIntervalSince1970] * 1000;
    _K = _P / (_P + _R);
    _X = _X + _K * (observation - _X);
    _P = (1 - _K) * _P + _Q;
    
    return _X;
}

@end

初始化及調(diào)用:

……
#define val_Q       0.00002
#define val_R        0.0004
#define val_X0            0
#define val_P0            1
……
@property (strong, nonatomic) KalmanFilter *kalmanFilter;
……
// 初始化
_kalmanFilter = [[KalmanFilter alloc] initWithQ:val_Q
                                              R:val_R
                                             X0:val_X0
                                             P0:val_P0];
……
// 完成一次測(cè)量時(shí)調(diào)用
float newX = [_kalmanFilter filterWithObservation:(float)新觀測(cè)值];
……

這樣每一次得到的newX就是濾波后的數(shù)據(jù)。如果同時(shí)要濾幾個(gè)軸的數(shù)據(jù),也可以放在矩陣?yán)镞\(yùn)算。但同樣地,如果幾個(gè)軸之間的數(shù)據(jù)互不影響的話,一起濾和分開(kāi)濾是一樣的。

前面提到的MGKalman是一個(gè)OC實(shí)現(xiàn)的矩陣形式的卡爾曼濾波器,其用法在GitHub上有說(shuō)明。本文的數(shù)學(xué)方程也是從那里摘抄以后修改而來(lái)的。

作者給的示例代碼中同時(shí)濾了兩個(gè)相互獨(dú)立的量;經(jīng)過(guò)我的測(cè)試,把他倆拆開(kāi),不用矩陣,分別用兩個(gè)濾波器來(lái)處理,效果確實(shí)一毛一樣。這也是我為什么我在項(xiàng)目中沒(méi)有用他矩陣的那一套,而是看過(guò)一點(diǎn)原理以后只用那三行代碼的原因。畢竟使用場(chǎng)景簡(jiǎn)單嘛。

卡爾曼濾波(KF)的簡(jiǎn)單使用就介紹到這里了。接下來(lái)還可以去實(shí)時(shí)調(diào)整Q和R,可以加控制量u(k),可以進(jìn)一步實(shí)現(xiàn)擴(kuò)展卡爾曼濾波(EKF)、無(wú)跡卡爾曼濾波(UKF)等各種算法來(lái)應(yīng)對(duì)非線性、非高斯等各種情況。只是對(duì)于我來(lái)說(shuō),現(xiàn)在這樣就暫時(shí)夠用了。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 卡爾曼濾波在我當(dāng)學(xué)生的時(shí)候就用過(guò),但是當(dāng)年我似乎就是套公式,沒(méi)有理解其精髓,加之時(shí)間久了有點(diǎn)模糊,突然需要指導(dǎo)學(xué)生...
    Roger_羅杰閱讀 85,354評(píng)論 41 159
  • 經(jīng)過(guò)看各種博客和文章,讓我最清楚明白的,是xiahouzuoxin 的博客,之后又看了一些國(guó)外的文獻(xiàn)進(jìn)行自己的理解...
    marine0131閱讀 7,784評(píng)論 4 11
  • kalman的學(xué)習(xí)策略 對(duì)于基礎(chǔ)公式,能夠理解就行了,基礎(chǔ)的數(shù)學(xué)概念要清楚不需要自己推導(dǎo),多做一些例子去看這個(gè)濾波...
    scott_yu779閱讀 3,119評(píng)論 0 2
  • 預(yù)估器 我們希望可以最大限度地使用測(cè)量結(jié)果來(lái)估計(jì)移動(dòng)物體的運(yùn)動(dòng)。所以,多個(gè)測(cè)量的累積可以讓我們檢測(cè)出不受噪聲影響的...
    JasonDing閱讀 6,912評(píng)論 4 10
  • 追溯遠(yuǎn)古的歷史,我們會(huì)知道巫師是指擁有了控制自然力量的人,他們通過(guò)咒語(yǔ)或魔藥來(lái)改變他人的命運(yùn)。 我們大概很難從定義...
    Margaret相宜閱讀 1,286評(píng)論 0 0

友情鏈接更多精彩內(nèi)容