1. 程式人生 > >Faster RCNN程式碼詳解(四):關於anchor的前世今生

Faster RCNN程式碼詳解(四):關於anchor的前世今生

在上一篇部落格中介紹了資料處理的整體結構:Faster RCNN程式碼詳解(三):資料處理的整體結構這一篇部落格介紹資料處理的細節——關於anchor的前世今生,程式碼在指令碼的:~/mx-rcnn/rcnn/io/rpn.py的assign_anchor函式中。

這一部分也是你想要深入瞭解Faster RCNN演算法細節的重要部分,因為anchor是Faster RCNN演算法的核心之一。具體而言,在這篇部落格中我將為你介紹:anchor是什麼?怎麼生成的?anchor的標籤是怎麼定義的?bbox(bounding box)的迴歸目標是怎麼定義的?bbox和anchor是什麼區別?

def assign_anchor
(feat_shape, gt_boxes, im_info, feat_stride=16, scales=(8, 16, 32), ratios=(0.5, 1, 2), allowed_border=0):
""" assign ground truth boxes to anchor positions :param feat_shape: infer output shape :param gt_boxes: assign ground truth :param im_info: filter out anchors overlapped with edges :param feat_stride: anchor position step :param scales: used to generate anchors, affects num_anchors (per location) :param ratios: aspect ratios of generated anchors :param allowed_border: filter out anchors with edge overlap > allowed_border :return: dict of label 'label': of shape (batch_size, 1) <- (batch_size, num_anchors, feat_height, feat_width) 'bbox_target': of shape (batch_size, num_anchors * 4, feat_height, feat_width) 'bbox_inside_weight': *todo* mark the assigned anchors 'bbox_outside_weight': used to normalize the bbox_loss, all weights sums to RPN_POSITIVE_WEIGHT """
def _unmap(data, count, inds, fill=0): """" unmap a subset inds of data into original data of size count """ if len(data.shape) == 1: ret = np.empty((count,), dtype=np.float32) ret.fill(fill) ret[inds] = data else: ret = np.empty((count,) + data.shape[1
:], dtype=np.float32) ret.fill(fill) ret[inds, :] = data return ret im_info = im_info[0] scales = np.array(scales, dtype=np.float32) # base_anchors是anchor的初始化結果,輸入中base_size=16,表示輸入影象到該層 # feature map的尺寸縮小倍數,對於resnet網路的conv4_x而言縮小倍數是16;ratios預設是[0.5,1,2]; # scales預設是[8,16,32]。base_anchors預設是9*4的numpy array,表示9個anchor的4個座標值, # 4個座標值用框的左上角座標和右下角座標。這9個anchor有一個共同點是中心座標點一樣, # 這正是和RPN網路的滑窗操作對應(第一個3*3的卷積層),滑窗每滑到一個3*3區域, # 則以該區域中心點為座標就會生成9個anchor。 base_anchors = generate_anchors(base_size=feat_stride, ratios=list(ratios), scales=scales) num_anchors = base_anchors.shape[0] # feat_height和feat_width表示該層feature map的size,比如對於resnet的res4而言, # 縮放係數是16,所以如果輸入影象是600*900,則feat_height=600/16,feat_width=900/16 feat_height, feat_width = feat_shape[-2:] logger.debug('anchors: %s' % base_anchors) logger.debug('anchor shapes: %s' % np.hstack((base_anchors[:, 2::4] - base_anchors[:, 0::4], base_anchors[:, 3::4] - base_anchors[:, 1::4]))) logger.debug('im_info %s' % im_info) logger.debug('height %d width %d' % (feat_height, feat_width)) logger.debug('gt_boxes shape %s' % np.array(gt_boxes.shape)) logger.debug('gt_boxes %s' % gt_boxes) # 1. generate proposals from bbox deltas and shifted anchors # 前面說到base_anchors是滑窗每滑到一個區域時生成的9個anchor,因為滑窗所作用的物件 # 是38*50的feature map,當stride=1時,一共要滑動38*50次,也就是一共會得到 # 38*50*9=17100個anchor,接下來這部分程式碼就是要完成這樣的操作。 # shift_x和shift_y是根據feat_stride生成的偏移量, # 根據這兩個偏移量就可以將前面計算的base_anchors推廣到38*50中的每個點。 shift_x = np.arange(0, feat_width) * feat_stride shift_y = np.arange(0, feat_height) * feat_stride shift_x, shift_y = np.meshgrid(shift_x, shift_y) shifts = np.vstack((shift_x.ravel(), shift_y.ravel(), shift_x.ravel(), shift_y.ravel())).transpose() # add A anchors (1, A, 4) to # cell K shifts (K, 1, 4) to get # shift anchors (K, A, 4) # reshape to (K*A, 4) shifted anchors # A預設是9 A = num_anchors # K其實就是該層feature map的寬*高,比如高是38,寬是50,那麼K就是1900。 # 注意all_anchors最後一個維度是4,表示4個座標相關的資訊。 K = shifts.shape[0] all_anchors = base_anchors.reshape((1, A, 4)) + shifts.reshape((1, K, 4)).transpose((1, 0, 2)) all_anchors = all_anchors.reshape((K * A, 4)) # 以前面的K是1900為例,總共會產生9*1900=17100個anchor。 total_anchors = int(K * A) # only keep anchors inside the image # inds_inside表示anchor的4個點座標都在影象內部的anchor的index。 inds_inside = np.where((all_anchors[:, 0] >= -allowed_border) & (all_anchors[:, 1] >= -allowed_border) & (all_anchors[:, 2] < im_info[1] + allowed_border) & (all_anchors[:, 3] < im_info[0] + allowed_border))[0] logger.debug('total_anchors %d' % total_anchors) logger.debug('inds_inside %d' % len(inds_inside)) # keep only inside anchors # 將不完全在影象內部(初始化的anchor的4個座標點超出影象邊界)的anchor都過濾掉, # 一般過濾後只會有原來1/3左右的anchor。如果不將這部分anchor過濾,則會使訓練過程難以收斂。 anchors = all_anchors[inds_inside, :] logger.debug('anchors shape %s' % np.array(anchors.shape)) # label: 1 is positive, 0 is negative, -1 is dont care # 前面得到的只是anchor的4個座標資訊,接下來就要為每個anchor分配標籤了, # 初始化的時候標籤都用-1來填充,-1表示無效,這類標籤的資料不會對梯度更新起到幫助。 labels = np.empty((len(inds_inside),), dtype=np.float32) labels.fill(-1) # 這個條件語句是判斷輸入影象中是否包含object,標籤分配只針對ground truth中有object的影象。 # bbox_overlaps函式計算的是兩個框之間的IOU,這裡是計算每個anchor和每個object的IOU, # 生成的overlaps(二維的numpy array,假設為n*m,n表示anchor數量,m表示object數量)的 # 每一行表示anchor,每一列表示object。 argmax_overlaps是計算每個anchor和哪個object的IOU最大, # 維度是n*1,值是object的index。max_overlaps是具體的IOU值。 # gt_argmax_overlaps = overlaps.argmax(axis=0)則是計算每個object和哪個anchor的IOU最大, # 維度是m*1,值是anchor的index,另外因為如果有多個anchor和某個object的IOU值都是最大且一樣, # 那麼gt_argmax_overlaps只會得到index最小的那個, # 所以需要gt_argmax_overlaps = np.where(overlaps == gt_max_overlaps)[0]將IOU最大的那些anchor都撈出來。 # gt_max_overlaps是具體的IOU值。 if gt_boxes.size > 0: # overlap between the anchors and the gt boxes # overlaps (ex, gt) overlaps = bbox_overlaps(anchors.astype(np.float), gt_boxes.astype(np.float)) argmax_overlaps = overlaps.argmax(axis=1) max_overlaps = overlaps[np.arange(len(inds_inside)), argmax_overlaps] gt_argmax_overlaps = overlaps.argmax(axis=0) gt_max_overlaps = overlaps[gt_argmax_overlaps, np.arange(overlaps.shape[1])] gt_argmax_overlaps = np.where(overlaps == gt_max_overlaps)[0] # 這個條件語句預設是執行的,目的是將IOU小於某個閾值的anchor的標籤都標為0,也就是背景類。 # 閾值config.TRAIN.RPN_NEGATIVE_OVERLAP預設是0.3。 # 如果某個anchor和所有object的IOU的最大值比這個閾值小,那麼就是背景。 if not config.TRAIN.RPN_CLOBBER_POSITIVES: # assign bg labels first so that positive labels can clobber them labels[max_overlaps < config.TRAIN.RPN_NEGATIVE_OVERLAP] = 0 # fg label: for each gt, anchor with highest overlap # 有兩種型別的anhor其標籤是1,標籤1表示foreground,也就是包含object。 # 第一種是和任意一個object有最大IOU的anchor,也就是前面得到的gt_argmax_overlaps。 labels[gt_argmax_overlaps] = 1 # fg label: above threshold IoU # 第二種是和所有object的IOU的最大值超過某個閾值的anchor, # 其中閾值config.TRAIN.RPN_POSITIVE_OVERLAP預設是0.7。 labels[max_overlaps >= config.TRAIN.RPN_POSITIVE_OVERLAP] = 1 # 這一部分是和前面if not config.TRAIN.RPN_CLOBBER_POSITIVES條件語句互斥的, # 區別在於背景類anchor的標籤定義先後順序不同,這主要涉及到標籤1和標籤0之間的覆蓋。 if config.TRAIN.RPN_CLOBBER_POSITIVES: # assign bg labels last so that negative labels can clobber positives labels[max_overlaps < config.TRAIN.RPN_NEGATIVE_OVERLAP] = 0 else: # 如果ground truth中沒有object,則所有標籤都是背景。 labels[:] = 0 # subsample positive labels if we have too many # 在RPN網路中,對回傳損失的正負樣本數量做了限定,如果不做限定的話,負樣本的數量會非常多。 # 論文中預設正負樣本的總數量是256(config.TRAIN.RPN_BATCH_SIZE), # 因此接下來會分別針對正負樣本的數量做欠取樣,取樣通過將不需要的樣本標籤設定為-1來實現。 # 首先是對正樣本(標籤是1)的欠取樣操作(一般而言正樣本都很少,所以很少會執行欠取樣操作這個條件語句)。 # config.TRAIN.RPN_FG_FRACTION表示RPN網路最終生成的正樣本佔所有樣本的最大比例,預設是0.5, # 表示RPN網路最終輸出的正負樣本比例一樣。num_fg是期望得到的1標籤數量, # fg_inds是實際的1標籤數量,因此如果你實際的1標籤數量大於期望得到的1標籤數量,那麼就要做欠取樣。 num_fg = int(config.TRAIN.RPN_FG_FRACTION * config.TRAIN.RPN_BATCH_SIZE) fg_inds = np.where(labels == 1)[0] if len(fg_inds) > num_fg: disable_inds = npr.choice(fg_inds, size=(len(fg_inds) - num_fg), replace=False) if logger.level == logging.INFO: disable_inds = fg_inds[:(len(fg_inds) - num_fg)] labels[disable_inds] = -1 # subsample negative labels if we have too many # 接下來這一段程式碼是對負樣本(標籤是0)的欠取樣操作(一般而言負樣本都很多,所以一般都會執行欠取樣操作)。 # num_bg就是RPN網路中batch size減去前面得到的正樣本數量。npr.choice是呼叫python中的random庫的choice函式, # 用來從指定序列(bg_inds)中隨機選擇指定數量(size)的值。 # 預設 if logger.level == logging.INFO條件語句是執行的,這樣的話disable的樣本 # 就是從bg_inds開頭到指定數量位置,而不是隨機disable。不要的樣本標籤也是置為-1,表示無效。 num_bg = config.TRAIN.RPN_BATCH_SIZE - np.sum(labels == 1) bg_inds = np.where(labels == 0)[0] if len(bg_inds) > num_bg: disable_inds = npr.choice(bg_inds, size=(len(bg_inds) - num_bg), replace=False) if logger.level == logging.INFO: disable_inds = bg_inds[:(len(bg_inds) - num_bg)] labels[disable_inds] = -1 # bbox_target是每個bbox迴歸的ground truth,初始化為len(inds_inside)*4大小的numpy array, # 所以包含了標籤為1,0和-1三種類型的bbox。bbox_transform函式用來生成bbox_targets, # 輸入中gt_boxes原本是k*5的numpy array,k表示有幾個object, # 這裡通過gt_boxes[argmax_overlaps, :4]擴增並取前4列值, # 因為argmax_overlaps是和anchor的IOU最大的object的index,所以這種寫法相當於複製 # 指定index的object的gt_boxes資訊,因此某個anchor的座標迴歸目標利用的就是和 # 該anchor的IOU最大的object的座標通過一定公式轉換後的資訊, # 這裡的公式就是bbox_transform函式實現的,或者看最後附錄截圖的後兩行公式。 bbox_targets = np.zeros((len(inds_inside), 4), dtype=np.float32) if gt_boxes.size > 0: bbox_targets[:] = bbox_transform(anchors, gt_boxes[argmax_overlaps, :4]) # bbox_weights # bbox_weights變數是後續用來指定哪些anchor用於梯度更新的0,1矩陣,相當於一個mask, # 只有標籤是1的bbox的weight才有值,值是config.TRAIN.RPN_BBOX_WEIGHTS, # 該變數預設4個值都是1。因此標籤是0或-1的weight都是0。 bbox_weights = np.zeros((len(inds_inside), 4), dtype=np.float32) bbox_weights[labels == 1, :] = np.array(config.TRAIN.RPN_BBOX_WEIGHTS) if logger.level == logging.DEBUG: _sums = bbox_targets[labels == 1, :].sum(axis=0) _squared_sums = (bbox_targets[labels == 1, :] ** 2).sum(axis=0) _counts = np.sum(labels == 1) means = _sums / (_counts + 1e-14) stds = np.sqrt(_squared_sums / _counts - means ** 2) logger.debug('means %s' % means) logger.debug('stdevs %s' % stds) # map up to original set of anchors # 前面介紹的是labels、bbox_targets和bbox_weights這3個重要變數的構建, # 其構建的基礎都是基於indx_inside這個重要的變數,也就是將初始anchor中座標值在影象尺寸以外的bbox都過濾掉。 # 而接下來的3行程式碼則是將labels、bbox_targets和bbox_weights這3個變數重新映射回過濾之前的bbox。 # 所以假設RPN網路的輸入feature map大小是38*50,那麼最終這3個變數的第一個維度就是9*38*50=17100, # 也就是最原始的anchor數量。當然,被過濾掉的bbox的label都是-1,bbox_targets都是0, # bbox_weights都是0,因此對於RPN網路的訓練而言沒有幫助。 labels = _unmap(labels, total_anchors, inds_inside, fill=-1) bbox_targets = _unmap(bbox_targets, total_anchors, inds_inside, fill=0) bbox_weights = _unmap(bbox_weights, total_anchors, inds_inside, fill=0) if logger.level == logging.DEBUG: if gt_boxes.size > 0: logger.debug('rpn: max max_overlaps %f' % np.max(max_overlaps)) logger.debug('rpn: num_positives %f' % np.sum(labels == 1)) logger.debug('rpn: num_negatives %f' % np.sum(labels == 0)) _fg_sum = np.sum(labels == 1) _bg_sum = np.sum(labels == 0) _count = 1 logger.debug('rpn: num_positive avg %f' % (_fg_sum / _count)) logger.debug('rpn: num_negative avg %f' % (_bg_sum / _count)) # 最後就是一些reshape操作和字典的構造並返回。最後幾個輸出的情況:labels的維度是1*17100, # bbox_targets的維度是1*36*38*50,bbox_targets的維度是1*36*38*50。 labels = labels.reshape((1, feat_height, feat_width, A)).transpose(0, 3, 1, 2) labels = labels.reshape((1, A * feat_height * feat_width)) bbox_targets = bbox_targets.reshape((1, feat_height, feat_width, A * 4)).transpose(0, 3, 1, 2) bbox_weights = bbox_weights.reshape((1, feat_height, feat_width, A * 4)).transpose((0, 3, 1, 2)) label = {'label': labels, 'bbox_target': bbox_targets, 'bbox_weight': bbox_weights} return label

