1. 程式人生 > 實用技巧 >單目標跟蹤之相關濾波 MOSSE

單目標跟蹤之相關濾波 MOSSE

相關濾波

相關操作

卷積操作

MOSSE

基本思想

具體操作流程

程式碼解讀

初始化

線上更新

缺點

  論文名稱:Visual Object Tracking using Adaptive Correlation Filters

  文獻地址:https://www.cs.colostate.edu/~vision/publications/bolme_cvpr10.pdf

  原始碼地址:https://github.com/xingqing45678/Mosse_CF

  由於基於CNNs深度學習在單目標跟蹤方法的引數量和計算量都較大,難以與目標檢測演算法一起移植到嵌入式中。同時,受CVPR2020 AutoTrack

的影響,開始從基於傳統CF,DCF思想角度入手,對相關濾波CF的鼻祖MOSSE進行攻讀。

  本文主要介紹相關濾波系列演算法的開篇——MOSSE基本原理及其python程式碼實現流程。

  由於論文中涉及到大量傅立葉變換的公式,可能顯得MOSSE晦澀難懂,本篇儘量淡化傅立葉變換的公式內容,突出MOSSE在跟蹤過程中所做的工作。

相關濾波

  相關濾波(CF)源於訊號處理領域,有這麼一句話"兩個訊號越相似,其相關值越高。在跟蹤,就是找到與跟蹤目標響應最大的項" 。通過後續程式碼的解讀,會體會到相關值越高在MOSSE中的作用。

相關操作

  假設有兩個訊號f和g,則兩個訊號的相關性(correlation)為:

其中,f表示f的複共軛...(說好的淡化公式的... 看不明白不要緊,只要明白卷積就可以往下看)

  對於影象而言,相關性可以理解為相關核在影象上進行點積操作,如下圖所示。影象的相關公式可以表達為g = f ⓧ h,具體而言,對應元素相乘在求和:

  從上圖的結果可以看出,輸出正好是相關核旋轉了180°

  具體步驟:

  a. 相關核,中心分別位於輸入影象的f(i, j)畫素上進行滑動;(會補零)

  b. 利用上式進行點積操作,並求和,得到輸出影象對應的畫素值g(i, j);

  c. 在輸出影象上進行滑動,重複b的操作,求出輸出影象上的所有畫素值。

  你可能會覺得相關操作和卷積操作一樣,但二者實際上是有區別的。

卷積操作

  影象卷積操作的公式:g = f ★ h,具體而言,對應到每個畫素的表示式如下所示,可以看出,卷積操作滿足交換律。

  In Convolution operation, the kernel is first flipped by an angle of 180 degrees and is then applied to the image.

  也就是說, 卷積操作與相關操作的差異在於卷積將核kernel旋轉了180度(順逆都一樣);相關可以反應兩個訊號的相似度,卷積不可以;卷積滿足交換律,相關不可以;卷積可以直接通過卷積定理(時域上的卷積等於頻域上的乘積)來加速運算,相關不可以。

參考文章:https://towardsdatascience.com/convolution-vs-correlation-af868b6b4fb5

MOSSE

基本思想

  MOSSE的基本思想:以上一幀目標位置為bbox,在當前幀影象上擷取目標,使用相關核找到當前擷取影象上的最大響應,這裡的最大響應就是當前目標的中心,然後更新當前幀目標的中心,並通過第一幀給出的bbox的寬高為寬高,生成此時物體的框體。(這裡有一個假設,就是相鄰幀物體不會發生過大的位移,即使用上一幀的bbox擷取當前幀的影象,仍可以獲得到目標的中心) --- 可以看出,MOSSE對於物體大小的變化,以及物體的消失沒有絲毫抵抗能力。

