網絡細節資料不少,不作贅述,主要總結演化思路和解決問題。python
YOLO網絡結構由24個卷積層與2個全鏈接層構成,網絡入口爲448x448(v2爲416x416),圖片進入網絡先通過resize,輸出格式爲:git
其中,S爲劃分網格數,B爲每一個網格負責目標個數,C爲類別個數。B表示每一個小格對應B組可能的框,5表示每一個框的四個座標和一個置信度,C表示類別,同時也說明B個框只能隸屬於同一個類別。github
損失函數有四部分組成,算法
上文中的紅圈符號表示是否開關,好比第一個符號表示i號格子j號座標框中若是含有obj則爲1,不然爲0網絡
損失函數第一部分的寬高計算加根號,這是由於:一個一樣將一個100x100的目標與一個10x10的目標都預測大了10個像素,預測框爲110 x 110與20 x 20。顯然第一種狀況咱們還能夠失道接受,但第二種狀況至關於把邊界框預測大了一倍,但若是不使用根號函數,那麼損失相同,都爲200,若是使用根號則能夠表示出二者的差別。架構
Ci表示第i個框含有物體的置信度,相似於RCNN中的二分類部分思想,因爲大部分框中沒有物體,爲平衡損失函數,本部分的權重取小爲0.5app
中c爲正確類別則值爲1,不然爲0dom
1) 對小物體及鄰近特徵檢測效果差:當一個小格中出現多於兩個小物體或者一個小格中出現多個不一樣物體時效果欠佳。緣由:B表示每一個小格預測邊界框數,而YOLO默認同格子裏全部邊界框爲同種類物體。ide
(2) 圖片進入網絡前會先進行resize爲448 x 448,下降檢測速度(it takes about 10ms in 25ms),若是直接訓練對應尺寸會有加速空間。函數
(3) 基礎網絡計算量較大
v2沒有一個明確的主線創新,就是把各類奇技淫巧融入v1中,等到更好的網絡。
做者並無手動設定 anchor,而是在訓練集的 b-box 上用了 k-means 聚類來自動找到 anchor。距離度量若是使用標準的歐氏距離,大盒子會比小盒子產生更多的錯誤。例。所以這裏使用其餘的距離度量公式。聚類的目的是anchor boxes和臨近的ground truth有更大的IOU值,這和anchor box的尺寸沒有直接關係。自定義的距離度量公式:
到聚類中心的距離越小越好,但IOU值是越大越好,因此使用 1 - IOU,這樣就保證距離越小,IOU值越大。
使用的聚類原始數據是隻有標註框的檢測數據集,YOLOv二、v3都會生成一個包含標註框位置和類別的TXT文件,其中每行都包含
,即ground truth boxes相對於原圖的座標,
是框的中心點,
是框的寬和高,N是全部標註框的個數;
首先給定k個聚類中心點
,這裏的
是anchor boxes的寬和高尺寸,因爲anchor boxes位置不固定,因此沒有(x,y)的座標,只有寬和高;
計算每一個標註框和每一個聚類中心點的距離 d=1-IOU(標註框,聚類中心),計算時每一個標註框的中心點都與聚類中心重合,這樣才能計算IOU值,即
。將標註框分配給「距離」最近的聚類中心;
全部標註框分配完畢之後,對每一個簇從新計算聚類中心點,計算方式爲
,
是第i個簇的標註框個數,就是求該簇中全部標註框的寬和高的平均值。
重複第三、4步,直到聚類中心改變量很小。
做者對 k-means 算法取了各類k值,而且畫了一個曲線圖:
最終選擇了k=5,這是在模型複雜度和高召回率之間取了一個折中。
from os import listdir from os.path import isfile, join import argparse #import cv2 import numpy as np import sys import os import shutil import random import math def IOU(x,centroids): ''' :param x: 某一個ground truth的w,h :param centroids: anchor的w,h的集合[(w,h),(),...],共k個 :return: 單個ground truth box與全部k個anchor box的IoU值集合 ''' IoUs = [] w, h = x # ground truth的w,h for centroid in centroids: c_w,c_h = centroid #anchor的w,h if c_w>=w and c_h>=h: #anchor包圍ground truth iou = w*h/(c_w*c_h) elif c_w>=w and c_h<=h: #anchor寬矮 iou = w*c_h/(w*h + (c_w-w)*c_h) elif c_w<=w and c_h>=h: #anchor瘦長 iou = c_w*h/(w*h + c_w*(c_h-h)) else: #ground truth包圍anchor means both w,h are bigger than c_w and c_h respectively iou = (c_w*c_h)/(w*h) IoUs.append(iou) # will become (k,) shape return np.array(IoUs) def avg_IOU(X,centroids): ''' :param X: ground truth的w,h的集合[(w,h),(),...] :param centroids: anchor的w,h的集合[(w,h),(),...],共k個 ''' n,d = X.shape sum = 0. for i in range(X.shape[0]): sum+= max(IOU(X[i],centroids)) #返回一個ground truth與全部anchor的IoU中的最大值 return sum/n #對全部ground truth求平均 def write_anchors_to_file(centroids,X,anchor_file,input_shape,yolo_version): ''' :param centroids: anchor的w,h的集合[(w,h),(),...],共k個 :param X: ground truth的w,h的集合[(w,h),(),...] :param anchor_file: anchor和平均IoU的輸出路徑 ''' f = open(anchor_file,'w') anchors = centroids.copy() print(anchors.shape) if yolo_version=='yolov2': for i in range(anchors.shape[0]): #yolo中對圖片的縮放倍數爲32倍,因此這裏除以32, # 若是網絡架構有改變,根據實際的縮放倍數來 #求出anchor相對於縮放32倍之後的特徵圖的實際大小(yolov2) anchors[i][0]*=input_shape/32. anchors[i][1]*=input_shape/32. elif yolo_version=='yolov3': for i in range(anchors.shape[0]): #求出yolov3相對於原圖的實際大小 anchors[i][0]*=input_shape anchors[i][1]*=input_shape else: print("the yolo version is not right!") exit(-1) widths = anchors[:,0] sorted_indices = np.argsort(widths) print('Anchors = ', anchors[sorted_indices]) for i in sorted_indices[:-1]: f.write('%0.2f,%0.2f, '%(anchors[i,0],anchors[i,1])) #there should not be comma after last anchor, that's why f.write('%0.2f,%0.2f\n'%(anchors[sorted_indices[-1:],0],anchors[sorted_indices[-1:],1])) f.write('%f\n'%(avg_IOU(X,centroids))) print() def kmeans(X,centroids,eps,anchor_file,input_shape,yolo_version): N = X.shape[0] #ground truth的個數 iterations = 0 print("centroids.shape",centroids) k,dim = centroids.shape #anchor的個數k以及w,h兩維,dim默認等於2 prev_assignments = np.ones(N)*(-1) #對每一個ground truth分配初始標籤 iter = 0 old_D = np.zeros((N,k)) #初始化每一個ground truth對每一個anchor的IoU while True: D = [] iter+=1 for i in range(N): d = 1 - IOU(X[i],centroids) D.append(d) D = np.array(D) # D.shape = (N,k) 獲得每一個ground truth對每一個anchor的IoU print("iter {}: dists = {}".format(iter,np.sum(np.abs(old_D-D)))) #計算每次迭代和前一次IoU的變化值 #assign samples to centroids assignments = np.argmin(D,axis=1) #將每一個ground truth分配給距離d最小的anchor序號 if (assignments == prev_assignments).all() : #若是前一次分配的結果和此次的結果相同,就輸出anchor以及平均IoU print("Centroids = ",centroids) write_anchors_to_file(centroids,X,anchor_file,input_shape,yolo_version) return #calculate new centroids centroid_sums=np.zeros((k,dim),np.float) #初始化以便對每一個簇的w,h求和 for i in range(N): centroid_sums[assignments[i]]+=X[i] #將每一個簇中的ground truth的w和h分別累加 for j in range(k): #對簇中的w,h求平均 centroids[j] = centroid_sums[j]/(np.sum(assignments==j)+1) prev_assignments = assignments.copy() old_D = D.copy() def main(argv): parser = argparse.ArgumentParser() parser.add_argument('-filelist', default = r'E:\BaiduNetdiskDownload\darknetHG8245\scripts\train.txt', help='path to filelist\n' ) parser.add_argument('-output_dir', default = r'E:\BaiduNetdiskDownload\darknetHG8245', type = str, help='Output anchor directory\n' ) parser.add_argument('-num_clusters', default = 0, type = int, help='number of clusters\n' ) ''' 須要注意的是yolov2輸出的值比較小是相對特徵圖來講的, yolov3輸出值較大是相對原圖來講的, 因此yolov2和yolov3的輸出是有區別的 ''' parser.add_argument('-yolo_version', default='yolov2', type=str, help='yolov2 or yolov3\n') parser.add_argument('-yolo_input_shape', default=416, type=int, help='input images shape,multiples of 32. etc. 416*416\n') args = parser.parse_args() if not os.path.exists(args.output_dir): os.mkdir(args.output_dir) f = open(args.filelist) lines = [line.rstrip('\n') for line in f.readlines()] annotation_dims = [] for line in lines: line = line.replace('JPEGImages','labels') line = line.replace('.jpg','.txt') line = line.replace('.png','.txt') print(line) f2 = open(line) for line in f2.readlines(): line = line.rstrip('\n') w,h = line.split(' ')[3:] #print(w,h) annotation_dims.append((float(w),float(h))) annotation_dims = np.array(annotation_dims) #保存全部ground truth框的(w,h) eps = 0.005 if args.num_clusters == 0: for num_clusters in range(1,11): #we make 1 through 10 clusters anchor_file = join( args.output_dir,'anchors%d.txt'%(num_clusters)) indices = [ random.randrange(annotation_dims.shape[0]) for i in range(num_clusters)] centroids = annotation_dims[indices] kmeans(annotation_dims,centroids,eps,anchor_file,args.yolo_input_shape,args.yolo_version) print('centroids.shape', centroids.shape) else: anchor_file = join( args.output_dir,'anchors%d.txt'%(args.num_clusters)) indices = [ random.randrange(annotation_dims.shape[0]) for i in range(args.num_clusters)] centroids = annotation_dims[indices] kmeans(annotation_dims,centroids,eps,anchor_file,args.yolo_input_shape,args.yolo_version) print('centroids.shape', centroids.shape) if __name__=="__main__": main(sys.argv)
除此以外,YOLO_v2的實例YOLO9000在超多類分類(9000類)也作出了實踐性質的創新,感興趣的能夠看一看。
貼上兩個項目地址:
https://github.com/qqwweee/keras-yolo3
https://github.com/wizyoung/YOLOv3_TensorFlow
延續了v2的思路,繼續修修補補:
簡單說一下網絡結構:yolo_v3也和v2同樣,backbone都會將輸出特徵圖縮小到輸入的1/32,一般都要求輸入圖片是32的倍數。這點能夠對比v2和v3的backbone看看:(DarkNet-19 與 DarkNet-53)
y1,y2和y3的深度都是255,邊長的規律是13:26:52,對於COCO類別而言,有80個種類,因此每一個box應該對每一個種類都輸出一個機率,yolo v3設定的是每一個網格單元預測3個box,因此每一個box須要有(x, y, w, h, confidence)五個基本參數,而後還要有80個類別的機率。因此3*(5 + 80) = 255。
9是做者聚類獲得的預測框數目建議,在使用搭用tiny-darknet的狀況時更改成6。
xy_loss = object_mask * box_loss_scale * K.binary_crossentropy(raw_true_xy, raw_pred[..., 0:2], from_logits=True) wh_loss = object_mask * box_loss_scale * 0.5 * K.square(raw_true_wh - raw_pred[..., 2:4]) confidence_loss = object_mask * K.binary_crossentropy(object_mask, raw_pred[..., 4:5], from_logits=True) + \ (1 - object_mask) * K.binary_crossentropy(object_mask, raw_pred[..., 4:5], from_logits=True) * ignore_mask class_loss = object_mask * K.binary_crossentropy(true_class_probs, raw_pred[..., 5:], from_logits=True) xy_loss = K.sum(xy_loss) / mf wh_loss = K.sum(wh_loss) / mf confidence_loss = K.sum(confidence_loss) / mf class_loss = K.sum(class_loss) / mf loss += xy_loss + wh_loss + confidence_loss + class_loss
v3部分參考:http://www.javashuo.com/article/p-cykvmldc-gc.html
S