介紹完了anchor的定義、生成、標籤分配,相信你對anchor的瞭解會更近一步。anchor是從初始化開始就固定了,所以anchor這個名字真的非常形象(翻譯過來是錨)

在Faster RCNN演算法中你肯定還會經常聽到另一個名詞:region proposal(或者簡稱proposal,或者簡稱ROI),可以說RPN網路的目的就是為了得到proposal,這些proposal是對ground truth更好的刻畫(和anchor相比,座標更貼近ground truth,畢竟anchor的座標都是批量地按照scale和aspect ratio複製的)。如果你還記得在系列二中關於網路結構的介紹,那麼你就應該瞭解到RPN網路的迴歸支路輸出的值(offset)作為smooth l1損失函式的輸入之一時,其含義就是使得proposal和anchor之間的offset(RPN網路的迴歸支路輸出)儘可能與ground truth和anchor之間的offset(RPN網路的迴歸支路的迴歸目標,也就是這篇部落格程式碼中的’bbox_target’)接近。

至此,關於RPN網路中anchor的內容就都介紹完了。我們知道在Faster RCNN演算法中,RPN網路只是其中的一部分,在RPN網路得到proposal後還會經過一系列的過濾操作才會得到送入檢測網路的proposal,這個在系列二中關於網路結構的構造中已經介紹得很清楚了。但是在系列二中有一個自定一個網路層用來將2000個proposal過濾成128個,且為這128個proposal分配標籤、迴歸目標、定義正負樣本的1:3比例等,這部分算是RPN網路和檢測網路(Fast RCNN)的銜接,因此下一篇部落格就來介紹該自定義層的內容:Faster RCNN程式碼詳解(五):關於檢測網路(Fast RCNN)的proposal