具體操作流程

  通過基本思想可以看出,需要首先獲得一個可以在目標中心獲得最大響應的相關核h。即下述公式操作後,g中最大值是物體的中心。

  由於上式在計算機中計算量較大,作者對上式採用快速傅立葉變換(FFT):

  便得到論文中給出的

  因此,跟蹤的任務便是找到相應的H*

  在實際跟蹤中,需要考慮目標外觀的變化等因素的影響,需要進行一個優化的求解,即輸出的響應與期望的響應越接近越好:

  該優化的求解可以根據偏導數進行求取,求取結果可以直接表達為:

  以上的操作均是在跟蹤第一幀完成的。也就是說,相關核H的確定是通過第一幀標出的目標線上得出的。而上式中的i,對應到程式碼中即為若干次影象的仿射變換。而對應的輸出響應的期望G*是高斯核,後續會結合程式碼具體解讀。

  為了提升演算法的魯棒性,應對目標在跟蹤過程中的外觀變換,需要對核進行線上的更新,更新策略如下所示:

  值得一提的是,F、G、H的尺寸都是一致的。並且,在工程實踐中確實將H分解為A,B兩部分,並進行迭代更新。

程式碼解讀

初始化

  初始化應用於第一幀的功能實現,包括初始bbox資訊的獲取(中心點、寬、高、特徵),以及核的生成。其中,包含影象的預處理和準備工作:

  • 灰度化(輸入特徵為灰度,特徵表達能力有限)
  • 歸一化
  • 餘弦窗的生成
  • 期望輸出響應G的生成(高斯核,同時讓圖片的左邊緣與右邊緣連線,上邊緣與下邊緣連線)

  之後,便是最終要的核的生成。程式碼如下:

 1 def init(self,first_frame,bbox):
 2     if len(first_frame.shape)!=2:
 3         assert first_frame.shape[2] == 3
 4         first_frame=cv2.cvtColor(first_frame, cv2.COLOR_BGR2GRAY)  
 5     first_frame=first_frame.astype(np.float32)/255   # 歸一化
 6     x,y,w,h=tuple(bbox)   # x,y為bbox的左下角點,和寬高
 7     self._center=(x+w/2, y+h/2)  # 中心點座標
 8     self.w,self.h=w,h
 9     w,h=int(round(w)), int(round(h))  # 取整
10     self.cos_window=cos_window((w,h))   # 生成寬高的一維hanning窗 後再進行矩陣乘法 形成h*w的hanning矩陣
11     self._fi=cv2.getRectSubPix(first_frame,(w,h), self._center)   # 根據寬高和中心點的位置擷取影象/特徵
12     self._G=np.fft.fft2(gaussian2d_labels((w,h), self.sigma))   # 高斯相應圖
13     self.crop_size=(w,h)   # 裁剪尺寸
14     self._Ai=np.zeros_like(self._G)
15     self._Bi=np.zeros_like(self._G)
16     for _ in range(8):
17         fi=self._rand_warp(self._fi)   # 對裁剪的影象進行8次仿射變化(旋轉),從而求得H*
18         Fi=np.fft.fft2(self._preprocessing(fi,self.cos_window))   # 進行餘弦窗處理
19         self._Ai += self._G*np.conj(Fi)   # conj共軛負數
20         self._Bi += Fi*np.conj(Fi)     #  求解H*所需

其中,程式碼第4-5行:影象轉化為灰度圖;

   程式碼第6-9行:獲取第一幀影象的資訊,將左下角點,寬高資訊轉化為中心點座標。(bbox是通過cv2.selectROI()獲得到目標的左下角點座標和寬高資訊。)

   程式碼第10行:獲取餘弦窗;

   程式碼第11行:通過中心點座標和寬高在第一幀影象上擷取目標影象;

   程式碼第12行:獲得高斯響應作為期望輸出G。

   程式碼第16-20行:通過8次仿射變換,獲取相關核H。

  餘弦窗的生成

  餘弦窗通過漢寧窗生成。

1 def cos_window(sz):
2     cos_window = np.hanning(int(sz[1]))[:, np.newaxis].dot(np.hanning(int(sz[0]))[np.newaxis, :])
3     return cos_window

  漢寧窗的模樣

  通過下圖餘弦矩陣的顏色分佈可以看出,餘弦窗具有兩邊小中間大的特性,並且四周的值趨近於零,有利於突出靠近中心的目標。

  期望響應輸出G的生成

  G相當於是真值,但此處不需要人為的標註。而是通過高斯函式生成即可。高斯函式產生的最大值在影象的中心。生成後,需要進行傅立葉變換轉化到頻域。

