聚类kmeans算法在yolov3中的应用

yolov3 kmeans

yolov3在作boundingbox预测的时候,用到了anchor boxes.这个anchors的含义即最有可能的object的width,height.事先经过聚类获得.好比某一个像素单元,我想对这个像素单元预测出一个object,围绕这个像素单元,能够预测出无数种object的形状,并非随便预测的,要参考anchor box的大小,即从已标注的数据中经过聚类统计到的最有可能的object的形状.html

.cfg文件内的配置以下:python

[yolo]
mask = 3,4,5
anchors = 10,14,  23,27,  37,58,  81,82,  135,169,  344,319

在用咱们本身的数据作训练的时候,要先修改anchors,匹配咱们本身的数据.anchors大小经过聚类获得.git

通俗地说,聚类就是把挨得近的数据点划分到一块儿.
kmeans算法的思想很简单github

  • 随便指定k个cluster
  • 把点划分到与之最近的一个cluster
  • 上面获得的cluster确定是很差的,由于一开始的cluster是乱选的嘛
  • 更新每一个cluster为当前cluster的点的均值.
    这时候cluster确定变准了,为何呢?好比当前这个cluster里有3个点,2个点靠的很近,还有1个点离得稍微远点,那取均值的话,那至关于靠的很近的2个点有更多投票权,新算出来的cluster的中心会更加靠近这两个点.你要是非要抬杠:那万一一开始我随机指定的cluster中心点就特别准呢,从新取均值反而把中心点弄的不许了?事实上这是kmeans的一个缺陷:比较依赖初始的k个cluster的位置.选择不恰当的k值可能会致使糟糕的聚类结果。这也是为何要进行特征检查来决定数据集的聚类数目了。
  • 从新执行上述过程
    • 把点划分到与之最近的一个cluster
    • 更新每一个cluster为当前cluster的点的均值
  • 不断重复上述过程,直至cluster中心变化很小

yolov3要求的label文件格式

<object-class> <x_center> <y_center> <width> <height>
Where:
<object-class> - integer object number from 0 to (classes-1)
<x_center> <y_center> <width> <height> - float values relative to width and height of image, it can be equal from (0.0 to 1.0]
> for example: <x> = <absolute_x> / <image_width> or <height> = <absolute_height> / <image_height>
atention: <x_center> <y_center> - are center of rectangle (are not top-left corner)

举例:
1 0.716797 0.395833 0.216406 0.147222
全部的值都是比例.(中心点x,中心点y,目标宽,目标高)算法

kmeans实现

通常来讲,计算样本点到质心的距离的时候直接算的是两点之间的距离,而后将样本点划归为与之距离最近的一个质心.
在yolov3中样本点的数据是有具体的业务上的含义的,咱们其实最终目的是想知道最有可能的object对应的bounding box的形状是什么样子的. 因此这个距离的计算咱们并非直接算两点之间的距离,咱们计算两个box的iou,即2个box的类似程度.d=1-iou(box1,box_cluster). 这样d越小,说明box1与box_cluster越相似.将box划归为box_cluster.app

数据加载

f = open(args.filelist)
  
    lines = [line.rstrip('\n') for line in f.readlines()]
    
    annotation_dims = []

    size = np.zeros((1,1,3))
    for line in lines:
                    
        #line = line.replace('images','labels')
        #line = line.replace('img1','labels')
        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(tuple(map(float,(w,h))))
    annotation_dims = np.array(annotation_dims)

看着一大段,其实重点就一句函数

w,h = line.split(' ')[3:]            
annotation_dims.append(tuple(map(float,(w,h))))

这里涉及到了python的语法,map用法https://www.runoob.com/python/python-func-map.html
这样就生成了一个N*2矩阵. N表明你的样本个数.ui

  • 定义样本点到质心点的距离
    计算样本x表明的box和k个质心box的IOU.(即比较box之间的形状类似程度).
    这里涉及到一个IOU的概念:即交并集比例.交叉面积/总面积.
