NMS

def yolo_eval(yolo_outputs,
              anchors,
              num_classes,
              image_shape,
              max_boxes=20,
              score_threshold=.6,
              iou_threshold=.5):
      #FPN的层数(yolov3是3个尺度)
      num_layers = len(yolo_outputs)
      #对应三个尺度anchor的index,注意对于bbx网络预测的是3xNXN个(t_x,t_y,t_w和t_h)
      anchor_mask = [[6,7,8], [3,4,5], [0,1,2]] if num_layers==3 else [[3,4,5], [1,2,3]] 
      #输入大小是最后一层feature map的32倍
      input_shape = K.shape(yolo_outputs[0])[1:3] * 32
      boxes = []
      box_scores = []
      #获取所有的boxes及对应的box_scores
      for l in range(num_layers):
           #这里的_box_scores是box_confidence * box_class_probs的结果
            _boxes, _box_scores = yolo_boxes_and_scores(yolo_outputs[l],anchors[anchor_mask[l]], num_classes, input_shape, image_shape)
            boxes.append(_boxes)
            box_scores.append(_box_scores)
      boxes = K.concatenate(boxes, axis=0)#4维
      box_scores = K.concatenate(box_scores, axis=0)#80维
      #对类别分量选出置信度大于阈值的所有类别的boxes
      mask = box_scores >= score_threshold#80维
      max_boxes_tensor = K.constant(max_boxes, dtype='int32')
      boxes_ = []
      scores_ = []
      classes_ = []
      #针对每一个类别做NMS
      for c in range(num_classes):
            #根据mask获取置信度大于该类别阈值boxes
            class_boxes = tf.boolean_mask(boxes, mask[:, c])#如果类别分量c的取值大于score_threshold,则留下这个box
            class_box_scores = tf.boolean_mask(box_scores[:, c], mask[:, c])#如果类别分量c的取值大于score_threshold,则留下这个box_score
            nms_index = tf.image.non_max_suppression(class_boxes, class_box_scores, max_boxes_tensor, iou_threshold=iou_threshold)
            class_boxes = K.gather(class_boxes, nms_index)
            class_box_scores = K.gather(class_box_scores, nms_index)
            classes = K.ones_like(class_box_scores, 'int32') * c
            boxes_.append(class_boxes)
            scores_.append(class_box_scores)
            classes_.append(classes)
      boxes_ = K.concatenate(boxes_, axis=0)
      scores_ = K.concatenate(scores_, axis=0)
      classes_ = K.concatenate(classes_, axis=0)

      return boxes_, scores_, classes_
  

获取yolo输出的box和对应的scores:

def yolo_boxes_and_scores(feats, anchors, num_classes, input_shape, image_shape):
    '''Process Conv layer output'''
    box_xy, box_wh, box_confidence, box_class_probs = yolo_head(feats,
        anchors, num_classes, input_shape)
    boxes = yolo_correct_boxes(box_xy, box_wh, input_shape, image_shape)
    boxes = K.reshape(boxes, [-1, 4])
    box_scores = box_confidence * box_class_probs
    box_scores = K.reshape(box_scores, [-1, num_classes])
    return boxes, box_scores

获取yolo的原始输出t_x,t_y,t_w,t_h,进一步得到b_x,b_y,b_w,b_h:

def yolo_head(feats, anchors, num_classes, input_shape, calc_loss=False):
    """Convert final layer features to bounding box parameters."""
    num_anchors = len(anchors)
    # Reshape to batch, height, width, num_anchors, box_params.
    anchors_tensor = K.reshape(K.constant(anchors), [1, 1, 1, num_anchors, 2])

    grid_shape = K.shape(feats)[1:3] # height, width
    grid_y = K.tile(K.reshape(K.arange(0, stop=grid_shape[0]), [-1, 1, 1, 1]),
        [1, grid_shape[1], 1, 1])
    grid_x = K.tile(K.reshape(K.arange(0, stop=grid_shape[1]), [1, -1, 1, 1]),
        [grid_shape[0], 1, 1, 1])
    grid = K.concatenate([grid_x, grid_y])
    grid = K.cast(grid, K.dtype(feats))

    feats = K.reshape(
        feats, [-1, grid_shape[0], grid_shape[1], num_anchors, num_classes + 5])

    # Adjust preditions to each spatial grid point and anchor size.
    box_xy = (K.sigmoid(feats[..., :2]) + grid) / K.cast(grid_shape[::-1], K.dtype(feats))
    box_wh = K.exp(feats[..., 2:4]) * anchors_tensor / K.cast(input_shape[::-1], K.dtype(feats))
    box_confidence = K.sigmoid(feats[..., 4:5])
    box_class_probs = K.sigmoid(feats[..., 5:])

    if calc_loss == True:
        return grid, feats, box_xy, box_wh
    return box_xy, box_wh, box_confidence, box_class_probs

矫正bbx,并scale至原图对应的尺寸

