Anchor Free,框即是點,CenterNet

目錄

前沿:

Loss設計:

如何將網絡預測轉化爲座標框:

網絡預測轉化爲座標框:

網絡預測轉化爲輪廓點:

網絡結構:

2d檢測任務:

姿態估計任務:

實驗結果:

牛刀小試:

官方模型測試:

自己數據訓練目標檢測:

網絡基礎結構改進:

總結:


 

論文:Objects as Points

Github:https://github.com/xingyizhou/CenterNet

 

CVPR 2019


前沿:

CenterNet,一個anchor free的新的檢測算法,算是對cornerNet的改進,在cornerNet基礎上,引入了中心點的概念,因此,稱爲CenterNet。

算法亮點,

  1. anchor free,大大減少了anchor部分的計算量,並且不需要nms這樣的後處理。
  2. 一個框架可以做2d檢測,3d檢測,pose姿態估計,3種不同的任

速度夠快,速度和精度的良好平衡,在MS-COCO上28.1%的MAP,142FPS,或者,37.4%的MAP,52FPS。採用多尺度測試的話,可以達到45.1%的MAP,1.4FPS。

CenterNet的anchor free思想和基於anchor框架的區別:

CenterNet可以看作基於anchor框架的演變,可以看作只有一個形狀的anchor。

A center point can be seenas a single shape-agnostic anchor

傳統的基於anchor的思想,需要計算anchor和ground truth的IOU,IOU>0.7是正樣本,IOU<0.3是負樣本,其他的忽略掉。

CenterNet只取ground truh邊框的中心一點作爲anchor,只回歸該中心點和寬,高。該點周圍的其餘點當作負樣本。

  1. CenterNet只根據位置佈置anchor,背部關心IOU,不需要像基於anchor的框架那樣人工設置閾值來進行前景背景分類。
  2. 由於每一個物體只有一個正的anchor,因此不再需要後處理的NMS,只需要提取輸出feature map種峯值最高的點即可。
  3. CenterNet的網絡輸出特徵層尺度更大,是原圖的1/4,而基於anchor的方法,最後一層特徵圖大小是原圖的1/16,因此基於anchor的方法需要多個不同長寬比和尺度的anchor。這裏爲什麼anchor free的方法,需要輸出是原圖的1/4呢?因爲,大多數anchor free的方法,基本都需要下采樣+上採樣這樣的結構,來綜合底層和高層特徵,纔可以保證分割或者回歸的準確性。

Loss設計:

輸入圖片大小爲I (W ×H×3),其中W=H=512,

網絡最後輸出的特徵層Y大小爲,(W/R ×H/R×C),C爲輸出的通道數,R爲輸出的滑動步長,Y(x,y,c)=1表示檢測到的關鍵點,Y(x,y,c)=0表示背景

實際訓練中的groundtruth使用高斯核進行處理,核函數爲,

對於一維標準高斯核函數來說,公式如下,

其中,

a表示核函數的最大值,因爲e函數的指數是一個分子比分母大的函數,e函數最大就是e0=1,所以,核函數最大值就是a,也就是核函數曲線的最高點峯值座標

b表示核函數的均值u,也就是核函數曲線的中心軸座標

c表示核函數的方差seigema,也就是核函數曲線的寬度

 

二維核函數與此類比,

那麼回到論文的問題,

(1)這裏的高斯核函數的方差是根據圖像中目標大小進行自適應確定的,不同的物體具有不同的方差。

得到的收益就是,如果這個物體大,那麼經過對groundtruth進行高斯濾波後,得到的點的光圈就會比較大,反之,如果目標小,得到的點的光圈就會比較小。

而所有的groundtruth進行高斯濾波後的最高點波峯值都是1

如果兩個核函數的區域相交,則分別取對應位置的最大值,而不是像傳統的密度估計中那樣,對所有的高斯核處理後的結果進行累加,造成波谷1+波谷2>=波峯的情況。

If two Gaussians of the same class overlap, we take the element-wise maximum [4]

(2)這裏爲什麼需要對groundtruth進行一個高斯濾波呢?似乎很多問題都是這樣的操作,包括密度估計,關鍵點檢測等,那麼這裏不做可以不可以?

