Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

keras-yolo3 compatible for TF 2.x #756

Open
wants to merge 9 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 14 additions & 16 deletions convert.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#! /usr/bin/env python
"""
Reads Darknet config and weights and creates Keras model with TF backend.
Reads Darknet config and weights and creates tensorflow.keras model with TF backend.

"""

Expand All @@ -11,29 +11,27 @@
from collections import defaultdict

import numpy as np
from keras import backend as K
from keras.layers import (Conv2D, Input, ZeroPadding2D, Add,
UpSampling2D, MaxPooling2D, Concatenate)
from keras.layers.advanced_activations import LeakyReLU
from keras.layers.normalization import BatchNormalization
from keras.models import Model
from keras.regularizers import l2
from keras.utils.vis_utils import plot_model as plot
from tensorflow.keras import backend as K
from tensorflow.keras.layers import Conv2D, Input, ZeroPadding2D, Add,UpSampling2D, MaxPooling2D,LeakyReLU,Concatenate,BatchNormalization

from tensorflow.keras.models import Model
from tensorflow.keras.regularizers import l2
from tensorflow.keras.utils import plot_model as plot

parser = argparse.ArgumentParser(description='Darknet To Keras Converter.')

parser = argparse.ArgumentParser(description='Darknet To tensorflow.keras Converter.')
parser.add_argument('config_path', help='Path to Darknet cfg file.')
parser.add_argument('weights_path', help='Path to Darknet weights file.')
parser.add_argument('output_path', help='Path to output Keras model file.')
parser.add_argument('output_path', help='Path to output tensorflow.keras model file.')
parser.add_argument(
'-p',
'--plot_model',
help='Plot generated Keras model and save as image.',
help='Plot generated tensorflow.keras model and save as image.',
action='store_true')
parser.add_argument(
'-w',
'--weights_only',
help='Save as Keras weights file instead of model file.',
help='Save as tensorflow.keras weights file instead of model file.',
action='store_true')

def unique_config_sections(config_file):
Expand Down Expand Up @@ -84,7 +82,7 @@ def _main(args):
cfg_parser = configparser.ConfigParser()
cfg_parser.read_file(unique_config_file)

print('Creating Keras model.')
print('Creating tensorflow.keras model.')
input_layer = Input(shape=(None, None, 3))
prev_layer = input_layer
all_layers = []
Expand Down Expand Up @@ -240,10 +238,10 @@ def _main(args):
print(model.summary())
if args.weights_only:
model.save_weights('{}'.format(output_path))
print('Saved Keras weights to {}'.format(output_path))
print('Saved tensorflow.keras weights to {}'.format(output_path))
else:
model.save('{}'.format(output_path))
print('Saved Keras model to {}'.format(output_path))
print('Saved tensorflow.keras model to {}'.format(output_path))