def yolo_correct_boxes(box_xy, box_wh, input_shape, image_shape):
    '''Get corrected boxes'''
    box_yx = box_xy[..., ::-1]
    box_hw = box_wh[..., ::-1]
    input_shape = K.cast(input_shape, K.dtype(box_yx))
    image_shape = K.cast(image_shape, K.dtype(box_yx))
    new_shape = K.round(image_shape * K.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 =  K.concatenate([
        box_mins[..., 0:1],  # y_min
        box_mins[..., 1:2],  # x_min
        box_maxes[..., 0:1],  # y_max
        box_maxes[..., 1:2]  # x_max
    ])

    # Scale boxes back to original image shape.
    boxes *= K.concatenate([image_shape, image_shape])
    return boxes

tf.boolean_mask:(1)要保证tensor的第一个维度与mask的第一个维度相等(2)剩余的维度,如果mask包含对应的维度,mask对应的维度取值要与tensor对应的维度取值相等(见t4),否则会出错(见t3,t5,t7)

import tensorflow as tf
import numpy as np

t1 = [0,1,2,3]
mask1 = np.array([True,False,True,False])
with tf.Session() as sess:
        print(sess.run(tf.boolean_mask(t1,mask1)))
#[0 2]

t2 = [[0,1],[2,3],[4,5]]
mask2 = np.array([True,False,True])
with tf.Session() as sess:
        print(sess.run(tf.boolean_mask(t2,mask2)))
#[[0 1]
# [4 5]]

t3 = [[0,1,2,3]]
mask3 = np.array([True])
with tf.Session() as sess:
        print(sess.run(tf.boolean_mask(t3,mask3)))
#[[0 1 2 3]]
t3 = [[0,1,2,3]]
mask3 = np.array([[True]])
with tf.Session() as sess:
        print(sess.run(tf.boolean_mask(t3,mask3)))
#ValueError: Shapes (1, 4) and (1, 1) are incompatible


t4 = [[0,1],[2,3],[4,5]]
mask4 = np.array([[True,False],[False,True],[True,True]])
with tf.Session() as sess:
        print(sess.run(tf.boolean_mask(t4,mask4)))
#[0 3 4 5]

t5 = [[0,1],[2,3],[4,5]]
mask5 = np.array([[True,False,True],[False,True,True],[True,True,False]])
with tf.Session() as sess:
        print(sess.run(tf.boolean_mask(t5,mask5)))
#ValueError: Shapes (3, 2) and (3, 3) are incompatible

t6 = [[0,1],[2,3],[4,5]]
mask6 = np.array([True,False])
with tf.Session() as sess:
        print(sess.run(tf.boolean_mask(t6,mask6)))
#ValueError: Shapes (3,) and (2,) are incompatible

t7 = [[0,1,2],[2,3,4],[4,5,6]]
mask7 = np.array([[True,False],[False,True],[False,False]])
with tf.Session() as sess:
        print(sess.run(tf.boolean_mask(t7,mask7)))
#ValueError: Shapes (3, 3) and (3, 2) are incompatible


利用python实现NMS:

import cv2
import argparse
import numpy as np
import pdb

def parse_parser():
    parser = argparse.ArgumentParser()
    parser.add_argument('--input_image',type=str,default='../test_data/multi.jpg')
    args = parser.parse_args()
    return args

def non_max_suppression(bbx_info:dict,threshold):
    for bbx_class,bbx in bbx_info.items():
        bbx_array = np.array(bbx)
        x_min,y_min,x_max,y_max,conf = bbx_array[:,0],bbx_array[:,1],bbx_array[:,2],bbx_array[:,3],bbx_array[:,4]
        ordered = np.argsort(conf)[::-1]#index
        area = np.maximum(x_max-x_min,0)*np.maximum(y_max-y_min,0)
        bbx_res = []
        while len(ordered)>0:
            i = ordered[0]
            bbx_res.append(i)
            #求交集
            xx1 = np.maximum(x_min[i],x_min[ordered[1:]])#当ordered只有一个元素时候,ordered[1:]是[]
            yy1 = np.maximum(y_min[i],y_min[ordered[1:]])
            xx2 = np.minimum(x_max[i],x_max[ordered[1:]])
            yy2 = np.minimum(y_max[i],y_max[ordered[1:]])
            interaction = np.maximum(xx2-xx1,0)*np.maximum(yy2-yy1,0)
            #求并集&&IOUs
            IOUs = interaction/(area[i]+area[ordered[1:]]-interaction)
            #pdb.set_trace()
            idx = np.where(IOUs<=threshold)[0]#idx:np.array
            ordered = ordered[idx+1]#+1是因为这里得到的idx是从1开始的,当idx为[]时候,idx+1也是[]
        bbx_left = bbx_array[bbx_res].tolist()
        bbx_info[bbx_class] = bbx_left
    return bbx_info

def draw_res(image_name,bbx_info,mode):
    assert mode in ('src','dst')
    img_BGR = cv2.imread(image_name)
    for bbx_class,bbxs in bbx_info.items():
        for bbx in bbxs:
            x_min,y_min,x_max,y_max,conf = bbx
            x_min,y_min,x_max,y_max = int(x_min),int(y_min),int(x_max),int(y_max)
            cv2.rectangle(img_BGR,(x_min,y_min),(x_max,y_max),(0,0,255),1)
            cv2.putText(img_BGR,bbx_class+':'+str(conf),(x_min,y_min),cv2.FONT_HERSHEY_COMPLEX,0.5,(255,0,0),1)
    cv2.imwrite('{}_{}.jpg'.format(image_name.rsplit('/')[-1].split('.')[0],mode),img_BGR)
            
if __name__=='__main__':
    args = parse_parser()
    #bbx_info = {'person':[[0,17,219,347,1.0],[10,17,200,300,0.95]],'car':[[0,135,48,190,0.84],[0,125,50,188,0.80],[0,20,48,100,0.6]]}
    bbx_info = {'preson':[[501,118,672,325,0.70],[417,90,637,346,0.85],[4,116,344,439,0.99],[490,120,672,300,0.67]],'car':[[18,0,778,534,0.88]]}
    draw_res(args.input_image,bbx_info,mode='src')
    bbx_info = non_max_suppression(bbx_info,0.6)
    draw_res(args.input_image,bbx_info,mode='dst')

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,921评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,635评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,393评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,836评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,833评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,685评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,043评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,694评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,671评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,670评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,779评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,424评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,027评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,984评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,214评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,108评论 2 351
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,517评论 2 343

推荐阅读更多精彩内容