首先這裏預測中心點的loss是一個分類的loss。也就是說預測完的每一個類別對應的feature上的點是每一個groundtruth位置內,只有一個像素比較亮,就是說,預測的特徵圖中只有0,1這樣2種類型的整形值。而不是像groundtruth一樣,是一個float類型的值,光圈中間點爲1,其餘周圍點慢慢降低的特徵圖。

而好多關鍵點檢測,deeplabcut使用的是迴歸,預測完就是一個float類型的特徵圖。而openpose也是基於迴歸做的,基於歐氏距離的loss,輸出也是一個float類型的特徵圖。好多密度估計的loss也是基於迴歸的loss。

另一個就是所謂groundtruth的中心點,怎麼就是最佳最合理的中心點呢?難度多一個像素,少一個像素就不是最佳的嗎?還是人達標的框一定是最佳的真實答案呢。肯定不是這樣的情況。所以這就是經過高斯核函數濾波的好處。

經過高斯濾波後,groundtruth=1的位置,會有loss傳遞,其他不爲1的位置是一個float類型的數值,也有相應的loss傳遞,float類型的groundtruth的值越大,loss越大。感覺還有種soft label的思想。

而如果不經過高斯濾波,那麼久會出現只有一個位置的點是groundtruth=1,其他位置都等於0。而這個點周圍的其他點,都有可能是最佳的那個點。但是實際訓練的時候,卻都當成了0處理,這樣訓練完,也許效果上就會有區別吧。

(3)這裏預測這個中心點,本質就是語義分割的思想。但是還有點區別,如果是語義分割的話,並且使用的是基於迴歸的loss,那麼類別應該是C類,如果是使用的基於分類的loss,那麼類別應該是C+1類,而這裏是C類,那麼就只能是groundtruth=0 的位置不進行loss的傳遞,計算loss的時候,通過對預測特徵圖和groundtruth進行對應位置乘積運算實現或者,支取對應的index實現。

def _slow_neg_loss(pred, gt):
  '''focal loss from CornerNet'''
  pos_inds = gt.eq(1)
  neg_inds = gt.lt(1)

  neg_weights = torch.pow(1 - gt[neg_inds], 4)

  loss = 0
  pos_pred = pred[pos_inds]
  neg_pred = pred[neg_inds]

通過這樣的實現,分類的這個分支只需要C個通道就可以。從程序來說,就是寫法上的區別。本質還是這個groundtruth只有0,1這2個區別,groundtruth=0的時候,如果沒有背景類別這個通道,則可以通過前景類別的通道間接傳遞loss,如果有背景類別的通道,也不可以通過背景類別的通道傳遞loss。本質是一樣的。主要還是因爲這裏的groundtruth是 one-hot形式,是hard-label,如果這裏將label改爲soft-label也就是說,groundtruth爲0.9, 0.1這樣的形式,就必須得是C+1個通道了。

 

plus:

假設都是用voc數據集

faster rcnn:最後的輸出層分類部分的全連接層輸出的個數是21。雖然faster已經先經過前面的RPN的2分類,過濾掉了大部分背景類別,但是後續仍然有可能存在背景類別。

https://github.com/ShaoqingRen/faster_rcnn

RPN:

layer {
   name: "proposal_bbox_pred"
   type: "Convolution"
   bottom: "conv_proposal1"
   top: "proposal_bbox_pred"
	param {
		lr_mult: 1.0
	}
	param {
		lr_mult: 2.0
	}
   convolution_param{
	   num_output: 36	# 4 * 9(anchors) 
	   kernel_size: 1
	   pad: 0
	   stride: 1
	   weight_filler {
		 type: "gaussian"
		 std: 0.01
	   }
	   bias_filler {
		 type: "constant"
		 value: 1
	   }
   }
}

#-----------------------output------------------------

# to enable the calculation of softmax loss, we first reshape blobs related to SoftmaxWithLoss
layer {
   bottom: "proposal_cls_score"
   top: "proposal_cls_score_reshape"
   name: "proposal_cls_score_reshape"
   type: "Reshape"
   reshape_param{
	   shape {
			dim: 0 
			dim: 2
			dim: -1 
			dim: 0
		}
	}
}

RCNN:

layer {
	bottom: "fc7"
	top: "cls_score"
	name: "cls_score"
	param {
		lr_mult: 1.0
	}
	param {
		lr_mult: 2.0
	}
	type: "InnerProduct"
	inner_product_param {
		num_output: 21
		weight_filler {
			type: "gaussian"
			std: 0.01
		}
		bias_filler {
			type: "constant"
			value: 0
		}
	}
}