# Check to see if all weights have been read.
remaining_weights = len(weights_file.read()) / 4
Expand Down
18 changes: 8 additions & 10 deletions train.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@
"""

import numpy as np
import keras.backend as K
from keras.layers import Input, Lambda
from keras.models import Model
from keras.optimizers import Adam
from keras.callbacks import TensorBoard, ModelCheckpoint, ReduceLROnPlateau, EarlyStopping
import tensorflow.keras.backend as K
from tensorflow.keras.layers import Input, Lambda
from tensorflow.keras.models import Model
from tensorflow.keras.optimizers import Adam
from tensorflow.keras.callbacks import TensorBoard, ModelCheckpoint, ReduceLROnPlateau, EarlyStopping

from yolo3.model import preprocess_true_boxes, yolo_body, tiny_yolo_body, yolo_loss
from yolo3.utils import get_random_data
Expand Down Expand Up @@ -56,7 +56,7 @@ def _main():

batch_size = 32
print('Train on {} samples, val on {} samples, with batch size {}.'.format(num_train, num_val, batch_size))
model.fit_generator(data_generator_wrapper(lines[:num_train], batch_size, input_shape, anchors, num_classes),
model.fit(data_generator_wrapper(lines[:num_train], batch_size, input_shape, anchors, num_classes),
steps_per_epoch=max(1, num_train//batch_size),
validation_data=data_generator_wrapper(lines[num_train:], batch_size, input_shape, anchors, num_classes),
validation_steps=max(1, num_val//batch_size),
Expand All @@ -75,7 +75,7 @@ def _main():

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(lines[:num_train], batch_size, input_shape, anchors, num_classes),
model.fit(data_generator_wrapper(lines[:num_train], batch_size, input_shape, anchors, num_classes),
steps_per_epoch=max(1, num_train//batch_size),
validation_data=data_generator_wrapper(lines[num_train:], batch_size, input_shape, anchors, num_classes),
validation_steps=max(1, num_val//batch_size),
Expand Down Expand Up @@ -105,7 +105,6 @@ def get_anchors(anchors_path):
def create_model(input_shape, anchors, num_classes, load_pretrained=True, freeze_body=2,
weights_path='model_data/yolo_weights.h5'):
'''create the training model'''
K.clear_session() # get a new session
image_input = Input(shape=(None, None, 3))
h, w = input_shape
num_anchors = len(anchors)
Expand Down Expand Up @@ -135,7 +134,6 @@ def create_model(input_shape, anchors, num_classes, load_pretrained=True, freeze
def create_tiny_model(input_shape, anchors, num_classes, load_pretrained=True, freeze_body=2,
weights_path='model_data/tiny_yolo_weights.h5'):
'''create the training model, for Tiny YOLOv3'''
K.clear_session() # get a new session
image_input = Input(shape=(None, None, 3))
h, w = input_shape
num_anchors = len(anchors)
Expand Down Expand Up @@ -163,7 +161,7 @@ def create_tiny_model(input_shape, anchors, num_classes, load_pretrained=True, f
return model

def data_generator(annotation_lines, batch_size, input_shape, anchors, num_classes):
'''data generator for fit_generator'''
'''data generator for fit'''
n = len(annotation_lines)
i = 0
while True:
Expand Down
19 changes: 9 additions & 10 deletions train_bottleneck.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@
"""
import os
import numpy as np
import keras.backend as K
from keras.layers import Input, Lambda
from keras.models import Model
from keras.optimizers import Adam
from keras.callbacks import TensorBoard, ModelCheckpoint, ReduceLROnPlateau, EarlyStopping
import tensorflow.keras.backend as K
from tensorflow.keras.layers import Input, Lambda
from tensorflow.keras.models import Model
from tensorflow.keras.optimizers import Adam
from tensorflow.keras.callbacks import TensorBoard, ModelCheckpoint, ReduceLROnPlateau, EarlyStopping