附:
Faster RCNN論文中的公式。bbox_transform函式中的第一個輸入anchors相當於這裡的xa,第二個輸入gt_boxes[argmax_overlaps, :4]相當於這裡的x*(y,w,h同理),而bbox_transform函式實現的就是截圖中下面兩行的4個式子,得到的tx*,ty*,tw*,th*就對應bbox_transform函式的輸出bbox_targets。而前面兩行式子計算的是在你得到預測的bbox資訊(x,y,w,h)後與anchor box資訊計算得到的tx,ty,tw,th。模型的迴歸部分損失函式計算是基於tx,ty,tw,th和tx*,ty*,tw*,th*。

這裡寫圖片描述

相關推薦

Faster RCNN程式碼關於anchor前世今生

在上一篇部落格中介紹了資料處理的整體結構:Faster RCNN程式碼詳解(三):資料處理的整體結構。這一篇部落格介紹資料處理的細節——關於anchor的前世今生,程式碼在指令碼的:~/mx-rcnn/rcnn/io/rpn.py的assign_anchor函式

Faster RCNN程式碼資料處理的整體結構

在上一篇部落格中介紹了Faster RCNN網路結構的構建:Faster RCNN程式碼詳解(二):網路結構構建。網路結構是Faster RCNN演算法中最重要兩部分之一,這篇部落格將介紹非常重要的另一部分:資料處理。 資料處理是通過AnchorLoader類

