前幾日YOLO系列迎來了YOLOv4,再來回看一遍YOLOv3。
anchor box
YOLO v1中,bounding-box做回歸時沒有限制,導致可能會預測一個距離很遠的object,效率不高。在YOLO v2中,開始引入了anchor box的概念,只對網格鄰近的object負責,正所謂各司其職。
anchor-box用於在邊界框預測時,通過伸縮、平移變換,最終能夠標定該物體。其尺寸大小的設定,對於網絡的運行效率影響較大。因為圖像中不同類型的特殊形狀的物體,通常在圖像中呈現的大小是不盡相同的(有的物體大,有的物體小)。因此,有必要對訓練數據集設定適合其anchor-box的大小(即anchor-box的寬高)。K-Means便可以用於解決這一問題。
(此時不需要對圖像進行416的處理 ,只需要標記的數據)
數據准備
在數據集標定的過程中,產生的樣本集中包含 數據的路徑、標定圖像四周的坐標、以及標定的類別編碼(4+classe), 如下所示,路徑與標定信息按照空格分隔:
1 D:\keras-yolo3-master-person/VOCdevkit/VOC2007/JPEGImages/person0000.jpg 115,93,414,519,0
在YOLO v3中,有三種尺度的預測,每種尺度根據其大小賦予其相應大小的anchor-box,即共需要9個anchor-box,這就決定了在K-Means中的聚類個數為9類。
K-Means代碼的梳理:
代碼主線:
1 def txt2clusters(self): 2 all_boxes = self.txt2boxes() # 將txt中數值信息轉化為圖像標記框的寬高,並返回 3 result = self.kmeans(all_boxes, k=self.cluster_number) 4 result = result[np.lexsort(result.T[0, None])] 5 self.result2txt(result) 6 print("K anchors:\n {}".format(result)) 7 print("Accuracy: {:.2f}%".format( 8 self.avg_iou(all_boxes, result) * 100))
1. 獲取訓練樣本標定數據框的寬高信息 self.txt2boxes()
逐行讀取文件,按空格將路徑和信息分隔。對信息中四周的坐標進行計算(右-左;下-上),獲取寬高信息,最終返回寬、高的二維數組。
1 for line in f: 2 infos = line.split(" ") 3 length = len(infos) 4 for i in range(1, length): 5 width = int(infos[i].split(",")[2]) - \ 6 int(infos[i].split(",")[0]) 7 height = int(infos[i].split(",")[3]) - \ 8 int(infos[i].split(",")[1]) 9 dataSet.append([width, height]) 10 result = np.array(dataSet)
2. 對獲取到的寬高信息進行聚類self.kmeans(all_boxes, k=self.cluster_number)
聚類過程:
1)隨機選取k個類中心
首先,在寬高信息中,隨機選取k個類中心。
1 clusters = boxes[np.random.choice(box_number, k, replace=False)]
2) 計算各點到類中心的距離
傳統的聚類是計算各樣本到各類中心的距離,將樣本歸為到類中心最近的類。最終的目的是使得屬於某一類的樣本到類中心的距離越小越好。
此處,由於樣本信息是寬高,是一個具體的形狀。此處采用的是IOU越大越好,為了與聚類的選擇方式相同 此處采用d=1-IOU 越小越好。
1 distances = 1 - self.iou(boxes, clusters)
IOU的實現 實際上式通過面積的計算來衡量的交集和並集的比例,下圖所示的是理解這么做是交集和並集的示例,紅色的為類中心的框,黑色的是3個示例樣本。
1 box_area = boxes[:, 0] * boxes[:, 1] # 把要聚類的框的寬高相乘,作為了一個box_area 2 box_area = box_area.repeat(k) # 要算到k個類中心的距離,需要搞一個每個都有k個的矩陣 3 box_area = np.reshape(box_area, (n, k)) 4 5 cluster_area = clusters[:, 0] * clusters[:, 1] 6 cluster_area = np.tile(cluster_area, [1, n]) 7 cluster_area = np.reshape(cluster_area, (n, k)) 8 # 把box和cluster的寬都整理成n行k列的形式,並把兩者做比較,最后還是一個n行k列的形式,這個 9 # 過程其實在比較box和兩個cluster的寬,並選出小的 10 box_w_matrix = np.reshape(boxes[:, 0].repeat(k), (n, k)) 11 cluster_w_matrix = np.reshape(np.tile(clusters[:, 0], (1, n)), (n, k)) 12 min_w_matrix = np.minimum(cluster_w_matrix, box_w_matrix) 13 # 把box和cluster的高都整理成n行k列的形式,並把兩者做比較,最后還是一個n行k列的形式,這個 14 # 過程其實在比較box和兩個cluster的高,並選出小的 15 box_h_matrix = np.reshape(boxes[:, 1].repeat(k), (n, k)) 16 cluster_h_matrix = np.reshape(np.tile(clusters[:, 1], (1, n)), (n, k)) 17 min_h_matrix = np.minimum(cluster_h_matrix, box_h_matrix) 18 # 將篩選出來的小的寬高 相乘 19 inter_area = np.multiply(min_w_matrix, min_h_matrix) 20 21 result = inter_area / (box_area + cluster_area - inter_area)
3)類中心的更新
為了避免標記的物體中,框存在特別小,或者特別大的情況,在類中心的更新中,采用的是聚到一類中的個體的中位數,而不是均值。如下述代碼所示,dist設置的是median(中位數)。
1 def kmeans(self, boxes, k, dist=np.median): 2 ... 3 for cluster in range(k): 4 clusters[cluster] = dist(boxes[current_nearest == cluster], axis=0)