From 624f57151903e7b524e381261e14ad7226069469 Mon Sep 17 00:00:00 2001 From: hellochick Date: Fri, 19 Jan 2018 19:24:38 +0800 Subject: [PATCH] add inference phase for ade20k dataset ( using pspnet50 ) --- .gitignore | 4 + evaluate.py | 11 +- inference.py | 81 +++++------ input/._test_256x512.png | Bin 4096 -> 0 bytes input/._test_720x720.png | Bin 4096 -> 0 bytes input/indoor_1.jpg | Bin 0 -> 25785 bytes input/indoor_2.jpg | Bin 0 -> 10935 bytes model.py | 267 +++++++++++++++++++++++++++++++++++- network.py | 58 +++----- output/._test_1024x2048.png | Bin 4096 -> 0 bytes output/._test_256x512.png | Bin 4096 -> 0 bytes output/._test_720x720.png | Bin 4096 -> 0 bytes output/indoor_1.jpg | Bin 0 -> 10472 bytes output/indoor_2.jpg | Bin 0 -> 6919 bytes tools.py | 77 ++++++++--- train.py | 8 +- utils/color150.mat | Bin 0 -> 502 bytes 17 files changed, 390 insertions(+), 116 deletions(-) create mode 100644 .gitignore delete mode 100644 input/._test_256x512.png delete mode 100644 input/._test_720x720.png create mode 100644 input/indoor_1.jpg create mode 100644 input/indoor_2.jpg delete mode 100644 output/._test_1024x2048.png delete mode 100644 output/._test_256x512.png delete mode 100644 output/._test_720x720.png create mode 100644 output/indoor_1.jpg create mode 100644 output/indoor_2.jpg create mode 100644 utils/color150.mat diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0755657 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +./ade20k_model +./cityscapes_model +model.ckpt-* +checkpoint diff --git a/evaluate.py b/evaluate.py index 8c53b96..b26674c 100644 --- a/evaluate.py +++ b/evaluate.py @@ -8,8 +8,7 @@ import tensorflow as tf import numpy as np -from model import PSPNet -from tools import decode_labels +from model import PSPNet101 from image_reader import ImageReader IMG_MEAN = np.array((103.939, 116.779, 123.68), dtype=np.float32) @@ -80,13 +79,12 @@ def main(): image_batch, label_batch = tf.expand_dims(image, dim=0), tf.expand_dims(label, dim=0) # Add one batch dimension. # Create network. - net = PSPNet({'data': image_batch}, is_training=False, num_classes=num_classes) + net = PSPNet101({'data': image_batch}, is_training=False, num_classes=num_classes) with tf.variable_scope('', reuse=True): flipped_img = tf.image.flip_left_right(image) flipped_img = tf.expand_dims(flipped_img, dim=0) - net2 = PSPNet({'data': flipped_img}, is_training=False, num_classes=num_classes) - + net2 = PSPNet101({'data': flipped_img}, is_training=False, num_classes=num_classes) # Which variables to load. restore_var = tf.global_variables() @@ -142,9 +140,8 @@ def main(): if step % 10 == 0: print('Finish {0}/{1}'.format(step, num_steps)) - print('step {0} mIoU: {1}'.format(step, sess.run(mIoU))) - print('step {0} mIoU: {1}'.format(step, sess.run(mIoU))) + print('mIoU: {1}'.format(step, sess.run(mIoU))) coord.request_stop() coord.join(threads) diff --git a/inference.py b/inference.py index d79b7d6..5eb2871 100644 --- a/inference.py +++ b/inference.py @@ -4,31 +4,36 @@ import os import sys import time -from PIL import Image import tensorflow as tf import numpy as np +from scipy import misc -from model import PSPNet -from tools import decode_labels +from model import PSPNet101, PSPNet50 +from tools import * -IMG_MEAN = np.array((103.939, 116.779, 123.68), dtype=np.float32) -input_size = [1024, 2048] -num_classes = 19 +ADE20k_param = {'crop_size': [473, 473], + 'num_classes': 150, + 'model': PSPNet50} +cityscapes_param = {'crop_size': [720, 720], + 'num_classes': 19, + 'model': PSPNet101} SAVE_DIR = './output/' SNAPSHOT_DIR = './model/' -crop_size = [720, 720] def get_arguments(): parser = argparse.ArgumentParser(description="Reproduced PSPNet") parser.add_argument("--img-path", type=str, default='', help="Path to the RGB image file.") - parser.add_argument("--model", type=str, default=SNAPSHOT_DIR, + parser.add_argument("--checkpoints", type=str, default=SNAPSHOT_DIR, help="Path to restore weights.") parser.add_argument("--save-dir", type=str, default=SAVE_DIR, help="Path to save output.") parser.add_argument("--flipped-eval", action="store_true", help="whether to evaluate with flipped img.") + parser.add_argument("--dataset", type=str, default='', + choices=['ade20k', 'cityscapes'], + required=True) return parser.parse_args() @@ -45,44 +50,23 @@ def load(saver, sess, ckpt_path): saver.restore(sess, ckpt_path) print("Restored model parameters from {}".format(ckpt_path)) -def load_img(img_path): - if os.path.isfile(img_path): - print('successful load img: {0}'.format(img_path)) - else: - print('not found file: {0}'.format(img_path)) - sys.exit(0) - - filename = img_path.split('/')[-1] - ext = filename.split('.')[-1] - - if ext.lower() == 'png': - img = tf.image.decode_png(tf.read_file(img_path), channels=3) - elif ext.lower() == 'jpg': - img = tf.image.decode_jpeg(tf.read_file(img_path), channels=3) - else: - print('cannot process {0} file.'.format(file_type)) - - return img, filename - -def preprocess(img, h, w): - # Convert RGB to BGR - img_r, img_g, img_b = tf.split(axis=2, num_or_size_splits=3, value=img) - img = tf.cast(tf.concat(axis=2, values=[img_b, img_g, img_r]), dtype=tf.float32) - # Extract mean. - img -= IMG_MEAN - - pad_img = tf.image.pad_to_bounding_box(img, 0, 0, h, w) - pad_img = tf.expand_dims(pad_img, dim=0) - - return pad_img - def main(): args = get_arguments() + # load parameters + if args.dataset == 'ade20k': + param = ADE20k_param + elif args.dataset == 'cityscapes': + param = cityscapes_param + + crop_size = param['crop_size'] + num_classes = param['num_classes'] + PSPNet = param['model'] + + # preprocess images img, filename = load_img(args.img_path) img_shape = tf.shape(img) h, w = (tf.maximum(crop_size[0], img_shape[0]), tf.maximum(crop_size[1], img_shape[1])) - img = preprocess(img, h, w) # Create network. @@ -92,8 +76,9 @@ def main(): flipped_img = tf.expand_dims(flipped_img, dim=0) net2 = PSPNet({'data': flipped_img}, is_training=False, num_classes=num_classes) - raw_output = net.layers['conv6'] + + # Do flipped eval or not if args.flipped_eval: flipped_output = tf.image.flip_left_right(tf.squeeze(net2.layers['conv6'])) flipped_output = tf.expand_dims(flipped_output, dim=0) @@ -103,7 +88,7 @@ def main(): raw_output_up = tf.image.resize_bilinear(raw_output, size=[h, w], align_corners=True) raw_output_up = tf.image.crop_to_bounding_box(raw_output_up, 0, 0, img_shape[0], img_shape[1]) raw_output_up = tf.argmax(raw_output_up, dimension=3) - pred = tf.expand_dims(raw_output_up, dim=3) + pred = decode_labels(raw_output_up, img_shape, num_classes) # Init tf Session config = tf.ConfigProto() @@ -113,11 +98,9 @@ def main(): sess.run(init) - saver = tf.train.Saver(var_list=tf.global_variables(), max_to_keep=10) - restore_var = tf.global_variables() - - ckpt = tf.train.get_checkpoint_state(args.model) + + ckpt = tf.train.get_checkpoint_state(args.checkpoints) if ckpt and ckpt.model_checkpoint_path: loader = tf.train.Saver(var_list=restore_var) load_step = int(os.path.basename(ckpt.model_checkpoint_path).split('-')[1]) @@ -127,11 +110,9 @@ def main(): preds = sess.run(pred) - msk = decode_labels(preds, num_classes=num_classes) - im = Image.fromarray(msk[0]) if not os.path.exists(args.save_dir): os.makedirs(args.save_dir) - im.save(args.save_dir + filename) + misc.imsave(args.save_dir + filename, preds[0]) if __name__ == '__main__': - main() + main() \ No newline at end of file diff --git a/input/._test_256x512.png b/input/._test_256x512.png deleted file mode 100644 index c7fdfd9d79581c5d32ee4729aebfb8a4537aad6d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4096 zcmeH~&kBM-5XQ$KqKmgYh3pfE1VQ1c2Z*9PbX|2Naa(QGqBrR=dZu2)#*!#6orC#c zm_O{dFu&VC-x@#g>$U9=BiAT0N0&Oyy-lSqri>4wHBvDoW0I3)Di(AKm>>Y5g-CYfCvx) zB0vO)01@~g&>WjhJZ1a)q+nVUS*1h4;!LM3QKi7MGgDm33zH%)Ss=Me@fL1ff1SSf Kf3GqIyRIJx5i807 diff --git a/input/._test_720x720.png b/input/._test_720x720.png deleted file mode 100644 index dddc32934113e8fcbfe5d519e053b14ad4cee451..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4096 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 literal 0 HcmV?d00001 diff --git a/model.py b/model.py index ebfe39b..c9688c8 100644 --- a/model.py +++ b/model.py @@ -1,7 +1,7 @@ from network import Network import tensorflow as tf -class PSPNet(Network): +class PSPNet101(Network): def setup(self, is_training, num_classes): '''Network definition. @@ -469,3 +469,268 @@ def setup(self, is_training, num_classes): .conv(3, 3, 512, 1, 1, biased=False, relu=False, padding='SAME', name='conv5_4') .batch_normalization(relu=True, name='conv5_4_bn') .conv(1, 1, num_classes, 1, 1, biased=True, relu=False, name='conv6')) + +class PSPNet50(Network): + def setup(self, is_training, num_classes): + '''Network definition. + + Args: + is_training: whether to update the running mean and variance of the batch normalisation layer. + If the batch size is small, it is better to keep the running mean and variance of + the-pretrained model frozen. + num_classes: number of classes to predict (including background). + ''' + (self.feed('data') + .conv(3, 3, 64, 2, 2, biased=False, relu=False, padding='SAME', name='conv1_1_3x3_s2') + .batch_normalization(relu=False, name='conv1_1_3x3_s2_bn') + .relu(name='conv1_1_3x3_s2_bn_relu') + .conv(3, 3, 64, 1, 1, biased=False, relu=False, padding='SAME', name='conv1_2_3x3') + .batch_normalization(relu=True, name='conv1_2_3x3_bn') + .conv(3, 3, 128, 1, 1, biased=False, relu=False, padding='SAME', name='conv1_3_3x3') + .batch_normalization(relu=True, name='conv1_3_3x3_bn') + .max_pool(3, 3, 2, 2, padding='SAME', name='pool1_3x3_s2') + .conv(1, 1, 256, 1, 1, biased=False, relu=False, name='conv2_1_1x1_proj') + .batch_normalization(relu=False, name='conv2_1_1x1_proj_bn')) + + (self.feed('pool1_3x3_s2') + .conv(1, 1, 64, 1, 1, biased=False, relu=False, name='conv2_1_1x1_reduce') + .batch_normalization(relu=True, name='conv2_1_1x1_reduce_bn') + .zero_padding(paddings=1, name='padding1') + .conv(3, 3, 64, 1, 1, biased=False, relu=False, name='conv2_1_3x3') + .batch_normalization(relu=True, name='conv2_1_3x3_bn') + .conv(1, 1, 256, 1, 1, biased=False, relu=False, name='conv2_1_1x1_increase') + .batch_normalization(relu=False, name='conv2_1_1x1_increase_bn')) + + (self.feed('conv2_1_1x1_proj_bn', + 'conv2_1_1x1_increase_bn') + .add(name='conv2_1') + .relu(name='conv2_1/relu') + .conv(1, 1, 64, 1, 1, biased=False, relu=False, name='conv2_2_1x1_reduce') + .batch_normalization(relu=True, name='conv2_2_1x1_reduce_bn') + .zero_padding(paddings=1, name='padding2') + .conv(3, 3, 64, 1, 1, biased=False, relu=False, name='conv2_2_3x3') + .batch_normalization(relu=True, name='conv2_2_3x3_bn') + .conv(1, 1, 256, 1, 1, biased=False, relu=False, name='conv2_2_1x1_increase') + .batch_normalization(relu=False, name='conv2_2_1x1_increase_bn')) + + (self.feed('conv2_1/relu', + 'conv2_2_1x1_increase_bn') + .add(name='conv2_2') + .relu(name='conv2_2/relu') + .conv(1, 1, 64, 1, 1, biased=False, relu=False, name='conv2_3_1x1_reduce') + .batch_normalization(relu=True, name='conv2_3_1x1_reduce_bn') + .zero_padding(paddings=1, name='padding3') + .conv(3, 3, 64, 1, 1, biased=False, relu=False, name='conv2_3_3x3') + .batch_normalization(relu=True, name='conv2_3_3x3_bn') + .conv(1, 1, 256, 1, 1, biased=False, relu=False, name='conv2_3_1x1_increase') + .batch_normalization(relu=False, name='conv2_3_1x1_increase_bn')) + + (self.feed('conv2_2/relu', + 'conv2_3_1x1_increase_bn') + .add(name='conv2_3') + .relu(name='conv2_3/relu') + .conv(1, 1, 512, 2, 2, biased=False, relu=False, name='conv3_1_1x1_proj') + .batch_normalization(relu=False, name='conv3_1_1x1_proj_bn')) + + (self.feed('conv2_3/relu') + .conv(1, 1, 128, 2, 2, biased=False, relu=False, name='conv3_1_1x1_reduce') + .batch_normalization(relu=True, name='conv3_1_1x1_reduce_bn') + .zero_padding(paddings=1, name='padding4') + .conv(3, 3, 128, 1, 1, biased=False, relu=False, name='conv3_1_3x3') + .batch_normalization(relu=True, name='conv3_1_3x3_bn') + .conv(1, 1, 512, 1, 1, biased=False, relu=False, name='conv3_1_1x1_increase') + .batch_normalization(relu=False, name='conv3_1_1x1_increase_bn')) + + (self.feed('conv3_1_1x1_proj_bn', + 'conv3_1_1x1_increase_bn') + .add(name='conv3_1') + .relu(name='conv3_1/relu') + .conv(1, 1, 128, 1, 1, biased=False, relu=False, name='conv3_2_1x1_reduce') + .batch_normalization(relu=True, name='conv3_2_1x1_reduce_bn') + .zero_padding(paddings=1, name='padding5') + .conv(3, 3, 128, 1, 1, biased=False, relu=False, name='conv3_2_3x3') + .batch_normalization(relu=True, name='conv3_2_3x3_bn') + .conv(1, 1, 512, 1, 1, biased=False, relu=False, name='conv3_2_1x1_increase') + .batch_normalization(relu=False, name='conv3_2_1x1_increase_bn')) + + (self.feed('conv3_1/relu', + 'conv3_2_1x1_increase_bn') + .add(name='conv3_2') + .relu(name='conv3_2/relu') + .conv(1, 1, 128, 1, 1, biased=False, relu=False, name='conv3_3_1x1_reduce') + .batch_normalization(relu=True, name='conv3_3_1x1_reduce_bn') + .zero_padding(paddings=1, name='padding6') + .conv(3, 3, 128, 1, 1, biased=False, relu=False, name='conv3_3_3x3') + .batch_normalization(relu=True, name='conv3_3_3x3_bn') + .conv(1, 1, 512, 1, 1, biased=False, relu=False, name='conv3_3_1x1_increase') + .batch_normalization(relu=False, name='conv3_3_1x1_increase_bn')) + + (self.feed('conv3_2/relu', + 'conv3_3_1x1_increase_bn') + .add(name='conv3_3') + .relu(name='conv3_3/relu') + .conv(1, 1, 128, 1, 1, biased=False, relu=False, name='conv3_4_1x1_reduce') + .batch_normalization(relu=True, name='conv3_4_1x1_reduce_bn') + .zero_padding(paddings=1, name='padding7') + .conv(3, 3, 128, 1, 1, biased=False, relu=False, name='conv3_4_3x3') + .batch_normalization(relu=True, name='conv3_4_3x3_bn') + .conv(1, 1, 512, 1, 1, biased=False, relu=False, name='conv3_4_1x1_increase') + .batch_normalization(relu=False, name='conv3_4_1x1_increase_bn')) + + (self.feed('conv3_3/relu', + 'conv3_4_1x1_increase_bn') + .add(name='conv3_4') + .relu(name='conv3_4/relu') + .conv(1, 1, 1024, 1, 1, biased=False, relu=False, name='conv4_1_1x1_proj') + .batch_normalization(relu=False, name='conv4_1_1x1_proj_bn')) + + (self.feed('conv3_4/relu') + .conv(1, 1, 256, 1, 1, biased=False, relu=False, name='conv4_1_1x1_reduce') + .batch_normalization(relu=True, name='conv4_1_1x1_reduce_bn') + .zero_padding(paddings=2, name='padding8') + .atrous_conv(3, 3, 256, 2, biased=False, relu=False, name='conv4_1_3x3') + .batch_normalization(relu=True, name='conv4_1_3x3_bn') + .conv(1, 1, 1024, 1, 1, biased=False, relu=False, name='conv4_1_1x1_increase') + .batch_normalization(relu=False, name='conv4_1_1x1_increase_bn')) + + (self.feed('conv4_1_1x1_proj_bn', + 'conv4_1_1x1_increase_bn') + .add(name='conv4_1') + .relu(name='conv4_1/relu') + .conv(1, 1, 256, 1, 1, biased=False, relu=False, name='conv4_2_1x1_reduce') + .batch_normalization(relu=True, name='conv4_2_1x1_reduce_bn') + .zero_padding(paddings=2, name='padding9') + .atrous_conv(3, 3, 256, 2, biased=False, relu=False, name='conv4_2_3x3') + .batch_normalization(relu=True, name='conv4_2_3x3_bn') + .conv(1, 1, 1024, 1, 1, biased=False, relu=False, name='conv4_2_1x1_increase') + .batch_normalization(relu=False, name='conv4_2_1x1_increase_bn')) + + (self.feed('conv4_1/relu', + 'conv4_2_1x1_increase_bn') + .add(name='conv4_2') + .relu(name='conv4_2/relu') + .conv(1, 1, 256, 1, 1, biased=False, relu=False, name='conv4_3_1x1_reduce') + .batch_normalization(relu=True, name='conv4_3_1x1_reduce_bn') + .zero_padding(paddings=2, name='padding10') + .atrous_conv(3, 3, 256, 2, biased=False, relu=False, name='conv4_3_3x3') + .batch_normalization(relu=True, name='conv4_3_3x3_bn') + .conv(1, 1, 1024, 1, 1, biased=False, relu=False, name='conv4_3_1x1_increase') + .batch_normalization(relu=False, name='conv4_3_1x1_increase_bn')) + + (self.feed('conv4_2/relu', + 'conv4_3_1x1_increase_bn') + .add(name='conv4_3') + .relu(name='conv4_3/relu') + .conv(1, 1, 256, 1, 1, biased=False, relu=False, name='conv4_4_1x1_reduce') + .batch_normalization(relu=True, name='conv4_4_1x1_reduce_bn') + .zero_padding(paddings=2, name='padding11') + .atrous_conv(3, 3, 256, 2, biased=False, relu=False, name='conv4_4_3x3') + .batch_normalization(relu=True, name='conv4_4_3x3_bn') + .conv(1, 1, 1024, 1, 1, biased=False, relu=False, name='conv4_4_1x1_increase') + .batch_normalization(relu=False, name='conv4_4_1x1_increase_bn')) + + (self.feed('conv4_3/relu', + 'conv4_4_1x1_increase_bn') + .add(name='conv4_4') + .relu(name='conv4_4/relu') + .conv(1, 1, 256, 1, 1, biased=False, relu=False, name='conv4_5_1x1_reduce') + .batch_normalization(relu=True, name='conv4_5_1x1_reduce_bn') + .zero_padding(paddings=2, name='padding12') + .atrous_conv(3, 3, 256, 2, biased=False, relu=False, name='conv4_5_3x3') + .batch_normalization(relu=True, name='conv4_5_3x3_bn') + .conv(1, 1, 1024, 1, 1, biased=False, relu=False, name='conv4_5_1x1_increase') + .batch_normalization(relu=False, name='conv4_5_1x1_increase_bn')) + + (self.feed('conv4_4/relu', + 'conv4_5_1x1_increase_bn') + .add(name='conv4_5') + .relu(name='conv4_5/relu') + .conv(1, 1, 256, 1, 1, biased=False, relu=False, name='conv4_6_1x1_reduce') + .batch_normalization(relu=True, name='conv4_6_1x1_reduce_bn') + .zero_padding(paddings=2, name='padding13') + .atrous_conv(3, 3, 256, 2, biased=False, relu=False, name='conv4_6_3x3') + .batch_normalization(relu=True, name='conv4_6_3x3_bn') + .conv(1, 1, 1024, 1, 1, biased=False, relu=False, name='conv4_6_1x1_increase') + .batch_normalization(relu=False, name='conv4_6_1x1_increase_bn')) + + (self.feed('conv4_5/relu', + 'conv4_6_1x1_increase_bn') + .add(name='conv4_6') + .relu(name='conv4_6/relu') + .conv(1, 1, 2048, 1, 1, biased=False, relu=False, name='conv5_1_1x1_proj') + .batch_normalization(relu=False, name='conv5_1_1x1_proj_bn')) + + (self.feed('conv4_6/relu') + .conv(1, 1, 512, 1, 1, biased=False, relu=False, name='conv5_1_1x1_reduce') + .batch_normalization(relu=True, name='conv5_1_1x1_reduce_bn') + .zero_padding(paddings=4, name='padding31') + .atrous_conv(3, 3, 512, 4, biased=False, relu=False, name='conv5_1_3x3') + .batch_normalization(relu=True, name='conv5_1_3x3_bn') + .conv(1, 1, 2048, 1, 1, biased=False, relu=False, name='conv5_1_1x1_increase') + .batch_normalization(relu=False, name='conv5_1_1x1_increase_bn')) + + (self.feed('conv5_1_1x1_proj_bn', + 'conv5_1_1x1_increase_bn') + .add(name='conv5_1') + .relu(name='conv5_1/relu') + .conv(1, 1, 512, 1, 1, biased=False, relu=False, name='conv5_2_1x1_reduce') + .batch_normalization(relu=True, name='conv5_2_1x1_reduce_bn') + .zero_padding(paddings=4, name='padding32') + .atrous_conv(3, 3, 512, 4, biased=False, relu=False, name='conv5_2_3x3') + .batch_normalization(relu=True, name='conv5_2_3x3_bn') + .conv(1, 1, 2048, 1, 1, biased=False, relu=False, name='conv5_2_1x1_increase') + .batch_normalization(relu=False, name='conv5_2_1x1_increase_bn')) + + (self.feed('conv5_1/relu', + 'conv5_2_1x1_increase_bn') + .add(name='conv5_2') + .relu(name='conv5_2/relu') + .conv(1, 1, 512, 1, 1, biased=False, relu=False, name='conv5_3_1x1_reduce') + .batch_normalization(relu=True, name='conv5_3_1x1_reduce_bn') + .zero_padding(paddings=4, name='padding33') + .atrous_conv(3, 3, 512, 4, biased=False, relu=False, name='conv5_3_3x3') + .batch_normalization(relu=True, name='conv5_3_3x3_bn') + .conv(1, 1, 2048, 1, 1, biased=False, relu=False, name='conv5_3_1x1_increase') + .batch_normalization(relu=False, name='conv5_3_1x1_increase_bn')) + + (self.feed('conv5_2/relu', + 'conv5_3_1x1_increase_bn') + .add(name='conv5_3') + .relu(name='conv5_3/relu')) + + conv5_3 = self.layers['conv5_3/relu'] + shape = tf.shape(conv5_3)[1:3] + + (self.feed('conv5_3/relu') + .avg_pool(60, 60, 60, 60, name='conv5_3_pool1') + .conv(1, 1, 512, 1, 1, biased=False, relu=False, name='conv5_3_pool1_conv') + .batch_normalization(relu=True, name='conv5_3_pool1_conv_bn') + .resize_bilinear(shape, name='conv5_3_pool1_interp')) + + (self.feed('conv5_3/relu') + .avg_pool(30, 30, 30, 30, name='conv5_3_pool2') + .conv(1, 1, 512, 1, 1, biased=False, relu=False, name='conv5_3_pool2_conv') + .batch_normalization(relu=True, name='conv5_3_pool2_conv_bn') + .resize_bilinear(shape, name='conv5_3_pool2_interp')) + + (self.feed('conv5_3/relu') + .avg_pool(20, 20, 20, 20, name='conv5_3_pool3') + .conv(1, 1, 512, 1, 1, biased=False, relu=False, name='conv5_3_pool3_conv') + .batch_normalization(relu=True, name='conv5_3_pool3_conv_bn') + .resize_bilinear(shape, name='conv5_3_pool3_interp')) + + (self.feed('conv5_3/relu') + .avg_pool(10, 10, 10, 10, name='conv5_3_pool6') + .conv(1, 1, 512, 1, 1, biased=False, relu=False, name='conv5_3_pool6_conv') + .batch_normalization(relu=True, name='conv5_3_pool6_conv_bn') + .resize_bilinear(shape, name='conv5_3_pool6_interp')) + + (self.feed('conv5_3/relu', + 'conv5_3_pool6_interp', + 'conv5_3_pool3_interp', + 'conv5_3_pool2_interp', + 'conv5_3_pool1_interp') + .concat(axis=-1, name='conv5_3_concat') + .conv(3, 3, 512, 1, 1, biased=False, relu=False, padding='SAME', name='conv5_4') + .batch_normalization(relu=True, name='conv5_4_bn') + .conv(1, 1, num_classes, 1, 1, biased=True, relu=False, name='conv6')) diff --git a/network.py b/network.py index cc2519d..51cebca 100644 --- a/network.py +++ b/network.py @@ -4,6 +4,11 @@ DEFAULT_PADDING = 'VALID' DEFAULT_DATAFORMAT = 'NHWC' +BN_param_map = {'scale': 'gamma', + 'offset': 'beta', + 'variance': 'moving_variance', + 'mean': 'moving_mean'} + def layer(op): '''Decorator for composable network layers.''' @@ -57,12 +62,16 @@ def load(self, data_path, session, ignore_missing=False): session: The current TensorFlow session ignore_missing: If true, serialized weights for missing layers are ignored. ''' - data_dict = np.load(data_path).item() + data_dict = np.load(data_path, encoding='latin1').item() for op_name in data_dict: with tf.variable_scope(op_name, reuse=True): - for param_name, data in data_dict[op_name].iteritems(): + for param_name, data in data_dict[op_name].items(): try: + if 'bn' in op_name: + param_name = BN_param_map[param_name] + data = np.squeeze(data) + var = tf.get_variable(param_name) session.run(var.assign(data)) except ValueError: @@ -241,43 +250,18 @@ def softmax(self, input, name): @layer def batch_normalization(self, input, name, scale_offset=True, relu=False): - """ - # NOTE: Currently, only inference is supported - with tf.variable_scope(name) as scope: - shape = [input.get_shape()[-1]] - if scale_offset: - scale = self.make_var('scale', shape=shape) - offset = self.make_var('offset', shape=shape) - else: - scale, offset = (None, None) - output = tf.nn.batch_normalization( - input, - mean=self.make_var('mean', shape=shape), - variance=self.make_var('variance', shape=shape), - offset=offset, - scale=scale, - # TODO: This is the default Caffe batch norm eps - # Get the actual eps from parameters - variance_epsilon=1e-5, - name=name) - if relu: - output = tf.nn.relu(output) - return output - """ + output = tf.layers.batch_normalization( + input, + momentum=0.95, + epsilon=1e-5, + training=self.is_training, + name=name + ) - with tf.variable_scope(name) as scope: - output = tf.layers.batch_normalization( - input, - momentum=0.95, - epsilon=1e-5, - training=self.is_training, - name=name - ) - - if relu: - output = tf.nn.relu(output) + if relu: + output = tf.nn.relu(output) - return output + return output @layer def dropout(self, input, keep_prob, name): diff --git a/output/._test_1024x2048.png b/output/._test_1024x2048.png deleted file mode 100644 index 42483632f38e3ccda45ceca0e7cf9bc43595e39c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4096 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDJkFz{^v(m+1nBL)UWIUt(=a103v0xGG3 z=wO%uWQ(C`VFdD_;-bm LUXfvt`~M#R{pBlm diff --git a/output/._test_256x512.png b/output/._test_256x512.png deleted file mode 100644 index 2b38d12fd354e40490567af65221699620a7ff33..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4096 zcmeH~O$x#=5QRrU#7%d0B_$^)A_z)X9zYcB!fmVxhQ_o_tKv;OhG+63b*e?Q8`r`- z80Lq(zv=y)4n9v)E&S}rEjNWS0*1rMC>T$r+;JHNODj%FoP)+M)Di(AKm>>Y5g-CYfCvx) zB0vO)01@~gP#v39JmrOZl`|{yObplh!nBYD+iGrNyoGDmU#IW= K->VG3E6WG>BrE#> diff --git a/output/._test_720x720.png b/output/._test_720x720.png deleted file mode 100644 index 5192e993246a8b3081ac2682c7395bc893c69df7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4096 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDJkFz{^v(m+1nBL)UWIUt(=a103v0xC&> z=wO%uWQ(C`VFdD_;-bm LUXfvt`~M#RSPv_W diff --git a/output/indoor_1.jpg b/output/indoor_1.jpg new file mode 100644 index 0000000000000000000000000000000000000000..8c801fc611bf0889e4b1b55011dfd50dc4f0e703 GIT binary patch literal 10472 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 literal 0 HcmV?d00001 diff --git a/output/indoor_2.jpg b/output/indoor_2.jpg new file mode 100644 index 0000000000000000000000000000000000000000..3482c6c6dad91c48d7cd97c224567a049af392ea GIT binary patch literal 6919 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 num_images), 'Batch size %d should be greater or equal than number of images to save %d.' % (n, num_images) - outputs = np.zeros((num_images, h, w, 3), dtype=np.uint8) - for i in range(num_images): - img = Image.new('RGB', (len(mask[i, 0]), len(mask[i]))) - pixels = img.load() - for j_, j in enumerate(mask[i, :, :, 0]): - for k_, k in enumerate(j): - if k < num_classes: - pixels[k_,j_] = label_colours[k] - outputs[i] = np.array(img) - return outputs + ,(119, 10, 32)] + # 18 = bicycle + +matfn = './color150.mat' +def read_labelcolours(matfn): + mat = sio.loadmat(matfn) + color_table = mat['colors'] + shape = color_table.shape + color_list = [tuple(color_table[i]) for i in range(shape[0])] + + return color_list + +def decode_labels(mask, img_shape, num_classes): + if num_classes == 150: + color_table = read_labelcolours(matfn) + else: + color_table = label_colours + + color_mat = tf.constant(color_table, dtype=tf.float32) + onehot_output = tf.one_hot(mask, depth=num_classes) + onehot_output = tf.reshape(onehot_output, (-1, num_classes)) + pred = tf.matmul(onehot_output, color_mat) + pred = tf.reshape(pred, (1, img_shape[0], img_shape[1], 3)) + + return pred def prepare_label(input_batch, new_size, num_classes, one_hot=True): with tf.name_scope('label_encode'): @@ -39,4 +50,36 @@ def prepare_label(input_batch, new_size, num_classes, one_hot=True): if one_hot: input_batch = tf.one_hot(input_batch, depth=num_classes) - return input_batch \ No newline at end of file + return input_batch + + +def load_img(img_path): + if os.path.isfile(img_path): + print('successful load img: {0}'.format(img_path)) + else: + print('not found file: {0}'.format(img_path)) + sys.exit(0) + + filename = img_path.split('/')[-1] + ext = filename.split('.')[-1] + + if ext.lower() == 'png': + img = tf.image.decode_png(tf.read_file(img_path), channels=3) + elif ext.lower() == 'jpg': + img = tf.image.decode_jpeg(tf.read_file(img_path), channels=3) + else: + print('cannot process {0} file.'.format(file_type)) + + return img, filename + +def preprocess(img, h, w): + # Convert RGB to BGR + img_r, img_g, img_b = tf.split(axis=2, num_or_size_splits=3, value=img) + img = tf.cast(tf.concat(axis=2, values=[img_b, img_g, img_r]), dtype=tf.float32) + # Extract mean. + img -= IMG_MEAN + + pad_img = tf.image.pad_to_bounding_box(img, 0, 0, h, w) + pad_img = tf.expand_dims(pad_img, dim=0) + + return pad_img \ No newline at end of file diff --git a/train.py b/train.py index d21df83..bdc3d1d 100644 --- a/train.py +++ b/train.py @@ -12,13 +12,13 @@ import tensorflow as tf import numpy as np -from model import PSPNet -from tools import decode_labels, prepare_label +from model import PSPNet101 +from tools import prepare_label from image_reader import ImageReader IMG_MEAN = np.array((103.939, 116.779, 123.68), dtype=np.float32) -BATCH_SIZE = 4 +BATCH_SIZE = 2 DATA_DIRECTORY = '/SSD_data/cityscapes_dataset/cityscape' DATA_LIST_PATH = './list/train_list.txt' IGNORE_LABEL = 255 @@ -120,7 +120,7 @@ def main(): coord) image_batch, label_batch = reader.dequeue(args.batch_size) - net = PSPNet({'data': image_batch}, is_training=True, num_classes=args.num_classes) + net = PSPNet101({'data': image_batch}, is_training=True, num_classes=args.num_classes) raw_output = net.layers['conv6'] diff --git a/utils/color150.mat b/utils/color150.mat new file mode 100644 index 0000000000000000000000000000000000000000..c518b64fbbe899d4a8b2705f012eeba795339892 GIT binary patch literal 502 zcmeZu4DoSvQZUssQ1EpO(M`+DN!3vZ$Vn_o%P-2cQgHY2i*PhE(NSu~0BFFf>yj5-`93qo*%Fke$cKz)&$qH&8pLIY7iV z`t&uuKx5Q$jYMq3ZSjtU|->VwgbfCzwbn=%d7NH$myQZ#OmBnh6AQ-;od&ZJu zyq7{l-MF?qciEL*!S(vL=_1(+4eL*;oo?G6JWcxW)ddqTsx*fh&FFj@A%0v@`qvWK zZPvHkK9sB|njZ7b#pIgBQJ&Q5sT&g3ROYAD_okin5HfG=nd-CD*sLqPnC