目標檢測:Faster R-CNN、Faster RCNN接口

日萌社

人工智能AI:Keras PyTorch MXNet TensorFlow PaddlePaddle 深度學習實戰(不定時更新)


 

5.5 Faster R-CNN

學習目標

  • 目標
    • 瞭解Faster R-CNN的特點
    • 知道RPN的原理以及作用
  • 應用

在Fast R-CNN還存在着瓶頸問題:Selective Search(選擇性搜索)。要找出所有的候選框,那我們有沒有一個更加高效的方法來求出這些候選框呢?

5.5.1 Faster R-CNN介紹

SPPnet和Fast R-CNN等研究已經減少了這些檢測網絡的運行時間,使得區域提出計算成爲一個瓶頸。

在Faster R-CNN中加入一個提取邊緣的神經網絡,也就說找候選框的工作也交給神經網絡來做了。這樣,目標檢測的四個基本步驟(候選區域生成,特徵提取,分類,位置精修)終於被統一到一個深度網絡框架之內。

在這項工作中,引入了一個區域提出網絡(RPN)。

5.5.1.1 RPN網絡

  • RPN是一個全卷積網絡,可以同時在每個位置預測目標邊界和目標分數。RPN經過端到端的訓練,可以生成高質量的區域提出,由Fast R-CNN用於檢測。

Faster R-CNN可以簡單地看成是區域生成網絡+Fast R-CNN的模型,用區域生成網絡(Region Proposal Network,簡稱RPN)來代替Fast R-CNN中的選擇性搜索方法,整個系統是一個單個的,統一的目標檢測網絡。RPN模塊告訴Fast R-CNN模塊在哪裏尋找。結構如下:

  • 1、首先向CNN網絡(VGG-16)輸入圖片,Faster RCNN使用一組基礎的conv+relu+pooling層提取feature map。該feature map被共享用於後續RPN層和全連接層。
  • 2、Region Proposal Networks。RPN網絡用於生成region proposals,faster rcnn中稱之爲anchor bbox(anchor)

  • 3、後續就是Fast RCNN操作

注:RPN模塊作爲這個統一網絡的“注意力”,告訴Fast R-CNN模塊在哪裏尋找。

實現過程:

  • 全卷積網絡:爲nxn的卷積層,後接兩個同級的1x1的卷積層(分別對應reg和cls)

我們可以假定輸入圖像爲MxN=1000x600,經過Conv layers,圖片大小變成(M/16)x(N/16),即:60x40(1000/16≈60,600/16≈40);則,Feature Map就是60x40x512-d(中間有13個conv,13個relu,4個poolling層)

RPN:Feature Map進入RPN後,先經過一次3x3的卷積,同樣,特徵圖大小依然是60x40,數量512,接着看到兩個全卷積,即kernel_size=1*1,p=0,stride=1。剩下兩部分分別到cls和reg

  • 1、rpn_cls:60x40x512-d =>1x1x512x18 ==> 60x40x9x2
  • 2、rpn_bbox:60x40x512-d =>1x1x512x36==>60x40x9x4

5.5.1.3 平移不變的anchor

  • 平移不變性

    • Anchors這種方法具有平移不變性,就是說在圖像中平移了物體,窗口建議也會跟着平移。
  • 過程:

在生成Anchors時,我們先定義一個base_anchor,大小爲16x16的box(因爲特徵圖(60x40)上的一個點,可以對應到原圖(1000x600)上一個16x16大小的區域)。可以變成一個[0,0,15,15]的數組,參數ratios=[1:1, 1:2, 2:1]得到不同尺度,然後如果經過scales[8, 16, 32]變化,即長、寬分別均爲 (16x8=128)、(16x16=256)、(16x32=512)。

5.5.2 Faster RCNN訓練

Faster R-CNN的訓練分爲兩部分,即兩個網絡的訓練。

5.5.2.1 RPN訓練:

  • 目的:從衆多的候選區域中提取出score較高的,並且經過regression調整的候選區域

RPN網絡被ImageNet網絡(ZF或VGG-16)進行了有監督預訓練,利用其訓練好的網絡參數初始化;爲了訓練RPN,給每個anchor分配一個二進制的標籤(是不是目標)。分配正標籤給兩類anchor:

1、正負樣本標記

  • 某個ground truth(GT)大於0.7的IoU交疊的anchor。注意到一個GT可能分配正標籤給多個anchor
  • 負標籤給與所有GT的IoU比率都低於0.3的anchor。非正非負的anchor對訓練目標沒有任何作用

2、RPN網絡的損失函數

有了這些定義,遵循Fast R-CNN中的多任務損失,最小化目標函數。對一個圖像的損失函數定義爲

3、RPN的迴歸實質

RPN的迴歸於Faster RCNN的迴歸計算損失的時候有哪些不同呢?