elastic-job失效轉移

shard out utm monit 設置 borde 點滴 title 等於 elastic-job中最關鍵的特性之一就是失效轉移。配置了失效轉移之後,如果在任務執行過程中有一個執行實例掛了,那麽之前被分配到這個實例的任務(或者分片)會在下次任務執行之前被重新分配到其他

ZookeeperZookeeper中的zkCli.sh客戶端使用

zkCli.sh zookeeper客戶端 最好配置上環境變量連接操作:zkCli.sh -timeout 1000 -r -server 127.0.0.1 # -timeout 設置客戶端和服務器之間的超時時長,單位毫秒 # -r 只讀模式,不加就是讀寫模式 # -server IP:PORT 要

安卓專案實戰之強大的網路請求框架okGo使用Cookie的管理

Cookie概念相關 具體來說cookie機制採用的是在客戶端保持狀態的方案,而session機制採用的是在伺服器端保持狀態的方案。同時我們也看到,由於採用伺服器端保持狀態的方案在客戶端也需要儲存一個標識,所以session機制是需要藉助於cookie機制來達到儲存標識的目的,所謂ses

Tkinter 元件Radiobutton

Tkinter 元件詳解之Radiobutton Radiobutton(單選按鈕)元件用於實現多選一的問題。Radiobutton 元件可以包含文字或影象,每一個按鈕都可以與一個 Python 的函式或方法與之相關聯,當按鈕被按下時,對應的函式或方法將被自動執行。 Radiobutto

