TensorFlow 2 实战之从零开始构建 YOLOv3 目标检测网络

TensorFlow 2 实战之从零开始构建 YOLOv3 目标检测网络_第1张图片

TensorFlow 2 实战之从零开始构建 YOLOv3 目标检测网络_第2张图片

引言

今天为大家带来社区作者的精选推荐《Tensorfow 2 实战之从零开始构建 YOLOv3 目标检测网络》。想知道如何正确使用 Tensorflow 2 实现 YOLOv3 算法?CSDN 认证博客专家 @ZONG_XP 为你带来专业讲解,手把手教你搭建目标检测网络!

TensorFlow 2 实战之从零开始构建 YOLOv3 目标检测网络_第3张图片

TensorFlow 2 实战之从零开始构建 YOLOv3 目标检测网络_第4张图片

TensorFlow 2 实战之从零开始构建 YOLOv3 目标检测网络_第5张图片

网上虽然有很多利用 TensorFlow 实现 YOLOv3 的代码和文章,但感觉讲解得还不够透彻,对于新手而言,存在一定的理解难度。本文目的是为了从零开始构建 YOLOv3 目标检测网络,对网络和代码细节做详细地介绍,使大家对 TensorFlow 的使用方法有一个基本的掌握。 

本文主要包含以下四个方面的内容:

  1. 为什么选择学习 TensorFlow,以及如何安装 TensorFlow?

  2. 为什么选择 YOLOv3 目标检测网络?

  3. 深度学习前向推理的整个流程是什么样的?

  4. 对 YOLOv3 网络构建部分进行详细的代码介绍。

1 Why TensorFlow ?

1.1 TensorFlow 特点

很多入门深度学习的同学都会有类似的困惑,市面上有很多开源框架,如 PyTorch、Caffe、MXNet、PaddlePaddle等,该如何进行选择呢?在我看来,TensorFlow 的生态建设是最完整的,因为:

  • 在开发语言方面,它支持 Python 开发、C++ 开发、JavaScript 开发、Swift 开发;

  • 在模型开发方面,支持数据输入预处理、模型构建、模型训练、模型导出全流程;

  • 在模型部署方面,支持在移动设备、嵌入式设备、服务端多种方式部署;

  • 在硬件平台方面,支持在 CPU、GPU、TPU、RPI 等不同硬件上运行;

  • 在开发工具方面,支持 TensorBoard 训练可视化、TensorFlow Hub 丰富的模型库

TensorFlow 2 实战之从零开始构建 YOLOv3 目标检测网络_第6张图片

基于 TensorFlow 丰富的生态特点,我将 TensorFlow 作为开发的第一框架,便于自己快速在生产环境中部署深度学习模型。

1.2 TensorFlow 安装

在官方安装教程中,介绍了两种安装方法,分别是 pip 软件包管理器以及 docker 容器两种方式,同时还提供了在 Google Colab 上调试的方法(需要稳定的网络)。

  • 官方安装教程
    https://tensorflow.google.cn/install

  • Google Colab
    https://colab.research.google.com/notebooks/welcome.ipynb

docker 可以有效的对环境进行隔离,容器中已经配置好了相关环境,可以免去很多手动安装操作,但对于 docker 操作不熟悉的同学来说,使用起来不太方便。

为了避免环境之间的影响,这里我推荐使用 conda 来创建虚拟环境。本文不介绍 conda 的安装及使用方法,没有安装的同学可以参考相关教程。

创建 conda 环境,安装GPU 版本的 TensorFlow 

conda create -n tensorflow-gpu-2 python=3.7  
conda activate tensorflow-gpu-2  
pip install tensorflow-gpu

经过以上步骤,会在你的系统中安装好最新版本的 TensorFlow 。

2 Why YOLOv3?

YOLOv3 作为 one-stage 目标检测算法典型代表,在工业界应用非常广泛,尤其是对于实时性要求较高的场合,YOLOv3 及其各种变体基本上是第一选择。虽然现在已经有 YOLOv4 和 YOLOv5 更先进的算法,但也都是在 YOLOv3 的基础上改进而来,因此有必要先掌握 YOLOv3 再学习 YOLOv4 和 YOLOv5。

YOLOv3 原论文写得还是很“随意”的,很多技术细节没有讲,但这并不妨碍大家对他的肯定,关于对 YOLOv3 的解读,推荐两篇文章,分别是《深入浅出Yolo系列之 Yolov3 & Yolov4 & Yolov5 核心基础知识完整讲解》以及《你一定从未看过如此通俗易懂的 YOLO 系列(从 V1 到 V5 )模型解读!》。

  • 深入浅出Yolo系列之 Yolov3 & Yolov4 & Yolov5 核心基础知识完整讲解
    https://zhuanlan.zhihu.com/p/143747206