RPN網絡中bounding-box迴歸的實質其實就是計算出預測窗口。這裏以anchor窗口爲基準,計算Ground Truth對其的平移縮放變化參數。在真正去計算迴歸損失的時候,預測值和目標值之間會有一個變化。因爲是以anchor窗口爲基準,所以只要使這兩組參數越接近,以此構建目標函數求最小值,那預測窗口就越接近Ground Truth,達到迴歸的目的。

4、優化過程參數

RPN,通過反向傳播和隨機梯度下降端到端訓練。每個mini-batch由包含了許多正負樣本的單個圖像組成。

  • 1、隨機採樣256個anchor,計算mini-batch的損失函數,其中採樣的正負anchor的比例是1:1。
    • 如果一個圖像中的正樣本數小於128,就用負樣本填補這個mini-batch。
  • 2、通過從零均值標準差爲0.01的高斯分佈中獲取的權重來隨機初始化所有新層(最後一個卷積層其後的層),所有其他層(即共享的卷積層)是通過對ImageNet分類預訓練的模型來初始化的
  • 3、調整ZF網絡的所有層,以及conv3_1,併爲VGG網絡做準備,以節約內存。在PASCAL數據集上對於60k個mini-batch用的學習率爲0.001,對於下一20k個mini-batch用的學習率是0.0001。動量是0.9,權重衰減爲0.0005。(論文中作者使用的是caffe訓練的)

5、共享卷積訓練

RPN和Fast R-CNN都是獨立訓練的,要用不同方式修改它們的卷積層。因此我們需要開發一種允許兩個網絡間共享卷積層的技術,因此我們需要開發一種允許兩個網絡間共享卷積層的技術,而不是分別學習兩個網絡。

  • 原因是Fast R-CNN訓練依賴於固定的目標建議框,而且並不清楚當同時改變建議機制時,學習Fast R-CNN會不會收斂。

論文開發了一種實用的4步訓練算法,通過交替優化來學習共享的特徵。

  • 第一步:依上述訓練RPN,該網絡用ImageNet預訓練的模型初始化,並端到端微調用於區域建議任務。
  • 第二步:利用第一步的RPN生成的建議框,由Fast R-CNN訓練一個單獨的檢測網絡,這個檢測網絡同樣是由ImageNet預訓練的模型初始化的,這時候兩個網絡還沒有共享卷積層。
  • 第三步:用檢測網絡初始化RPN訓練,但去固定共享的卷積層,並且只微調RPN獨有的層,現在兩個網絡共享卷積層了。
  • 第四步:保持共享的卷積層固定,微調Fast R-CNN的fc層。這樣,兩個網絡共享相同的卷積層,構成一個統一的網絡。

細節:有些RPN建議框和其他建議框大量重疊,爲了減少冗餘,我們基於建議區域的cls得分,對其採用非極大值抑制(non-maximum suppression, NMS)。我們固定對NMS的IoU閾值爲0.7,這樣每個圖像只剩2k個建議區域。

5.5.2 Faster R-CNN效果

1、PASCAL VOC 2007和2012測試集檢測結果

  • 1、數據集的增加也提高了效果
  • 2、RPN的效果更好

注:使用300個建議框的原因是經過大量實驗得到300左右框是mAP效果比較高的。其它數量都會下降。anchors的數目,論文文中提到對於1000×600的一張圖像,大約有20000(~60×40×9)個anchors,忽略超出邊界的anchors剩下6000個anchors,利用非極大值抑制去掉重疊區域,剩2000個區域建議用於訓練; 測試時在2000個區域建議中選擇Top-N(論文中爲300個區域建議)用於Fast R-CNN檢測。

  • 論文當中的測試結果顯示多級別的建議框對比例的測試mAP會有提高,還有參數\lambdaλ爲10合適。

問題:FasterRCNN的檢測對於小物體檢測的效果(論文實現細節部分)

多尺度特徵提取可能提高準確率但是不利於速度與準確率之間的權衡。VOC2007系列的圖片像素尺寸大小不一,但是橫向圖的尺寸大約在500375左右。 MS COCO 數據集是這樣定義小目標的(不同數據集定義不一樣)。面積小於 32 32 的物體,MS COCO 就認爲它是一個小物體。這樣相當於一個典型的PASCAL圖像(~500x375)經過卷積之後變爲10*10個像素大小。

  • 原因:卷積、池化等操作進行下采樣導致分辨率損失嚴重,導致不容易檢測小目標

  • 解決辦法:論文中通過對輸入圖片進行rescale,FasterRCNN會將原圖像最短邊rescale到600的大小。其實要檢測小物體,既需要一張足夠大的featmap來提供更加精細的特徵和做更加密集的採樣

2、測試性能

