YOLOV3算法詳解


 YOLOV3

YOLO3主要的改進有:調整了網絡結構;利用多尺度特征進行對象檢測;對象分類用Logistic取代了softmax。

新的網絡結構Darknet -53

darknet-53借用了resnet的思想,在網絡中加入了殘差模塊,這樣有利於解決深層次網絡的梯度問題,每個殘差模塊由兩個卷積層和一個shortcut connections,

1,2,8,8,4代表有幾個重復的殘差模塊,整個v3結構里面,沒有池化層和全連接層,網絡的下采樣是通過設置卷積的stride為2來達到的,每當通過這個卷積層之后

圖像的尺寸就會減小到一半。而每個卷積層的實現又是包含     卷積+BN+Leaky relu  ,每個殘差模塊之后又要加上一個zero padding,具體實現可以參考下面的一張圖。

論文中所給的網絡結構如下,由卷積模塊和殘差模塊組成;

 

 

 模型可視化

 具體的全部模型結構可以從這個網站的工具進行可視化分析:

https://lutzroeder.github.io/netron/

從Yolo的官網上下載yolov3的權重文件,然后通過官網上的指導轉化為H5文件,然后可以再這個瀏覽器工具里直接看yolov3的每一層是如何分布的;類似下邊截圖是一部分網絡(最后的拼接部分);

 

 

整體來看Yolov3的輸入與輸出形式如下:

輸入416*416*3的圖像,通過darknet網絡得到三種不同尺度的預測結果,每個尺度都對應N個通道,包含着預測的信息;

每個網格每個尺寸的anchors的預測結果。

對比下yolov1,有7*7*2個預測;

對比下yolov2,有13*13*5個預測;

YOLOv3共有13*13*3 + 26*26*3  + 52*52*3個預測 。

每個預測對應85維,分別是4(坐標值)、1(置信度分數)、80(coco類別數)

 



 

 

 

 

多尺度檢測:

對於多尺度檢測來說,采用多個尺度進行預測,具體形式是在網絡預測的最后某些層進行上采樣拼接的操作來達到;對於分辨率對預測的影響如下解釋:

分辨率信息直接反映的就是構成object的像素的數量。一個object,像素數量越多,它對object的細節表現就越豐富越具體,也就是說分辨率信息越豐富。這也就是為什么大尺度feature map提供的是分辨率信息了。語義信息在目標檢測中指的是讓object區分於背景的信息,即語義信息是讓你知道這個是object,其余是背景。在不同類別中語義信息並不需要很多細節信息,分辨率信息大,反而會降低語義信息,因此小尺度feature map在提供必要的分辨率信息下語義信息會提供的更好。(而對於小目標,小尺度feature map無法提供必要的分辨率信息,所以還需結合大尺度的feature map)

 

YOLO3更進一步采用了3個不同尺度的特征圖來進行對象檢測。能夠檢測的到更加細粒度的特征。

對於這三種檢測的結果並不是同樣的東西,這里的粗略理解是不同給的尺度檢測不同大小的物體。

網絡的最終輸出有3個尺度分別為1/32,1/16,1/8;

在第79層之后經過幾個卷積操作得到的是1/32 (13*13) 的預測結果,下采樣倍數高,這里特征圖的感受野比較大,因此適合檢測圖像中尺寸比較大的對象。

然后這個結果通過上采樣與第61層的結果進行concat,再經過幾個卷積操作得到1/16的預測結果;它具有中等尺度的感受野,適合檢測中等尺度的對象。

91層的結果經過上采樣之后在於第36層的結果進行concat,經過幾個卷積操作之后得到的是1/8的結果,它的感受野最小,適合檢測小尺寸的對象。

concat:張量拼接。將darknet中間層和后面的某一層的上采樣進行拼接。拼接的操作和殘差層add的操作是不一樣的,拼接會擴充張量的維度,而add只是直接相加不會導致張量維度的改變。

 

使用Kmeans聚類的方法來決定anchors的尺寸大小:

 

