1. 程式人生 > >計算機視覺之目標跟蹤——論文Learning to Track at 100 FPS with Deep Regression Networks

計算機視覺之目標跟蹤——論文Learning to Track at 100 FPS with Deep Regression Networks

論文解讀

本文采用深度學習迴歸模型GOTURN(Generic Object Tracking Using Regression Networks)解決單目標跟蹤問題。單目標跟蹤問題的難點在於物體的平移、旋轉、大小變化、視角變化、明暗變化、變形以及遮擋等情況。

作者一直在強調的是本模型可以離線訓練,這樣可以從大量的訓練資料中學到資料的分佈;而且在使用的時候由於只有正向的一個推理過程,在GPU上的速度可以達到100fps(其他基於神經網路的跟蹤器速度在0.8fps-15fps之間,效能最好的神經網路跟蹤器的速遞為1fps。);另外大量的訓練資料也使得模型的泛化效能比較好,可以跟蹤其他沒有見過的物體。需要補充一點,速度的提升的原因除了離線訓練還有模型本身的原因,本文提出的模型是迴歸模型,網路只需正向跑一次,而其他深度跟蹤模型本身是分類模型,需要對多個候選patches進行打分,以得分最高者作為目標。

模型:

這裡寫圖片描述

輸入:

GOTURN是按幀處理視訊的,根據上一張圖中目標的位置來判斷當前圖片的目標位置,本質上還是在處理圖片。假設上一張圖片目標以c=(cx,cy)為中心,寬wh,那麼以c為中心寬k1wk1h的截圖作為神經網路的一個輸入。並在當前圖片中同樣以c為中心截寬k2wk2h的圖,我們稱這個截圖為搜尋區域,是神經網路的另一個輸入。

那麼問題來了,不同圖片上的目標的BBox大小形狀不一,導致截圖不一樣,但是神經網路的輸入要求固定大小,怎麼辦呢?對截圖進行reshape處理,縮放到一樣大小。

為什麼要在當前圖片中以c為中心截寬k2wk2h截圖?是因為作者簡化了問題,認為當物體運動速度不是特別快時是會出現在這個區域中的。那麼問題來了,如果運動速度過快怎麼辦?作者只是說可以增大搜索區域的範圍,但是這樣的話超參k

就是動態的了,需要先判斷物體運動速度,然後再選擇k;或者結合其他的跟蹤方法,這一點作者留在了未來工作中。這個搜尋區域對於遮擋情況也處理不了,這一點不是很理解,希望大家指教。

輸出:

跟蹤目標在搜尋區域中的相對位置,左上角及右下角的位置(x1,y1,x2,y2)。注意作者設定的座標範圍不是在[0,1]之間,而是乘以擴充套件因子變換到[0,10]之間,不知道為什麼?

模型結構:

五層卷積層:CaffeNet結構的前五層
三層4096全連線層:使用ReLU非線性函式,層之間加上dropout層

訓練

損失函式為L1距離。

motion model

作者針對視訊中物體的運動情況進行統計,發現物體由上一幀運動到下一幀的過程是有規律的。假設上一張圖片的BBox的中心點為c

,當前圖片BBox的中心點為cx,我們為兩者建立以下模型:

cx=cx+wΔx(1) cy=cy+hΔy(2)
其中wh分別為上一張圖片的寬和高。ΔxΔy兩個隨機變數,作者發現這兩個隨機變數服從均值為0的Laplace分佈。
對BBox的大小變化建立以下模型:
w=wγw(3) h=hγh(4)
其中wh是當前BBox的寬和高。γwγh是兩個隨機變數,可以用均值為1的Laplace分佈建模。

訓練資料

訓練資料既有視訊,還由影象。使用大量圖片資料進行訓練可以增加模型的泛化能力,追蹤更多種類的目標。

那對於一張標明目標的圖片,怎麼產生兩張圖片呢?通過檢視作者的實現發現,深度迴歸網路的兩個輸入是一樣的,都是對這幅圖片的填充完全截圖。而對於用random crop生成的人造資料,是對圖片的BBox進行變換,然後再根據新BBox在圖片上進行截圖,得到人造當前截圖。