在一塊K40 GPU上的用時(ms),測試性能如下:

  • 在2K個SS建議框需要1~2秒,平均1.51秒;如果採用VGG-16這個版本只需要198ms,採用ZF網絡的幀率爲17fps。沒采用ZF是因爲mAP相比VGG版本來講低了許多

5.5.3 Faster R-CNN總結

  • 優點
    • 提出RPN網絡
    • 端到端網絡模型
  • 缺點
    • 訓練參數過大
    • 小目標檢測效果不好

可以改進的需求:

  • RPN(Region Proposal Networks) 改進對於小目標選擇利用多尺度特徵信息進行RPN
  • 速度提升,如YOLO系列算法,刪去了RPN,直接對proposal進行分類迴歸,極大的提升了網絡的速度

5.5.4 總結

  • Faster R-CNN的改進特點
  • RPN的原理過程
  • RPN的訓練的樣本、損失函數以及優化訓練過程
  • Faster R-CNN的效果的提高

5.6 Faster RCNN接口介紹

學習目標

  • 目標
    • 瞭解COCO數據集以及API的使用
    • 掌握FasterRCNN接口使用
  • 應用
    • 應用FasterRCNN完成模型的訓練以及預測

5.6.1 COCO 數據集介紹

5.6.1.1COCO數據集

目標檢測領域一個比較有名的數據集 MS COCO (Microsoft COCO: Common Objects in Context) .MSCOCO 數據集是微軟構建的一個數據集,其包含 detection, segmentation, keypoints等任務。MSCOCO主要是爲了解決detecting non-iconic views of objects(對應常說的detection), contextual reasoning between objects and the precise 2D localization of objects(對應常說的分割問題) 這三種場景下的問題。

與PASCAL COCO數據集相比,COCO中的圖片包含了自然圖片以及生活中常見的目標圖片,背景比較複雜,目標數量比較多,目標尺寸更小,因此COCO數據集上的任務更難,對於檢測任務來說,現在衡量一個模型好壞的標準更加傾向於使用COCO數據集上的檢測結果。

  • 圖片大多數來源於生活中,背景更復雜
  • 每張圖片上的實例目標個數多,平均每張圖片7.7個
  • 小目標更多
  • 評估標準更嚴格

對比與特點

  • 1、MSCOCO總共包含91個類別,每個類別的圖片數量如下:

2、幾個不同數據集的總類別數量,以及每個類別的總實例數量,一個實例就是圖片上的一個目標。

COCO數據集的類別總數雖然沒有 ImageNet 中用於detection的類別總數多,但是每個類別的實例目標總數要比PASCAL和ImageNet都要多。

  • 3、COCO數據集中的小目標數量佔比更多

2014版本包含82,783訓練,40,504驗證和40,775測試圖像(約1/2火車,1/4 val和/ 4測試)。僅2014年train + val數據中,就有近270,000的分段人員和總共886,000的分段對象實例。 2015年累積發行版將總共包含165,482列火車,81,208 val和81,434張測試圖像。

5.6.1.2 下載

COCO所有的數據集都比較打,下面是下載官方鏈接。2017年的數據集,訓練集18G,驗證集750M,測試集6.2G。由於教學原因,後面例子中我們只會做一張圖片訓練和測試演示,讀取過多圖片運行會過慢。

2014年的數據在官網是可以下載的,2015年只有test部分,train和val部分的數據沒有。另外2017年的數據並沒有什麼新的圖片,只是將數據重新劃分,train的數據更多了,如下:

coco2017 數據集下載鏈接

http://images.cocodataset.org/zips/train2017.zip http://images.cocodataset.org/annotations/annotations_trainval2017.zip

http://images.cocodataset.org/zips/val2017.zip http://images.cocodataset.org/annotations/stuff_annotations_trainval2017.zip

http://images.cocodataset.org/zips/test2017.zip http://images.cocodataset.org/annotations/image_info_test2017.zip

5.6.2 COCO API

5.6.2.1 格式

COCO API 提供了 Matlab, Python 和 Lua 的 API 接口. 該 API 接口可以提供完整的圖像標籤數據的加載, parsing 和可視化。此外,網站還提供了數據相關的文章, 教程等。在使用 COCO 數據庫提供的 API 和 demo 之前, 需要首先下載 COCO 的圖像和標籤數據(類別標誌、類別數量區分、像素級的分割等 ):

  • 圖像數據在到 coco/images/ 文件夾中
  • 標籤數據在到 coco/annotations/ 文件夾中

1、images目錄下爲訓練的圖片

2、annotations爲圖片的標籤註釋,格式如下

/root/cv_project/image_detection/fasterRCNN/data/coco2017/annotations/instances_train2017.json

/root/cv_project/image_detection/fasterRCNN/data/coco2017/annotations/instances_val2017.json