layer {
	bottom: "fc7"
	top: "bbox_pred"
	name: "bbox_pred"
	type: "InnerProduct"
	param {
		lr_mult: 1.0
	}
	param {
		lr_mult: 2.0
	}
	inner_product_param {
		num_output: 84  #21*4
		weight_filler {
			type: "gaussian"
			std: 0.001
		}
		bias_filler {
			type: "constant"
			value: 0
		}
	}
}

SSD:分類的類別爲21類,因爲,使用softmax loss,肯定會有一個值最大,所以必須得加背景類別。

https://github.com/chuanqi305/MobileNet-SSD/blob/master/train.prototxt

layer {
  name: "mbox_loss"
  type: "MultiBoxLoss"
  bottom: "mbox_loc"
  bottom: "mbox_conf"
  bottom: "mbox_priorbox"
  bottom: "label"
  top: "mbox_loss"
  include {
    phase: TRAIN
  }
  propagate_down: true
  propagate_down: true
  propagate_down: false
  propagate_down: false
  loss_param {
    normalization: VALID
  }
  multibox_loss_param {
    loc_loss_type: SMOOTH_L1
    conf_loss_type: SOFTMAX
    loc_weight: 1.0
    num_classes: 21
    share_location: true
    match_type: PER_PREDICTION
    overlap_threshold: 0.5
    use_prior_for_matching: true
    background_label_id: 0
    use_difficult_gt: true
    neg_pos_ratio: 3.0
    neg_overlap: 0.5
    code_type: CENTER_SIZE
    ignore_cross_boundary_bbox: false
    mining_type: MAX_NEGATIVE
  }
}

yolov3:20類,因爲使用的是多個sigmoid來代替softmax,本質上每一個sigmoid都是前景,背景分類問題。https://github.com/pjreddie/darknet/blob/master/cfg/yolov3-voc.cfg

[yolo]
mask = 0,1,2
anchors = 10,13,  16,30,  33,23,  30,61,  62,45,  59,119,  116,90,  156,198,  373,326
classes=20
num=9
jitter=.3
ignore_thresh = .5
truth_thresh = 1
random=1

 

最終,中心點的loss函數爲基於focal loss改進的損失函數,

α ,β是focal loss的超參數,分別取α =2,β=4,N表示一幅圖像I中的點的數目

 

偏移offset使用L1 loss進行計算,

其中,R爲網絡的下采樣率,

p表示所有物體中心點的groundtruth,比如中心點的,

p~表示預測的物體中心點的座標,

2個做差後,就是預測的中心點和實際座標點的偏移,就是需要回歸的真實的偏移的groundtruth

Op~就表示預測的偏移量

 

問題來了,這裏的offset分支,可不可以沒有,沒有會有什麼問題產生?爲什麼faster RCNN沒有 offset分支?

按照論文的下采樣率,從輸入圖片到網絡最後一層特徵圖,會進行2次下采樣,也就是最後一層特徵圖上1個像素表示原圖的4*4=16個像素。那麼問題來了,這個座標點到底對應這16個像素的哪一個,是不知道的。所以爲了得到這個對應關係,這裏必須得有一個offset分支。就好比ctpn,也有這個offset 分支,來對左右的邊界進行精確定位。

Faster RCNN基於anchor迴歸得到的tx,ty,tw,th都是float類型的數值,可以對應爲原圖的任意一個位置,自然就不存在offset問題。而本文的CenterNet得到的框的位置是整數值,所以會有這樣的問題。

 

物體的size使用L1 loss進行計算,

最終,整體的loss 就是中心點的loss(Lk)+物體寬高的loss(Lsize)+偏移的loss(Loff)

參數,λsize = 0:1 , λoff = 1

在推斷部分,每一個類別一個輸出特徵圖。如果一個點周圍的8領域的像素都比中心該點的像素值小,則將其當作一個檢測出的peak 點(該操作可以通過3*3的max pooling實現)。每一個特徵圖,取前100個peak點,最後通過卡閾值得到最終的結果。

 

對於這塊的3*3 pooling,在src/lib/models/decode.py中,

