├── LICENSE ├── README.md ├── __init__.py ├── imresize.py └── test ├── README.md ├── lena_123x234_double.png ├── lena_123x234_uint8.png ├── lena_512x512.png └── test_imresize.py /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Alex 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # matlab_imresize 2 | Python implementation of MatLab imresize() function. 3 | 4 | ## Table of contents 5 | 1. [Background](#background) 6 | 2. [System requirements](#req) 7 | 3. [Usage](#usage) 8 | 4. [Additional info](#addinfo) 9 | 5. [Note](#note) 10 | ## Background 11 | In the latest Super Resolution challenges (e.g. see [NTIRE 2017](http://www.vision.ee.ethz.ch/ntire17/)) the downscaling - *bicubic interpolation* - is performed via MatLab imresize() function. 12 | 13 | [Track info](http://www.vision.ee.ethz.ch/ntire17/#challenge): 14 | > Track 1: bicubic uses the **bicubic downscaling (Matlab imresize)**, one of the most common settings from the recent single-image super-resolution literature. 15 | 16 | [More info](https://competitions.codalab.org/competitions/16303): 17 | > For obtaining the low res images we use the **Matlab function "imresize" with default settings (bicubic interpolation)** and the desired downscaling factors: 2, 3, and 4. 18 | 19 | Moreover, the quality (PSNR) of a tested solution is compared with the reference solution - upsampling with bicubic interpolation - which is done again with MatLab imresize() function with the default settings. 20 | 21 | All this leads to: 22 | 1. Preparing train database (downscaling High-Resolution images) using MatLab 23 | 2. Reference solution (upscaling with bicubic interpolation) is also should be done using MatLab 24 | 25 | As the most of the Deep Learning code is written under the python, we need to do some additional preprocessing/postprocessing using completely different environment (MatLab), and can't do upscaling/downscaling in-place using simple python functions. As a result, the implemented python imresize() function is done to overcome these difficulties. 26 | ## System requirements 27 | * python 2.7 28 | * numpy 29 | ## Usage 30 | imresize of *uint8* image using scale (e.g. 0.5 or 2): 31 | ```python 32 | Img_out = imresize(Img_in, scalar_scale=0.333) # Img_out of type uint8 33 | ``` 34 | imresize of *uint8* image using shape (e.g. (100, 200)): 35 | ```python 36 | Img_out = imresize(Img_in, output_shape=(123, 324)) # Img_out of type uint8 37 | ``` 38 | Above examples are working when input image `Img_in` is of the type **uint8**. But often the image processing is done in **float64**, and converted to uint8 only before saving on disk. The following code is for obtaining the same result as doing *imresize+imwrite* in MatLab for input image of doubles: 39 | ```python 40 | import numpy as np 41 | from skimage.io import imsave, imread 42 | from skimage import img_as_float 43 | img_uint8 = imread('test.png') 44 | img_double = img_as_float(img_uint8) 45 | new_img_double = imresize(img_double, output_shape=(123, 324)) 46 | imsave('test_double.png', convertDouble2Byte(new_img_double)) 47 | ``` 48 | ## Additional information 49 | Actually, the implemented python code was made by re-writing MatLab code `toolbox/images/images/imresize.m`, and it can't be done without brilliant insight made by [S. Sheen](https://stackoverflow.com/users/6073407/s-sheen) about how `imresizemex` can be implemented (originally, it is binary provided with MatLab distribution): [stackoverflow](https://stackoverflow.com/questions/36047357/what-does-imresizemex-do-in-matlab-imresize-function). 50 | 51 | In fact, if you have **OpenCV** and have the ability to re-compile it, probably the best solution is to change parameter `A` inside function `interpolateCubic`, which is located (at least for release 3.2.0) on line `3129` inside file `opencv-3.2.0/modules/imgproc/src/imgwarp.cpp`, from *-0.75f* to *-0.5f* (the value used in MatLab). Then simply use function `cv::resize` from OpenCV. For more information please refer to [stackoverflow](https://stackoverflow.com/questions/26823140/imresize-trying-to-understand-the-bicubic-interpolation). Also, see [another stackoverflow answer](https://stackoverflow.com/questions/29958670/how-to-use-matlabs-imresize-in-python) about different ways of resizing the image inside python. 52 | ## Note 53 | Please note that no optimization (aside from preliminary numpy-based vectorizing) was made, so the code can be (and it is) times slower than the original MatLab code. 54 | -------------------------------------------------------------------------------- /__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fatheral/matlab_imresize/d7cc04f26f87e6001d0c3068378073015ab3b19d/__init__.py -------------------------------------------------------------------------------- /imresize.py: -------------------------------------------------------------------------------- 1 | from __future__ import print_function 2 | import numpy as np 3 | from math import ceil, floor 4 | 5 | def deriveSizeFromScale(img_shape, scale): 6 | output_shape = [] 7 | for k in range(2): 8 | output_shape.append(int(ceil(scale[k] * img_shape[k]))) 9 | return output_shape 10 | 11 | def deriveScaleFromSize(img_shape_in, img_shape_out): 12 | scale = [] 13 | for k in range(2): 14 | scale.append(1.0 * img_shape_out[k] / img_shape_in[k]) 15 | return scale 16 | 17 | def triangle(x): 18 | x = np.array(x).astype(np.float64) 19 | lessthanzero = np.logical_and((x>=-1),x<0) 20 | greaterthanzero = np.logical_and((x<=1),x>=0) 21 | f = np.multiply((x+1),lessthanzero) + np.multiply((1-x),greaterthanzero) 22 | return f 23 | 24 | def cubic(x): 25 | x = np.array(x).astype(np.float64) 26 | absx = np.absolute(x) 27 | absx2 = np.multiply(absx, absx) 28 | absx3 = np.multiply(absx2, absx) 29 | f = np.multiply(1.5*absx3 - 2.5*absx2 + 1, absx <= 1) + np.multiply(-0.5*absx3 + 2.5*absx2 - 4*absx + 2, (1 < absx) & (absx <= 2)) 30 | return f 31 | 32 | def contributions(in_length, out_length, scale, kernel, k_width): 33 | if scale < 1: 34 | h = lambda x: scale * kernel(scale * x) 35 | kernel_width = 1.0 * k_width / scale 36 | else: 37 | h = kernel 38 | kernel_width = k_width 39 | x = np.arange(1, out_length+1).astype(np.float64) 40 | u = x / scale + 0.5 * (1 - 1 / scale) 41 | left = np.floor(u - kernel_width / 2) 42 | P = int(ceil(kernel_width)) + 2 43 | ind = np.expand_dims(left, axis=1) + np.arange(P) - 1 # -1 because indexing from 0 44 | indices = ind.astype(np.int32) 45 | weights = h(np.expand_dims(u, axis=1) - indices - 1) # -1 because indexing from 0 46 | weights = np.divide(weights, np.expand_dims(np.sum(weights, axis=1), axis=1)) 47 | aux = np.concatenate((np.arange(in_length), np.arange(in_length - 1, -1, step=-1))).astype(np.int32) 48 | indices = aux[np.mod(indices, aux.size)] 49 | ind2store = np.nonzero(np.any(weights, axis=0)) 50 | weights = weights[:, ind2store] 51 | indices = indices[:, ind2store] 52 | return weights, indices 53 | 54 | def imresizemex(inimg, weights, indices, dim): 55 | in_shape = inimg.shape 56 | w_shape = weights.shape 57 | out_shape = list(in_shape) 58 | out_shape[dim] = w_shape[0] 59 | outimg = np.zeros(out_shape) 60 | if dim == 0: 61 | for i_img in range(in_shape[1]): 62 | for i_w in range(w_shape[0]): 63 | w = weights[i_w, :] 64 | ind = indices[i_w, :] 65 | im_slice = inimg[ind, i_img].astype(np.float64) 66 | outimg[i_w, i_img] = np.sum(np.multiply(np.squeeze(im_slice, axis=0), w.T), axis=0) 67 | elif dim == 1: 68 | for i_img in range(in_shape[0]): 69 | for i_w in range(w_shape[0]): 70 | w = weights[i_w, :] 71 | ind = indices[i_w, :] 72 | im_slice = inimg[i_img, ind].astype(np.float64) 73 | outimg[i_img, i_w] = np.sum(np.multiply(np.squeeze(im_slice, axis=0), w.T), axis=0) 74 | if inimg.dtype == np.uint8: 75 | outimg = np.clip(outimg, 0, 255) 76 | return np.around(outimg).astype(np.uint8) 77 | else: 78 | return outimg 79 | 80 | def imresizevec(inimg, weights, indices, dim): 81 | wshape = weights.shape 82 | if dim == 0: 83 | weights = weights.reshape((wshape[0], wshape[2], 1, 1)) 84 | outimg = np.sum(weights*((inimg[indices].squeeze(axis=1)).astype(np.float64)), axis=1) 85 | elif dim == 1: 86 | weights = weights.reshape((1, wshape[0], wshape[2], 1)) 87 | outimg = np.sum(weights*((inimg[:, indices].squeeze(axis=2)).astype(np.float64)), axis=2) 88 | if inimg.dtype == np.uint8: 89 | outimg = np.clip(outimg, 0, 255) 90 | return np.around(outimg).astype(np.uint8) 91 | else: 92 | return outimg 93 | 94 | def resizeAlongDim(A, dim, weights, indices, mode="vec"): 95 | if mode == "org": 96 | out = imresizemex(A, weights, indices, dim) 97 | else: 98 | out = imresizevec(A, weights, indices, dim) 99 | return out 100 | 101 | def imresize(I, scalar_scale=None, method='bicubic', output_shape=None, mode="vec"): 102 | if method == 'bicubic': 103 | kernel = cubic 104 | elif method == 'bilinear': 105 | kernel = triangle 106 | else: 107 | raise ValueError('unidentified kernel method supplied') 108 | 109 | kernel_width = 4.0 110 | # Fill scale and output_size 111 | if scalar_scale is not None and output_shape is not None: 112 | raise ValueError('either scalar_scale OR output_shape should be defined') 113 | if scalar_scale is not None: 114 | scalar_scale = float(scalar_scale) 115 | scale = [scalar_scale, scalar_scale] 116 | output_size = deriveSizeFromScale(I.shape, scale) 117 | elif output_shape is not None: 118 | scale = deriveScaleFromSize(I.shape, output_shape) 119 | output_size = list(output_shape) 120 | else: 121 | raise ValueError('either scalar_scale OR output_shape should be defined') 122 | scale_np = np.array(scale) 123 | order = np.argsort(scale_np) 124 | weights = [] 125 | indices = [] 126 | for k in range(2): 127 | w, ind = contributions(I.shape[k], output_size[k], scale[k], kernel, kernel_width) 128 | weights.append(w) 129 | indices.append(ind) 130 | B = np.copy(I) 131 | flag2D = False 132 | if B.ndim == 2: 133 | B = np.expand_dims(B, axis=2) 134 | flag2D = True 135 | for k in range(2): 136 | dim = order[k] 137 | B = resizeAlongDim(B, dim, weights[dim], indices[dim], mode) 138 | if flag2D: 139 | B = np.squeeze(B, axis=2) 140 | return B 141 | 142 | def convertDouble2Byte(I): 143 | B = np.clip(I, 0.0, 1.0) 144 | B = 255*B 145 | return np.around(B).astype(np.uint8) 146 | 147 | -------------------------------------------------------------------------------- /test/README.md: -------------------------------------------------------------------------------- 1 | # imresize test 2 | Here the test script and input data are located. 3 | ## Test procedure 4 | Input image (`lena_512x512.png`) was resized by MatLab's `imresize` in two different ways: as an uint8 image and as a double image with values from interval [0,1]. These two options were tested as the most widespread among DL-scientists. Also, MatLab's `imresize` behaves differently (in terms of clipping its values) for uint8 and double data types (it doesn't clip the values when dealing with doubles, it does clipping when saving image on disk by `imwrite`). 5 | 6 | The produced images are: 7 | ``` 8 | lena_123x234_uint8.png 9 | lena_123x234_double.png 10 | ``` 11 | The MatLab code for producing these images is the following: 12 | ```matlab 13 | img_uint8 = imread('lena_512x512.png'); 14 | img_double = double(img_uint8) / 255; 15 | new_size = [123, 234]; 16 | 17 | new_img_uint8 = imresize(img_uint8, new_size); 18 | imwrite(new_img_uint8, 'lena_123x234_uint8.png'); 19 | 20 | new_img_double = imresize(img_double, new_size); 21 | imwrite(new_img_double, 'lena_123x234_double.png'); 22 | ``` 23 | ## Supposed output 24 | The output of the script `test_imresize.py` should be the following (at least for MatLab R2015b): 25 | ``` 26 | Python/MatLab uint8 diff: min = 0 max = 0 27 | Python/Matlab double diff: min = 0 max = 0 28 | ``` 29 | It shows max/min differences between uint8 images produced by Python's and its MatLab's origin `imresize` function. 30 | -------------------------------------------------------------------------------- /test/lena_123x234_double.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fatheral/matlab_imresize/d7cc04f26f87e6001d0c3068378073015ab3b19d/test/lena_123x234_double.png -------------------------------------------------------------------------------- /test/lena_123x234_uint8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fatheral/matlab_imresize/d7cc04f26f87e6001d0c3068378073015ab3b19d/test/lena_123x234_uint8.png -------------------------------------------------------------------------------- /test/lena_512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fatheral/matlab_imresize/d7cc04f26f87e6001d0c3068378073015ab3b19d/test/lena_512x512.png -------------------------------------------------------------------------------- /test/test_imresize.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from skimage.io import imsave, imread 3 | from skimage import img_as_float 4 | import sys 5 | sys.path.append('..') 6 | from imresize import * 7 | 8 | # 1. Load original image and do imresize+save both im UINT8 and FLOAT64 types 9 | img_uint8 = imread('lena_512x512.png') 10 | new_size = (123, 234) 11 | new_img_uint8 = imresize(img_uint8, output_shape=new_size) 12 | imsave('py_lena_123x234_uint8.png', new_img_uint8) 13 | img_double = img_as_float(img_uint8) 14 | new_img_double = imresize(img_double, output_shape=new_size) 15 | imsave('py_lena_123x234_double.png', convertDouble2Byte(new_img_double)) 16 | 17 | # 2. Load images resized by python's imresize() and compare with images resized by MatLab's imresize() 18 | matlab_uint8 = imread('lena_123x234_uint8.png') 19 | python_uint8 = imread('py_lena_123x234_uint8.png') 20 | matlab_double = imread('lena_123x234_double.png') 21 | python_double = imread('py_lena_123x234_double.png') 22 | diff_uint8 = matlab_uint8.astype(np.int32) - python_uint8.astype(np.int32) 23 | diff_double = matlab_double.astype(np.int32) - python_double.astype(np.int32) 24 | print 'Python/MatLab uint8 diff: min =', np.amin(diff_uint8), 'max =', np.amax(diff_uint8) 25 | print 'Python/Matlab double diff: min =', np.amin(diff_double), 'max =', np.amax(diff_double) 26 | --------------------------------------------------------------------------------