在無人機上光流定位通常是借助于無人機底部的一個攝像頭采集圖像資料,然後采用光流算法計算兩幀圖像的位移,進而實作對無人機的定位,這種定位手段配合GPS可以在室外實作對無人機的精準控制,并且在市内沒有GPS信号的時候,也可以實作對無人機的高精度的定位,實作更加平穩的控制。
在光流理論中,前提是下面兩個假設成立:
1)攝像頭采集到的兩幀圖像之間的像素灰階不變;
2)相鄰的兩幀像素具有相對運動;
根據第一個假設,如果兩幀的灰階值不變,那麼有以下關系成立:
其中 I(x,y,t)表示在時間dt後移動到第二幀圖像(x+dx,y+dy)的位置,采用泰勒級數對兩邊進行展開,消去相同的項,就可以得到如下方程:
其中:
以上就是光流方程,其中fx和fy表示圖像的梯度,ft表示時間梯度,但是上述方法是無法得到(u,v),因為一個等式無法求解兩個未知數,為了解決這個問題,我們可以采用經典的lucas-Kanade方法來進行求解。
在lucas-Kanade方法中,我們需要用到我們第二個假設了,即在目标點的鄰域内所有的點都具有相似的運動,這就是lucas-kanade方法的核心,基于該假設,其利用一個3X3鄰域中的9個點具有相同運動得到9個光流方程,然後采用最小二乘進行拟合求解,最終得到(u,v)如下:
以上就是光流法計算像素點的移動速度的方法,在使用的時候,我們隻需要對圖像中的一些點去跟蹤,采用上面的方法就可以計算得到光流向量,根據得到的光流向量,就可以進一步優化無人機的姿态控制,實作更加準确的控制。後期我們将在該理論的基礎上,結合dragonboard 410c和OpenCV圖像處理庫,進一步介紹如何在dragonboard 410c上用opencv來實作光流跟蹤。
在dragonboard 410c上來用Python程式設計實作光流算法,雖然python語言編寫出來的處理效率不高,但是便于我們了解整個光流算法應用方法,并且後續我們使用C或者其他高效率的語言來實作光流算法會變得更簡單。
在實作光流分析的過程中,我們使用cv2子產品提供的算法接口來實作光流計算,在cv2中也就是OpenCV提供的Python接口,lucas-kanade算法被封裝在cv2.calcOpticalFlowPyrLK()函數中,通過該接口可以友善的建立光流處理程式。但是在實作中我們首先要确定我們要跟蹤的點,然後才能使用lucas-kanade算法來對這些點進行疊代跟蹤,接下來參考cv2中提供的Python例程代碼詳細介紹利用cv2提供的光流計算處理函數接口如何完成基于光流的跟蹤實作,其核心代碼如下:
首先為了友善調用cv2.calcOpticalFlowPyrLK算法,我們需要定義一個算法參數結構體,确定算法參數,用Python定義如下:
lk_params = dict( winSize = (15, 15),
maxLevel = 2,
criteria = (cv2.TERM_CRITERIA_EPS | cv2.TERM_CRITERIA_COUNT, 10, 0.03),
derivLambda = 0.0 )
同時在調用cv2.calcOpticalFlowPyrLK算法的時候我們還需要調用cv2.goodFeatureToTrack函數來确定需要跟蹤的點,是以還需要給該接口傳遞一個參數,具體定義如下:
feature_params = dict( maxCorners = 500,
qualityLevel = 0.3,
minDistance = 7,
blockSize = 7 )
完成參數定義後,就可以開始編寫核心代碼了,這裡我們在讀取視訊幀的時候,根據前文的分析可以知道,光流算法需要依靠前後幀的資料來進行分析,是以在讀取視訊第一幀的時候,我們還需要在第0幀的時候進行初始化處理,這裡使用便利frame_idx和detect_interval兩個參數來進行控制,對第一幀進行處理,其中前者為幀數,後者為檢測間隔,在這裡初始化處理主要是需要找到适合跟蹤的點,具體代碼如下:
if self.frame_idx % self.detect_interval == 0:
mask = np.zeros_like(frame_gray)
mask[:] = 255
for x, y in [np.int32(tr[-1]) for tr in self.tracks]:
cv2.circle(mask, (x, y), 5, 0, -1)
p = cv2.goodFeaturesToTrack(frame_gray, mask = mask, **feature_params)
if p is not None:
for x, y in np.float32(p).reshape(-1, 2):
self.tracks.append([(x, y)])
完成第一幀處理後,就需要進一步對後續幀進行處理,這裡處理其實就是一個循環疊代的過程,不斷的根據前後幀來執行光流算法,調用cv2calcOpticalFlowPyrLK函數來計算光流資料,這裡通過tracks參數來進行控制,核心代碼如下:
if len(self.tracks) > 0:
img0, img1 = self.prev_gray, frame_gray
p0 = np.float32([tr[-1] for tr in self.tracks]).reshape(-1, 1, 2)
p1, st, err = cv2.calcOpticalFlowPyrLK(img0, img1, p0, None, **lk_params)
p0r, st, err = cv2.calcOpticalFlowPyrLK(img1, img0, p1, None, **lk_params)
d = abs(p0-p0r).reshape(-1, 2).max(-1)
good = d < 1
new_tracks = [] for tr, (x, y),
good_flag in zip(self.tracks, p1.reshape(-1, 2), good):
if not good_flag:
continue
tr.append((x, y))
if len(tr) > self.track_len:
del tr[0]
new_tracks.append(tr)
cv2.circle(vis, (x, y), 2, (0, 255, 0), -1)
self.tracks = new_tracks
cv2.polylines(vis, [np.int32(tr) for tr in self.tracks], False, (0, 255, 0))
draw_str(vis, (20, 20), 'track count: %d' % len(self.tracks))
以上就是整個光流算法進行點跟蹤的處理核心過程。