from yolo3.model import preprocess_true_boxes, yolo_body, tiny_yolo_body, yolo_loss
from yolo3.utils import get_random_data
Expand Down Expand Up @@ -63,7 +63,7 @@ def _main():
print("Training last layers with bottleneck features")
print('with {} samples, val on {} samples and batch size {}.'.format(num_train, num_val, batch_size))
last_layer_model.compile(optimizer='adam', loss={'yolo_loss': lambda y_true, y_pred: y_pred})
last_layer_model.fit_generator(bottleneck_generator(lines[:num_train], batch_size, input_shape, anchors, num_classes, bottlenecks_train),
last_layer_model.fit(bottleneck_generator(lines[:num_train], batch_size, input_shape, anchors, num_classes, bottlenecks_train),
steps_per_epoch=max(1, num_train//batch_size),
validation_data=bottleneck_generator(lines[num_train:], batch_size, input_shape, anchors, num_classes, bottlenecks_val),
validation_steps=max(1, num_val//batch_size),
Expand All @@ -77,7 +77,7 @@ def _main():
'yolo_loss': lambda y_true, y_pred: y_pred})
batch_size = 16
print('Train on {} samples, val on {} samples, with batch size {}.'.format(num_train, num_val, batch_size))
model.fit_generator(data_generator_wrapper(lines[:num_train], batch_size, input_shape, anchors, num_classes),
model.fit(data_generator_wrapper(lines[:num_train], batch_size, input_shape, anchors, num_classes),
steps_per_epoch=max(1, num_train//batch_size),
validation_data=data_generator_wrapper(lines[num_train:], batch_size, input_shape, anchors, num_classes),
validation_steps=max(1, num_val//batch_size),
Expand All @@ -96,7 +96,7 @@ def _main():

batch_size = 4 # 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(lines[:num_train], batch_size, input_shape, anchors, num_classes),
model.fit(data_generator_wrapper(lines[:num_train], batch_size, input_shape, anchors, num_classes),
steps_per_epoch=max(1, num_train//batch_size),
validation_data=data_generator_wrapper(lines[num_train:], batch_size, input_shape, anchors, num_classes),
validation_steps=max(1, num_val//batch_size),
Expand Down Expand Up @@ -126,7 +126,6 @@ def get_anchors(anchors_path):
def create_model(input_shape, anchors, num_classes, load_pretrained=True, freeze_body=2,
weights_path='model_data/yolo_weights.h5'):
'''create the training model'''
K.clear_session() # get a new session
image_input = Input(shape=(None, None, 3))
h, w = input_shape
num_anchors = len(anchors)
Expand Down Expand Up @@ -174,7 +173,7 @@ def create_model(input_shape, anchors, num_classes, load_pretrained=True, freeze
return model, bottleneck_model, last_layer_model

def data_generator(annotation_lines, batch_size, input_shape, anchors, num_classes, random=True, verbose=False):
'''data generator for fit_generator'''
'''data generator for fit'''
n = len(annotation_lines)
i = 0
while True:
Expand Down
32 changes: 12 additions & 20 deletions yolo.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,17 @@
Class definition of YOLO_v3 style detection model on image and video
"""

import colorsys
import os
from timeit import default_timer as timer

import colorsys
import numpy as np
from keras import backend as K
from keras.models import load_model
from keras.layers import Input
import tensorflow.keras.backend as K
from tensorflow.keras.models import load_model
from tensorflow.keras.layers import Input
from PIL import Image, ImageFont, ImageDraw
from timeit import default_timer as timer

from yolo3.model import yolo_eval, yolo_body, tiny_yolo_body
from yolo3.utils import letterbox_image
import os
from keras.utils import multi_gpu_model

class YOLO(object):
_defaults = {
Expand All @@ -41,7 +38,6 @@ def __init__(self, **kwargs):
self.__dict__.update(kwargs) # and update with user overrides
self.class_names = self._get_class()
self.anchors = self._get_anchors()
self.sess = K.get_session()
self.boxes, self.scores, self.classes = self.generate()

def _get_class(self):
Expand Down Expand Up @@ -93,10 +89,13 @@ def generate(self):
# Generate output tensor targets for filtered bounding boxes.
self.input_image_shape = K.placeholder(shape=(2, ))
if self.gpu_num>=2:
self.yolo_model = multi_gpu_model(self.yolo_model, gpus=self.gpu_num)
strategy = tf.distribute.MirroredStrategy()
with strategy.scope():
self.yolo_model = load_model(model_path, compile=False)
boxes, scores, classes = yolo_eval(self.yolo_model.output, self.anchors,
len(self.class_names), self.input_image_shape,
score_threshold=self.score, iou_threshold=self.iou)

return boxes, scores, classes

def detect_image(self, image):
Expand All @@ -116,13 +115,9 @@ def detect_image(self, image):
image_data /= 255.
image_data = np.expand_dims(image_data, 0) # Add batch dimension.

out_boxes, out_scores, out_classes = self.sess.run(
[self.boxes, self.scores, self.classes],
feed_dict={
self.yolo_model.input: image_data,
self.input_image_shape: [image.size[1], image.size[0]],
K.learning_phase(): 0
})
out_boxes, out_scores, out_classes = yolo_eval(self.yolo_model.predict(image_data), self.anchors,
len(self.class_names), (image_data.shape[0], image_data.shape[0]),
score_threshold=self.score, iou_threshold=self.iou)

print('Found {} boxes for {}'.format(len(out_boxes), 'img'))

Expand Down Expand Up @@ -166,8 +161,6 @@ def detect_image(self, image):
print(end - start)
return image

def close_session(self):
self.sess.close()

def detect_video(yolo, video_path, output_path=""):
import cv2
Expand Down Expand Up @@ -208,5 +201,4 @@ def detect_video(yolo, video_path, output_path=""):
out.write(result)
if cv2.waitKey(1) & 0xFF == ord('q'):
break
yolo.close_session()

38 changes: 18 additions & 20 deletions yolo3/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,10 @@

import numpy as np
import tensorflow as tf
from keras import backend as K
from keras.layers import Conv2D, Add, ZeroPadding2D, UpSampling2D, Concatenate, MaxPooling2D
from keras.layers.advanced_activations import LeakyReLU
from keras.layers.normalization import BatchNormalization
from keras.models import Model
from keras.regularizers import l2
from tensorflow.keras import backend as K
from tensorflow.keras.layers import Conv2D, Add, ZeroPadding2D, UpSampling2D, Concatenate, MaxPooling2D, LeakyReLU, BatchNormalization
from tensorflow.keras.models import Model
from tensorflow.keras.regularizers import l2

from yolo3.utils import compose

Expand Down Expand Up @@ -123,22 +121,22 @@ 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])
anchors_tensor = tf.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]),
grid_y = tf.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_x = tf.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))
grid = tf.cast(grid, feats.dtype)

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_xy = (K.sigmoid(feats[..., :2]) + grid) / tf.cast(grid_shape[::-1], feats.dtype)
box_wh = K.exp(feats[..., 2:4]) * anchors_tensor / tf.cast(input_shape[::-1], feats.dtype)
box_confidence = K.sigmoid(feats[..., 4:5])
box_class_probs = K.sigmoid(feats[..., 5:])

Expand All @@ -151,8 +149,8 @@ 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))
input_shape = tf.cast(input_shape, box_yx.dtype)
image_shape = tf.cast(image_shape, box_yx.dtype)
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
Expand Down Expand Up @@ -362,11 +360,11 @@ def yolo_loss(args, anchors, num_classes, ignore_thresh=.5, print_loss=False):
yolo_outputs = args[:num_layers]
y_true = args[num_layers:]
anchor_mask = [[6,7,8], [3,4,5], [0,1,2]] if num_layers==3 else [[3,4,5], [1,2,3]]
input_shape = K.cast(K.shape(yolo_outputs[0])[1:3] * 32, K.dtype(y_true[0]))
grid_shapes = [K.cast(K.shape(yolo_outputs[l])[1:3], K.dtype(y_true[0])) for l in range(num_layers)]
input_shape = tf.cast(K.shape(yolo_outputs[0])[1:3] * 32, y_true[0].dtype)
grid_shapes = [tf.cast(K.shape(yolo_outputs[l])[1:3], y_true[0].dtype) for l in range(num_layers)]
loss = 0
m = K.shape(yolo_outputs[0])[0] # batch size, tensor
mf = K.cast(m, K.dtype(yolo_outputs[0]))
mf = tf.cast(m, yolo_outputs[0].dtype)

for l in range(num_layers):
object_mask = y_true[l][..., 4:5]
Expand All @@ -383,13 +381,13 @@ def yolo_loss(args, anchors, num_classes, ignore_thresh=.5, print_loss=False):
box_loss_scale = 2 - y_true[l][...,2:3]*y_true[l][...,3:4]

# Find ignore mask, iterate over each of batch.
ignore_mask = tf.TensorArray(K.dtype(y_true[0]), size=1, dynamic_size=True)
object_mask_bool = K.cast(object_mask, 'bool')
ignore_mask = tf.TensorArray(y_true[0].dtype, size=1, dynamic_size=True)
object_mask_bool = tf.cast(object_mask, 'bool')
def loop_body(b, ignore_mask):
true_box = tf.boolean_mask(y_true[l][b,...,0:4], object_mask_bool[b,...,0])
iou = box_iou(pred_box[b], true_box)
best_iou = K.max(iou, axis=-1)
ignore_mask = ignore_mask.write(b, K.cast(best_iou<ignore_thresh, K.dtype(true_box)))
ignore_mask = ignore_mask.write(b, tf.cast(best_iou<ignore_thresh, true_box.dtype)))
return b+1, ignore_mask
_, ignore_mask = K.control_flow_ops.while_loop(lambda b,*args: b<m, loop_body, [0, ignore_mask])
ignore_mask = ignore_mask.stack()
Expand Down
1 change: 0 additions & 1 deletion yolo_video.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ def detect_img(yolo):
else:
r_image = yolo.detect_image(image)
r_image.show()
yolo.close_session()

FLAGS = None

Expand Down