Tutorials/CNTK_302A_Evaluation_of_Pretrained_Super-resolution_Models.ipynb
Contributed by Borna Vukorepa October 30, 2017
<a href="https://arxiv.org/pdf/1608.00367.pdf">Accelerating the Super-Resolution Convolutional Neural Network</a>
<a href="http://cvlab.cse.msu.edu/pdfs/Tai_Yang_Liu_CVPR2017.pdf">Image Super-Resolution via Deep Recursive Residual Network</a>
<a href="https://arxiv.org/pdf/1511.04587.pdf">Accurate Image Super-Resolution Using Very Deep Convolutional Networks</a>
<a href="https://arxiv.org/pdf/1609.04802.pdf">Photo-Realistic Single Image Super-Resolution Using a Generative Adversarial Network</a>
<a href="https://arxiv.org/pdf/1606.01299.pdf">RAISR: Rapid and Accurate Image Super Resolution</a>
Encouraged by the recent results in deep learning (e.g. GANs), our goal is to explore the space of image super resolution, and look at both <b>GAN and non-GAN</b> approaches. All work is done for upscaling factor of 2. For other factors, the dataset preparation and methods are completely analoguous.
import cntk as C
from PIL import Image
import os
import numpy as np
import urllib
from scipy.misc import imsave
try:
from urllib.request import urlretrieve, urlopen
except ImportError:
from urllib import urlretrieve, urlopen
try:
C.device.try_set_default_device(C.device.gpu(0))
except:
print("GPU unavailable. Using CPU instead.")
# Determine the data path for testing
# Check for an environment variable defined in CNTK's test infrastructure
envvar = 'CNTK_EXTERNAL_TESTDATA_SOURCE_DIRECTORY'
def is_test(): return envvar in os.environ
if is_test():
test_data_path_base = os.path.join(os.environ[envvar], "Tutorials", "data")
test_data_dir = os.path.join(test_data_path_base, "BerkeleySegmentationDataset")
test_data_dir = os.path.normpath(test_data_dir)
#prefer our default path for the data
data_dir = os.path.join("data", "BerkeleySegmentationDataset")
if not os.path.exists(data_dir):
os.makedirs(data_dir)
#folder with images to be evaluated
example_folder = os.path.join(data_dir, "example_images")
if not os.path.exists(example_folder):
os.makedirs(example_folder)
#folders with resulting images
results_folder = os.path.join(data_dir, "example_results")
if not os.path.exists(results_folder):
os.makedirs(results_folder)
#names of used models
model_names = ["VDSR", "DRNN", "SRResNet", "SRGAN"]
#output dimensions of models respectively (assumed that output is a square)
output_dims = [64, 64, 224, 224]
The evaluation algorithm above is implemented here in function <code>evaluate</code>. See code comments for details about each step.
#filename - relative path of image being processed
#model - the model for super-resolution
#outfile - relative path of the image which will be saved
#output_dims - dimensions of current model output image
# - it is assumed that model output image is a square
#pre_upscale - if True, image will be upscaled by a specified factor with bicubic interpolation at the start
# - the resulting image then replaces the original one in the next operations
# - if False, that step is skipped
# - this should be set on True for models which are clearing up the image and don't make upscaling by themselves
#clear_up - if True, the forwarded image will be cleared up by the model and not upscaled
# - this is important to know because step variables are different then (see code)
# - notice that we exit the function if pre_upscale is True and clear_up false because if image was pre-upscaled,
# it should be cleared up afterwards
#residual_model - is the model learning residual image only (the difference between blurry and original patch)?
# - if true, residual is added to the low resolutin image to produce the result
# - otherwise, we only need to scale back the result (see code below)
def evaluate(filename, model, outfile, output_dims, pre_upscale = False, clear_up = False, residual_model = False):
img = Image.open(filename)
#upscaling coefficient
coef = 2
#at each step, we will evaluate subpatch (x : x + range_x, y : y + range_y) of original image
#patch by patch, we will resolve the whole image
range_x = output_dims // coef
range_y = output_dims // coef
#how many bounding pixels from resulting patch should be excluded?
#this is important because boundaries tend to be predicted less accurately
offset = output_dims // 10
#after we evaluate a subpatch, how much we move down/right to get the next one
#we subtract offset to cover those pixels which were boundary in the previous subpatch
step_x = range_x - offset
step_y = range_y - offset
#situation which should not occur, if we need preprocess, we will need to clear up the result
if((pre_upscale) and (not clear_up)):
print("Pre-magnified image is not being cleared up.")
return
#pre-magnify picture if needed
if(pre_upscale):
img = img.resize((coef * img.width, coef * img.height), Image.BICUBIC)
#if the current image is being cleared up with no further uspcaling,
#set coef to 1 and other parameters accordingly
if(clear_up):
result = np.zeros((img.height, img.width, 3))
range_x = output_dims
range_y = output_dims
step_x = range_x - 2 * offset
step_y = range_y - 2 * offset
coef = 1
#otherwise, set result to be coef (2 by default) times larger than image
else:
result = np.zeros((coef * img.height, coef * img.width, 3))
rect = np.array(img, dtype = np.float32)
#if the image is too small for some models to work on it, pad it with zeros
if(rect.shape[0] < range_y):
pad = np.zeros((range_y - rect.shape[0], rect.shape[1], rect.shape[2]))
rect = np.concatenate((rect, pad), axis = 0).astype(dtype = np.float32)
if(rect.shape[1] < range_x):
pad = np.zeros((rect.shape[0], range_x - rect.shape[1], rect.shape[2]))
rect = np.concatenate((rect, pad), axis = 1).astype(dtype = np.float32)
x = 0
y = 0
#take subpatch by subpatch and resolve them to get the final image result
while(y < img.width):
x = 0
while(x < img.height):
rgb_patch = rect[x : x + range_x, y : y + range_y]
rgb_patch = rgb_patch[..., [2, 1, 0]]
rgb_patch = np.ascontiguousarray(np.rollaxis(rgb_patch, 2))
pred = np.squeeze(model.eval({model.arguments[0] : [rgb_patch]}))
img1 = np.ascontiguousarray(rgb_patch.transpose(2, 1, 0))
img2 = np.ascontiguousarray(pred.transpose(2, 1, 0))
#if model predicts residual image,
#scale back the prediction and add to starting patch
#otherwise just scale back
if(residual_model):
img2 = 255.0 * img2 + img1
else:
img2 = pred.transpose(2, 1, 0)
img2 = img2 * 255.0
# make sure img2 is C Contiguous as we just transposed it
img2 = np.ascontiguousarray(img2)
#make sure no pixels are outside [0, 255] interval
for _ in range(2):
img2 = C.relu(img2).eval()
img2 = np.ones(img2.shape) * 255.0 - img2
rgb = img2[..., ::-1]
patch = rgb.transpose(1, 0, 2)
#fill in the pixels in the middle of the subpatch
#don't fill those within offset range to the boundary
for h in range(coef * x + offset, coef * x + output_dims - offset):
for w in range(coef * y + offset, coef * y + output_dims - offset):
for col in range(0, 3):
result[h][w][col] = patch[h - coef * x][w - coef * y][col]
#pad top
if(x == 0):
for h in range(offset):
for w in range(coef * y, coef * y + output_dims):
for col in range(0, 3):
result[h][w][col] = patch[h][w - coef * y][col]
#pad left
if(y == 0):
for h in range(coef * x, coef * x + output_dims):
for w in range(offset):
for col in range(0, 3):
result[h][w][col] = patch[h - coef * x][w][col]
#pad bottom
if(x == img.height - range_x):
for h in range(coef * img.height - offset, coef * img.height):
for w in range(coef * y, coef * y + output_dims):
for col in range(0, 3):
result[h][w][col] = patch[h - coef * x][w - coef * y][col]
#pad right
if(y == img.width - range_y):
for h in range(coef * x, coef * x + output_dims):
for w in range(coef * img.width - offset, coef * img.width):
for col in range(0, 3):
result[h][w][col] = patch[h - coef * x][w - coef * y][col]
#reached bottom of image
if(x == img.height - range_x):
break
#next step by x, we must not go out of bounds
x = min(x + step_x, img.height - range_x)
#reached right edge of image
if(y == img.width - range_x):
break
#next step by y, we must not go out of bounds
y = min(y + step_y, img.width - range_x)
result = np.ascontiguousarray(result)
#save result
imsave(outfile, result.astype(np.uint8))
#Get the path for pre-trained models and example images
if is_test():
models_dir = os.path.join(test_data_dir, "PretrainedModels")
image_dir = os.path.join(test_data_dir, "Images")
else:
models_dir = os.path.join(data_dir, "PretrainedModels")
if not os.path.exists(models_dir):
os.makedirs(models_dir)
image_dir = os.path.join(data_dir, "Images")
if not os.path.exists(image_dir):
os.makedirs(image_dir)
print("Model directory", models_dir)
print("Image directory", image_dir)
if not os.path.isfile(os.path.join(models_dir, "VDSR.model")):
print("Downloading VDSR model...")
urlretrieve("https://www.cntk.ai/Models/SuperResolution/VDSR.model", os.path.join(models_dir, "VDSR.model"))
else:
print("Using cached VDSR model")
if not os.path.isfile(os.path.join(models_dir, "DRNN.model")):
print("Downloading DRNN model...")
urlretrieve("https://www.cntk.ai/Models/SuperResolution/DRNN.model", os.path.join(models_dir, "DRNN.model"))
else:
print("Using cached DRNN.model")
if not os.path.isfile(os.path.join(models_dir, "SRResNet.model")):
print("Downloading SRResNet model...")
urlretrieve("https://www.cntk.ai/Models/SuperResolution/SRResNet.model", os.path.join(models_dir, "SRResNet.model"))
else:
print("Using cached SRResNet.model")
if not os.path.isfile(os.path.join(models_dir, "SRGAN.model")):
print("Downloading SRGAN model...")
urlretrieve("https://www.cntk.ai/Models/SuperResolution/SRGAN.model", os.path.join(models_dir, "SRGAN.model"))
else:
print("Using cached SRGAN model")
print("Loading pretrained models...")
VDSR_model = C.load_model(os.path.join(models_dir, "VDSR.model"))
DRNN_model = C.load_model(os.path.join(models_dir, "DRNN.model"))
SRResNet_model = C.load_model(os.path.join(models_dir, "SRResNet.model"))
SRGAN_model = C.load_model(os.path.join(models_dir, "SRGAN.model"))
models = [VDSR_model, DRNN_model, SRResNet_model, SRGAN_model]
print("Loaded pretrained models.")
from shutil import copyfile
if not os.path.isfile(os.path.join(image_dir, "253027.jpg")):
print("Downloading example image ...")
link = "https://www2.eecs.berkeley.edu/Research/Projects/CS/vision/grouping/segbench/BSDS300/html/images/plain/normal/color/253027.jpg"
urlretrieve(link, os.path.join(example_folder, "253027.jpg"))
else:
print("Using cached image file")
copyfile(os.path.join(image_dir, "253027.jpg"), os.path.join(example_folder, "253027.jpg"))
We will also save a copy of bicubic interpolation effect for every image we are testing on, just for reference.
save_folder = os.path.join(results_folder, "bicubic")
#upscale by bicubic and save for reference
for entry in os.listdir(example_folder):
filename = os.path.join(example_folder, entry)
if not os.path.exists(save_folder):
os.makedirs(save_folder)
img = Image.open(filename)
out = img.resize((2 * img.width, 2 * img.height), Image.BICUBIC)
out.save(os.path.join(save_folder, entry))
#loop thorugh every model
for i in range(4):
save_folder = os.path.join(results_folder, model_names[i] + "_results")
#loop through every image in example_folder
for entry in os.listdir(example_folder):
filename = os.path.join(example_folder, entry)
if not os.path.exists(save_folder):
os.makedirs(save_folder)
outfile = os.path.join(save_folder, entry)
print("Now creating: " + outfile)
#function calls for different models
if(i < 2):
#residual learning, image is pre-upscaled and then cleared up
evaluate(filename, models[i], outfile, output_dims[i], pre_upscale = True, clear_up = True, residual_model = True)
else:
#all upscaling is within the model
evaluate(filename, models[i], outfile, output_dims[i], pre_upscale = False, clear_up = False, residual_model = False)
#loop through models which can additionally clear up image after we increased it (DRNN and VDSR)
for j in range(2):
#loop through results of previously applied model
for entry in os.listdir(save_folder):
filename = os.path.join(save_folder, entry)
filter_folder = os.path.join(results_folder, model_names[j] + "_" + model_names[i] + "_results")
if not os.path.exists(filter_folder):
os.makedirs(filter_folder)
outfile = os.path.join(filter_folder, entry)
print("Now creating: " + outfile)
#additionally clear up image without pre-magnifying
evaluate(filename, models[j], outfile, output_dims[j], pre_upscale = False, clear_up = True, residual_model = True)
from IPython.display import Image
Image(url = "https://superresolution.blob.core.windows.net/superresolutionresources/example.PNG")
Image(url = "https://superresolution.blob.core.windows.net/superresolutionresources/analysis_zebra.PNG")
Image(url = "https://superresolution.blob.core.windows.net/superresolutionresources/analysis_village.PNG")
Image(url = "https://superresolution.blob.core.windows.net/superresolutionresources/analysis_pot.PNG")