YOLOv3 整体结构如图所示,可以看到尺寸为 416 x 416 的输入图片进入 Darknet-53 网络后得到了 3 个分支,这些分支在经过一系列的卷积、上采样以及合并等操作后得到三个尺寸不一的 feature map,形状分别为 [13, 13, 255]、[26, 26, 255] 和 [52, 52, 255],对这三个尺度的 feature map 经过一系列的后处理,即可得到输入图像的目标检测框。

TensorFlow 2 实战之从零开始构建 YOLOv3 目标检测网络_第7张图片

YOLOv3 主要有以下几个特点: 

  1. 采用 darknet53 作为特征提取网络;

  2. 多尺度预测,输出三个分支,分别是 8 倍、16 倍、32 倍下采样,分别实现对小目标、中目标、大目标的检测;

  3. 每个尺度使用三种宽高比的 anchor 进行预测,所以总共包含 9 个 anchor;

  4. 网络的三个基本组件,分别是 DBL、res unit、rexn,具体细节参考上图;

  5. 没有池化层和全连接层,通过改变卷积核的步长来调整张量的尺寸;

通过上边的归纳,相信你对 YOLOv3 的算法原理有了基本的了解,接下来重点介绍代码实现部分。

3 Show me code

3.1 总体流程

要完成一个深度学习模型的推理,我们要依次完成以下步骤:

  1. 定义模型输入

  2. 定义模型输出

  3. 加载权重文件

  4. 准备输入数据

  5. 模型前向推理

  6. 输出后处理

  7. 结果可视化

总体代码如下(代码是在 YunYang1994/TensorFlow2.0-Examples (github.com/YunYang1994/TensorFlow2.0-Examples) 的基础上修改),本质上深度学习模型推理都是类似的流程,只不过不同的算法在网络实现部分会有差异。

# 1、定义模型输入  
input_size   = 416  
input_layer  = tf.keras.layers.Input([input_size, input_size, 3])  

# 2、定义模型输出  
# 获得三种尺度的卷积输出  
# 具体实现见 YOLOv3 函数说明  
feature_maps = YOLOv3(input_layer)  
bbox_tensors = []  
# 依次遍历小、中、大尺寸的特征图  
 for i, fm in enumerate(feature_maps):  
     # 对每个分支的通道信息进行解码,得到预测框的大小、置信度和类别概率  
     # 具体操作见 decode 函数说明  
     bbox_tensor = decode(fm, i)  
     bbox_tensors.append(bbox_tensor)  

 # 3 加载权重文件  
 # 根据上边定义好的输入输出,实例化模型  
 model = tf.keras.Model(input_layer, bbox_tensors)  
 # 加载权重文件  
 utils.load_weights(model, "./yolov3.weights")  
 # 输出模型信息  
 model.summary()  

 # 4、准备输入数据  
 image_path   = "./docs/kite.jpg"  
 original_image      = cv2.imread(image_path)  
 original_image      = cv2.cvtColor(original_image, cv2.COLOR_BGR2RGB)  
 original_image_size = original_image.shape[:2]  
 image_data = utils.image_preporcess(np.copy(original_image), [input_size, input_size])  
 image_data = image_data[np.newaxis, ...].astype(np.float32)  

 # 5、模型前向推理  
 pred_bbox = model.predict(image_data)  
 pred_bbox = [tf.reshape(x, (-1, tf.shape(x)[-1])) for x in pred_bbox]  
 pred_bbox = tf.concat(pred_bbox, axis=0)  

 # 6、输出后处理,  
 bboxes = utils.postprocess_boxes(pred_bbox, original_image_size, input_size, 0.3)  
 bboxes = utils.nms(bboxes, 0.45, method='nms')  

 # 7、结果可视化  
 image = utils.draw_bbox(original_image, bboxes)  
 image = Image.fromarray(image)  
 image.save("result.jpg")  
 # image.show()

    

其中模型定义、数据后处理是关键,这里对其具体的实现进行介绍。

3.2 网络结构