def _nms(heat, kernel=3):
    pad = (kernel - 1) // 2

    hmax = nn.functional.max_pool2d(
        heat, (kernel, kernel), stride=1, padding=pad)
    keep = (hmax == heat).float()
    return heat * keep

其實pytorch的pooling是可以返回index的,這點就比tf的更靈活,像segnet裏面,都是作者使用caffe自己實現的。

pytorch 的pooling 接口:

class torch.nn.MaxPool2d(kernel_size, stride=None, padding=0, dilation=1, return_indices=False, ceil_mode=False)

return_indices - 如果等於True,會返回輸出最大值的序號,對於上採樣操作會有幫助

tensorflow的pooling 接口:

tf.nn.max_pool(
    value,
    ksize,
    strides,
    padding,
    data_format='NHWC',
    name=None
)

並沒有index返回。

實現測試,

import torch
from torch import nn

input = torch.Tensor([1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16]).view(1,1,4,4)
print(input)
downsample ,index= nn.MaxPool2d(kernel_size=2, stride=2,return_indices=True)(input)
out = nn.MaxUnpool2d(kernel_size =2, stride=2)(downsample,index)
print(out)

“””
tensor([[[[ 1.,  2.,  3.,  4.],
          [ 5.,  6.,  7.,  8.],
          [ 9., 10., 11., 12.],
          [13., 14., 15., 16.]]]])
tensor([[[[ 0.,  0.,  0.,  0.],
          [ 0.,  6.,  0.,  8.],
          [ 0.,  0.,  0.,  0.],
          [ 0., 14.,  0., 16.]]]])
“””

 

這裏爲什麼需要一個3*3的max pooling,這裏個人理解,其實就是很類似NMS的一個操作。本質出現這個問題,還是因爲中心點的最佳位置不是可以確切肯定的。這樣就可以去掉框上累加框的情況。但是這個思想,本質也沒有解決高遮擋的高IOU目標檢測問題。NMS沒解決這個問題,3*3的max pooling也麼有解決。

但是centerNet由於自身下采樣次數少,所以高IOU問題影響較少,只有0.1%,而基於anchor思想的卻有大約2%的影響。faster RCNN在iou 0.5的閾值下,有20%的高遮擋問題。

CenterNet is unable to predict < 0:1% of objects due to collisions in center points. This is much less than slow- or fastRCNN miss due to imperfect region proposals [52] (∼ 2%),and fewer than anchor-based methods miss due to insufficient anchor placement [46] (20:0% for Faster-RCNN with15 anchors at 0:5 IOU threshold). In addition, 715 pairs of objects have bounding box IoU > 0:7 and would beassigned to two anchors, hence a center-based assignment causes fewer collisions.

 

如果是需要mul scale的推斷的話,每一個scale輸出的結果就需要通過NMS的融合。如果只是單一scale做推斷,就不需要NMS操作。

For multi-scale, we use NMS to merge results

 

如何將網絡預測轉化爲座標框:

網絡預測轉化爲座標框:

其中,(xi,yi)表示預測的中心點x,y座標。

(δxi; δyi) = Oxi;yi,表示預測的中心點座標x,y的偏移
( wi; hi) = Sxi;yi,表示物體框的大小寬度和高度。

 

通過中心點座標加上中心點座標在x,y兩個方向的偏移量,得到準確的中心點座標。然後使用中心點座標分別減去寬,高的一半,即得到人體目標框的左上角座標點(x1,y1)和右下角座標點(x2,y2)。

 

網絡預測轉化爲輪廓點:

使用下面的公式轉化爲最終的關鍵點座標

其中,(x,y)表示目標物體中心點的座標,即hm分支。Jxyj表示第j個點的(x,y)座標的偏移量,即hps分支。但是這樣的基於直接回歸的方式預測的關鍵點座標的誤差是相對較大的。

因此,這裏使用一個技巧,即根據直接回歸可以確定出每一個輪廓點的大致位置,然後找這個位置附近的置信度大於0.1的權值最大的那個點Lj(hm_hp),將其作爲真正的輪廓點。然後使用該點加上偏移(hp_offset)得到最終的輪廓點的座標。

Lj = hm_hp+hp_offset

 


網絡結構:

(a)沙漏(HourGlass)網絡結構

(b)ResNet結構+反捲積

(c)原生的DLA-34結構

(d)本文基於DLA-34,修改後的結構,增加了skip connections。

