Yolov3 和 Yolov3

您所在的位置:网站首页 yolov3怎么用h5 Yolov3 和 Yolov3

Yolov3 和 Yolov3

2023-07-31 16:53| 来源: 网络整理| 查看: 265

文章目录 前言一、Yolov3 和 Yolov3-tiny1.网络结构yolov3-tinyyolov3框的回归 二、配置训练参数1.目标检测数据集2.设置anchor box 和classes 三、 配置训练过程四、模型预测总结更新进度

前言

上一篇文章 神奇的目标检测 已经介绍了目标检测的基础啦。目标检测呢,就是在图片中定位出目标的位置,把它“框”出来就好了。本篇文章使用Yolov3 和Yolov3-tiny,以训练VOC2007和口罩检测为例。教大家如何快速的搭建自己的目标检测平台。下面是资源链接:

内容链接VOC2007 数据集链接戴口罩数据集链接权重文件链接 提取码:y32mgithub项目地址链接完整项目地址(包含所有文件)链接 提取码 jmpl 一、Yolov3 和 Yolov3-tiny

2018 年,推出了Yolov3,相比于Yolov2 最主要的改进又一下几点: 1. 加深了网络,使用Darknet53,提升了模型得检测能力。 2.使用了FPN结构(空间金字塔结构),能增强不同大小目标的检测能力。 3.使用了focal loss,解决了样本不均和分类难得问题。 对于tiny版本来说,只使用了简单的44层卷积用作普通的特征提取,只有两个输出的yolo head (Yolov3有3个yolo head)每个网格点使用两3个anchor boxes(和Yolov3一样)。所以tiny版本检测速度是很快的哦~。 优点:检测速度快,背景误检率低,泛化性强 缺点:召回率低,定位精度较差,对于靠近或遮挡的目标,小目标检测能力弱,容易出现漏检。

1.网络结构

网络结构中包含了很多基础块,我们先实现这些基本的块,然后像搭积木一样将这些块给组装起来。每个块的用途我已经写在代码注释里了。

#定义的卷积设置初始化方法和卷积步长和填充方式 @wraps(Conv2D) def DarknetConv2D(*args, **kwargs): """Wrapper to set Darknet parameters for Convolution2D.""" #定义卷积块 darknet_conv_kwargs = {'kernel_regularizer': l2(5e-4)} darknet_conv_kwargs['padding'] = 'valid' if kwargs.get('strides')==(2,2) else 'same' darknet_conv_kwargs.update(kwargs) return Conv2D(*args, **darknet_conv_kwargs) def DarknetConv2D_BN_Leaky(*args, **kwargs): #定义的卷积块包含了BN Leaky 激活函数 """Darknet Convolution2D followed by BatchNormalization and LeakyReLU.""" no_bias_kwargs = {'use_bias': False} no_bias_kwargs.update(kwargs) return compose( DarknetConv2D(*args, **no_bias_kwargs), BatchNormalization(), LeakyReLU(alpha=0.1)) def resblock_body(x, num_filters, num_blocks): '''A series of resblocks starting with a downsampling Convolution2D''' #定义 yolo 主干使用的残差快 # Darknet uses left and top padding instead of 'same' mode x = ZeroPadding2D(((1,0),(1,0)))(x) x = DarknetConv2D_BN_Leaky(num_filters, (3,3), strides=(2,2))(x) for i in range(num_blocks): y = compose( DarknetConv2D_BN_Leaky(num_filters//2, (1,1)), DarknetConv2D_BN_Leaky(num_filters, (3,3)))(x) x = Add()([x,y]) return x def darknet_body(x): '''Darknent body having 52 Convolution2D layers''' #darknet 53 #卷积核大小3x3 32 个卷积核 x = DarknetConv2D_BN_Leaky(32, (3,3))(x) x = resblock_body(x, 64, 1) x = resblock_body(x, 128, 2) x = resblock_body(x, 256, 8) x = resblock_body(x, 512, 8) x = resblock_body(x, 1024, 4) return x def make_last_layers(x, num_filters, out_filters): '''6 Conv2D_BN_Leaky layers followed by a Conv2D_linear layer''' # 这里是输入yolo,制造最后一层的代码 也就是yolo head x = compose( DarknetConv2D_BN_Leaky(num_filters, (1,1)), DarknetConv2D_BN_Leaky(num_filters*2, (3,3)), DarknetConv2D_BN_Leaky(num_filters, (1,1)), DarknetConv2D_BN_Leaky(num_filters*2, (3,3)), DarknetConv2D_BN_Leaky(num_filters, (1,1)))(x) y = compose( DarknetConv2D_BN_Leaky(num_filters*2, (3,3)), DarknetConv2D(out_filters, (1,1)))(x) return x, y yolov3-tiny

tiny 版本网络结构比较简单,我们先来看一个图: 在这里插入图片描述 网络中就是普通的卷积核和池化,且网络很浅,网络的计算过程如箭头所示。是不是网络很简单呀~~~~~。 我们接下来看代码如何实现。

def tiny_yolo_body(inputs, num_anchors, num_classes): #------------------------------------------------------------------- # inputs 输入向量 num_anchors anchor boxes的数量 num_classes 类别数 #------------------------------------------------------------------ '''Create Tiny YOLO_v3 model CNN body in keras.''' x1 = compose( DarknetConv2D_BN_Leaky(16, (3,3)), MaxPooling2D(pool_size=(2,2), strides=(2,2), padding='same'), DarknetConv2D_BN_Leaky(32, (3,3)), MaxPooling2D(pool_size=(2,2), strides=(2,2), padding='same'), DarknetConv2D_BN_Leaky(64, (3,3)), MaxPooling2D(pool_size=(2,2), strides=(2,2), padding='same'), DarknetConv2D_BN_Leaky(128, (3,3)), MaxPooling2D(pool_size=(2,2), strides=(2,2), padding='same'), DarknetConv2D_BN_Leaky(256, (3,3)))(inputs) x2 = compose( MaxPooling2D(pool_size=(2,2), strides=(2,2), padding='same'), DarknetConv2D_BN_Leaky(512, (3,3)), MaxPooling2D(pool_size=(2,2), strides=(1,1), padding='same'), DarknetConv2D_BN_Leaky(1024, (3,3)), DarknetConv2D_BN_Leaky(256, (1,1)))(x1) y1 = compose( DarknetConv2D_BN_Leaky(512, (3,3)), DarknetConv2D(num_anchors*(num_classes+5), (1,1)))(x2) x2 = compose( DarknetConv2D_BN_Leaky(128, (1,1)), UpSampling2D(2))(x2) y2 = compose( Concatenate(), DarknetConv2D_BN_Leaky(256, (3,3)), DarknetConv2D(num_anchors*(num_classes+5), (1,1)))([x2,x1]) return Model(inputs, [y1,y2])

DarknetConv2D DarknetConv2D_BN_Leaky 是使用二维卷积定义的块,可以去代码里查看,很好理解的。

yolov3

yolov3使用了残差结构和FPN, 网络较深,结构复杂,我们先来看一下他的整体网络结构: 在这里插入图片描述 这里使用的FPN(Feature Pyramid Network) 特征金字塔如下图所示:

在目标检测中,往往会包含不用大小的目标,多层卷积后,小目标的语义丢失比较严重,使用FPN 能有效的利用多层特征信息 加强浅层小目标的特征新信息,提升网络的检测能力。

def yolo_body(inputs, num_anchors, num_classes): """Create YOLO_V3 model CNN body in Keras.""" #------------------------------------------------------------------ # inputs 输入向量 num_anchors anchor boxes的数量 num_classes 类别数 #------------------------------------------------------------------ darknet = Model(inputs, darknet_body(inputs)) x, y1 = make_last_layers(darknet.output, 512, num_anchors*(num_classes+5)) x = compose( DarknetConv2D_BN_Leaky(256, (1,1)), UpSampling2D(2))(x) x = Concatenate()([x,darknet.layers[152].output]) x, y2 = make_last_layers(x, 256, num_anchors*(num_classes+5)) x = compose( DarknetConv2D_BN_Leaky(128, (1,1)), UpSampling2D(2))(x) x = Concatenate()([x,darknet.layers[92].output]) x, y3 = make_last_layers(x, 128, num_anchors*(num_classes+5)) # yolo head的输出。 return Model(inputs, [y1,y2,y3])

代码中的 make_last_layers 是产生YOLO的输出层,对于参数 :

num_anchors*(num_classes+5)

yolo3 每个网格点有3个anchor boxes,num_achors=3 ,每个anchor box都要预测所有的类别,假设我们使用的是coco数据集有80类别,num_classes=80, 5代表框的p(框中有目标的概率),x_offset、y_offset、h和w 4个值。yolo head 中的输出维度就为 [batch_size,w,h,3x(4+1+80)]。 如下图所示: 在这里插入图片描述 大家都会说Yolo会将图片划分为13x13,26x26, 52x52 的网格,但不是直接的物理划分,而是用这样的卷积层来表示。将每一个网格点的参数,藏在卷积特征层中,来表示物体的位置信息和类别。 在这里插入图片描述

网格13*1326*2652*52感受野大 (大目标)中 (中目标)小 (小目标)先验框(coco)116x90,156x198,373x32630x61,62x45,59x11910x13,16x30,33x23

上图中 黄色表示真实框, 红色表示目标中心点所在的网格。蓝色表示所设置的anchor boxes,分别检测大中小三种目标。不仅仅是以红色网格点为中心有先验框,会以每个网格点为中心都会有三个这样的先验证框。预测框数 == 网格数*锚框数。也就是总共有32x32x3+26x26x3+52x52x3个锚框。所以Yolo算法是对图片使用了"人海战术"。

框的回归

在这里插入图片描述 首先我们要明白,特征图(也就是Yolo head)表示预测框的信息: 1.坐标信息: t x , t y , t w , t h t_x,t_y,t_w,t_h tx​,ty​,tw​,th​ 2.坐标置信度(有无目标): P o b j P_obj Po​bj 3.分类: p c p_c pc​ 上图中锚框为长宽[Pw,Ph],中心点为[cx,cy],预测框为[bw,bh],现在我们需要把锚框向真实框靠近需要进行两步. 第一步:中心点偏移。

其中,δ 为sigmoid 函数,bx,by为预测框中心点坐标。

第二步:宽高拉伸

这样就得到了框的长宽。我们就能还原出这样一个真实物体的框啦。

通过这样的方式就会将特征图的信息还原成,真实框啦,训练时我们训练得到的就是这些能让锚框偏移的信息。 为什么要用sigmoid 函数 和exp指数呢?。因为在中心点偏移时,我们希望中心点只在它所在的网格里偏移,所以需要将其转化为0~1之间。sigmoid激活函数正好做了这件事。 宽高拉伸是因为我们宽和高的取值都是正的,那么exp函数的值域正好为0~正无穷。

二、配置训练参数 1.目标检测数据集

以VOC 2007 数据集为例,首先来看一下文件树:

─VOC2007 ├─Annotations │ └─000005.xml │ └─000006.xml │ └─xxxx.xml ├─ImageSets │ └─Main └─JPEGImages │ └─000005.jpg │ └─000006.jpg │ └─xxxx.jpg

每一个xml 包含同名jpg 中目标的位置以及类别,在训练时,首先要将数据 通过 voc_annotation.py 转换到记事本中,方便训练时读取。记事本形式如下:

VOC2007/JPEGImages/000005.jpg(图片路径)98,267,194,383(框的位置) ,1(框中目标的类别) VOC2007/JPEGImages/000006.jpg(图片路径)99,205,198,318(框的位置) ,1(框中目标的类别)

这样的数据是无法直接和YOLO的输出进行计算的,那么我们还需要将,这种数据编码成和yolo head 的格式一样,才能去计算loss,反向传播调整参数 通过如下函数将y_ture,转换成和y_predict 一样的形式:

def preprocess_true_boxes(true_boxes, input_shape, anchors, num_classes): '''Preprocess true boxes to training input format Parameters ---------- true_boxes: array, shape=(m, T, 5) Absolute x_min, y_min, x_max, y_max, class_id relative to input_shape. input_shape: array-like, hw, multiples of 32 anchors: array, shape=(N, 2), wh num_classes: integer Returns ------- y_true: list of array, shape like yolo_outputs, xywh are reletive value ''' assert (true_boxes[..., 4]# use custom yolo_loss Lambda layer. "yolo_loss": lambda y_true, y_pred: y_pred} ) print("Train on {} samples, val on {} samples, with batch size {}.".format(num_train, num_val, batch_size)) model.fit( data_generator_wrapper(train_lines,batch_size,input_shape,anchors,num_classes), steps_per_epoch=max(1,num_train//batch_size), validation_data=data_generator_wrapper(val_lines,batch_size,input_shape,anchors,num_classes), validation_steps=max(1,num_val//batch_size), epochs=50, initial_epoch=0, callbacks=[logging,checkpoint] ) model.save_weights(weights_dir+'trained_weights_stage_1.h5') #----------------------------------------------------------------- # 解冻所有层,并调小学习率训练 #----------------------------------------------------------------- if True: for i in range(len(model.layers)): model.layers[i].trainable = True model.compile(optimizer=Adam(lr=1e-4), loss={'yolo_loss': lambda y_true, y_pred: y_pred}) # recompile to apply the change print('Unfreeze all of the layers.') batch_size = 32 # note that more GPU memory is required after unfreezing the body print('Train on {} samples, val on {} samples, with batch size {}.'.format(num_train, num_val, batch_size)) model.fit_generator(data_generator_wrapper(train_lines, batch_size, input_shape, anchors, num_classes), steps_per_epoch=max(1, num_train//batch_size), validation_data=data_generator_wrapper(val_lines, batch_size, input_shape, anchors, num_classes), validation_steps=max(1, num_val//batch_size), epochs=100, initial_epoch=50, callbacks=[logging, checkpoint, reduce_lr, early_stopping]) model.save_weights(log_dir + 'trained_weights_final.h5') if __name__=='__main__': train()

上述就是训练过程了,训练时, 注意: 使用model 时,tiny 和YOLOv3 achor 不一样,一个是6 类 一个是9类 ,记得更换。 tips:训练过错先冻结一部分层去训练,这样训练比较快,当损失稳定之后,使用更小的学习率去,趋势模型收敛更好。 训练结果如下: 在这里插入图片描述 可以看出收敛效果还是相当不错的。。。。

四、模型预测

模型预测,将图片输入到保存的模型当中,如果输出一个跟yolo head 一样的维度,很显然这个时候我们是无法获图片的类别还有框的,那么需要通过解码,拿到我们有用的数据。这个工作在项目中的yolo.py 中实现。

#这里不给出全部代码了,但在预测是,网络的权重文件 achor boxes 和classes 要保持一直,在文件中这个位置配置 _defaults = { # ---------------------------------- # 模型路径, anchor路径,class 路径, # sorce 一本设为0.5 置信度阈值 iou # 输入图片大小 一般是13 的倍数 # gpu 1 # 遇上维度 不匹配问题 可能是训练时 忘记让model_path 和class_path 对应 # ----------------------------------- "model_path": r"logs\yolov3_log\trained_weights_final.h5", "anchors_path": "model_data\mask_anchor.txt", "classes_path": "model_data\mask_classes.txt", "score": 0.3, # 置信度 ,自己改大一点 "iou": 0.3, "model_image_size": (416, 416), "gpu_num": 1, # 这里使用1 ,多gpu 由于TensorFlow 版本问题 我给注解掉了 }

模型预测完整代码:

import time import cv2 import numpy as np import tensorflow as tf from PIL import Image from yolo import YOLO,detect_video import os from tqdm import tqdm def predict(): #------------------------------------------------------------- # 新建 yolo 对象,使用模型权重路径,以及其他参数,到yolo.py 中更改 # predict_model 为 img 预测图片, video 输入为视频,记得更改视频路劲 video_path 为 0 时调用摄像头 为视频路径时 读取视频 # predict_model 为 dir_predict 时输入为存放图片的路径 ,预测完成后 放入out_img 使用时记得修改路劲 # 要预测 指定类别是 ,可以设置好重新训练模型,或者进入detect_image 修改参数 if predicted_classes='car' #-------------------------------------------------------------- yolo=YOLO() predict_model='dir_predict' video_path= 0 video_save_path="" dir_img_input='img/' dir_save_path='out_img/' if predict_model=='img': while(True): img=input('Input image filename:') try: image=Image.open(img) except: print('Open Image Error! Please Try Again') continue else: out_image=yolo.detect_image(image) out_image.show() elif predict_model=='video': detect_video(yolo,video_path,video_save_path) # 可以获取fps elif predict_model=='dir_predict': #--------------------------------- #拿到所有图片 通过detect image 检测 #------------------------------- imgs=os.listdir(dir_img_input) for img_name in tqdm(imgs): if img_name.lower().endswith(('.bmp', '.dib', '.png', '.jpg', '.jpeg', '.pbm', '.pgm', '.ppm', '.tif', '.tiff')): image_path = os.path.join(dir_img_input, img_name) image = Image.open(image_path) r_image = yolo.detect_image(image) if not os.path.exists(dir_save_path): os.makedirs(dir_save_path) r_image.save(os.path.join(dir_save_path, img_name)) else: raise AssertionError("Please specify the correct mode: 'img', 'video', 'dir_predict'.") if __name__=='__main__': predict()

使用方法写在注释里了。支持视频,图片 文件夹 检测结果: 在这里插入图片描述

总结

目标检测网络结构,其实跟图像分类差不多,但对数据读取过程,编码解码过程比较复杂,对于这个过程,我掌握不是很多,所以就不在这里讲解了。 目标检测训练比较难,数据标注比较麻烦。后面可能会出一期视频,教大家如何配置。所有代码和资源均会上传,写代码不易,欢迎点赞~

更新进度

2022/1/15日。重置了YOLOV3 Anchor 框的设置理论讲解。接下来完成anchor boxes 框是如何回归及loss 计算过程。 2022/2/5日。添加了anchor boxes 和预测框之间的计算过程。 2022/2/14日,更改冗余错误,接下来会去重置代码。



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3