zoukankan      html  css  js  c++  java
  • 【源码解读】YOLO v3

      

      在实际预测的过程中,主要包括两个部分:

    • 输入图像的标准化处理
    • 从模型输出的y1,y2,y3中进行分类和定位

      虽然会先生成yolo的对象,即预测评估的运算过程。

     输入图像的处理

    1 def detect_img(yolo):
    2     path = "VOCdevkit/VOC2007/SegmentationClass/*.jpg"
    3     outdir = "./VOCdevkit/VOC2007/SegmentationObject"
    4     for jpgfile in glob.glob(path):
    5         img = Image.open(jpgfile)
    6         img = yolo.detect_image(img)   # (731*575)
    7         img.save(os.path.join(outdir, os.path.basename(jpgfile)))
    8     yolo.close_session()

      在代码的第6行yolo.detect_image(img)中对输入的图像进行处理。

    1 def detect_image(self, image):

    A. 要获得图像数据image_data:

    • 判断设置的输入尺寸是不是能够整除32的,
    • 将输入的图片等比缩放至设定的model_image_size的大小中(此处为416*416)

      -- letterbox_image()中, 过程与y_true的等比缩放的过程相同(详见:https://www.cnblogs.com/monologuesmw/p/12794278.html),放在灰片上也会使图像放置在正中央的位置, 只不过此时没有标定框的事宜。

    1 if self.model_image_size != (None, None):   # 判断是否设定图像输入模型的尺寸范围
    2     assert self.model_image_size[0] % 32 == 0, 'Multiples of 32 required'    # 这个是关于模型设定的尺寸的大小是否符合规范,
    3     assert self.model_image_size[1] % 32 == 0, 'Multiples of 32 required'    # 而不是图像的大小(对图像没有要求)
    4     boxed_image = letterbox_image(image, tuple(reversed(self.model_image_size)))    # 把输入图像缩放至规定的方位内
    5 else:
    6     new_image_size = (image.width - (image.width % 32),
    7                       image.height - (image.height % 32))
    8     boxed_image = letterbox_image(image, new_image_size)
    9 image_data = np.array(boxed_image, dtype='float32') 

    B. 图像数据归一化,并将其维度调整至运算过程中可用的维度

      扩展数组的形状,在第0维的位置多1维。 即 416*416*3--》1*416*416*3

      #通过上述的扩展转换,使得输入的尺寸符合网络输入格式 [batch_size, width, high, channels]

    1 image_data /= 255.  # 归一化了
    2 image_data = np.expand_dims(image_data, 0)  # Add batch dimension. 

    C. 通过喂数据,获得边框、类别信息

      该部分的主干在于yolo初始化的generate过程,这个时候返回的检测每个物体的图像仅有一个。(即已经经过了nms等过滤系统)

    1 out_boxes, out_scores, out_classes = self.sess.run(
    2     [self.boxes, self.scores, self.classes],
    3     feed_dict={
    4         self.yolo_model.input: image_data,     # 图像的数据
    5         self.input_image_shape: [image.size[1], image.size[0]],    # 原始图像的宽高
    6         K.learning_phase(): 0    # 学习模式, 0: 测试模型, 1: 训练模型
    7     })     # 该部分的目的在于求解generate的过程,

    D. 将获得的边框,类别、类别概率信息绘制在原图上

    • 进行字体、厚度的设置
    1 font = ImageFont.truetype(font='font/FiraMono-Medium.otf',
    2             size=np.floor(3e-2 * image.size[1] + 0.5).astype('int32'))   # 字体
    3 thickness = (image.size[0] + image.size[1]) // 300  # 厚度
    • 按照输出的类别进行循环,并进行绘制
    1 for i, c in reversed(list(enumerate(out_classes))):

      获得框、类等信息

    1 predicted_class = self.class_names[c]  # 预测的类别
    2 box = out_boxes[i]   # 框信息
    3 score = out_scores[i]   # 框的得分(IOU)

      获取预测边框的尺寸

    1 top, left, bottom, right = box
    2 top = max(0, np.floor(top + 0.5).astype('int32'))
    3 left = max(0, np.floor(left + 0.5).astype('int32'))
    4 bottom = min(image.size[1], np.floor(bottom + 0.5).astype('int32'))
    5 right = min(image.size[0], np.floor(right + 0.5).astype('int32'))

      上、左会+0.5 并且向下取整;

      下、右会+0.5 并且向下取整,再和原图的宽高对比取最小

    1 print(label, (left, top), (right, bottom))    # 边框坐标

      根据预测框的位置,调整文本的位置label_size--》[110, 20]

    1 if top - label_size[1] >= 0:   # 101-20>=0
    2     text_origin = np.array([left, top - label_size[1]])  # 
    3 else:
    4     text_origin = np.array([left, top + 1])

      绘制

    1 for i in range(thickness):
    2     draw.rectangle(
    3         [left + i, top + i, right - i, bottom - i],
    4         outline=self.colors[c])
    5 draw.rectangle(
    6     [tuple(text_origin), tuple(text_origin + label_size)],
    7     fill=self.colors[c])
    8 draw.text(text_origin, label, fill=(0, 0, 0), font=font)

    2. 模型预测

      始于YOLO()类的初始化。

    1 detect_img(YOLO())

      关于模型的预测,全系于代码第8行generate中。

    1 def __init__(self, **kwargs):
    2     self.__dict__.update(self._defaults)  # set up default values
    3     self.__dict__.update(kwargs)   # and update with user overrides
    4     self.class_names = self._get_class()   # 通过路径获取class_names的值
    5     self.anchors = self._get_anchors()   # 获取边界的宽高, 并将原始数据转化为两列
    6     self.sess = K.get_session()
    7     self.boxes, self.scores, self.classes = self.generate()

      generate内部包含处理的函数如下思维导图所示:

    • yolo_head: 将模型最后一层的输出转化为中心点坐标、宽、高、框置信度、类别概率。即那些公式的计算。
    • yolo_correct_boxes: 转换成真实图像上的坐标
    • yolo_boxes_and_scores:中除了包含两个函数以外,还会进行框的得分的计算:

       框的得分=框的置信度*类别置信度

      并且返回框的信息和框的得分

    • yolo_eval:中除了包含上述的函数,还会根据框的得分和阈值进行过滤,并进行NMS抑制被标定多次的框。

      接下来将从最底层往顶层扒~

    A. yolo_head()

    1 def yolo_head(feats, anchors, num_classes, input_shape, calc_loss=False):
    2     """Convert final layer features to bounding box parameters."""

      获得anchors的个数,并初始化一个相关的张量

    1 num_anchors = len(anchors)
    2 # Reshape to batch, height, width, num_anchors, box_params.
    3 anchors_tensor = K.reshape(K.constant(anchors), [1, 1, 1, num_anchors, 2])

      创建网格

      这个网格用于后续将偏移量转化为每个中心点的坐标

    • 会包含x轴的网格和y轴的网格 两部分
    • 每部分每行都是从0到12的网格数
    • 最后将其合并在一起,组成了一个13*13*1*2的矩阵
    1 grid_shape = K.shape(feats)[1:3]  # height, width   获取网格的尺寸 eg:13*13
    2 grid_y = K.tile(K.reshape(K.arange(0, stop=grid_shape[0]), [-1, 1, 1, 1]),
    3     [1, grid_shape[1], 1, 1])   # 0~12的 从代码上看,只有grid_shape[1]能够体现reshape的这个功能 一个像素一个格子?
    4 grid_x = K.tile(K.reshape(K.arange(0, stop=grid_shape[1]), [1, -1, 1, 1]),
    5     [grid_shape[0], 1, 1, 1])   #  用于生成网格grid
    6 grid = K.concatenate([grid_x, grid_y])
    7 grid = K.cast(grid, K.dtype(feats))  #  -》(13,13,1,2)

      将最后一位展开,网格info与其它分离

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

      进行计算

    • 中心点坐标
    • 宽高
    • 框的置信度
    • 类别概率
    1 box_xy = (K.sigmoid(feats[..., :2]) + grid) / K.cast(grid_shape[::-1], K.dtype(feats))
    2 box_wh = K.exp(feats[..., 2:4]) * anchors_tensor / K.cast(input_shape[::-1], K.dtype(feats))
    3 box_confidence = K.sigmoid(feats[..., 4:5])  # 框置信度
    4 box_class_probs = K.sigmoid(feats[..., 5:])   # 类别置信度    # 可以一次性的把所有网格包含的内容都算出来

    中心点的坐标是相对于网格尺寸的位置

    宽高是相对于416*416尺寸的位置

    B. yolo_correct_boxes()

    1 def yolo_correct_boxes(box_xy, box_wh, input_shape, image_shape):
    2     '''转化成真实的坐标'''

      进行坐标的翻转

    1 box_yx = box_xy[..., ::-1]
    2  box_hw = box_wh[..., ::-1]

      获得图像经过等比缩放以后的尺寸(没有灰边的)--- new_shape

    1 input_shape = K.cast(input_shape, K.dtype(box_yx))
    2 image_shape = K.cast(image_shape, K.dtype(box_yx))
    3 new_shape = K.round(image_shape * K.min(input_shape/image_shape))

    将box的中心点、宽高调整至原图尺寸

    1 offset = (input_shape-new_shape)/2./input_shape
    2 scale = input_shape/new_shape
    3 box_yx = (box_yx - offset) * scale
    4 box_hw *= scale

      将中心点、宽高信息转化为四个坐标点 xmin ymin xmax ymax

    1 box_mins = box_yx - (box_hw / 2.)
    2 box_maxes = box_yx + (box_hw / 2.)
    3 boxes =  K.concatenate([
    4     box_mins[..., 0:1],  # y_min
    5     box_mins[..., 1:2],  # x_min
    6     box_maxes[..., 0:1],  # y_max
    7     box_maxes[..., 1:2]  # x_max
    8 ])# 即转化为真实坐标
    9 return boxes     ---- 此处的boxes已经是原图上的了  

    C. yolo_boxes_and_scores()

    1 def yolo_boxes_and_scores(feats, anchors, num_classes, input_shape, image_shape):
    2     '''Process Conv layer output'''  处理卷积层输出的结果

      执行A.B

    1 box_xy, box_wh, box_confidence, box_class_probs = yolo_head(feats,
    2     anchors, num_classes, input_shape)
    3 boxes = yolo_correct_boxes(box_xy, box_wh, input_shape, image_shape)
    4 boxes = K.reshape(boxes, [-1, 4])

      计算框的得分

       框的得分=框的置信度*类别概率

    1 box_scores = box_confidence * box_class_probs   # 框的得分=框的置信度*类别置信度
    2 box_scores = K.reshape(box_scores, [-1, num_classes])
    3 return boxes, box_scores   --- 原图上的boxes  而且是四坐标

    D. yolo_eval()

    1 def yolo_eval(yolo_outputs,     # 网络神经元输出的feature map(last layer)
    2               anchors,
    3               num_classes,
    4               image_shape,
    5               max_boxes=20,
    6               score_threshold=.6,
    7               iou_threshold=.5):
    8     """Evaluate YOLO model on given input and return filtered boxes."""

      相关使用的信息初始化:

    1 num_layers = len(yolo_outputs)
    2 anchor_mask = [[6,7,8], [3,4,5], [0,1,2]] if num_layers==3 else [[3,4,5], [1,2,3]] # default setting
    3 input_shape = K.shape(yolo_outputs[0])[1:3] * 32      # 模型最后一层有三个神经元---> (32,16,8)13-26-52  (下采样的时候的尺度缩放)
    4 boxes = []                                            # 每个神经元会有3个anchor_box
    5 box_scores = []

      input_shape 为 416*416的;

      逐层获得的边框信息和框的分的信息:

    1 for l in range(num_layers):
    2     _boxes, _box_scores = yolo_boxes_and_scores(yolo_outputs[l],
    3         anchors[anchor_mask[l]], num_classes, input_shape, image_shape)   # 处理卷积层的输出 内含yolo_head
    4     boxes.append(_boxes)
    5     box_scores.append(_box_scores)    # 

      将boxes 和 box_scores的数据展平:

    1 boxes = K.concatenate(boxes, axis=0)   # 将数据展平 (?,4)---四坐标(与标定一样)
    2 box_scores = K.concatenate(box_scores, axis=0)

      获得了一个边界框的得分是否大于阈值的布尔向量,用于将低于阈值的边界框过滤掉

    1 mask = box_scores >= score_threshold     # 对三种尺度的得分进行筛选(阈值) 得到一堆符合条件的布尔值

      初始化:

    1 max_boxes_tensor = K.constant(max_boxes, dtype='int32')
    2 boxes_ = []
    3 scores_ = []
    4 classes_ = []

      按类进行进一步的框框筛选:

    1 for c in range(num_classes):

      过滤掉一些得分小于阈值的边框:

    1 class_boxes = tf.boolean_mask(boxes, mask[:, c])   # 过滤掉一些得分小于阈值的边界框
    2 class_box_scores = tf.boolean_mask(box_scores[:, c], mask[:, c])

      进行NMS处理(处理掉标定同一物体的多个标定的框)

    1 nms_index = tf.image.non_max_suppression(
    2     class_boxes, class_box_scores, max_boxes_tensor, iou_threshold=iou_threshold)  # 运行非最大抑止—用于排出同一目标被标注多次的框
    1 class_boxes = K.gather(class_boxes, nms_index)   # 在class_boxes中搜寻nms_index下表的向量
    2 class_box_scores = K.gather(class_box_scores, nms_index)
    3 classes = K.ones_like(class_box_scores, 'int32') * c  
    4 boxes_.append(class_boxes)
    5 scores_.append(class_box_scores)
    6 classes_.append(classes)

      从此结束循环

    1 boxes_ = K.concatenate(boxes_, axis=0)
    2 scores_ = K.concatenate(scores_, axis=0)
    3 classes_ = K.concatenate(classes_, axis=0)
    4 return boxes_, scores_, classes_

      generate中包含导入模型以及给不同的类分配一种颜色的框,然后就是yolo_eval().

  • 相关阅读:
    如何学习WindDbg
    如何在程序中嵌入google的V8 Javascript引擎
    理解程序内存
    如何学习Windows编程
    如何让窗口控件半透明
    Sessions, Window Stations and Desktops
    QQ截图时窗口自动识别的原理
    为什么设计模式在C++社区没有Java社区流行?
    当年写的俄罗斯方块
    如何判断一个C++对象是否在堆上
  • 原文地址:https://www.cnblogs.com/monologuesmw/p/12794883.html
Copyright © 2011-2022 走看看