模型效果:Hourglass > DLA > Resnet

網絡輸入爲512*512,輸出爲128*128,網絡中使用的卷積conv是可變形卷積DeformConv,可變形卷積對性能的提升還是很明顯的。

可變形卷積的使用感觸:

(1)參數量相比正常卷積大

(2)計算量比正常卷積大,速度能慢出近1半來。

(3)效果提升非常明顯,loss下降的可以更低。

(4)DCNv2還加入了卷積像素的加權,類似attention,效果更好。

(5)缺點,pytorch並不原生支持。

數據增強方式包括,random flip, random scaling (0.6-1.3), cropping, color jittering

網絡的基礎結構可以採用,ResNet-18, ResNet-101, DLA-34, Hourglass-104,在這些基礎結構的基礎上進行了下面2個修改,

  1. 增加可變形卷積(deformable convolution)
  2. 增加反捲積,採用沙漏結構(Hourglass)

CenterNet模型可以適用於傳統的2d目標檢測,3d目標檢測,姿態估計,等3個任務,不同的任務,最終的模型輸出層略有區別。

2d檢測任務:

輸出3個分支,分別是,

  1. 物體的特徵圖,一個類別一個channel,包含C個channel。
  2. 物體中心點的偏移,包含x,y兩個偏移量,因此是2個channel。
  3. 物體的大小,也就是寬,高,因此也是2個channel。

最終網絡輸出,C+4個預測分支。

 

姿態估計任務:

這裏我的訓練任務的pose關鍵點有5個。

輸出6個分支,分別是,

  1. hm,128*128*C,物體的特徵圖,一個類別一個channel,包含C個channel。
  2. reg,128*128*2,物體中心點的偏移,包含x,y兩個偏移量,因此是2個channel。
  3. wh,128*128*2,物體的大小,也就是寬,高,因此也是2個channel。
  4. hps,128*128*k*2,基於迴歸的思想Coordinate,物體的關鍵點基於中心點的偏移,輸出k*2個通道,k表示關鍵點的數目,2表示x,y兩個偏移。
  5. hm_hp,128*128*k,基於Heatmap分割的思想,得到的物體關鍵點的特徵圖,輸出k個通道,一個點一個通道。
  6. hp_offset,128*128*2,K個關鍵點的偏移,所有這些關鍵點採用同樣的偏移量,x,y兩個指標,輸出2個通道。

關鍵點回歸的Ground Truth 的構建問題,主要有兩種思路, Coordinate 和 Heatmap,Coordinate 即直接將關鍵點座標作爲最後網絡需要回歸的目標,這種情況下可以直接得到每個座標點的直接位置信息; Heatmap 即將每一類座標用一個概率圖來表示,對圖片中的每個像素位置都給一個概率,表示該點屬於對應類別關鍵點的概率, 比較自然的是,距離關鍵點位置越近的像素點的概率越接近 1,距離關鍵點越遠的像素點的概率越接近 0,具體可以通過相應函數進行模擬,如Gaussian 等。

Coordinate 的優點是參數量少,缺點是效果較差,容易訓練學習。

Heatmap 的優點是容易訓練,準確性高,缺點是參數量大,隨着要檢測的點的個數呈爆炸性增長。

實際使用的時候,基於迴歸預測的點的座標(4),肯定是沒有基於分割思路預測的點的座標(5)準確。所以,實際使用的時候,使用一個物體框內部,離得迴歸的點最近的分割的點作爲最終預測的點。

We then assign each regressed location lj to its closest detected keypoint arg minl2Lj (l - lj)2 considering only joint
detections within the bounding box of the detected object

 


實驗結果:

不同基礎網絡結構對比,

COCO數據集2d檢測結果對比,

KITTI 3d檢測結果對比,

COCO pose檢測結果對比,


 

cuda9.0,pytorch0.4.1可能安裝錯誤:

(1)RuntimeError: cuDNN version mismatch: PyTorch was compiled against 7102 but linked against 7301

解決辦法:conda install cudnn=7.1.2