{
     "info":      #第一個info信息
          {       #數據集信息
                  "description": "COCO 2014 Dataset",
                  "url": "http://cocodataset.org",
                  "version": "1.0",
                  "year": 2014,
                  "contributor": "COCO Consortium",
                  "date_created": "2017/09/01"
         },
      "images":  #第二個圖片信息,數組包含了多張圖像
      [   {      #每張圖像的具體信息
                  "license": 5,
                  "file_name": "COCO_train2014_000000057870.jpg",
                  "coco_url": "http://images.cocodataset.org/train2014/COCO_train2014_000000057870.jpg",
                  "height": 480,
                  "width": 640,
                  "date_captured": "2013-11-14 16:28:13",
                  "flickr_url": "http://farm4.staticflickr.com/3153/2970773875_164f0c0b83_z.jpg",
                  "id": 57870
           },
          ......
          ......   #此處省略很多圖片
         {
                  "license": 4,
                  "file_name": "COCO_train2014_000000475546.jpg",
                  "http://images.cocodataset.org/train2014/COCO_train2014_000000475546.jpg",
                  "height": 375,
                  "width":500,;、
                  "date_captured": "2013-11-25 21:20:23",
                  "flickr_url": "http://farm1.staticflickr.com/167/423175046_6cd9d0205a_z.jpg",
                  "id": 475546
           }],         #圖像描述結束,下面開始介紹licenses


    "licenses":
         [ {
                  "url": "http://creativecommons.org/licenses/by-nc-sa/2.0/",
                  "id": 1,
                  "name": "Attribution-NonCommercial-ShareAlike License"
           },
            .....#此處省略七個license
            .....
         {
                  "url": "http://creativecommons.org/licenses/by-nc-nd/2.0/",
                  "id": 8,
                  "name": "Attribution-NonCommercial-NoDerivs License"
         }],

      "annotations":
      [   { 
                 "segmentation":[[312.29,562.89,402.25,511.49,400.96,425.38,398.39,372.69,
                                  388.11,332.85,318.71,325.14,295.58,305.86,269.88,314.86,
                                  258.31,337.99,217.19,321.29,182.49,343.13,141.37,348.27,
                                  132.37,358.55,159.36,377.83,116.95,421.53,167.07,499.92,
                                  232.61,560.32,300.72,571.89]],
                "area": 54652.9556,
                "iscrowd": 0,
                "image_id": 480023,
                "bbox": [116.95,305.86,285.3,266.03],
                "category_id": 58,"id": 86
          },
            .....#此處省略很多圖像的分割標籤
            .....
                "segmentation":[[312.29,562.89,402.25,511.49,400.96,425.38,398.39,372.69,
                                388.11,332.85,318.71,325.14,295.58,305.86,269.88,314.86,
                                258.31,337.99,217.19,321.29,182.49,343.13,141.37,348.27,
                                132.37,358.55,159.36,377.83,116.95,421.53,167.07,499.92,
                                232.61,560.32,300.72,571.89]],
              "area": 54652.9556,
              "iscrowd": 0,
              "image_id": 480023,
              "bbox": [116.95,305.86,285.3,266.03],
              "category_id": 58,
              "id": 86
          },
      "categories":#類別信息
     [   {
              "supercategory": "person",
              "id": 1,
              "name": "person"
          },
              .......#此處省略很多圖像的類標籤
              .......
          {
              "supercategory": "vehicle",
              "id": 2,
              "name": "bicycle"
          },
        {
              "supercategory": "kitchen",#大類
              "id": 50,
              "name": "spoon"
        }

5.6.2.2 利用 json 文件實例化 COCO API 對象

  • 參數
    • -annotation_file=None (str): location of annotation file

需要下載安裝

  • pip install Cython
  • pip install pycocotools
  • pip install opencv-python
from pycocotools.coco import COCO
dataDir = './coco'
dataType = 'val2017'
annFile = '{}/annotations/instances_{}.json'.format(dataDir, dataType)
coco = COCO(annFile)
# 若實例化成功, 則會返回:
# loading annotations into memory...
# Done (t=0.81s)
# creating index...
# index created!
  • 其他API參考官網

5.6.2.3 COCO接口獲取數據代碼

下面是通過COCOAPI獲取數據集的代碼模塊介紹

  • coco.py爲coco獲取數據類CocoDataSet(類似於之前封裝的Sequence類別)

    • 從COCO dataset加載數據集

    • 方法:

      • def get_categories(self):Get list of category names.
    • 返回:

      • def __getitem__(self, idx):
            '''Load the image and its bboxes for the given index.
        
            Args
            ---
                idx: the index of images.
        
            Returns
            ---
                tuple: A tuple containing the following items: image, 
                    bboxes, labels.
        
  • transforms:

    • class ImageTransform(object):處理圖片
      • 包括大小處理、標準化
    • class BboxTransform(object): Preprocess ground truth bboxes.
      • rescale bboxes according to image size
      • flip bboxes (if needed)

獲取API

class CocoDataSet(object):
    def __init__(self, 
                 dataset_dir,  # dataset_dir: The root directory of the COCO dataset.
                 subset,  # subset: What to load (train, val).
                 flip_ratio=0,  # flip_ratio: Float. The ratio of flipping an image and its bounding boxes.
                 pad_mode='fixed',  # pad_mode: Which padded method to use (fixed, non-fixed)
                 mean=(0, 0, 0),  # mean: Tuple. Image mean.
                 std=(1, 1, 1),  # Tuple. Image standard deviation.
                 scale=(1024, 800),  # Tuple of two integers.
                 debug=False):

5.6.1.4 獲取2017數據集代碼案例

  • coco.CocoDataSet獲取數據集元組
    • data_generator.DataGenerator:打亂數據獲取得到一個Generator
  • tf.data.Dataset:
    • from_generator:從生成器中獲取到Dataset類數據集合
import os
import tensorflow as tf
from tensorflow import keras
import numpy as np

from detection.datasets import coco, data_generator
import os
os.environ["TF_CPP_MIN_LOG_LEVEL"] = "2"

train_dataset = coco.CocoDataSet('./data/coco2017', 'train',
                                     flip_ratio=0.5,
                                     pad_mode='fixed',
                                     mean=(123.675, 116.28, 103.53),
                                     std=(1., 1., 1.),
                                     scale=(800, 1216))
train_generator = data_generator.DataGenerator(train_dataset)
train_tf_dataset = tf.data.Dataset.from_generator(
  train_generator, (tf.float32, tf.float32, tf.float32, tf.int32))
train_tf_dataset = train_tf_dataset.batch(1).prefetch(100).shuffle(100)

for (batch, inputs) in enumerate(train_tf_dataset):
  batch_imgs, batch_metas, batch_bboxes, batch_labels = inputs
  print(batch_imgs, batch_metas, batch_bboxes, batch_labels)

打印結果當中包含圖片數據,批次元信息,GT位置信息,目標標籤

5.6.2 Faster RCNN 訓練以及預測

5.6.2.1 Faster RCNN流程圖詳解

Faster R-CNN整體的流程可以分爲三步:

  • 1、提取特徵:圖片經過預訓練的網絡(常用ZF和VGG16、ResNet),提取到了圖片的特徵(feature)

  • 2、Region Proposal network: 利用提取的特徵(feature),經過RPN網絡,找出一定數量的rois(region of interests)

  • 3、分類與迴歸:將rois和圖像特徵features,輸入到RoI pooing,負責對由RPN產生的ROI進行分類和微調。對RPN找出的ROI,判斷它是否包含目標,並修正框的位置和座標。

5.6.2.2 開源keras Faster RCNN 模型代碼結構

源碼組成結構:

  • core:核心模塊功能,損失、anchor生成
  • models:網絡實現,CNN主架構(Resnet)、FSRCNN主結構、roi以及rpn
  • utils:網絡計算工具

5.6.2.3 FaterRCNN 模型訓練使用

使用FasterRCNN非常簡單,導入from detection.models.detectors import faster_rcnn

model = faster_rcnn.FasterRCNN(num_classes=num_classes)

訓練過程

  • 1、建立模型
  • 2、迭代訓練
from detection.models.detectors import faster_rcnn

# 2、建立模型
num_classes = len(train_dataset.get_categories())
model = faster_rcnn.FasterRCNN(num_classes=num_classes)
optimizer = keras.optimizers.SGD(1e-3, momentum=0.9, nesterov=True)

# 3、迭代訓練
for epoch in range(1):

    loss_history = []
    for (batch, inputs) in enumerate(train_tf_dataset):

        batch_imgs, batch_metas, batch_bboxes, batch_labels = inputs
        with tf.GradientTape() as tape:
            rpn_class_loss, rpn_bbox_loss, rcnn_class_loss, rcnn_bbox_loss = model(
                (batch_imgs, batch_metas, batch_bboxes, batch_labels), training=True)

            loss_value = rpn_class_loss + rpn_bbox_loss + rcnn_class_loss + rcnn_bbox_loss

        grads = tape.gradient(loss_value, model.trainable_variables)
        optimizer.apply_gradients(zip(grads, model.trainable_variables))

        loss_history.append(loss_value.numpy())

        if batch % 10 == 0:
            print('迭代次數: %d, 批次數: %d, 損失: %f' % (epoch+1, batch+1, np.mean(loss_history)))

這裏爲了演示,用一張圖片進行訓練迭代一次,輸出結果

迭代次數: 1, 批次數: 1, 損失: 41.552353

5.6.3 代碼核心解析

5.6.3.1 模型主體過程

    def call(self, inputs, training=True):
        """

        :param inputs: [1, 1216, 1216, 3], [1, 11], [1, 14, 4], [1, 14]
        :param training:
        :return:
        """
        if training: # training
            imgs, img_metas, gt_boxes, gt_class_ids = inputs
        else: # inference
            imgs, img_metas = inputs
        # 1、主幹網絡計算,ResNet五層特徵輸出保留
        # [1, 304, 304, 256] => [1, 152, 152, 512]=>[1,76,76,1024]=>[1,38,38,2048]
        C2, C3, C4, C5 = self.backbone(imgs, 
                                       training=training)
        # [1, 304, 304, 256] <= [1, 152, 152, 256]<=[1,76,76,256]<=[1,38,38,256]=>[1,19,19,256]
        # FPN進行處理金字塔特徵
        P2, P3, P4, P5, P6 = self.neck([C2, C3, C4, C5], 
                                       training=training)
        # 用於RPN計算
        rpn_feature_maps = [P2, P3, P4, P5, P6]
        # 用於後面的RCNN計算
        rcnn_feature_maps = [P2, P3, P4, P5]
        # [1, 369303, 2] [1, 369303, 2], [1, 369303, 4], includes all anchors on pyramid level of features
        # 2、RPN計算輸出2000候選框
        # 得到輸出結果
        rpn_class_logits, rpn_probs, rpn_deltas = self.rpn_head(
            rpn_feature_maps, training=training)
        # [369303, 4] => [215169, 4], valid => [6000, 4], performance =>[2000, 4],  NMS
        # 過濾
        proposals_list = self.rpn_head.get_proposals(
            rpn_probs, rpn_deltas, img_metas)
        # 進行區域分配GT,標記正負樣本
        if training: # get target value for these proposal target label and target delta
            rois_list, rcnn_target_matchs_list, rcnn_target_deltas_list = \
                self.bbox_target.build_targets(
                    proposals_list, gt_boxes, gt_class_ids, img_metas)
        else:
            rois_list = proposals_list
        # rois_list only contains coordinates, rcnn_feature_maps save the 5 features data=>[192,7,7,256]
        # 對多層特徵進行ROIpooling操作
        # Implements ROI Pooling on multiple levels of the feature pyramid.
        pooled_regions_list = self.roi_align(#
            (rois_list, rcnn_feature_maps, img_metas), training=training)
        # [192, 81], [192, 81], [192, 81, 4]
        # RCNN部分計算輸出
        rcnn_class_logits_list, rcnn_probs_list, rcnn_deltas_list = \
            self.bbox_head(pooled_regions_list, training=training)
                # 3、RPN損失以及輸出RCNN的損失計算
        if training:
            rpn_class_loss, rpn_bbox_loss = self.rpn_head.loss(
                rpn_class_logits, rpn_deltas, gt_boxes, gt_class_ids, img_metas)

            rcnn_class_loss, rcnn_bbox_loss = self.bbox_head.loss(
                rcnn_class_logits_list, rcnn_deltas_list, 
                rcnn_target_matchs_list, rcnn_target_deltas_list)

            return [rpn_class_loss, rpn_bbox_loss, 
                    rcnn_class_loss, rcnn_bbox_loss]
        else:
            detections_list = self.bbox_head.get_bboxes(
                rcnn_probs_list, rcnn_deltas_list, rois_list, img_metas)

            return detections_list

5.6.3.2 FPN(瞭解)

實現論文源碼的時候,引入了(Feature Pyramid Networks for Object Detection (FPN))圖像金字塔特徵(後面要說明的SSD算法也是類似的方案,MaskRCNN中的關鍵技術之一就是FPN)。圖像金字塔或特徵金字塔是傳統CV方法中常用的技巧。

  • 定義:

RPN加入FPN

在金字塔每層的輸出feature map上都接上這樣的head結構(3×3的卷積 + two sibling 1×1的卷積)。同時,我們不再使用多尺度的anchor box,而是在每個level上分別使用不同大小的anchor box。

對應於特徵金字塔的5個level的特徵,P2 - P6,anchor box的大小分別是32^2,64^2,128^2,256^2,512^2。同樣也有三種不同的比例。總共就有15中類型的anchorbox,比原來多6種。

5.6.3.3 RPN

RPN用來提供ROI的proposal。backbone網絡輸出的single feature map上接了3×33×3大小的卷積核來實現sliding window的功能,後面接兩個1×1的卷積分別用來做objectness的分類和bounding box基於anchor box的迴歸。我們把最後的classifier和regressor部分叫做head。

整體邏輯

  • 1、
def call(self, inputs, training=True):
    '''
    Args
    ---
        inputs: [batch_size, feat_map_height, feat_map_width, channels] 
            one level of pyramid feat-maps.

    Returns
    ---
        rpn_class_logits: [batch_size, num_anchors, 2]
        rpn_probs: [batch_size, num_anchors, 2]
        rpn_deltas: [batch_size, num_anchors, 4]
    '''

    layer_outputs = []

    for feat in inputs: # for every anchors feature maps
        """
        (1, 304, 304, 256)
        (1, 152, 152, 256)
        (1, 76, 76, 256)
        (1, 38, 38, 256)
        (1, 19, 19, 256)
        rpn_class_raw: (1, 304, 304, 6)
        rpn_class_logits: (1, 277248, 2)
        rpn_delta_pred: (1, 304, 304, 12)
        rpn_deltas: (1, 277248, 4)
        rpn_class_raw: (1, 152, 152, 6)
        rpn_class_logits: (1, 69312, 2)
        rpn_delta_pred: (1, 152, 152, 12)
        rpn_deltas: (1, 69312, 4)
        rpn_class_raw: (1, 76, 76, 6)
        rpn_class_logits: (1, 17328, 2)
        rpn_delta_pred: (1, 76, 76, 12)
        rpn_deltas: (1, 17328, 4)
        rpn_class_raw: (1, 38, 38, 6)
        rpn_class_logits: (1, 4332, 2)
        rpn_delta_pred: (1, 38, 38, 12)
        rpn_deltas: (1, 4332, 4)
        rpn_class_raw: (1, 19, 19, 6)
        rpn_class_logits: (1, 1083, 2)
        rpn_delta_pred: (1, 19, 19, 12)
        rpn_deltas: (1, 1083, 4)

        """
        # print(feat.shape)
        shared = self.rpn_conv_shared(feat)
        shared = tf.nn.relu(shared)

        x = self.rpn_class_raw(shared)
        # print('rpn_class_raw:', x.shape)
        rpn_class_logits = tf.reshape(x, [tf.shape(x)[0], -1, 2])
        rpn_probs = tf.nn.softmax(rpn_class_logits)
        # print('rpn_class_logits:', rpn_class_logits.shape)

        x = self.rpn_delta_pred(shared)
        # print('rpn_delta_pred:', x.shape)
        rpn_deltas = tf.reshape(x, [tf.shape(x)[0], -1, 4])
        # print('rpn_deltas:', rpn_deltas.shape)

        layer_outputs.append([rpn_class_logits, rpn_probs, rpn_deltas])
        # print(rpn_class_logits.shape, rpn_probs.shape, rpn_deltas.shape)
        """
        (1, 277248, 2) (1, 277248, 2) (1, 277248, 4)
        (1, 69312, 2) (1, 69312, 2) (1, 69312, 4)
        (1, 17328, 2) (1, 17328, 2) (1, 17328, 4)
        (1, 4332, 2) (1, 4332, 2) (1, 4332, 4)
        (1, 1083, 2) (1, 1083, 2) (1, 1083, 4)

        """

    outputs = list(zip(*layer_outputs))
    outputs = [tf.concat(list(o), axis=1) for o in outputs]
    rpn_class_logits, rpn_probs, rpn_deltas = outputs
    # (1, 369303, 2) (1, 369303, 2) (1, 369303, 4)
    # print(rpn_class_logits.shape, rpn_probs.shape, rpn_deltas.shape)

    return rpn_class_logits, rpn_probs, rpn_deltas

根據輸出進行篩選proposals:過濾分數、進行NMS等操作

    def get_proposals(self, 
                      rpn_probs, 
                      rpn_deltas, 
                      img_metas, 
                      with_probs=False):
        '''
        Calculate proposals.

        Args
        ---
            rpn_probs: [batch_size, num_anchors, (bg prob, fg prob)]
            rpn_deltas: [batch_size, num_anchors, (dy, dx, log(dh), log(dw))]
            img_metas: [batch_size, 11]
            with_probs: bool.

        Returns
        ---
            proposals_list: list of [num_proposals, (y1, x1, y2, x2)] in 
                normalized coordinates if with_probs is False. 
                Otherwise, the shape of proposals in proposals_list is 
                [num_proposals, (y1, x1, y2, x2, score)]

        Note that num_proposals is no more than proposal_count. And different 
           images in one batch may have different num_proposals.
        '''
        anchors, valid_flags = self.generator.generate_pyramid_anchors(img_metas)
        # [369303, 4], [b, 11]
        # [b, N, (background prob, foreground prob)], get anchor's foreground prob, [1, 369303]
        rpn_probs = rpn_probs[:, :, 1]
        # [[1216, 1216]]
        pad_shapes = calc_pad_shapes(img_metas)

        proposals_list = [
            self._get_proposals_single(
                rpn_probs[i], rpn_deltas[i], anchors, valid_flags[i], pad_shapes[i], with_probs)
            for i in range(img_metas.shape[0])
        ]

        return proposals_list

計算RPN的損失,分類和迴歸損失:

def loss(self, rpn_class_logits, rpn_deltas, gt_boxes, gt_class_ids, img_metas):
  """

        :param rpn_class_logits: [N, 2]
        :param rpn_deltas: [N, 4]
        :param gt_boxes:  [GT_N]
        :param gt_class_ids:  [GT_N]
        :param img_metas: [11]
        :return:
        """
  # valid_flags indicates anchors located in padded area or not.
  anchors, valid_flags = self.generator.generate_pyramid_anchors(img_metas)

  # 進行anhor匹配
  rpn_target_matchs, rpn_target_deltas = self.anchor_target.build_targets(
    anchors, valid_flags, gt_boxes, gt_class_ids)

  rpn_class_loss = self.rpn_class_loss(
    rpn_target_matchs, rpn_class_logits)
  rpn_bbox_loss = self.rpn_bbox_loss(
    rpn_target_deltas, rpn_target_matchs, rpn_deltas)

  return rpn_class_loss, rpn_bbox_loss

5.6.3.4 測試輸出

讀取驗證集合一張圖片,輸入模型進行預測輸出

def test():

    train_dataset = coco.CocoDataSet('./data/coco2017', 'val')

    # 獲取數據和模型
    train_generator = data_generator.DataGenerator(train_dataset)
    tf_dataset = tf.data.Dataset.from_generator(train_generator,
                                                (tf.float32, tf.float32, tf.float32, tf.float32))
    tf_dataset = tf_dataset.batch(1).prefetch(100).shuffle(100)
    num_classes = len(train_dataset.get_categories())
    model = faster_rcnn.FasterRCNN(num_classes=num_classes)

    for (batch, inputs) in enumerate(tf_dataset):
        img, img_meta, _, _ = inputs
        print(img, img_meta)

        detections_list = model((img, img_meta), training=False)

        print(detections_list)


if __name__ == '__main__':
    # train()
    test()

輸出結果

[<tf.Tensor: id=10027, shape=(20, 6), dtype=float32, numpy=
array([[0.000e+00, 0.000e+00, 0.000e+00, 0.000e+00, 3.300e+01, 1.000e+00],
       [0.000e+00, 1.024e+03, 1.024e+03, 1.024e+03, 2.700e+01, 1.000e+00],
       [0.000e+00, 1.024e+03, 1.024e+03, 1.024e+03, 2.700e+01, 1.000e+00],
       [0.000e+00, 1.024e+03, 1.024e+03, 1.024e+03, 2.700e+01, 1.000e+00],
       [0.000e+00, 1.024e+03, 1.024e+03, 1.024e+03, 2.700e+01, 1.000e+00],
       [0.000e+00, 1.024e+03, 1.024e+03, 1.024e+03, 2.700e+01, 1.000e+00],
       [0.000e+00, 1.024e+03, 1.024e+03, 1.024e+03, 2.700e+01, 1.000e+00],
       [0.000e+00, 1.024e+03, 1.024e+03, 1.024e+03, 2.700e+01, 1.000e+00],
       [0.000e+00, 1.024e+03, 1.024e+03, 1.024e+03, 2.700e+01, 1.000e+00],
       [0.000e+00, 1.024e+03, 1.024e+03, 1.024e+03, 2.700e+01, 1.000e+00],
       [1.024e+03, 1.024e+03, 1.024e+03, 1.024e+03, 7.000e+00, 1.000e+00],
       [1.024e+03, 1.024e+03, 1.024e+03, 1.024e+03, 7.000e+00, 1.000e+00],
       [1.024e+03, 1.024e+03, 1.024e+03, 1.024e+03, 7.000e+00, 1.000e+00],
       [1.024e+03, 1.024e+03, 1.024e+03, 1.024e+03, 7.000e+00, 1.000e+00],
       [1.024e+03, 1.024e+03, 1.024e+03, 1.024e+03, 7.000e+00, 1.000e+00],
       [1.024e+03, 1.024e+03, 1.024e+03, 1.024e+03, 7.000e+00, 1.000e+00],
       [0.000e+00, 1.024e+03, 1.024e+03, 1.024e+03, 2.700e+01, 1.000e+00],
       [1.024e+03, 1.024e+03, 1.024e+03, 1.024e+03, 7.000e+00, 1.000e+00],
       [1.024e+03, 1.024e+03, 1.024e+03, 1.024e+03, 7.000e+00, 1.000e+00],
       [1.024e+03, 1.024e+03, 1.024e+03, 1.024e+03, 2.700e+01, 1.000e+00]],
      dtype=float32)>]

5.6.4 總結

  • COCO數據集以及API的使用
  • FasterRCNN接口使用
  • FasterRCNN完成模型的訓練以及預測

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章