【源碼解讀】YOLO v3 - 01K-means 生成適合識別圖像的anchor-box


  前幾日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) 

 


免責聲明!

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



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