(2)skipping 'pycocotools/_mask.c' Cython extension (up-to-date)
building 'pycocotools._mask' extension
x86_64-conda_cos6-linux-gnu-gcc -pthread -Wno-unused-result -Wsign-compare -DNDEBUG -fwrapv -O2 -Wall -Wstrict-prototypes -march=nocona -mtune=haswell -ftree-vectorize -fPIC -fstack-protector-strong -fno-plt -O2 -pipe -march=nocona -mtune=haswell -ftree-vectorize -fPIC -fstack-protector-strong -fno-plt -O2 -pipe -fPIC -I/home/jiangxiaolong/Anaconda3/lib/python3.6/site-packages/numpy/core/include -I../common -I/home/jiangxiaolong/Anaconda3/include/python3.6m -c ../common/maskApi.c -o build/temp.linux-x86_64-3.6/../common/maskApi.o -Wno-cpp -Wno-unused-function -std=c99
unable to execute 'x86_64-conda_cos6-linux-gnu-gcc': No such file or directory
error: command 'x86_64-conda_cos6-linux-gnu-gcc' failed with exit status 1
make: *** [all] 錯誤 1

解決辦法:conda install gxx_linux-64

 

cuda10.0,pytorch1.0.0可能安裝錯誤:

安裝pytorch:

pip3 install -U https://download.pytorch.org/whl/cu100/torch-1.0.0-cp36-cp36m-linux_x86_64.whl

安裝可變形卷積:

cd src/lib/models/networks
rm -rf DCNv2
git clone https://github.com/CharlesShang/DCNv2.git
cd DCNv2
./make.sh         # build
python3 testcpu.py    # run examples and gradient check on cpu
python3 testcuda.py

 

自測pytorch0.4.1&torchvision0.2.1,pytorch1.0.0&torchvision0.2.1,pytorch1.4.0&torchvision0.5.0都可以進行訓練和測試。

 

牛刀小試:

官方模型測試:

目標檢測

python demo.py ctdet --demo /path/to/image/or/folder/or/video --load_model ../models/ctdet_coco_dla_2x.pth

關鍵點檢測:

python demo.py multi_pose --demo /path/to/image/or/folder/or/video/or/webcam --load_model ../models/multi_pose_dla_3x.pth

官方模型訓練:

目標檢測:

python main.py ctdet --exp_id coco_dla --arch dla_34  --batch_size 32 --master_batch 15 --lr 1.25e-4  --gpus 3,4 --load_model ../models/ctdet_coco_dla_2x.pth --resume

關鍵點檢測:

python main.py multi_pose --exp_id dla_3x --dataset coco_hp --batch_size 16 --master_batch 2 --lr 5e-4 --load_model ../models/multi_pose_dla_3x.pth --gpus 3,4 --num_workers 2 --num_epochs 320 --lr_step 270,300

 

自己數據訓練目標檢測:

比如最近很火的kesci大賽,水下目標檢測算法賽(2020年全國水下機器人(湛江)大賽),https://www.kesci.com/home/competition/5e535a612537a0002ca864ac

下載大賽數據集,然後進行xml轉化爲coco的json格式,

import os
import cv2
import json
import xml.dom.minidom
import xml.etree.ElementTree as ET

data_dir = './train' #根目錄文件,其中包含image文件夾和box文件夾(根據自己的情況修改這個路徑)

image_file_dir = os.path.join(data_dir, 'image')
xml_file_dir = os.path.join(data_dir, 'box')

annotations_info = {'images': [], 'annotations': [], 'categories': []}

categories_map = {'holothurian': 1, 'echinus': 2, 'scallop': 3, 'starfish': 4}

for key in categories_map:
    categoriy_info = {"id":categories_map[key], "name":key}
    annotations_info['categories'].append(categoriy_info)

file_names = [image_file_name.split('.')[0]
              for image_file_name in os.listdir(image_file_dir)]
