1.前言深度學習【目標追蹤】專欄寫過一篇對Siamese FC網絡的解析。接著Siamese網絡在單目標追蹤任務(SOT)上的應用,我們展開對Siamese RPN的論文解讀和代碼解析。提到RPN層,了解雙階段法目標檢測模型(FasterRCNN)的各位必定不會陌生。RPN層全稱叫做Region-Proposal-Network,中文翻譯:區域提議網絡,通過該網絡,產生一些圖像中前景和背景的候選框。但是就只是前景和背景,並沒有對前景候選框中的類別進行詳述。這裡放上FasterRCNN中的RPN層,如下圖FasterRCNN中的RPN層可以看出來,RPN層是有兩個分支的,上面一個特徵圖通道為18(18=9x2),用來獲得所有anchor(先驗框)前景和背景置信度(簡單來說,就是判別這個先驗框anchor中是不是前景或者背景),下面一個特徵圖通道數為36(36=9x4),用來獲得所有anchor(先驗框)的位置調整變量(簡單來說,就是每個先驗框anchor位置是固定的,所以需要通過這四個變量進行調整,成為預測框)。至此,對RPN的簡單介紹就完畢了。那麼RPN層和Siamese網絡如何進行融合,做到比傳統的核方法以及上一篇提到的Siamese FC更好的結果呢?這個疑問將通過本文的代碼解析和論文解讀進行分析,強烈推薦大家去讀原文。代碼連結如下,很輕便的代碼,就兩個py文件:https://github.com/huanglianghua/siamrpn-pytorch本文將對Siamese RPN進行詳細解析,主要從以下幾個部分進行解析
2.網絡結構原論文中給了Siamese RPN的兩個網絡結構圖,我們先看第一個,圖上紅色字體是我標註的一些符號,便於後面說明。由此可見,Siamese RPN網絡主要有Siamese 和 RPN網絡構成。Siamese網絡翻譯為孿生網絡比較合適,因為它由兩個分支構成(如上圖),這兩個分支的卷積神經網絡共享權重(實質就是同一個網絡,為了便於理解才這麼說的)。原論文中提到,
Here we use the modified AlexNet , where the groups from conv2 and conv4 are removed .這裡說明Siamese RPN用到了非常簡單的AlexNet網絡進行圖像特徵的提取。有人會問,這論文2018年發表的,那時候特徵提取網絡滿天飛,怎麼還在用老古董AlexNet?這是因為研究者們也嘗試將ResNet作為Siamese圖像特徵提取網絡,但是發現收效甚微,這個原因將在SiameseRPN++進行解析,本篇不做涉及。
self.feature = nn.Sequential(# conv1 nn.Conv2d(3, 192, 11, 2), nn.BatchNorm2d(192), nn.ReLU(inplace=True), nn.MaxPool2d(3, 2),# conv2 nn.Conv2d(192, 512, 5, 1), nn.BatchNorm2d(512), nn.ReLU(inplace=True), nn.MaxPool2d(3, 2),# conv3 nn.Conv2d(512, 768, 3, 1), nn.BatchNorm2d(768), nn.ReLU(inplace=True),# conv4 nn.Conv2d(768, 768, 3, 1), nn.BatchNorm2d(768), nn.ReLU(inplace=True),# conv5 nn.Conv2d(768, 512, 3, 1), nn.BatchNorm2d(512))結構簡單,清晰明了呀!從上面的圖可以清晰看出,該孿生網絡接受兩個輸入,一個叫做template frame(模板幀),是從視頻第一幀中人為框出來(可以理解為原圖中裁剪的一個區域)的物體位置;另一個輸入叫做detection frame(檢測幀),是被檢測的視頻段除了第一幀之外的其他幀。該Siamese(AlexNet)網絡將這兩個圖像分別映射為6x6x256大小的特徵圖 和22x22x256大小的特徵圖 。上面我們有提到過RPN層有兩個分支,一個分支做前景背景區分的(後面稱為分支1),另一個分支用來調整anchor(先驗框)的位置(後面稱為分支2)。我們從上面的圖中也可以看出,該RPN網絡也是由兩個分支組成的,每個分支接受兩個輸入(所以看起來有點像四個分支,其實是兩個,看輸出就知道了)。該RPN網絡的每個分支接受Siamese網絡的兩個輸出 和 通過卷積層 ( 改變通道維度) 後的特徵圖作為輸入,對分支1來說,分別為 和 ,對分支2來說,分別為 和 。值得注意的是, 的通道維度為2k x 256, 的通道維度為4k x 256,這裡的k是anchor的數量,至於為什麼有這個2k和4k,相信接觸過FasterRCNN中RPN層的小夥伴們必定不會陌生。2k中存放著每個cell的k個先驗框中物體為前景和背景的置信度為多少,由於存放兩個信息,所以就是2k。同理,4k包含著每個cell的k個先驗框為了靠近真實框而做的位置移動信息。解釋到這裡,會有人問了,這個256是什麼?其實是這樣的,網絡的輸出 和 是通道為2k和4k的特徵圖,如上圖所示。那麼這個 和 相當於卷積核參數,被卷積的對象是 和 。圖中的五角星運算符就是卷積運算的意思。那麼這個256就是被卷積對象的維度,2k和4k就是卷積核的個數。class SiamRPN(nn.Module):
def __init__(self, anchor_num=5):super(SiamRPN, self).__init__()self.anchor_num = anchor_numself.feature = nn.Sequential(# conv1 nn.Conv2d(3, 192, 11, 2), nn.BatchNorm2d(192), nn.ReLU(inplace=True), nn.MaxPool2d(3, 2),# conv2 nn.Conv2d(192, 512, 5, 1), nn.BatchNorm2d(512), nn.ReLU(inplace=True), nn.MaxPool2d(3, 2),# conv3 nn.Conv2d(512, 768, 3, 1), nn.BatchNorm2d(768), nn.ReLU(inplace=True),# conv4 nn.Conv2d(768, 768, 3, 1), nn.BatchNorm2d(768), nn.ReLU(inplace=True),# conv5 nn.Conv2d(768, 512, 3, 1), nn.BatchNorm2d(512))
self.conv_reg_z = nn.Conv2d(512, 512 * 4 * anchor_num, 3, 1)self.conv_reg_x = nn.Conv2d(512, 512, 3)self.conv_cls_z = nn.Conv2d(512, 512 * 2 * anchor_num, 3, 1)self.conv_cls_x = nn.Conv2d(512, 512, 3)self.adjust_reg = nn.Conv2d(4 * anchor_num, 4 * anchor_num, 1)
def forward(self, z, x):return self.inference(x, **self.learn(z))
def learn(self, z): z = self.feature(z) kernel_reg = self.conv_reg_z(z) kernel_cls = self.conv_cls_z(z)
k = kernel_reg.size()[-1] kernel_reg = kernel_reg.view(4 * self.anchor_num, 512, k, k) kernel_cls = kernel_cls.view(2 * self.anchor_num, 512, k, k)
return kernel_reg, kernel_cls
def inference(self, x, kernel_reg, kernel_cls): x = self.feature(x) x_reg = self.conv_reg_x(x) x_cls = self.conv_cls_x(x)
out_reg = self.adjust_reg(F.conv2d(x_reg, kernel_reg)) out_cls = F.conv2d(x_cls, kernel_cls)
return out_reg, out_cls網絡先定義了一些相關層,比如使用AlexNet作為Siamese網絡的特徵提取網絡。代碼中的x和z分別對應上圖中 和 ,其他對應關係如下:·x_reg對應 ·x_cls對應 ·kernel_reg對應 ·kernel_cls對應 ·out_reg 對應 ·out_cls 對應 out_reg = self.adjust_reg(F.conv2d(x_reg, kernel_reg)) out_cls = F.conv2d(x_cls, kernel_cls)至此,對SiameseRPN的網絡結構解析就結束了。可以看出來,SiameseRPN網絡並不複雜。有點尷尬的是這段代碼不包含訓練過程,我重新找了一段代碼,代碼連接如下:https://github.com/zllrunning/SiameseX.PyTorch/issues根據上面代碼中的siamese RPN的部分代碼,我們對Siamese RPN的訓練過程進行解析。3.訓練過程詳細解析上面對Siamese RPN網絡的結構進行了詳細的解析,這裡我們根據網絡結構對訓練過程的一些細節進行詳細解析。具體包括:上述連結代碼中的 文件中的函數 前面有這樣的定義,如下train_loader = torch.utils.data.DataLoader(dataset.listDataset(args.ilsvrc, args.youtube, args.data_type,shuffle=True,transform=transforms.Compose([transforms.ToTensor(), transforms.Normalize(mean=[0.485, 0.456, 0.406],std=[0.229, 0.224, 0.225]),]),train=True,
batch_size=args.batch_size,num_workers=args.workers, coco=coco),batch_size=args.batch_size)這裡引出了一個簡單的數據集定義 ,我們進入該數據集定義中,找到 __getitem__(self, index)定義中的下面代碼: elif self.data_type == 'RPN':
z, x, gt_box, regression_target, label = load_data_rpn(pair_infos, self.coco, rpnpp=self.rpnpp)
if self.transform is not None: z = self.transform(z) x = self.transform(x)
regression_target = torch.from_numpy(regression_target) label = torch.from_numpy(label)
return z, x, regression_target, label我們進入函數load_data_rpn中,代碼定義如下def load_data_rpn(pair_infos, discrim, train=True, rpnpp=False):if not rpnpp:anchors = generate_anchor(8, [8, ], [0.33, 0.5, 1, 2, 3], 17)gt = np.zeros((1, 17, 17))else:anchors = generate_anchor(8, [8, ], [0.33, 0.5, 1, 2, 3], 25)gt = np.zeros((1, 25, 25))gt[:, :, :] = -1 #所有都設置為-1gt[0, 8, 8] = 1. #中心設置為1
img_path1 = pair_infos[0][0] #圖片地址img_path2 = pair_infos[1][0]
bs1 = pair_infos[0][1] # xmin xmax ymin ymaxbs2 = pair_infos[1][1]
gt1 = Rectangle(bs1[0], bs1[2], bs1[1] - bs1[0], bs1[3] - bs1[2]) #xmin ymin w hgt2 = Rectangle(bs2[0], bs2[2], bs2[1] - bs2[0], bs2[3] - bs2[2])
gt1 = convert_bbox_format(gt1, to='center-based') # x_cer,y_cer,w,hgt2 = convert_bbox_format(gt2, to='center-based')
img1 = Image.open(img_path1).convert('RGB')img2 = Image.open(img_path2).convert('RGB')
zbox1 = get_zbox(gt1, 0.25)zbox2 = get_zbox(gt2, 0.25)
scales_w = 1.04 ** (random.random() * 6 - 3) # 1.08..scales_h = 1.04 ** (random.random() * 6 - 3)
zbox2_scaled = Rectangle(zbox2.x, zbox2.y, zbox2.width * scales_w, zbox2.height * scales_h)
dx = 0
dy = 0
xbox2 = get_xbox(zbox2_scaled, dx, dy) # we assume second is the search region
z = gen_xz(img1, zbox1, to='z')x = gen_xz(img2, xbox2, to='x')
info = [dx, dy, gt2.width / scales_w / zbox2.width, gt2.height / scales_h / zbox2.height]
gt_box = np.array([-info[0] * 64, -info[1] * 64, info[2] * 128, info[3] * 128])
anchor_xctr = anchors[:, :1]anchor_yctr = anchors[:, 1:2]anchor_w = anchors[:, 2:3]anchor_h = anchors[:, 3:]gt_cx, gt_cy, gt_w, gt_h = gt_box
target_x = (gt_cx - anchor_xctr) / anchor_wtarget_y = (gt_cy - anchor_yctr) / anchor_htarget_w = np.log(gt_w / anchor_w)target_h = np.log(gt_h / anchor_h)regression_target = np.hstack((target_x, target_y, target_w, target_h))
iou = compute_iou(anchors, gt_box).flatten() # print(np.max(iou))
pos_index = np.where(iou > 0.4)[0]neg_index = np.where(iou < 0.3)[0]
label = np.ones_like(iou) * -1label[pos_index] = 1label[neg_index] = 0
return z, x, gt_box, regression_target, label·讀取search image 和template image 並進行裁剪,獲取相應的region·產生anchor,並獲得ground true box。·anchor與ground true box 之間的offset進行編碼,產生 regression_target·根據anchor與ground true box 之間的IOU產生正樣本和負樣本,作為置信度label最後,根據真實的標籤和網絡輸出的預測值,進行損失函數的設定即可這裡我們回到 中的函數 中,找到以下代碼段 # 預測pred_score, pred_regression = model(z, x)
pred_conf = pred_score.reshape(-1, 2, 5 * 17 * 17).permute(0, 2, 1)
pred_offset = pred_regression.reshape(-1, 4, 5 * 17 * 17).permute(0, 2, 1)
# 目標targetregression_target = regression_target.type(torch.FloatTensor).cuda()conf_target = conf_target.type(torch.LongTensor).cuda()
# 損失cls_loss = rpn_cross_entropy(pred_conf, conf_target)reg_loss = rpn_smoothL1(pred_offset, regression_target, conf_target)
loss = cls_loss + reg_loss我們發現Siamese RPN中分類損失採用了交叉熵,回歸損失採用了smoothL1。至此,對Siamese RPN的解析就結束了。有空的話大家可以跑一跑模型,或者對代碼中的細節進行深度解讀,相信大家會有更多收穫的。3.總結本文我們介紹了一種單目標檢測器Siamese RPN網絡,並對該網絡結構和訓練的一些細節進行了詳細的解析,當然由於時間問題並沒有對一些更小的細節進行解析,如果後面時間充裕,會進行一些補充,謝謝大家支持!