YOLO2已經開始采用K-means聚類得到先驗框的尺寸,YOLO3延續了這種方法,為每種下采樣尺度設定3種先驗框,總共聚類出9種尺寸的先驗框。
在COCO數據集這9個先驗框是:(10x13),(16x30),(33x23),(30x61),(62x45),(59x119),(116x90),(156x198),(373x326)。
分配上,在最小的13*13特征圖上(有最大的感受野)應用較大的先驗框(116x90),(156x198),(373x326),適合檢測較大的對象。中等的26*26
特征圖上(中等感受野)應用中等的先驗框(30x61),(62x45),(59x119),適合檢測中等大小的對象。較大的52*52特征圖上(較小的感受野)應用
較小的先驗框(10x13),(16x30),(33x23),適合檢測較小的對象。
 
yolo v3對bbox進行預測的時候,采用了logistic regression。yolo v3每次對b-box進行predict時,輸出和v2一樣都是(tx,ty,tw,th,to)​ ,然后通過公式1計算出絕對的(x, y, w, h, c)。

logistic回歸用於對anchor包圍的部分進行一個目標性評分(objectness score),(用於NMS),即這塊位置是目標的可能性有多大。

公式一:

 

yolo_v3只會對1個prior進行操作,也就是那個最佳prior。而logistic回歸就是用來從9個anchor priors中找到objectness score(目標存在可能性得分)最高的那一個。

 

對象分類softmax改成logistic

預測對象類別時不使用softmax,改成使用logistic的輸出進行預測。這樣能夠支持多標簽對象(比如一個人有Woman 和 Person兩個標簽)。

 

YOLO的LOSS函數:

 1         # K.binary_crossentropy is helpful to avoid exp overflow.
 2         xy_loss = object_mask * box_loss_scale * K.binary_crossentropy(raw_true_xy, raw_pred[...,0:2], from_logits=True)
 3         wh_loss = object_mask * box_loss_scale * 0.5 * K.square(raw_true_wh-raw_pred[...,2:4]) 4 confidence_loss = object_mask * K.binary_crossentropy(object_mask, raw_pred[...,4:5], from_logits=True)+ \ 5 (1-object_mask) * K.binary_crossentropy(object_mask, raw_pred[...,4:5], from_logits=True) * ignore_mask 6 class_loss = object_mask * K.binary_crossentropy(true_class_probs, raw_pred[...,5:], from_logits=True) 7 8 xy_loss = K.sum(xy_loss) / mf 9 wh_loss = K.sum(wh_loss) / mf 10 confidence_loss = K.sum(confidence_loss) / mf 11 class_loss = K.sum(class_loss) / mf 12 loss += xy_loss + wh_loss + confidence_loss + class_loss 13 if print_loss: 14 loss = tf.Print(loss, [loss, xy_loss, wh_loss, confidence_loss, class_loss, K.sum(ignore_mask)], message='loss: ') 15 return loss

yolov3再keras中定義的Loss函數如上,基本可以看出,對於回歸預測的部分是采用多個mse均方差相加來進行的(類似於上邊所提到的v1中的Loss函數),對於分類部分和置信度是采用K.binary_crossentropy來進行的,最后把兩種Loss相加得出最終的loss

 

 

 

部分源碼分析:

 1 def yolo_eval(yolo_outputs,
 2               anchors,
 3               num_classes,
 4               image_shape,
 5               max_boxes=20,
 6               score_threshold=.6,
 7               iou_threshold=.5):
 8     """Evaluate YOLO model on given input and return filtered boxes."""
'''此函數的作用是對於Yolo模型的輸出進行篩選,通過yolo_boxes_and_scores來給yolo_outputs打分,
然后通過(
box_scores >= score_threshold)來初步把分數較低的預測去掉,再通過NMS操作對每個對象的anchors進行篩選,最終為每個對象得出一個分數最大且不重復的預測結果。'''

 9     num_layers = len(yolo_outputs) 10     anchor_mask = [[6,7,8], [3,4,5], [0,1,2]] if num_layers==3 else [[3,4,5], [1,2,3]] # default setting