ann_id = 1
for i, file_name in enumerate(file_names):

    image_file_name = file_name + '.jpg'
    xml_file_name = file_name + '.xml'
    image_file_path = os.path.join(image_file_dir, image_file_name)
    xml_file_path = os.path.join(xml_file_dir, xml_file_name)

    image_info = dict()
    image = cv2.cvtColor(cv2.imread(image_file_path), cv2.COLOR_BGR2RGB)
    height, width, _ = image.shape
    image_info = {'file_name': image_file_name, 'id': i+1,
                  'height': height, 'width': width}
    annotations_info['images'].append(image_info)

    DOMTree = xml.dom.minidom.parse(xml_file_path)
    collection = DOMTree.documentElement

    names = collection.getElementsByTagName('name')
    names = [name.firstChild.data for name in names]

    xmins = collection.getElementsByTagName('xmin')
    xmins = [xmin.firstChild.data for xmin in xmins]
    ymins = collection.getElementsByTagName('ymin')
    ymins = [ymin.firstChild.data for ymin in ymins]
    xmaxs = collection.getElementsByTagName('xmax')
    xmaxs = [xmax.firstChild.data for xmax in xmaxs]
    ymaxs = collection.getElementsByTagName('ymax')
    ymaxs = [ymax.firstChild.data for ymax in ymaxs]

    object_num = len(names)

    for j in range(object_num):
        if names[j] in categories_map:
            image_id = i + 1
            x1,y1,x2,y2 = int(xmins[j]),int(ymins[j]),int(xmaxs[j]),int(ymaxs[j])
            x1,y1,x2,y2 = x1 - 1,y1 - 1,x2 - 1,y2 - 1

            if x2 == width:
                x2 -= 1
            if y2 == height:
                y2 -= 1

            x,y = x1,y1
            w,h = x2 - x1 + 1,y2 - y1 + 1
            category_id = categories_map[names[j]]
            area = w * h
            annotation_info = {"id": ann_id, "image_id":image_id, "bbox":[x, y, w, h], "category_id": category_id, "area": area,"iscrowd": 0}
            annotations_info['annotations'].append(annotation_info)
            ann_id += 1

with  open('./annotations.json', 'w')  as f:
    json.dump(annotations_info, f, indent=4)

print('---整理後的標註文件---')
print('所有圖片的數量:',  len(annotations_info['images']))
print('所有標註的數量:',  len(annotations_info['annotations']))
print('所有類別的數量:',  len(annotations_info['categories']))

程序修改:

src/lib/opts.py

