├── 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 |
--------------------------------------------------------------------------------