目標檢測中tensorflow常用API以及備選框篩選程式碼分析
目標檢測演算法中,因為產生的備選框特別多,需要刪減。而刪減的方法是NMS(非極大抑制演算法)。網上很多演算法是自己編寫功能程式碼。但是這不是tensorflow中自帶的功能,所以在使用tensorflow恢復模型的時候,sess並不能hold住他們。因此別人需要用的時候,還需要額外的配置這些程式碼,如果採用TensorFlow自帶的一些功能,那麼呼叫pb檔案的時候就可以直接實現一步到位的結果。下面結合一下YOLO3中的yolo_predict中的程式碼,介紹一下tensorflow中對目標檢測演算法比較常見的一些功能。
def predict(self, inputs, image_shape): """ Introduction ------------ 構建預測模型 Parameters ---------- inputs: 處理之後的輸入圖片 image_shape: 影象原始大小 Returns ------- boxes: 物體框座標 scores: 物體概率值 classes: 物體類別 """ model = yolo(config.norm_epsilon, config.norm_decay, self.anchors_path, self.classes_path, pre_train = False) output = model.yolo_inference(inputs, config.num_anchors // 3, config.num_classes, training = False) boxes, scores, classes = self.eval(output, image_shape, max_boxes = 20) return boxes, scores, classes
在經過model.yolo_inference()後,是輸出的三個Tensor,這在我之前的一篇文章裡面有過介紹。但是生成的備選框和每個備選框對應的分數太多。我們需要通過自己設定的threshold進行賽選。這個賽選就是通過eval()方法實現的。
def eval(self, yolo_outputs, image_shape, max_boxes = 20): """ Introduction ------------ 根據Yolo模型的輸出進行非極大值抑制,獲取最後的物體檢測框和物體檢測類別 Parameters ---------- yolo_outputs: yolo模型輸出 image_shape: 圖片的大小 max_boxes: 最大box數量 Returns ------- boxes_: 物體框的位置 scores_: 物體類別的概率 classes_: 物體類別 """ anchor_mask = [[6, 7, 8], [3, 4, 5], [0, 1, 2]] boxes = [] box_scores = [] input_shape = tf.shape(yolo_outputs[0])[1 : 3] * 32 # 對三個尺度的輸出獲取每個預測box座標和box的分數,score計算為置信度x類別概率 for i in range(len(yolo_outputs)): _boxes, _box_scores = self.boxes_and_scores(yolo_outputs[i], self.anchors[anchor_mask[i]], len(self.class_names), input_shape, image_shape) boxes.append(_boxes) box_scores.append(_box_scores) boxes = tf.concat(boxes, axis = 0) box_scores = tf.concat(box_scores, axis = 0) mask = box_scores >= self.obj_threshold max_boxes_tensor = tf.constant(max_boxes, dtype = tf.int32) boxes_ = [] scores_ = [] classes_ = [] for c in range(len(self.class_names)): class_boxes = tf.boolean_mask(boxes, mask[:, c]) class_box_scores = tf.boolean_mask(box_scores[:, c], mask[:, c]) nms_index = tf.image.non_max_suppression(class_boxes, class_box_scores, max_boxes_tensor, iou_threshold = self.nms_threshold) class_boxes = tf.gather(class_boxes, nms_index) class_box_scores = tf.gather(class_box_scores, nms_index) classes = tf.ones_like(class_box_scores, 'int32') * c boxes_.append(class_boxes) scores_.append(class_box_scores) classes_.append(classes) boxes_ = tf.concat(boxes_, axis = 0) scores_ = tf.concat(scores_, axis = 0) classes_ = tf.concat(classes_, axis = 0) return boxes_, scores_, classes_
1. tf.shape():
這個不用多說了,就是獲取tensor的維度。
input_shape = tf.shape(yolo_outputs[0])[1 : 3] * 32
這句程式碼就是恢復darknet網路輸入的圖片尺寸(這裡相對於原圖尺寸也是經過resize的)。下面經過三次迴圈,分別求出尺寸為13,26,52的Tensor對應的備選框和分數進行賽選。從而程式進入:
def boxes_and_scores(self, feats, anchors, classes_num, input_shape, image_shape):
def boxes_and_scores(self, feats, anchors, classes_num, input_shape, image_shape): """ Introduction ------------ 將預測出的box座標轉換為對應原圖的座標,然後計算每個box的分數 Parameters ---------- feats: yolo輸出的feature map anchors: anchor的位置 class_num: 類別數目 input_shape: 輸入大小 image_shape: 圖片大小 Returns ------- boxes: 物體框的位置 boxes_scores: 物體框的分數,為置信度和類別概率的乘積 """ box_xy, box_wh, box_confidence, box_class_probs = self._get_feats(feats, anchors, classes_num, input_shape) boxes = self.correct_boxes(box_xy, box_wh, input_shape, image_shape) boxes = tf.reshape(boxes, [-1, 4]) box_scores = box_confidence * box_class_probs box_scores = tf.reshape(box_scores, [-1, classes_num]) return boxes, box_scores
接著進入:
def _get_feats(self, feats, anchors, num_classes, input_shape):
def _get_feats(self, feats, anchors, num_classes, input_shape):
"""
Introduction
------------
根據yolo最後一層的輸出確定bounding box
Parameters
----------
feats: yolo模型最後一層輸出
anchors: anchors的位置
num_classes: 類別數量
input_shape: 輸入大小
Returns
-------
box_xy, box_wh, box_confidence, box_class_probs
"""
num_anchors = len(anchors)
anchors_tensor = tf.reshape(tf.constant(anchors, dtype=tf.float32), [1, 1, 1, num_anchors, 2])
grid_size = tf.shape(feats)[1:3]
predictions = tf.reshape(feats, [-1, grid_size[0], grid_size[1], num_anchors, num_classes + 5])
# 這裡構建13*13*1*2的矩陣,對應每個格子加上對應的座標
grid_y = tf.tile(tf.reshape(tf.range(grid_size[0]), [-1, 1, 1, 1]), [1, grid_size[1], 1, 1])
grid_x = tf.tile(tf.reshape(tf.range(grid_size[1]), [1, -1, 1, 1]), [grid_size[0], 1, 1, 1])
grid = tf.concat([grid_x, grid_y], axis=-1)
grid = tf.cast(grid, tf.float32)
# 將x,y座標歸一化為佔416的比例
box_xy = (tf.sigmoid(predictions[..., :2]) + grid) / tf.cast(grid_size[::-1], tf.float32)
# 將w,h也歸一化為佔416的比例
box_wh = tf.exp(predictions[..., 2:4]) * anchors_tensor / tf.cast(input_shape[::-1], tf.float32)
box_confidence = tf.sigmoid(predictions[..., 4:5])
box_class_probs = tf.sigmoid(predictions[..., 5:])
return box_xy, box_wh, box_confidence, box_class_probs
2. tf.range():
進入函式內部可以看到其定義為: range(start, limit=None, delta=1, dtype=None, name="range"):就是產生一個等差數列。delta為步子的大小。以尺寸為13的那個Tensor來講tf.range(13) = [0,1,2,...,...,12].
3. tf.title()
tf.tile()
應用於需要張量擴充套件的場景,具體說來就是:
如果現有一個形狀如[width
, height
]的張量,需要得到一個基於原張量的,形狀如[batch_size
,width
,height
]的張量,其中每一個batch的內容都和原張量一模一樣(引用自)
最終形成了gridx = gridy =[13,13,1,1],grid = tf.cast(grid,tf.float32).,其shape = (13,13,1,2),表示在13*13的矩陣,其channel為1,每一個channel裡面有兩個資料表示也就是他們的座標值。
然後通過yolo的特徵圖到darknet的輸入圖直接的對映關係,輸出備選框在輸入影象的座標和長寬,每一個框的置信度以及類別分數。
隨後程式進入:
def correct_boxes(self, box_xy, box_wh, input_shape, image_shape):
def correct_boxes(self, box_xy, box_wh, input_shape, image_shape):
"""
Introduction
------------
計算物體框預測座標在原圖中的位置座標
Parameters
----------
box_xy: 物體框左上角座標
box_wh: 物體框的寬高
input_shape: 輸入的大小
image_shape: 圖片的大小
Returns
-------
boxes: 物體框的位置
"""
box_yx = box_xy[..., ::-1]
box_hw = box_wh[..., ::-1]
input_shape = tf.cast(input_shape, dtype = tf.float32)
image_shape = tf.cast(image_shape, dtype = tf.float32)
new_shape = tf.round(image_shape * tf.reduce_min(input_shape / image_shape))
offset = (input_shape - new_shape) / 2. / input_shape
scale = input_shape / new_shape
box_yx = (box_yx - offset) * scale
box_hw *= scale
box_mins = box_yx - (box_hw / 2.)
box_maxes = box_yx + (box_hw / 2.)
boxes = tf.concat([
box_mins[..., 0:1],
box_mins[..., 1:2],
box_maxes[..., 0:1],
box_maxes[..., 1:2]
], axis = -1)
boxes *= tf.concat([image_shape, image_shape], axis = -1)
return boxes
這個函式主要是計算了從輸入影象尺寸到原圖的對映。
4:tf.round():
四捨五入的函式,切輸入必須是浮點型。
再次回到eval函式,此後經過三次迴圈,把輸出的所有框和框對應的類的分數資訊都分別加入到了boxes和box_scores.
然後對box_scores的分數大於threshold的進行賽選留下。
5:tf.boolean_mask():
這個函式的作用就是對通過threshold的box_scores和boxses進行保留。其作用可參考該文章
6:tf.image.non_max_suppression():
這個就是TensorFlow自帶的計算NMS的函式。非常方便。
7:gather(params, indices, validate_indices=None, name=None, axis=0)
這張圖很清楚瞭解釋了該函式的用法。
8:tf.ones_like():
建立一個將所有元素都設定為1的Tensor。