1 def gaussian2d_labels(sz,sigma):
2     w, h=sz
3     xs, ys = np.meshgrid(np.arange(w), np.arange(h))   # 生成兩個矩陣 一個矩陣各行是0-w-1  一個矩陣各列是0-h-1
4     center_x, center_y = w / 2, h / 2
5     dist = ((xs - center_x) ** 2 + (ys - center_y) ** 2) / (sigma**2)
6     labels = np.exp(-0.5*dist)
7     return labels

  為什麼可以用高斯函式產生的值來作為期望的響應輸出呢?這是由於高斯函式自帶中間大兩邊小的特性,十分符合物體中心是最大值的要求。並且在第一幀框定目標時,也會標出十分貼合的bbox,bbox的中心可以近似為物體的中心

  隨機仿射變換

  通過隨機數的生成控制仿射變換的力度。

 1 def _rand_warp(self,img):   # 輸入是裁剪下的圖形
 2     h, w = img.shape[:2]
 3     C = .1
 4     ang = np.random.uniform(-C, C)
 5     c, s = np.cos(ang), np.sin(ang)
 6     W = np.array([[c + np.random.uniform(-C, C), -s + np.random.uniform(-C, C), 0],
 7                   [s + np.random.uniform(-C, C), c + np.random.uniform(-C, C), 0]])
 8     center_warp = np.array([[w / 2], [h / 2]])
 9     tmp = np.sum(W[:, :2], axis=1).reshape((2, 1))
10     W[:, 2:] = center_warp - center_warp * tmp  # 仿射矩陣
11     warped = cv2.warpAffine(img, W, (w, h), cv2.BORDER_REFLECT)    # 進行仿射變換  W是仿射矩陣
12     return warped

線上更新

  線上更新核心操作在於相關核的更新(下述程式碼第6行)以及影響相關核的兩個因素的更新(下述程式碼22-23行)。

  從程式碼14-18行以及返回值可以看出,線上更新的過程只有中心點位置的更新,寬高不更新,如果物體的大小發生明顯的變化,很難自適應。

 1 def update(self,current_frame,vis=False):
 2     if len(current_frame.shape)!=2:
 3         assert current_frame.shape[2]==3
 4         current_frame=cv2.cvtColor(current_frame,cv2.COLOR_BGR2GRAY)
 5     current_frame=current_frame.astype(np.float32)/255
 6     Hi=self._Ai/self._Bi   # Ht  kernel
 7     fi=cv2.getRectSubPix(current_frame,(int(round(self.w)),int(round(self.h))),self._center)   # 裁剪
 8     fi=self._preprocessing(fi,self.cos_window)
 9     Gi=Hi*np.fft.fft2(fi)   # fft
10     gi=np.real(np.fft.ifft2(Gi))   # 返回負數型別引數的實部, 以及二維傅立葉反變換
11     if vis is True:
12         self.score=gi
13     curr=np.unravel_index(np.argmax(gi, axis=None),gi.shape)   # 找到最大那個相應的位置
14     dy,dx=curr[0]-(self.h/2),curr[1]-(self.w/2)   # 中心點位置移動量
15     x_c,y_c=self._center
16     x_c+=dx
17     y_c+=dy   # 中心點更新
18     self._center=(x_c,y_c)
19     fi=cv2.getRectSubPix(current_frame,(int(round(self.w)),int(round(self.h))),self._center)
20     fi=self._preprocessing(fi,self.cos_window)
21     Fi=np.fft.fft2(fi)
22     self._Ai=self.interp_factor*(self._G*np.conj(Fi))+(1-self.interp_factor)*self._Ai
23     self._Bi=self.interp_factor*(Fi*np.conj(Fi))+(1-self.interp_factor)*self._Bi
24     return [self._center[0]-self.w/2,self._center[1]-self.h/2,self.w,self.h]

缺點

  • 輸入的特徵為單通道灰度影象,特徵表達能力有限
  • 沒有尺度更新,對於尺度變化的跟蹤目標不敏感