HTTPJAVA實現HTTP請求

通過上幾篇的文章,我們對HTTP已經已經有了一個初步的認識,對於"為什麼要用HTTP","怎麼用HTTP","HTTP是什麼"相信大家都有了一個了一個屬於自己的看法,今天這篇文章主要是程式碼的角度上去實現HTTP的請求。

Pygameevent 模組

pygame.event 用於處理事件與事件佇列的 Pygame 模組。 函式 pygame.event.pump()  —  讓 Pygame 內部自動處理事件 pygame.event.get()  —&nb

郵件實現------JavaMail 發送帶圖片和附件和接收郵件

發送 網絡圖 發送對象 true n) com 訪問權限 sub map   好了,進入這個系列教程最主要的步驟了,前面郵件的理論知識我們都了解了,那麽這篇博客我們將用代碼完成郵件的發送。這在實際項目中應用的非常廣泛,比如註冊需要發送郵件進行賬號激活,再比如OA項目中利用郵

Quartz學習——SSMM(Spring+SpringMVC+Mybatis+Mysql)和Quartz集成

webapp cron表達式 msi 接口 cli post 定時 報錯 gets Quartz學習——SSMM(Spring+SpringMVC+Mybatis+Mysql)和Quartz集成詳解(四) 當任何時候覺你得難受了,其實你的大腦是在進化,當任何時候你覺得

07-Linux中DNS

用戶 mail all 驗證 src 更改 條目 http nslookup 接“06-Linux中DNS詳解(三)” 九、配置主從DNS服務器實現域名解析容錯 1、實驗環境zhangyujia.com(192.168.80.100)為主區域,com(192.168.8

編碼原理---之字形掃描

便是 集中 img 詳解 工作 -- 漢字 如何 編碼原理 上一篇我們講到,經過量化後得到了諸多零值和整數值,本篇接下來講講編碼過程中過對這些值如何組織和處理,那就是ZigZag掃描嘍。 一、簡介 ZigZag掃描也稱作之字形掃描,何以得此稱謂,是因為其掃描的路徑特

Nginx模塊

nginx https fastcgi 一、Nginx之目錄瀏覽二、Nginx之log模塊三、Ning之gzip模塊四、Nginx之https服務五、Nginx之fastCGI模塊 一、配置Nginx提供目錄瀏覽功能 1.修改nginx配置文件 server { listen

Keepalived

mysql pan 節點 ios all -s 關閉 定義 interval 一.通過vrrp_script實現對集群資源的監控: Keepalived基礎HA功能時用到了vrrp_script這個模塊,此模塊專門用於對集群中服務資源進行監控。與此模塊一起使用

PE文件格式

ebs 位置 數位 地址 inf font pe文件 。。 地址轉換 PE文件格式詳解(四) 0x00 前言 上一篇介紹了區塊表的信息,以及如何在hexwrokshop找到區塊表。接下來,我們繼續深入了解區塊,並且學會文件偏移和虛擬地址轉換的知識。 0x01 區塊對齊值

PE檔案格式

PE檔案格式詳解(四) 0x00 前言   上一篇介紹了區塊表的資訊,以及如何在hexwrokshop找到區塊表。接下來,我們繼續深入瞭解區塊,並且學會檔案偏移和虛擬地址轉換的知識。 0x01 區塊對齊值   首先我們要知道啥事區塊對齊?為啥要區塊對齊?這個問題

【SpringBoot學習之路】08.Springboot配置檔案

轉載宣告:商業轉載請聯絡作者獲得授權,非商業轉載請註明出處.原文來自 © 呆萌鍾【SpringBoot學習之路】08.Springboot配置檔案詳解(四)  自動配置原理 配置檔案到底能寫什麼?怎麼寫?自動配置原理; 配置檔案能配置的屬性參照

Java 反射機制

Java 反射機制詳解(四) 4. 反射與泛型  定義一個泛型類: public class DAO<T> { //根據id獲取一個物件 T get(Integer id){ return null; }

Redis底層 整數集合

一、集合概述         對於集合,STL 的 set 相信大家都不陌生,它的底層實現是紅黑樹。無論插入、刪除、查詢都是 O(log n) 的時間複雜度。當然,如果用雜湊表來實現集合,插入、刪除、查詢都可以達到 O(1)。那麼為什麼集合要用紅黑樹