代码中的 common.convolutional() 和 common.upsample() 是实现卷积层和上采样操作,其定义在工程中 (TensorFlow2.0-Examples) common.py 文件中,这里不再进一步展开。 

 def YOLOv3(input_layer):  
      # 输入层进入 Darknet-53 网络后,得到了三个分支  
      route_1, route_2, conv = backbone.darknet53(input_layer)  
      # 见上图中的橘黄色模块(DBL),一共需要进行5次卷积操作  
      conv = common.convolutional(conv, (1, 1, 1024,  512))  
      conv = common.convolutional(conv, (3, 3,  512, 1024))  
      conv = common.convolutional(conv, (1, 1, 1024,  512))  
      conv = common.convolutional(conv, (3, 3,  512, 1024))  
      conv = common.convolutional(conv, (1, 1, 1024,  512))  
     conv_lobj_branch = common.convolutional(conv, (3, 3, 512, 1024))  
     # conv_lbbox 用于预测大尺寸物体,shape = [None, 13, 13, 255]  
     conv_lbbox = common.convolutional(conv_lobj_branch, (1, 1, 1024, 3*(NUM_CLASS + 5)),   
                                                             activate=False, bn=False)  
     conv = common.convolutional(conv, (1, 1,  512,  256))  
     # 这里的 upsample 使用的是最近邻插值方法,这样的好处在于上采样过程不需要学习,从而减少了网络参数  
     conv = common.upsample(conv)  
     conv = tf.concat([conv, route_2], axis=-1)  
     conv = common.convolutional(conv, (1, 1, 768, 256))  
     conv = common.convolutional(conv, (3, 3, 256, 512))  
     conv = common.convolutional(conv, (1, 1, 512, 256))  
     conv = common.convolutional(conv, (3, 3, 256, 512))  
     conv = common.convolutional(conv, (1, 1, 512, 256))  
     conv_mobj_branch = common.convolutional(conv, (3, 3, 256, 512))  
     # conv_mbbox 用于预测中等尺寸物体,shape = [None, 26, 26, 255]  
     conv_mbbox = common.convolutional(conv_mobj_branch, (1, 1, 512, 3*(NUM_CLASS + 5)),  
                                                             activate=False, bn=False)  
     conv = common.convolutional(conv, (1, 1, 256, 128))  
     conv = common.upsample(conv)  
     conv = tf.concat([conv, route_1], axis=-1)  
     conv = common.convolutional(conv, (1, 1, 384, 128))  
     conv = common.convolutional(conv, (3, 3, 128, 256))  
     conv = common.convolutional(conv, (1, 1, 256, 128))       conv = common.convolutional(conv, (3, 3, 128, 256))  
     conv = common.convolutional(conv, (1, 1, 256, 128))  
     conv_sobj_branch = common.convolutional(conv, (3, 3, 128, 256))  
     # conv_sbbox 用于预测小尺寸物体,shape = [None, 52, 52, 255]  
     conv_sbbox = common.convolutional(conv_sobj_branch, (1, 1, 256, 3*(NUM_CLASS +5)),   
                                                             activate=False, bn=False)  
     return [conv_sbbox, conv_mbbox, conv_lbbox]

3.3 darknet53

darknet53 网络结构如图所示,主要是由卷积层、残差模块组成。

TensorFlow 2 实战之从零开始构建 YOLOv3 目标检测网络_第8张图片

利用 common.residual_block() 实现残差连接。

  def darknet53(input_data):  

      input_data = common.convolutional(input_data, (3, 3,  3,  32))  
      input_data = common.convolutional(input_data, (3, 3, 32,  64), downsample=True)  

      for i in range(1):  
          input_data = common.residual_block(input_data,  64,  32, 64)  

      input_data = common.convolutional(input_data, (3, 3,  64, 128), downsample=True)  

     for i in range(2):  
         input_data = common.residual_block(input_data, 128,  64, 128)  

     input_data = common.convolutional(input_data, (3, 3, 128, 256), downsample=True)  

     for i in range(8):  
         input_data = common.residual_block(input_data, 256, 128, 256)  

     route_1 = input_data  
     input_data = common.convolutional(input_data, (3, 3, 256, 512), downsample=True)  

     for i in range(8):  
         input_data = common.residual_block(input_data, 512, 256, 512)  

     route_2 = input_data  
     input_data = common.convolutional(input_data, (3, 3, 512, 1024), downsample=True)  

     for i in range(4):  
         input_data = common.residual_block(input_data, 1024, 512, 1024)  

     return route_1, route_2, input_data

3.4 decode 处理

YOLOv3 网络的三个分支输出会被送入 decode 模块对 feature map 的通道信息进行解码,输出的是预测框在原图上的 [x, y, w, h, score, prob]。

  def decode(conv_output, i=0):  
      """ 
      return tensor of shape [batch_size, output_size, output_size, anchor_per_scale, 5 + num_classes] 
              contains (x, y, w, h, score, probability) 
      """  

      conv_shape       = tf.shape(conv_output)  
      batch_size       = conv_shape[0]  
      output_size      = conv_shape[1]  

     # 对 tensor 进行 reshape  
     conv_output = tf.reshape(conv_output, (batch_size, output_size, output_size, 3, 5 + NUM_CLASS))  

     # 按顺序提取[x, y, w, h, c]  
     conv_raw_dxdy = conv_output[:, :, :, :, 0:2] # 中心位置的偏移量  
     conv_raw_dwdh = conv_output[:, :, :, :, 2:4] # 预测框长宽的偏移量  
     conv_raw_conf = conv_output[:, :, :, :, 4:5] # 预测框的置信度  
     conv_raw_prob = conv_output[:, :, :, :, 5: ] # 预测框的类别概率  

     # 好了,接下来是画网格。其中,output_size 等于 13、26 或者 52  
     y = tf.tile(tf.range(output_size, dtype=tf.int32)[:, tf.newaxis], [1, output_size])  
     x = tf.tile(tf.range(output_size, dtype=tf.int32)[tf.newaxis, :], [output_size, 1])  
     xy_grid = tf.concat([x[:, :, tf.newaxis], y[:, :, tf.newaxis]], axis=-1)  
     xy_grid = tf.tile(xy_grid[tf.newaxis, :, :, tf.newaxis, :], [batch_size, 1, 1, 3, 1])  
     xy_grid = tf.cast(xy_grid, tf.float32) # 计算网格左上角的位置,即cx cy的值  

     # 根据上图公式计算预测框的中心位置  
     # 这里的 i=0、1 或者 2, 以分别对应三种网格尺度  
     pred_xy = (tf.sigmoid(conv_raw_dxdy) + xy_grid) * STRIDES[i] # 计算预测框在原图尺寸上的x y  
     pred_wh = (tf.exp(conv_raw_dwdh) * ANCHORS[i]) * STRIDES[i] # 计算预测框在原图尺寸上的w h  
     pred_xywh = tf.concat([pred_xy, pred_wh], axis=-1) # 拼接起来  
     pred_conf = tf.sigmoid(conv_raw_conf) # 计算预测框里object的置信度  
     pred_prob = tf.sigmoid(conv_raw_prob) # 计算预测框里object的类别概率  

     return tf.concat([pred_xywh, pred_conf, pred_prob], axis=-1)

3.5 后处理

从网络结构拿到检测框之后,就需要对框进行后处理了,包括根据阈值去掉得分低的检测框、NMS过滤掉多余的检测框等,然后再把框在原图上绘制出来,就完成了整个检测流程。

4 Summary

通过本文的介绍,相信大家对 TensorFlow 的特点以及实现目标检测算法的流程有了基本的了解,在后续的文章中,我会进一步的介绍如何训练 YOLOv3 网络,以及如何在生产环境中部署 YOLOv3 网络,敬请期待!

最后

本文由 TensorFlow 社区作者创作,文章已入选 “TensorFlow 开发者出道计划” 精选推荐,并由 TensorFlow CSDN 社区进行收录。

·················END·················


—推荐阅读—

谷歌提出Meta Pseudo Labels,刷新ImageNet上的SOTA!

PyTorch 源码解读之 torch.autograd

涨点神器FixRes:两次超越ImageNet数据集上的SOTA

Transformer为何能闯入CV界秒杀CNN?

SWA:让你的目标检测模型无痛涨点1% AP

CondInst:性能和速度均超越Mask RCNN的实例分割模型

centerX: 用新的视角的方式打开CenterNet

mmdetection最小复刻版(十一):概率Anchor分配机制PAA深入分析

MMDetection新版本V2.7发布,支持DETR,还有YOLOV4在路上!

CNN:我不是你想的那样

TF Object Detection 终于支持TF2了!

无需tricks,知识蒸馏提升ResNet50在ImageNet上准确度至80%+

不妨试试MoCo,来替换ImageNet上pretrain模型!

重磅!一文深入深度学习模型压缩和加速

从源码学习Transformer!

mmdetection最小复刻版(七):anchor-base和anchor-free差异分析

mmdetection最小复刻版(四):独家yolo转化内幕

机器学习算法工程师


                                    一个用心的公众号


 

你可能感兴趣的:(算法,网络,人工智能,深度学习,机器学习)