對於視訊的上一圖片和當前圖片,除了前面介紹的真實截圖輸入,我們也對當前圖片進行random crop生成資料(上一張圖片的截圖保持不變)。具體來說,根據Laplace分佈隨機取樣位移ΔxΔy和大小變化γwγh,然後對當前圖片的BBox進行變化,得到新的BBox,再根據新的BBox在當前圖片上進行完全填充截圖。???一頭黑線啊,怎麼可以在當前圖片的BBox上進行變換呢?不是應該在上一張圖片的BBox上進行變化,然後再在當前圖片上截圖嗎?

實現

作者在Github上上傳了實現原始碼https://github.com/davheld/GOTURN。在測試集上跑了一下,跟蹤速度非常快,但是會出現目標錯誤、跟蹤範圍過大的情況,而且目標錯誤出現了多次。
這裡簡單地閱讀一下原始碼以便後面可以對程式碼做些修改。

說明

首先對解讀原始碼過程中用到的詞語做一下說明:
截圖、完全截圖、填充完全截圖、填充完全變換截圖:在截圖的時候,可能會出現截圖範圍超過圖片的情況。這時我們把在圖片內部的部分稱為截圖;整個截圖(包括超出部分)稱為完全截圖;超出部分用黑色填充稱為填充完全截圖;對目標BBox做變換後再截圖稱為填充完全變換截圖。

原始碼解讀

src/helper資料夾

bounding_box.cpp:

全域性變數:

  • kContextFactor = 2:為目標填充多少上下文,BBox大小的相對值
  • kScaleFactor = 10:基於神經網路的輸出值範圍,縮放BBox的座標
  • use_coordinates_output:決定在初始化BoundingBox時傳入的浮點數向量代表的座標含義。為true時,神經網路輸出(x1, y1, x2, y2);為false時,輸出(center_x, center_y, width, height)

類BoundingBox
     成員變數:

  • x1_, y1_, x2_, y2_:BBox的座標
  • scale_factor_:輸入神經網路前縮放BBox的座標

    成員函式

  • double compute_output_width() const:計算截圖的寬,為kContextFactor * bbox_width。
        引數:無
        返回
    output_weight:截圖的寬

  • double edge_spacing_x() const:當截圖的左邊界超過圖片的左邊界時,超出的長度。如果不超出,則返回0。

  • void Recenter(const BoundingBox& search_location,
    const double edge_spacing_x, const double edge_spacing_y,
    BoundingBox* bbox_gt_recentered) const
    :計算BBox相對搜尋區域(填充完全變換截圖)的位置。

  • void Scale(const cv::Mat& image, BoundingBox* bbox_scaled) const:根據圖片的大小規範化BBox,即不再以畫素點長度作為座標值而是以和圖片之間的比例。先縮放BBox的座標使其位於[0,1]之間,然後再乘以scale_factor_,使其到[0,scale_factor_]之間。
        引數
    image
    bbox_scaled:就是呼叫此函式的BoundingBox物件,現在儲存的座標值是在[0, scale_factor_]之間,scale_factor_在建立BoundingBox物件時初始化為kScaleFactor。
        返回:無

  • void Shift(const cv::Mat& image,
    const double lambda_scale_frac,
    const double lambda_shift_frac,
    const double min_scale, const double max_scale,
    const bool shift_motion_model,
    BoundingBox* bbox_rand) const
    :根據Laplace分佈得到BBox附近的一個BBox儲存在bbox_rand中

image_proc.cpp:

函式:

  • void CropPadImage(const BoundingBox& bbox_tight, const cv::Mat& image, cv::Mat* pad_image):在BBox周圍截圖。
        引數
    bbox_tight:BBox
    image:整幅圖片
    pad_image:儲存截圖

  • void CropPadImage(const BoundingBox& bbox_tight, const cv::Mat& image, cv::Mat* pad_image,
    BoundingBox* pad_image_location, double* edge_spacing_x, double* edge_spacing_y)
    :基於BBox截圖,並增加一些padding。根據BBox進行截圖後,因為考慮到完全截圖可能會超過圖片,所以截圖和完全截圖不一定一樣大小,作者又恢復了完整截圖,只不過對於超過圖片的部分用黑色進行填充。
        引數
    bbox_tight:BBox
    image:圖片
    pad_image:儲存被黑色填充的target_pad完全截圖
    pad_image_location:儲存截圖的BBox
    edge_spacing_x:截圖的左邊界超出圖片的時候,超出的長度;不超過的時候為0
    edge_spacing_y:截圖的上邊界超出圖片的時候,超出的長度;不超出的時候為0
        返回

  • void ComputeCropPadImageLocation(const BoundingBox& bbox_tight, const cv::Mat& image, BoundingBox* pad_image_location):計算截圖的位置,以BBox的中心為中心,大小為(output_weight, output_height)。如果截圖超過圖片,那麼只考慮圖片內的截圖。
        引數
    bbox_tight:BBox,圖片中目標的BBox
    image:整張圖片
    pad_image_location:儲存截圖的BBox
        返回