'ctdet': {'default_resolution': [512, 512], 'num_classes': 80

修改爲:

'ctdet': {'default_resolution': [512, 512], 'num_classes': 4,

因爲這個訓練集只有4個類別,

lib/datasets/dataset/coco.py,進行下面的修改,

  def __init__(self, opt, split):
    super(COCO, self).__init__()
    #self.data_dir = os.path.join(opt.data_dir, 'coco')
    #self.img_dir = os.path.join(self.data_dir, '{}2017'.format(split))
    self.data_dir = os.path.join(opt.data_dir, 'kesci')
    self.img_dir = os.path.join(self.data_dir, '{}'.format(split))
    if split == 'test':
      #self.annot_path = os.path.join(
      #    self.data_dir, 'annotations', 
      #    'image_info_test-dev2017.json').format(split)
      self.annot_path = os.path.join(self.data_dir,"annotations/annotations_test.json")
    else:
      if opt.task == 'exdet':
        #self.annot_path = os.path.join(
        #  self.data_dir, 'annotations', 
        #  'instances_extreme_{}2017.json').format(split)
        self.annot_path = os.path.join(self.data_dir,"annotations/annotations_val.json")
      else:
        self.annot_path = os.path.join(self.data_dir,"annotations/annotations_train.json")
        #self.annot_path = os.path.join(
        #  self.data_dir, 'annotations', 
        #  'instances_{}2017.json').format(split)
    self.max_objs = 128
    self.class_name = ['__background__','holothurian', 'echinus', 'scallop' ,'starfish']
    #self.class_name = [
    #  '__background__', 'person', 'bicycle', 'car', 'motorcycle', 'airplane',
    #  'bus', 'train', 'truck', 'boat', 'traffic light', 'fire hydrant',
    #  'stop sign', 'parking meter', 'bench', 'bird', 'cat', 'dog', 'horse',
    #  'sheep', 'cow', 'elephant', 'bear', 'zebra', 'giraffe', 'backpack',
    #  'umbrella', 'handbag', 'tie', 'suitcase', 'frisbee', 'skis',
    #  'snowboard', 'sports ball', 'kite', 'baseball bat', 'baseball glove',
    #  'skateboard', 'surfboard', 'tennis racket', 'bottle', 'wine glass',
    #  'cup', 'fork', 'knife', 'spoon', 'bowl', 'banana', 'apple', 'sandwich',
    #  'orange', 'broccoli', 'carrot', 'hot dog', 'pizza', 'donut', 'cake',
    #  'chair', 'couch', 'potted plant', 'bed', 'dining table', 'toilet', 'tv',
    #  'laptop', 'mouse', 'remote', 'keyboard', 'cell phone', 'microwave',
    #  'oven', 'toaster', 'sink', 'refrigerator', 'book', 'clock', 'vase',
    #  'scissors', 'teddy bear', 'hair drier', 'toothbrush']

然後開始訓練,

python main.py ctdet --exp_id coco_dla --arch dla_34  --batch_size 32 --master_batch 15 --lr 1.25e-4  --gpus 3,4 --load_model ../models/ctdet_coco_dla_2x.pth --resume

loss下降還是挺快的,v100 2卡,有30分鐘就可以看結果了,

模型保存在,../exp/ctdet/coco_dla/,訓練完成後,進行測試,

python demo.py ctdet --demo ../image_kesci --load_model ../exp/ctdet/coco_dla/model_best.pth   --gpus 5

 

自己數據訓練關鍵點檢測:

程序修改,整個程序寫的擴展性很差,修改的地方相對比較多,只要是涉及到類別數目的地方,涉及到點的個數的地方,都需要修改,

包括,

src/lib/opts.py

src/lib/datasets/dataset/coco_hp.py

src/lib/datasets/sample/multi_pose.py

src/lib/detectors/multi_pose.py

src/lib/utils/debugger.py

src/lib/utils/post_process.py

src/lib/models/utils.py

總之改完後,哪裏有錯誤,就改哪裏,最好定義變量num_classes ,num_keypoints ,實現一改全改。

 

訓練指令,

python main.py multi_pose --arch dla_34 --exp_id dla_3x --dataset coco_hp  --batch_size 16 --master_batch 2 --lr 5e-4 --load_model ../models/multi_pose_dla_3x.pth --gpus 3,4 --num_workers 2 --num_epochs 320 --lr_step 270,300

loss下降還是挺快的,v100 2卡,有30分鐘就可以看結果了,

測試效果,

python demo.py multi_pose --arch dla_34   --load_model ../exp/multi_pose/dla_3x/model_best.pth --demo ../image_fish --gpus 5

 

網絡基礎結構改進:

使用EfficientNet基礎結構替換DLA網絡結構,可以實現精度和速度的雙重平衡。

 

總結:

(1)簡單,快速,準確。

(2)anchor free領域新的里程碑

(3)一個框架,同時兼顧2d檢測,3d檢測,姿態估計

(4)實際訓練中,對於兩個物體中心點重疊的情況,CenterNet無能無力,只能將2個點,也就是2個物體,當作一個物體,一個點來處理。同理,測試的時候,對於兩個遮擋的中心點重合的物體,也只能檢測出一箇中心點。

  (5)   CenterNet的頭部分類分支,會隨着類別數目增加而爆炸性增長問題,這塊會是一個比較大的參數量。而face++的子彈頭檢測網絡這方面就做的很好。

(6)可變形卷積在移動端的不支持,替代爲傳統卷積,精度會降低。

(7)CenterNet相比於CornerNet,更像是將左上角點和右下角點的迴歸修改爲,一箇中心點和寬高的迴歸,從而不再需要corner pooling,不需要左上角點和右下角點的配對問題,不再需要後處理的NMS。

CenterNet相比於有anchor的檢測框架,本質上都是迴歸中心點和寬高,這點本質沒變。雖然centerNet的下采樣率較小,如果部anchor的話,應該布的更多才對。但是,centerNet在時間訓練的時候,只有groundtruth位置的中心點才被當成類似anchor的思路處理。這樣就大大的減少了anchor的數目,只需要一個anchor就可以,也就從本質上省去了NMS後處理操作。

其實認真一思考,其實本質上,也許就該是CenterNet這樣的思想來做。至於爲啥沒有,也許可能從最開始的機器學習中滑動窗口的過渡,需要延續這樣的思想了。人的思想也許很難有如此大跨度的昇華。

(8)  CenterNet爲了使用基於heatmap的關鍵點替代基於迴歸的關鍵點,直接取了離迴歸的點最近的heatmap的點。如果所訓練的點和點直接的距離較大,這樣做無所謂。但是如果,訓練的點和點之間的距離較小,這樣就會使得預測的2個不同的點都使用同一個heatmap的點。所以,如果點點之間距離較小,使用該方法就得慎重。

 

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