11     input_shape = K.shape(yolo_outputs[0])[1:3] * 32
12     boxes = [] 13     box_scores = [] 14     for l in range(num_layers): 15         _boxes, _box_scores = yolo_boxes_and_scores(yolo_outputs[l], 16  anchors[anchor_mask[l]], num_classes, input_shape, image_shape) 17  boxes.append(_boxes) 18  box_scores.append(_box_scores) 19     boxes = K.concatenate(boxes, axis=0) 20     box_scores = K.concatenate(box_scores, axis=0) 21 
22     mask = box_scores >= score_threshold
'''設定分數的閾值'''
23 max_boxes_tensor = K.constant(max_boxes, dtype='int32') 24 boxes_ = [] 25 scores_ = [] 26 classes_ = [] 27 for c in range(num_classes): 28 # TODO: use keras backend instead of tf. 29 class_boxes = tf.boolean_mask(boxes, mask[:, c]) 30 class_box_scores = tf.boolean_mask(box_scores[:, c], mask[:, c]) 31 nms_index = tf.image.non_max_suppression( 32 class_boxes, class_box_scores, max_boxes_tensor, iou_threshold=iou_threshold)
'''進行NMS非極大值抑制操作,設置iou_threshold,篩選得出最佳結果; 這里使用的是tensorflow自帶的nms方法,pytorch中一般都是自定義的nms'''
33 class_boxes = K.gather(class_boxes, nms_index) 34 class_box_scores = K.gather(class_box_scores, nms_index) 35 classes = K.ones_like(class_box_scores, 'int32') * c 36 boxes_.append(class_boxes) 37 scores_.append(class_box_scores) 38 classes_.append(classes) 39 boxes_ = K.concatenate(boxes_, axis=0) 40 scores_ = K.concatenate(scores_, axis=0) 41 classes_ = K.concatenate(classes_, axis=0) 42 43 return boxes_, scores_, classes_

下面這個函數是加載模型調用上面的yolo_eval函數產生boxes,scores,classes:

 1     def generate(self):
 2         model_path = os.path.expanduser(self.model_path)
 3         assert model_path.endswith('.h5'), 'Keras model or weights must be a .h5 file.'
 4 
 5         # Load model, or construct model and load weights.
 6         num_anchors = len(self.anchors)
 7         num_classes = len(self.class_names)
 8         is_tiny_version = num_anchors==6 # default setting
 9         try:
10             self.yolo_model = load_model(model_path, compile=False)
11         except:
12             self.yolo_model = tiny_yolo_body(Input(shape=(None,None,3)), num_anchors//2, num_classes) \
13                 if is_tiny_version else yolo_body(Input(shape=(None,None,3)), num_anchors//3, num_classes)
14             self.yolo_model.load_weights(self.model_path) # make sure model, anchors and classes match
15         else:
16             assert self.yolo_model.layers[-1].output_shape[-1] == \
17                 num_anchors/len(self.yolo_model.output) * (num_classes + 5), \
18                 'Mismatch between model and given anchor and class sizes'
19 
20         print('{} model, anchors, and classes loaded.'.format(model_path))
21 
22         # Generate colors for drawing bounding boxes.
23         hsv_tuples = [(x / len(self.class_names), 1., 1.)
24                       for x in range(len(self.class_names))]
25         self.colors = list(map(lambda x: colorsys.hsv_to_rgb(*x), hsv_tuples))
26         self.colors = list(
27             map(lambda x: (int(x[0] * 255), int(x[1] * 255), int(x[2] * 255)),
28                 self.colors))
29         np.random.seed(10101)  # Fixed seed for consistent colors across runs.
30         np.random.shuffle(self.colors)  # Shuffle colors to decorrelate adjacent classes.
31         np.random.seed(None)  # Reset seed to default.
32 
33         # Generate output tensor targets for filtered bounding boxes.
34         self.input_image_shape = K.placeholder(shape=(2, ))
35         if self.gpu_num>=2:
36             self.yolo_model = multi_gpu_model(self.yolo_model, gpus=self.gpu_num)
37         boxes, scores, classes = yolo_eval(self.yolo_model.output, self.anchors,
38                 len(self.class_names), self.input_image_shape,
39                 score_threshold=self.score, iou_threshold=self.iou)
40         return boxes, scores, classes

our system only assigns one bounding box prior for each ground truth object.論文原話,v3預測bbox只預測一個最准確的;在訓練過程中yolov3是預測9個anchors的,對於loss的計算是找到iou最大的哪個,但是預測的時候只選一個最精確的。

 1 def preprocess_true_boxes(true_boxes, input_shape, anchors, num_classes):
 2     '''Preprocess true boxes to training input format
 3 
 4     Parameters
 5     ----------
 6     true_boxes: array, shape=(m, T, 5)
 7         Absolute x_min, y_min, x_max, y_max, class_id relative to input_shape.
 8     input_shape: array-like, hw, multiples of 32
 9     anchors: array, shape=(N, 2), wh
10     num_classes: integer
11 
12     Returns
13     -------
14     y_true: list of array, shape like yolo_outputs, xywh are reletive value
15 
16     '''
17     assert (true_boxes[..., 4]<num_classes).all(), 'class id must be less than num_classes'
18     num_layers = len(anchors)//3 # default setting
19     anchor_mask = [[6,7,8], [3,4,5], [0,1,2]] if num_layers==3 else [[3,4,5], [1,2,3]]
20 
21     true_boxes = np.array(true_boxes, dtype='float32')
22     input_shape = np.array(input_shape, dtype='int32')
23     boxes_xy = (true_boxes[..., 0:2] + true_boxes[..., 2:4]) // 2
24     boxes_wh = true_boxes[..., 2:4] - true_boxes[..., 0:2]
25     true_boxes[..., 0:2] = boxes_xy/input_shape[::-1]
26     true_boxes[..., 2:4] = boxes_wh/input_shape[::-1]
27 
28     m = true_boxes.shape[0]
29     grid_shapes = [input_shape//{0:32, 1:16, 2:8}[l] for l in range(num_layers)]
30     y_true = [np.zeros((m,grid_shapes[l][0],grid_shapes[l][1],len(anchor_mask[l]),5+num_classes),
31         dtype='float32') for l in range(num_layers)]
32 
33     # Expand dim to apply broadcasting.
34     anchors = np.expand_dims(anchors, 0)
35     anchor_maxes = anchors / 2.
36     anchor_mins = -anchor_maxes
37     valid_mask = boxes_wh[..., 0]>0
38 
39     for b in range(m):
40         # Discard zero rows.
41         wh = boxes_wh[b, valid_mask[b]]
42         if len(wh)==0: continue
43         # Expand dim to apply broadcasting.
44         wh = np.expand_dims(wh, -2)
45         box_maxes = wh / 2.
46         box_mins = -box_maxes
47 
48         intersect_mins = np.maximum(box_mins, anchor_mins)
49         intersect_maxes = np.minimum(box_maxes, anchor_maxes)
50         intersect_wh = np.maximum(intersect_maxes - intersect_mins, 0.)
51         intersect_area = intersect_wh[..., 0] * intersect_wh[..., 1]
52         box_area = wh[..., 0] * wh[..., 1]
53         anchor_area = anchors[..., 0] * anchors[..., 1]
54         iou = intersect_area / (box_area + anchor_area - intersect_area)
55 
56         # Find best anchor for each true box
57         best_anchor = np.argmax(iou, axis=-1)
58         '''獲取最大Iou的那個anchor'''
59         for t, n in enumerate(best_anchor):
60             for l in range(num_layers):
61                 if n in anchor_mask[l]:
62                     i = np.floor(true_boxes[b,t,0]*grid_shapes[l][1]).astype('int32')
63                     j = np.floor(true_boxes[b,t,1]*grid_shapes[l][0]).astype('int32')
64                     k = anchor_mask[l].index(n)
65                     c = true_boxes[b,t, 4].astype('int32')
66                     y_true[l][b, j, i, k, 0:4] = true_boxes[b,t, 0:4]
67                     y_true[l][b, j, i, k, 4] = 1
68                     y_true[l][b, j, i, k, 5+c] = 1
69 
70     return y_true

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM