@@ -1,7 +1,12 @@
"""YOLO_v3 Model Defined in Keras."""
"""YOLO_v3 Model Defined in Keras.
from functools import wraps
Self-Driving Golf Cart, 2017-2019
Neil Nie
contact@neilnie.com.
"""
from functools import wraps
import numpy as np
import tensorflow as tf
from keras import backend as K
@@ -10,19 +15,22 @@
from keras .layers .normalization import BatchNormalization
from keras .models import Model
from keras .regularizers import l2
from utils import compose
from yolo3 . utils import compose
@ wraps ( Conv2D )
@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 ):
"""Darknet Convolution2D followed by BatchNormalization and LeakyReLU."""
no_bias_kwargs = {'use_bias' : False }
no_bias_kwargs .update (kwargs )
@@ -31,8 +39,10 @@ def DarknetConv2D_BN_Leaky(*args, **kwargs):
BatchNormalization (),
LeakyReLU (alpha = 0.1 ))
def resblock_body (x , num_filters , num_blocks ):
'''A series of resblocks starting with a downsampling Convolution2D'''
"""A series of resblocks starting with a downsampling Convolution2D"""
# 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 )
@@ -43,8 +53,9 @@ def resblock_body(x, num_filters, num_blocks):
x = Add ()([x ,y ])
return x
def darknet_body (x ):
''' Darknent body having 52 Convolution2D layers'''
""" Darknent body having 52 Convolution2D layers"""
x = DarknetConv2D_BN_Leaky (32 , (3 ,3 ))(x )
x = resblock_body (x , 64 , 1 )
x = resblock_body (x , 128 , 2 )
@@ -53,8 +64,9 @@ def darknet_body(x):
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'''
""" 6 Conv2D_BN_Leaky layers followed by a Conv2D_linear layer"""
x = compose (
DarknetConv2D_BN_Leaky (num_filters , (1 ,1 )),
DarknetConv2D_BN_Leaky (num_filters * 2 , (3 ,3 )),
@@ -86,8 +98,9 @@ def yolo_body(inputs, num_anchors, num_classes):
return Model (inputs , [y1 ,y2 ,y3 ])
def tiny_yolo_body (inputs , num_anchors , num_classes ):
''' Create Tiny YOLO_v3 model CNN body in keras.'''
""" 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' ),
@@ -148,7 +161,7 @@ def yolo_head(feats, anchors, num_classes, input_shape, calc_loss=False):
def yolo_correct_boxes (box_xy , box_wh , input_shape , image_shape ):
''' Get corrected boxes'''
""" Get corrected boxes"""
box_yx = box_xy [..., ::- 1 ]
box_hw = box_wh [..., ::- 1 ]
input_shape = K .cast (input_shape , K .dtype (box_yx ))
@@ -174,7 +187,7 @@ def yolo_correct_boxes(box_xy, box_wh, input_shape, image_shape):
def yolo_boxes_and_scores (feats , anchors , num_classes , input_shape , image_shape ):
''' Process Conv layer output'''
""" 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 )
@@ -211,7 +224,6 @@ def yolo_eval(yolo_outputs,
scores_ = []
classes_ = []
for c in range (num_classes ):
# TODO: use keras backend instead of tf.
class_boxes = tf .boolean_mask (boxes , mask [:, c ])
class_box_scores = tf .boolean_mask (box_scores [:, c ], mask [:, c ])
nms_index = tf .image .non_max_suppression (
@@ -230,7 +242,7 @@ def yolo_eval(yolo_outputs,
def preprocess_true_boxes (true_boxes , input_shape , anchors , num_classes ):
''' Preprocess true boxes to training input format
""" Preprocess true boxes to training input format
Parameters
----------
@@ -244,7 +256,7 @@ def preprocess_true_boxes(true_boxes, input_shape, anchors, num_classes):
-------
y_true: list of array, shape like yolo_outputs, xywh are reletive value
'''
"""
assert (true_boxes [..., 4 ]< num_classes ).all (), 'class id must be less than num_classes'
num_layers = len (anchors )// 3 # default setting
anchor_mask = [[6 ,7 ,8 ], [3 ,4 ,5 ], [0 ,1 ,2 ]] if num_layers == 3 else [[3 ,4 ,5 ], [1 ,2 ,3 ]]
@@ -302,7 +314,8 @@ def preprocess_true_boxes(true_boxes, input_shape, anchors, num_classes):
def box_iou (b1 , b2 ):
'''Return iou tensor
"""Return iou tensor
Parameters
----------
@@ -313,7 +326,7 @@ def box_iou(b1, b2):
-------
iou: tensor, shape=(i1,...,iN, j)
'''
"""
# Expand dim to apply broadcasting.
b1 = K .expand_dims (b1 , - 2 )
@@ -343,21 +356,20 @@ def box_iou(b1, b2):
def yolo_loss (args , anchors , num_classes , ignore_thresh = .5 , print_loss = False ):
'''Return yolo_loss tensor
"""Return yolo_loss tensor
Parameters
----------
yolo_outputs: list of tensor, the output of yolo_body or tiny_yolo_body
y_true: list of array, the output of preprocess_true_boxes
anchors: array, shape=(N, 2), wh
num_classes: integer
ignore_thresh: float, the iou threshold whether to ignore object confidence loss
Returns
-------
loss: tensor, shape=(1,)
'''
"""
num_layers = len (anchors )// 3 # default setting
yolo_outputs = args [:num_layers ]
y_true = args [num_layers :]