def IOU(x,centroids):
    similarities = []
    k = len(centroids)
    for centroid in centroids:
        c_w,c_h = centroid
        w,h = x
        if c_w>=w and c_h>=h:     #box(c_w,c_h)彻底包含box(w,h)
            similarity = w*h/(c_w*c_h)
        elif c_w>=w and c_h<=h:   #box(c_w,c_h)宽而扁平
            similarity = w*c_h/(w*h + (c_w-w)*c_h)
        elif c_w<=w and c_h>=h:
            similarity = c_w*h/(w*h + c_w*(c_h-h))
        else: #means both w,h are bigger than c_w and c_h respectively
            similarity = (c_w*c_h)/(w*h)
        similarities.append(similarity) # will become (k,) shape
    return np.array(similarities)

kmeans实现

def kmeans(X,centroids,eps,anchor_file):
    
    N = X.shape[0]
    iterations = 0
    k,dim = centroids.shape
    prev_assignments = np.ones(N)*(-1)    
    iter = 0
    old_D = np.zeros((N,k)) #距离矩阵  N个点,每一个点到k个质心 共计N*K个距离

    while True:
        D = [] 
        iter+=1           
        for i in range(N):
            d = 1 - IOU(X[i],centroids)  #d是一个k维的   
            D.append(d)   
        D = np.array(D) # D.shape = (N,k)
        
        print("iter {}: dists = {}".format(iter,np.sum(np.abs(old_D-D))))
            
        #assign samples to centroids 
        assignments = np.argmin(D,axis=1) #返回每一行的最小值的下标.即当前样本应该归为k个质心中的哪个质心.
        
        if (assignments == prev_assignments).all() :  #质心已经再也不变化
            print("Centroids = ",centroids)
            write_anchors_to_file(centroids,X,anchor_file)
            return

        #calculate new centroids   
        centroid_sums=np.zeros((k,dim),np.float)  #(k,2)
        for i in range(N):
            centroid_sums[assignments[i]]+=X[i]        #将每个样本划分到对应质心
        for j in range(k):            
            centroids[j] = centroid_sums[j]/(np.sum(assignments==j)) #更新质心
        
        prev_assignments = assignments.copy()     
        old_D = D.copy()
  • 计算每一个样本点到每个cluster质心的距离 d = 1- IOU(X[i],centroids)表示样本点到每一个cluster质心的距离.
  • np.argmin(D,axis=1)获得每个样本点离哪一个cluster质心最近
    argmin函数用法参考https://docs.scipy.org/doc/numpy/reference/generated/numpy.argmin.html
  • 计算每个cluster中的样本点总和,取平均,更新cluster质心.
for i in range(N):
    centroid_sums[assignments[i]]+=X[i]        #将每个样本划分到对应质心
for j in range(k):            
    centroids[j] = centroid_sums[j]/(np.sum(assignments==j)) #更新质心
  • 不断重复上述过程,直到质心再也不变化 聚类完成.

保存聚类获得的anchor box大小

def write_anchors_to_file(centroids,X,anchor_file):
    f = open(anchor_file,'w')
    
    anchors = centroids.copy()
    print(anchors.shape)

    for i in range(anchors.shape[0]):
        anchors[i][0]*=width_in_cfg_file/32.
        anchors[i][1]*=height_in_cfg_file/32.
         

    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()

因为yolo要求的label文件中,填写的是相对于width,height的比例.因此获得的anchor box的大小要乘以模型输入图片的尺寸.
上述代码里code

anchors[i][0]*=width_in_cfg_file/32.
        anchors[i][1]*=height_in_cfg_file/32.

这里除以32是yolov2的算法要求. yolov3实际上不须要.参见如下连接https://github.com/pjreddie/darknet/issues/911orm

for Yolo v2: width=704 height=576 in cfg-file
./darknet detector calc_anchors data/hand.data -num_of_clusters 5 -width 22 -height 18 -show
for Yolo v3: width=704 height=576 in cfg-file
./darknet detector calc_anchors data/hand.data -num_of_clusters 9 -width 704 -height 576 -show
And you can use any images with any sizes.

完整代码见https://github.com/AlexeyAB/darknet/tree/master/scripts 用法:python3 gen_anchors.py -filelist ../build/darknet/x64/data/park_train.txt

相关文章
相关标签/搜索