卡爾曼濾波原理及實現
版權宣告:本文為博主原創文章,未經博主允許不得轉載。 https://blog.csdn.net/lybaihu/article/details/54943545
卡爾曼濾波原理及實現
前一段時間,做專案研究了一下卡爾曼濾波,並且在專案當中實現了一個物體跟蹤的功能,所以,藉著新鮮勁兒,本次部落格對卡爾曼濾波進行一次整理。
卡爾曼濾波是什麼
卡爾曼濾波能做什麼
卡爾曼濾波的工作原理
舉個栗子
卡爾曼濾波是什麼
卡爾曼濾波適用於估計一個動態系統的最優狀態。即便是觀測到的系統狀態引數含有噪聲,觀測值不準確,卡爾曼濾波也能夠完成對狀態真實值的最優估計。網上大多數的教程講到卡爾曼的數學公式推導,會讓人很頭疼,難以把握其中的主線和思想。所以我參考了國外一位學者的文章,講述卡爾曼濾波的工作原理,然後編寫了一個基於OpenCV的小程式給大家做一下說明。下面的這個視訊請大家先直觀地看看熱鬧吧~
角度跟蹤視訊
卡爾曼濾波能做什麼
假設我們手頭有一輛DIY的移動小車。這輛車的外形是這樣的:
這輛車可以在荒野移動,為了便於對它進行控制,需要知道它的位置以及移動速度。所以,建立一個向量,用來儲存小車的位置和速度
xk−→=(p→,v→)
xk→=(p→,v→)
其實,一個系統的狀態有很多,選擇最關心的狀態來建立這個狀態向量是很重要的。例如,狀態還有水庫裡面水位的高低、鍊鋼廠高爐內的溫度、平板電腦上面指尖觸碰螢幕的位置等等這些需要持續跟蹤的物理量。好了,迴歸到正題,小車上面安裝了GPS感測器,這個感測器的精度是10米。但是如果小車行駛的荒野上面有河流和懸崖的話,10米的範圍就太大,很容易掉進去進而無法繼續工作。所以,單純靠GPS的定位是無法滿足需求的。另外,如果有人說小車本身接收操控著傳送的運動指令,根據車輪所轉動過的圈數時能夠知道它走了多遠,但是方向未知,並且在路上小車打滑車輪空轉的現象絕對是不可避免。所以,GPS以及車輪上面電機的碼盤等感測器是間接地為我們提供了小車的資訊,這些資訊包含了很多的和不確定性。如果將所有這些資訊綜合起來,是否能夠通過計算得到我們更想要的準確資訊呢?答案是可以的!
卡爾曼濾波的工作原理
1.先驗狀態估計
以之前我們建立的狀態變數為例,
x→=[pv]
x→=[pv]
下圖表示的是一個狀態空間圖,為了研究方便,假如小車在一條絕對筆直的線路上面行駛,其位置和速度的方向是確定的,不確定的是大小。
卡爾曼濾波器發生作用的前提是小車的速度和位置量在其定義域內具有正態的高斯分佈規律。每一個變數都具有一個平均值μμ(這個值在變數的概率密度函式分佈圖的最中心位置,代表該數值是最可能發生的)和σ2σ2(這個數值代表方差,表示變數的不確定性程度)。下圖中橫縱座標分別是位置和速度,相互獨立,也就是已知其中一個變數,無法推斷出另外一個的結果或者變化規律。
下面請看另外一種情況:下圖的位置和速度的分佈不再像上圖一樣了,速度越大,位置也越大,這種情況下,兩個變數相關(其實這種情況是很有可能發生的,因為速度越大的話,可能小車就遠離我們,速度越小,表明靠近我們)。那麼,通過協方差矩陣ΣΣ就能夠將幾個變數的相關程度描述清楚。矩陣當中的某一個元素ΣijΣij表示的是狀態向量第i個變數和第j個變數之間相關程度,Σij=Cov(xi,xj)=E[(xi−μi)(xj−μj)]Σij=Cov(xi,xj)=E[(xi−μi)(xj−μj)]
要注意的是協方差矩陣是一個對稱陣。感興趣的童鞋可以深入研究一下,協方差矩陣的特徵值和特徵向量所具有的幾何意義:the directions in which the data varies the most.啥意思,就是哪個方向變化快,特徵向量指哪兒。
協方差的特徵向量是什麼
協方差的幾何意義
兩個連結提供給大家,有興趣的好好看看,知識點融會貫通了才有意思!
下面是重頭戲:數學描述部分:
定義:
x^k=[positionvelocity]
x^k=[positionvelocity]
Pk=[ΣppΣvpΣpvΣvv](1)
(1)Pk=[ΣppΣpvΣvpΣvv]
x→kx→k是狀態向量。PkPk稱為協方差。
下圖描述了一個k-1時刻x→k−1x→k−1和k時刻x→kx→k的狀態:我們不清楚到底哪一個狀態是最準確的,但是卡爾曼濾波的狀態更新方程會根據前一時刻的結果預測出一個下一時刻的分佈情況:
定義FkFk,這個矩陣表示的是前後兩個狀態之間是如何轉換的:
pkpk=pk−1pk−1+δt+δtvk−1vk−1
vkvk= vk−1vk−1
寫成矩陣的形式就是:
x^kx^k=[10δt1]=[1δt01]x^k−1x^k−1=FkFkx^k−1x^k−1(2)(2)
狀態向量的更新有了,但是還缺少狀態向量之間相關性的更新,也就是協方差矩陣。
Cov(x)=Σ
Cov(x)=Σ
Cov(Ax)=AΣAT
Cov(Ax)=AΣAT
結合(2)得到:x^kx^k=Fk=Fkx^k−1x^k−1(先驗狀態估計向量)
PkPk=Fk=FkPk−1Pk−1FTkFkT(先驗狀態估計協方差矩陣)
外部確定性影響
到目前為止,我們只是關心繫統內部狀態的更新,但是如果在外部對系統產生了確定影響,比如:小車的操控者發出一條剎車的指令,我們通過建模該指令,假如小車的加速度為aa,根據運動學的公式,得到:
p^kp^k==pk−1pk−1+δtδtvk−1vk−1+1212aaδt2δt2
vkvk=vk−1vk−1+aaδtδt 寫成矩陣的形式就是:
x^kx^k=Fk=Fkxk−1xk−1+[δt22δt][δt22δt]aa=FkFkxk−1xk−1+BkBku→ku→k
其中,BkBk稱為控制矩陣,uk−→uk→是控制向量,由於本例子當中的控制實際上只包含了加速度,所以該向量包含元素的個數為1。
外部不確定性影響
現實世界往往是不那麼好描述清楚的,就是存在不確定的外部影響,會對系統產生不確定的干擾。我們是無法對這些干擾進行準確的跟蹤和量化的。所以,除了外界的確定項,還需要考慮不確定干擾項wkwk。
從而,得到最終先驗估計的更新方程:
x^kx^k=FkFkxk−1xk−1+BkBku→ku→k+wkwk
每一次的狀態更新,就是在原來的最優估計的基礎上面,下一次的狀態落在一個新的高斯分佈區域,從座標系上面看就像是一團雲狀的集合,最優的估計就在這個雲團當中的某一處。所以,我們首先要弄清楚這個雲團具有什麼樣的性質,也就是使用過程激勵噪聲協方差QkQk來描述不確定干擾。
考慮到不確定的干擾之後,和不考慮這一因素相比,狀態的期望不變,只是協方差產生變化,即wkwk的期望為0。
到此,先驗估計的過程結束,總結一下,形成如下的公式:
x^kx^k=Fk=Fkx^k−1x^k−1+BkBku→ku→k+wkwk
P^kP^k=Fk=FkP^k−1P^k−1FTkFkT+QkQk(3)(3)
重點來了:
先驗估計x^kx^k取決於如下三部分:一部分是上一次的最優估計值,一部分是確定性的外界影響值,另一部分是環境當中不確定的干擾。先驗估計協方差矩陣PkPk,首先是依據第k-1次卡爾曼估計(後驗估計)的協方差矩陣進行遞推,再與外界在這次更新中可能對系統造成的不確定的影響求和得到。
2.後驗估計(量測更新)
到此,利用xk^xk^和PkPk能夠對系統進行粗略的跟蹤,但是還不完善。因為安裝在系統上面的感測器會對系統的狀態進行測量反饋,糾正估計。下面看看感測器的量測更新是怎麼樣對最終的估計產生影響的。下圖說明的是狀態空間向觀測空間的對映。
在這裡需要說明一下,卡爾曼濾波器的觀測系統的維數小於等於動態系統的維數,即觀測量可以少於動態系統中狀態向量所包含的元素個數。
注意,感測器的輸出值不一定就是我們建立的狀態向量當中的元素,有時候需要進行一下簡單的換算。即使是,有可能單位也不對應,所以,需要一個轉換。這個轉換就是矩陣HkHk,在一些文獻當中也被稱作狀態空間到觀測空間的對映矩陣。
得到的公式如下:
μ→expected=Hkμ→expected=Hkx^kx^k
Σexpected=HkΣexpected=HkPkPkHTkHkT
那麼,感測器對系統某些狀態的測量真的準確嗎?是不是也會有偏差呢?答案是肯定的。系統在某一個狀態下,感測器輸出一組觀測值,當系統在另一個狀態下,感測器會輸出另外的觀測值。反過來想,就是說根據讀取到的觀測向量,可以推測系統的實際狀態。由於的存在,不同的系統狀態造成當前的觀測值的概率是不同的。所以,還需要一個觀測噪聲向量以及觀測噪聲協方差來衡量測量水平,我們將它們分別命名為v→kv→k和RkRk。下面的兩張圖說明這一點。
z→tz→t=HkHkx^kx^k+v→kv→k
觀測向量zk→zk→服從高斯分佈,並且其平均值認為就是本次的量測值(z1,z2)(z1,z2)。
下圖看到的是兩個雲團,一個是狀態預測值,另一個是觀測值。那麼到底哪一個具體的結果才是最好的呢?現在需要做的是對這兩個結果進行合理的取捨,通過一種方法完成最終的卡爾曼預測。即:從圖中粉紅色雲團和綠色雲團當中找到一個最合適的點。
問題來了,怎麼找?
首先來直觀理解一下:考察觀測向量zk→zk→和先驗估計x^kx^k:存在兩個事件:事件1感測器的輸出是對系統狀態真實值的完美測量,絲毫不差;事件2先驗狀態估計的結果就是系統狀態真實值的完美預測,也是絲毫不差。但是大家讀到這裡心裡非常清楚的一點是:兩個事件的發生都是概率性的,不能完全相信其中的任何一個!!!!!!!
如果我們具有兩個事件,如果都發生的話,從直覺或者是理性思維上講,是不是認定兩個事件發生就找到了那個最理想的估計值?好了,抽象一下,得到:兩個事件同時發生的可能性越大,我們越相信它!要想考察它們同時發生的可能性,就是將兩個事件單獨發生的概率相乘。
那麼,下一步就是對兩個雲團進行重疊,找到重疊最亮的點(實際上我們能夠把雲團看做一幀影象,這幀影象上面的每一個畫素具有一個灰度值,灰度值大小代表的是該事件發生在這個點的概率密度),最亮的點,從直覺上面講,就是以上兩種預測準確的最大化可能性。也就是得到了最終的結果。非常神奇的事情是,對兩個高斯分佈進行乘法運算,得到新的概率分佈規律仍然符合高斯分佈,然後就取下圖當中藍色曲線峰值對應的橫座標不就是結果了嘛。證明如下:
我們考察單隨機變數的高斯分佈,期望為μμ,方差為σ2σ2,概率密度函式為:
N(x,μ,σ)=1σ2π−−√e−(x−μ)22σ2(4)
(4)N(x,μ,σ)=1σ2πe−(x−μ)22σ2
如果存在兩個這樣的高斯分佈,只不過期望和方差不同,當兩個分佈相乘,得到什麼結果?
是不是下式成立
N(x,μ0,σ0)⋅N(x,μ1,σ1)=?N(x,μ′,σ′)(5)
(5)N(x,μ0,σ0)⋅N(x,μ1,σ1)=?N(x,μ′,σ′)
將(4)帶入(5),對照(4)的形式,求得
μ′μ′ = μ0μ0+k(k(μ1μ1- μ0μ0))
σ′2σ′2=σ20σ02- σ′2σ′2
其中,k=σ20σ20+σ21k=σ02σ02+σ12
以上是單變數概率密度函式的計算結果,如果是多變數的,那麼,就變成了協方差矩陣的形式:
K=Σ0(Σ0+Σ1)−1(6)
(6)K=Σ0(Σ0+Σ1)−1
μ→′=μ→0+K(μ→1−μ→0)(7)
(7)μ→′=μ→0+K(μ→1−μ→0)
Σ′=Σ0−KΣ0(8)
(8)Σ′=Σ0−KΣ0
K稱為卡爾曼增益,在下一步將會起到非常重要的作用。
好了,馬上就要接近真相!
卡爾曼估計
下面就是對感測器的量測結果和根據k-1時刻預測得到的結果進行融合。(由於剛才的推導是兩個單變數,並且處在同一個空間內,下面的推導為了方便起見,我們將先驗狀態估計對應的結果對映到觀測向量空間進行統一的運算)
第一個要解決的問題是:(7)和(8)兩個式子中那個平均值和方差都對應多少?
(μ0,Σ0)=(Hkx^k,HkPkHTk)(9)
(9)(μ0,Σ0)=(Hkx^k,HkPkHkT)
(μ1,Σ1)=(zk,Rk)(10)
(10)(μ1,Σ1)=(zk,Rk)
將(9)和(10)代入(6)、(7)、(8)三個式子,整理得到:
Hkx^′k=Hkx^k+K(zk→−Hkx^k)(11)
(11)Hkx^k′=Hkx^k+K(zk→−Hkx^k)
HkP′kHTk=HkPkHTk−KHkPkHTk(12)
(12)HkPk′HkT=HkPkHkT−KHkPkHkT
其中,卡爾曼增益的表示式為:K=HkPkHTk(HkPkHTk+Rk)−1K=HkPkHkT(HkPkHkT+Rk)−1
最後一步,更新結果:
x^′kx^k′=x^kx^k+K′K′(zk→(zk→-Hkx^k)Hkx^k)(13)(13)
P′kPk′=PkPk-K′K′HkPkHkPk(14)(14)
K′K′=PkHTk(HkPkHTk+Rk)−1PkHkT(HkPkHkT+Rk)−1(15)(15)
x^′kx^k′就是第k次卡爾曼預測結果,P′kPk′是該結果的協方差矩陣,它們都作為下一次預測的初始值參與到新的預測當中。總體上來講,卡爾曼濾波的步驟大致分為兩步,第一步是時間更新,也叫作先驗估計,第二步是量測更新,也叫作後驗估計,而當前的卡爾曼過程的後驗估計結果不僅可以作為本次的最終結果,還能夠作為下一次的先驗估計的初始值。下圖是卡爾曼濾波的工作流程:
對卡爾曼濾波原理的理解,我參考了這篇文章,圖片取自該文章,該文的圖片和公式顏色區分是一大亮點,在此表示對作者的感謝。
舉個栗子
這部分重點講解一下利用OpenCV如何實現一個對三維空間內物體的二維平面跟蹤。
背景:跟蹤一個移動速度大小和方向大致保持不變的物體,檢測該物體的感測器是通過對物體拍攝連續幀影象,經過逐個畫素的分析計算,得到x、y方向的速度,將兩個速度構成一個二維的列向量作為觀測向量。
下面看一下OpenCV當中對卡爾曼濾波的支援和提供的介面說明。
OpenCV2.4.13-KalmanFilter
下面是參與到卡爾曼濾波的一些資料結構,它們代表的意義在其後面用英文進行了描述。
OpenCV將以下的成員封裝在了KalmanFilter當中,我們使用時候,可以直接例項化一個物件,例如:
KalmanFilter m_KF;
1
//CV_PROP_RW Mat statePre; //!< predicted state (x'(k)): x(k)=A*x(k-1)+B*u(k)
//CV_PROP_RW Mat statePost; //!< corrected state (x(k)): x(k)=x'(k)+K(k)*(z(k)-H*x'(k))
//CV_PROP_RW Mat transitionMatrix; //!< state transition matrix (A)
//CV_PROP_RW Mat controlMatrix; //!< control matrix (B) (not used if there is no control)
//CV_PROP_RW Mat measurementMatrix; //!< measurement matrix (H)
//CV_PROP_RW Mat processNoiseCov; //!< process noise covariance matrix (Q)
//CV_PROP_RW Mat measurementNoiseCov;//!< measurement noise covariance matrix (R)
//CV_PROP_RW Mat errorCovPre; //!< priori error estimate covariance matrix (P'(k)): P'(k)=A*P(k-1)*At + Q)*/
//CV_PROP_RW Mat gain; //!< Kalman gain matrix (K(k)): K(k)=P'(k)*Ht*inv(H*P'(k)*Ht+R)
//CV_PROP_RW Mat errorCovPost; //!< posteriori error estimate covariance matrix (P(k)): P(k)=(I-K(k)*H)*P'(k)
1
2
3
4
5
6
7
8
9
10
11
KalmanFilter類的成員函式具有如下幾個:
KalmanFilter
init
predict
correct
1
2
3
4
方法很簡單,但是需要知道如何使用:
程式當中,我單獨寫了一個類,在我的計算執行緒(就是獲取到量測結果的執行緒)當中對該類進行例項化並且呼叫其中的方法。量測結果存放在
m_dispacementVector[0] = m_xSpeed;
m_dispacementVector[1] = m_ySpeed;
當中。
步驟一:
CV_WRAP KalmanFilter( int dynamParams, int measureParams, int controlParams = 0, int type = CV_32F );
/** @brief Re-initializes Kalman filter. The previous content is destroyed.
@param dynamParams Dimensionality of the state.
@param measureParams Dimensionality of the measurement.
@param controlParams Dimensionality of the control vector.
@param type Type of the created matrices that should be CV_32F or CV_64F.
*/
1
2
3
4
5
6
7
8
9
在卡爾曼濾波類的建構函式成員列表當中首先告知OpenCV過程狀態向量的維度和觀測向量的維度:
m_KF(4,2,0)
1
對m_KF成員的初始化:
1狀態向量初始化xkxk
我想對物體的位置資訊和速度資訊進行跟蹤,由於是二維的,所以位置資訊x、y方向兩個變數,速度資訊x、y方向兩個變數,從而m_KF.statePre和m_KF.statePost是一個四維列向量。
state=⎡⎣⎢⎢⎢⎢xyvxvy⎤⎦⎥⎥⎥⎥
state=[xyvxvy]
該向量在初始化時設定為零。
2狀態轉移矩陣初始化FkFk
在計算機螢幕上面,我自定義了一個該物體的運動空間,具有橫縱座標,後面會看到這個空間。由於相機的幀率是30fps,所以相鄰幀時間間隔δt≈30msδt≈30ms,被測物體的實際速度大約為10mm/s,所以在如此短的時間內,該物體能夠認為是做勻速直線運動,故得到狀態轉移方程
Fk=⎡⎣⎢⎢⎢10000100δt0100δt01⎤⎦⎥⎥⎥
Fk=[10δt0010δt00100001]
m_KF.transitionMatrix = (Mat_<float>(4, 4) << 1, 0, 0.03, 0,0,1,0,0.03,0,0,1,0,0,0,0,1);
1
3過程噪聲激勵協方差矩陣QkQk
setIdentity(m_KF.processNoiseCov, Scalar::all(1e-5));//Q
1
認為過程激勵噪聲比較弱,並且每個分量相互之間不存在相關係。
4觀測矩陣HkHk
setIdentity(m_KF.measurementMatrix);
1
初始化得到:
Hk=[00001001]
Hk=[00100001]
由於感測器只是檢測到了兩個方向的速度,對位移沒有檢測,所以要將矩陣前兩列初始化為0。
5預測估計協方差矩陣PkPk
setIdentity(m_KF.errorCovPost, Scalar::all(1));
1
初始化為單位陣。
6測量噪聲協方差矩陣RkRk
setIdentity(m_KF.measurementNoiseCov, Scalar::all(1e-1));//R
1
步驟二:
先驗估計:
m_prediction = m_KF.predict();
1
步驟三:
後驗估計:
首先需要告知卡爾曼濾波器最新的感測器資料:
m_dispacementVector[0] = m_xSpeed;
m_dispacementVector[1] = m_ySpeed;
1
2
m_pKalman->updateMeasurements(m_dispacementVector);
1
2
void kalmanFilter::updateMeasurements(double p[])
{
m_measurement = (Mat_<float>(2, 1) << p[0], p[1]);
}
1
2
3
4
接著完成後驗估計:
m_KF.correct(m_measurement);
1
KalmanFilter.h:
#include <QObject>
#include "opencv2/video/tracking.hpp"
#include "opencv2/highgui/highgui.hpp"
using namespace cv;
class kalmanFilter:public QObject
{
Q_OBJECT
public:
kalmanFilter();
~kalmanFilter();
void initKalman();
void kalmanPredict();
void updateMeasurements(double p[]);
void kalmamCorrect();
double *returnResult();
void drawArrow(Point start, Point end, Scalar color, int alpha, int len);
private:
KalmanFilter m_KF;
Mat m_state;
Mat m_postCorrectionState;
Mat m_processNoise;
Mat m_measurement;
Mat m_img;
Mat m_prediction;
Point2f m_PointCenter;
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
KalmanFilter.cpp:
#include "kalmanFilter.h"
#include <iostream>
#include <fstream>
//CV_PROP_RW Mat statePre; //!< predicted state (x'(k)): x(k)=A*x(k-1)+B*u(k)
//CV_PROP_RW Mat statePost; //!< corrected state (x(k)): x(k)=x'(k)+K(k)*(z(k)-H*x'(k))
//CV_PROP_RW Mat transitionMatrix; //!< state transition matrix (A)
//CV_PROP_RW Mat controlMatrix; //!< control matrix (B) (not used if there is no control)
//CV_PROP_RW Mat measurementMatrix; //!< measurement matrix (H)
//CV_PROP_RW Mat processNoiseCov; //!< process noise covariance matrix (Q)
//CV_PROP_RW Mat measurementNoiseCov;//!< measurement noise covariance matrix (R)
//CV_PROP_RW Mat errorCovPre; //!< priori error estimate covariance matrix (P'(k)): P'(k)=A*P(k-1)*At + Q)*/
//CV_PROP_RW Mat gain; //!< Kalman gain matrix (K(k)): K(k)=P'(k)*Ht*inv(H*P'(k)*Ht+R)
//CV_PROP_RW Mat errorCovPost; //!< posteriori error estimate covariance matrix (P(k)): P(k)=(I-K(k)*H)*P'(k)
using namespace cv;
using namespace std;
kalmanFilter::kalmanFilter()
:m_KF(4,2,0)
, m_state(4,1,CV_32F)
, m_processNoise(4, 1, CV_32F)
, m_measurement(2,1,CV_32F)
, m_img(300, 300, CV_8UC3)
, m_PointCenter(m_img.cols*0.5f, m_img.rows)
{
m_KF.transitionMatrix = (Mat_<float>(4, 4) << 1, 0, 0.03, 0,0,1,0,0.03,0,0,1,0,0,0,0,1);//A
setIdentity(m_KF.measurementMatrix);
setIdentity(m_KF.processNoiseCov, Scalar::all(1e-5));//Q
setIdentity(m_KF.measurementNoiseCov, Scalar::all(1e-1));//R
setIdentity(m_KF.errorCovPost, Scalar::all(1));
}
kalmanFilter::~kalmanFilter()
{
}
void kalmanFilter::initKalman()
{
m_state = (Mat_<float>(4, 1) << 0,0,0,0);
m_KF.statePre = (Mat_<float>(4, 1) << m_PointCenter.x, m_PointCenter.y, 0, 0);
m_KF.statePost = (Mat_<float>(4, 1) << m_PointCenter.x, m_PointCenter.y, 0, 0);
m_KF.measurementMatrix = (Mat_<float>(2, 4) << 0,0, 1, 0,0,0,0,1);
}
void kalmanFilter::updateMeasurements(double p[])
{
m_measurement = (Mat_<float>(2, 1) << p[0], p[1]);
}
void kalmanFilter::kalmanPredict()
{
m_prediction = m_KF.predict();
}
void kalmanFilter::kalmamCorrect()
{
m_KF.correct(m_measurement);
m_postCorrectionState = m_KF.statePost;
//show the result
m_img = Scalar::all(0);
//measured result
drawArrow(Point(m_KF.statePost.at<float>(0, 0), m_KF.statePost.at<float>(1, 0)),
Point(m_KF.statePost.at<float>(0, 0) + m_measurement.at<float>(0, 0), m_KF.statePost.at<float>(1, 0) + m_measurement.at<float>(1, 0)),
Scalar(0, 0, 255), 20, 15);//B G R
putText(m_img, "measurement", Point(m_KF.statePost.at<float>(0, 0) + m_measurement.at<float>(0, 0), m_KF.statePost.at<float>(1, 0) + m_measurement.at<float>(1, 0)-5), FONT_HERSHEY_PLAIN, 1, Scalar(0, 0, 255), 2);
//predicted result
drawArrow(Point(m_KF.statePost.at<float>(0, 0), m_KF.statePost.at<float>(1, 0)),
Point(m_KF.statePost.at<float>(0, 0) + m_KF.statePost.at<float>(2, 0), m_KF.statePost.at<float>(1, 0) + m_KF.statePost.at<float>(3, 0)),
Scalar(0,255, 0), 20, 15);
putText(m_img, "Kalman", Point(m_KF.statePost.at<float>(0, 0) + m_KF.statePost.at<float>(2, 0)-10, m_KF.statePost.at<float>(1, 0) + m_KF.statePost.at<float>(3, 0)-5), FONT_HERSHEY_PLAIN, 1, Scalar(0, 255, 0), 2);
imshow("Kalman", m_img);
ofstream myfile("example.txt", ios::app);
myfile << "measure" << " "<<m_measurement.at<float>(0, 0) << " " << m_measurement.at<float>(1, 0)<<" ";
myfile << "kalman" << " " << m_KF.statePost.at<float>(2, 0) << " " << m_KF.statePost.at<float>(3, 0) << endl;
}
double * kalmanFilter::returnResult()
{
double result[4];
for (int i = 0; i < 4; i++)
{
result[i] = m_postCorrectionState.at<double>(i, 1);
}
return result;
}
void kalmanFilter::drawArrow(Point start, Point end, Scalar color,int alpha,int len)
{
const double PI = 3.1415926;
Point arrow;
double angle = atan2((double)(start.y - end.y), (double)(start.x - end.x));
line(m_img, start, end, color,2);
arrow.x = end.x + len * cos(angle + PI * alpha / 180);
arrow.y = end.y + len * sin(angle + PI * alpha / 180);
line(m_img, end, arrow, color,1);
arrow.x = end.x + len * cos(angle - PI * alpha / 180);
arrow.y = end.y + len * sin(angle - PI * alpha / 180);
line(m_img, end, arrow, color,1);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
下面是錄製的程式執行效果圖(抱歉物體未顯示):
參考資料:
卡爾曼濾波器OpenCV自帶例子
教程
卡爾曼濾波的直覺理解