src/train資料夾

example_generator.cpp:

類ExampleGenerator:處理兩張圖片,生成訓練資料,包括真實樣本和生成樣本
    成員變數:

  • lambda_shift_:BBox的平移隨機變數服從的分佈的引數
  • lambda_scale_:BBox的縮放隨機變數服從的分佈的引數
  • min_scale_
  • max_scale_:限制BBox放大、縮小比例
  • image_curr_:當前的訓練圖片
  • bbox_curr_gt_:當前圖片中的目標的BBox
  • bbox_prev_gt_:上一張圖片中的目標的BBox
  • target_pad_:根據上一張圖片的目標擷取的用黑色填充的完全截圖
  • video_index_
  • frame_index_:當前樣本生成自哪一個視訊和哪一個幀,這兩個引數只在儲存圖片的額時候才會用到

    成員函式

  • 建構函式ExampleGenerator(const double lambda_shift,
    const double lambda_scale,
    const double min_scale,
    const double max_scale)

        引數
    lambda_shift:paramater of Laplace distribution of shift
    lambda_scale:paramater of Laplace distribution of rescale
    min_scale
    max_scale:constraint on change of size
        返回:無

  • void Reset(const BoundingBox& bbox_prev,
    const BoundingBox& bbox_curr,
    const cv::Mat& image_prev,
    const cv::Mat& image_curr)
    :根據輸入的引數設定ExampleGenerator物件對應的成員變數值
        引數
    bbox_prev:上一張圖片的BBox
    bbox_curr:當前圖片的BBox
    image_prev:上一張圖片
    image_curr:當前圖片
         返回:無

  • void get_default_bb_params(BBParams* default_params) const:把ExampleGenerator物件的lambda引數和scale引數儲存至defaut_params。

  • void MakeTrainingExampleBBShift(const bool visualize_example,
    const BBParams& bbparams,
    cv::Mat* rand_search_region,
    cv::Mat* target_pad,
    BoundingBox* bbox_gt_scaled) const
    :生成人造資料。對當前BBox進行平移和縮放(Laplace分佈),然後對變換BBox在當前圖片上進行填充完全截圖(稱為搜尋區域),並計算新BBox此時相對搜尋區域的位置得到新的bbox_gt_recentered,對這個BBox的座標縮放到[0, kScaleFactor]之間。
        引數
    visualize_example:是否視覺化
    bbparams:獲取ExampleGenerator物件的變換引數
    rand_search_region:填充完全轉換截圖
    target_pad:填充的完全截圖
    bbox_gt_scaled:BBox相對於搜尋區域的位置(經過規範化的,座標取值在[0, kScaleFactor]之間)
        返回:無

  • void MakeTrainingExampleBBShift(cv::Mat* image_rand_focus,
    cv::Mat* target_pad,
    BoundingBox* bbox_gt_scaled) const
    :平移和縮放
        引數
    image_rand_focus:儲存填充完全轉換截圖
    target_pad:儲存上一張圖片的目標BBox的完全填充截圖
    bbox_gt_scaled:儲存BBox相對於搜尋區域的位置(經過規範化的,座標取值在[0, kScaleFactor]之間)
        返回:無

  • void MakeTrainingExamples(const int num_examples,
    std::vector<cv::Mat>* images,
    std::vector<cv::Mat>* targets,
    std::vector<BoundingBox>* bboxes_gt_scaled)
    : 根據當前圖片生成num_examples個人造資料。根上一張圖片的截圖保持不變,對當前圖片的目標進行變換並截圖:[targets,images]就是神經網路的輸入。
        引數
    num_examples:合成樣本數
    images:儲存得到的填充完全變換截圖的向量
    targets:儲存填充完全截圖的向量
    bboxes_gt_scaled:儲存BBox在搜尋區域(填充完全變換截圖)中的相對位置,座標在[0, kScaleFactor]之間
        返回:無

  • void MakeTrueExample(cv::Mat* curr_search_region,
    cv::Mat* target_pad,
    BoundingBox* bbox_gt_scaled) const
    :根據當前圖片和上一圖片得到的截圖(真實資料)
        引數
    curr_search_region:當前圖片目標的填充完全截圖
    target_pad:上一圖片目標的填充完全截圖
    bbox_gt_scaled:ground truth
        返回

tracker_trainer.cpp:

變數:

  • const int kBatchSize = 50:batch大小
  • const int kGeneratedExamplesPerImage = 10:對每張圖片生成多少個人工樣本

類TrackerTrainer:
    成員變數

  • std::vector<cv::Mat> image_batch_:當前訓練batch的資料。這個是當前圖片的截圖
  • std::vector<cv::Mat> targets_batch_:上一張圖片的截圖
  • std::vector<BoundingBox> bboxes_gt_scaled_batch_:輸出的ground truth
  • ExampleGenerator* example_generator_:用來生成真實訓練資料以及人工資料
  • RegressorTrainBase* regressor_train_:神經網路
  • int num_batches_:已經訓練的batch總數

    成員函式:

  • void Train(const cv::Mat& image_prev, const cv::Mat& image_curr,
    const BoundingBox& bbox_prev, const BoundingBox& bbox_curr)
    :根據提供的當前圖片、上一圖片以及對應的BBox引數得到一個真實樣本以及kGeneratedExamplesPerImage個人工樣本。用這些樣本去補TrackerTrainer的batch資料。如果這些樣本數加上TrackerTrainer的資料超過了kBatchSize,那麼填滿batch後剩下的資料留作下一個batch用,並且由於TrackerTrainer的資料達到一個batch開始訓練,然後清空batch,並把由當前圖片得到的資料中沒有使用的部分再新增到空batch中;如果沒有達到kBatchSize個數據,那麼新增到batch中去等待再一個數據。
        引數:
    image_prev
    image_curr
    bbox_prev
    bbox_curr
        返回:

  • virtual void MakeTrainingExamples(std::vector<cv::Mat>* images,
    std::vector<cv::Mat>* targets,
    std::vector<BoundingBox>* bboxes_gt_scaled)
    :根據傳遞給train函式的引數設定成員變數example_generator_,然後呼叫example_generator_的MakeTrueExample()函式以及MakeTrainingExamples()得到真實樣本以及生成樣本
        引數
    images:儲存根據當前圖片目標生成的填充完全截圖以及填充完全變換截圖
    targets:儲存上一圖片的目標填充完全截圖
    bboxes_gt_scaled:儲存ground truth
        返回

  • virtual void ProcessBtch():根據TrackerTrainer的batch資料訓練網路
        引數:
        返回:

train.cpp

變數

  • kNumBatches = 500000:batch的數目

函式:

  • void train_image(const LoaderImagenetDet& image_loader,
    const std::vector<std::vector<Annotation> >& images,
    TrackerTrainer* tracker_trainer)
    :任意選擇一個圖片,並任意選擇它的一個Annotation,通過image_loader載入響應的圖片資料和BBox進行訓練
        引數
    image_loader:用於載入圖片資料和BBox
    images:所有圖片的所有Annotation
    tracker_trainer:訓練器
        返回

  • void train_video(const std::vector<Video>& videos, TrackerTrainer* tracker_trainer):任意選擇一個視訊,然後選擇該視訊中任意一個Annotation,以其對應的幀作為上一張圖片,以下一個Annotation對一個的幀作為當前圖片,載入兩個圖片和兩個BBox進行訓練
        引數:
    videos:所有視訊
    tracker_trainer:訓練器
        返回

  • main函式:需要資料檔案引數、網路檔案引數、以及目標BBox變換所需要的四個引數,用於載入圖片資料集和視訊資料集;建立神經網路;建立ExampleGenerator來生成訓練樣本。定義TrackerTrainer物件,然後隨機訓練一張圖片,隨機訓練一個視訊。

    問題

    怎麼保證神經網路的輸入是一樣大小的?不同目標的BBox的大小不一樣,所以截圖也不一樣;在人工資料中,對BBox進行的變換是不定的,對應的截圖大小也不一樣。
    在Regressor::Estimate()函式中會對資料reshape到神經網路需要的大小。