Skip to content

Commit 81de83e

Browse files
committed
update resize function
1 parent 0018f12 commit 81de83e

File tree

5 files changed

+296
-13
lines changed

5 files changed

+296
-13
lines changed

IL-NIQE.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212

1313
import time
1414
# import ray
15+
from matlab_resize import MATLABLikeResize
1516

1617
def reorder_image(img, input_order='HWC'):
1718
"""Reorder images to 'HWC' order.
@@ -245,7 +246,11 @@ def ilniqe(img, mu_pris_param, cov_pris_param, gaussian_window, principleVectors
245246
nanConst = 2000
246247

247248
if resize:
248-
img = cv2.resize(img, (normalizedWidth, normalizedWidth), interpolation=cv2.INTER_AREA)
249+
# img = cv2.resize(img, (normalizedWidth, normalizedWidth), interpolation=cv2.INTER_AREA)
250+
resize_func = MATLABLikeResize(output_shape=(normalizedWidth, normalizedWidth))
251+
img = resize_func.resize_img(img)
252+
img = np.clip(img, 0.0, 255.0)
253+
249254
h, w, _ = img.shape
250255

251256
num_block_h = math.floor(h / block_size_h)

README.md

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -22,21 +22,19 @@ You can also train your own model via training.m in the Matlab version. But the
2222

2323
|Image|IL-NIQE (using official .mat) (Matlab/Python)|IL-NIQE (using .mat trained in python) (Python)|IL-NIQE (w/o imresize) (Matlab/Python)|Time(sec) (Matlab/Python)|
2424
|:-|:-|:-|:-|:-|
25-
|pepper_0.png|29.1422 / 27.3655|28.1166|38.7078 / 38.9319|9.9567 / 103.4350|
26-
|pepper_1.png|36.9637 / 39.0683|38.7309|36.6869 / 37.0163|9.7487 / 90.1218|
27-
|pepper_2.png|29.5075 / 31.5751|29.5121|28.7137 / 28.6329|10.3733 / 103.6504|
28-
|pepper_3.png|78.0557 / 58.6855|49.9387|92.3750 / 92.9693|10.5093 / 97.8555|
29-
|pepper_4.png|46.8697 / 54.2524|41.9770|46.4926 / 46.8856|9.7452 / 103.4113|
25+
|pepper_0.png|29.1422 / 28.8966|30.3513|38.7078 / 38.9319|9.9567 / 103.4350|
26+
|pepper_1.png|36.9637 / 37.4120|37.6577|36.6869 / 37.0163|9.7487 / 90.1218|
27+
|pepper_2.png|29.5075 / 28.9969|28.4353|28.7137 / 28.6329|10.3733 / 103.6504|
28+
|pepper_3.png|78.0557 / 83.3886|74.5166|92.3750 / 92.9693|10.5093 / 97.8555|
29+
|pepper_4.png|46.8697 / 51.7191|46.9279|46.4926 / 46.8856|9.7452 / 103.4113|
3030

3131
For Matlab, it uses parpool for multiprocessing and is much faster than python. This implement supports multiprocessing via ray.
3232

33-
* Accuracy: Generally, without resizing the image, the difference is smaller than 1. I think this can be accepted since at current stage, no-reference metric cannot accurately reflect the quality of an image.
3433
* Difference: The main reasons of the difference may be due to the precision of float computing and different results of similar functions of Matlab and Python, i.e., imresize. (The large differences for 'pepper_3.png' and 'pepper_4.png' are mainly due to resize.)
3534

3635
After comparision, I have found some lines which generate different results, it can be more accurate if you can provide a better function to replace the current one:
3736

38-
- [imresize function:](https://github.com/IceClear/IL-NIQE/blob/master/IL-NIQE.py#L249) The difference between the imresize function between cv2 and Matlab seems affect the results the most. Maybe the solution is to rewrite the function in python.
37+
- [imresize function:](https://github.com/IceClear/IL-NIQE/blob/master/IL-NIQE.py#L249) The difference between the imresize function between python and Matlab seems affect the results the most. Maybe the solution is to rewrite the function in python.
3938
- [var:](https://github.com/IceClear/IL-NIQE/blob/master/IL-NIQE.py#L111) The varience of numpy is sometimes different from the var() function in Matlab. The difference is smaller than 1. Reasons are unknoen yet.
40-
- [mean:](https://github.com/IceClear/IL-NIQE/blob/master/IL-NIQE.py#L110) When the number is very small (<1e-15), this function will fail due to the limit of float64. This seems not the main problem.
4139

4240
Any suggestions for improvement are welcomed.

matlab_resize.py

Lines changed: 276 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,276 @@
1+
# This code is referenced from matlab_imresize with modifications
2+
# Reference: https://github.com/fatheral/matlab_imresize/blob/master/imresize.py # noqa
3+
# Original licence: Copyright (c) 2020 fatheral, under the MIT License.
4+
# Modified from MMediting: https://github.com/open-mmlab/mmediting
5+
import numpy as np
6+
7+
8+
def get_size_from_scale(input_size, scale_factor):
9+
"""Get the output size given input size and scale factor.
10+
11+
Args:
12+
input_size (tuple): The size of the input image.
13+
scale_factor (float): The resize factor.
14+
15+
Returns:
16+
list[int]: The size of the output image.
17+
"""
18+
19+
output_shape = [
20+
int(np.ceil(scale * shape))
21+
for (scale, shape) in zip(scale_factor, input_size)
22+
]
23+
24+
return output_shape
25+
26+
27+
def get_scale_from_size(input_size, output_size):
28+
"""Get the scale factor given input size and output size.
29+
30+
Args:
31+
input_size (tuple(int)): The size of the input image.
32+
output_size (tuple(int)): The size of the output image.
33+
34+
Returns:
35+
list[float]: The scale factor of each dimension.
36+
"""
37+
38+
scale = [
39+
1.0 * output_shape / input_shape
40+
for (input_shape, output_shape) in zip(input_size, output_size)
41+
]
42+
43+
return scale
44+
45+
46+
def _cubic(x):
47+
""" Cubic function.
48+
49+
Args:
50+
x (ndarray): The distance from the center position.
51+
52+
Returns:
53+
ndarray: The weight corresponding to a particular distance.
54+
55+
"""
56+
57+
x = np.array(x, dtype=np.float32)
58+
x_abs = np.abs(x)
59+
x_abs_sq = x_abs**2
60+
x_abs_cu = x_abs_sq * x_abs
61+
62+
# if |x| <= 1: y = 1.5|x|^3 - 2.5|x|^2 + 1
63+
# if 1 < |x| <= 2: -0.5|x|^3 + 2.5|x|^2 - 4|x| + 2
64+
f = (1.5 * x_abs_cu - 2.5 * x_abs_sq + 1) * (x_abs <= 1) + (
65+
-0.5 * x_abs_cu + 2.5 * x_abs_sq - 4 * x_abs + 2) * ((1 < x_abs) &
66+
(x_abs <= 2))
67+
68+
return f
69+
70+
71+
def get_weights_indices(input_length, output_length, scale, kernel,
72+
kernel_width):
73+
"""Get weights and indices for interpolation.
74+
75+
Args:
76+
input_length (int): Length of the input sequence.
77+
output_length (int): Length of the output sequence.
78+
scale (float): Scale factor.
79+
kernel (func): The kernel used for resizing.
80+
kernel_width (int): The width of the kernel.
81+
82+
Returns:
83+
list[ndarray]: The weights and the indices for interpolation.
84+
85+
86+
"""
87+
if scale < 1: # modified kernel for antialiasing
88+
89+
def h(x):
90+
return scale * kernel(scale * x)
91+
92+
kernel_width = 1.0 * kernel_width / scale
93+
else:
94+
h = kernel
95+
kernel_width = kernel_width
96+
97+
# coordinates of output
98+
x = np.arange(1, output_length + 1).astype(np.float32)
99+
100+
# coordinates of input
101+
u = x / scale + 0.5 * (1 - 1 / scale)
102+
left = np.floor(u - kernel_width / 2) # leftmost pixel
103+
p = int(np.ceil(kernel_width)) + 2 # maximum number of pixels
104+
105+
# indices of input pixels
106+
ind = left[:, np.newaxis, ...] + np.arange(p)
107+
indices = ind.astype(np.int32)
108+
109+
# weights of input pixels
110+
weights = h(u[:, np.newaxis, ...] - indices - 1)
111+
112+
weights = weights / np.sum(weights, axis=1)[:, np.newaxis, ...]
113+
114+
# remove all-zero columns
115+
aux = np.concatenate(
116+
(np.arange(input_length), np.arange(input_length - 1, -1,
117+
step=-1))).astype(np.int32)
118+
indices = aux[np.mod(indices, aux.size)]
119+
ind2store = np.nonzero(np.any(weights, axis=0))
120+
weights = weights[:, ind2store]
121+
indices = indices[:, ind2store]
122+
123+
return weights, indices
124+
125+
126+
def resize_along_dim(img_in, weights, indices, dim):
127+
"""Resize along a specific dimension.
128+
129+
Args:
130+
img_in (ndarray): The input image.
131+
weights (ndarray): The weights used for interpolation, computed from
132+
[get_weights_indices].
133+
indices (ndarray): The indices used for interpolation, computed from
134+
[get_weights_indices].
135+
dim (int): Which dimension to undergo interpolation.
136+
137+
Returns:
138+
ndarray: Interpolated (along one dimension) image.
139+
"""
140+
141+
img_in = img_in.astype(np.float32)
142+
w_shape = weights.shape
143+
output_shape = list(img_in.shape)
144+
output_shape[dim] = w_shape[0]
145+
img_out = np.zeros(output_shape)
146+
147+
if dim == 0:
148+
for i in range(w_shape[0]):
149+
w = weights[i, :][np.newaxis, ...]
150+
ind = indices[i, :]
151+
img_slice = img_in[ind, :]
152+
img_out[i] = np.sum(np.squeeze(img_slice, axis=0) * w.T, axis=0)
153+
elif dim == 1:
154+
for i in range(w_shape[0]):
155+
w = weights[i, :][:, :, np.newaxis]
156+
ind = indices[i, :]
157+
img_slice = img_in[:, ind]
158+
img_out[:, i] = np.sum(np.squeeze(img_slice, axis=1) * w.T, axis=1)
159+
160+
if img_in.dtype == np.uint8:
161+
img_out = np.clip(img_out, 0, 255)
162+
return np.around(img_out).astype(np.uint8)
163+
else:
164+
return img_out
165+
166+
167+
class MATLABLikeResize:
168+
"""Resize the input image using MATLAB-like downsampling.
169+
170+
Currently support bicubic interpolation only. Note that the output of
171+
this function is slightly different from the official MATLAB function.
172+
173+
Required keys are the keys in attribute "keys". Added or modified keys
174+
are "scale" and "output_shape", and the keys in attribute "keys".
175+
176+
Args:
177+
keys (list[str]): A list of keys whose values are modified.
178+
scale (float | None, optional): The scale factor of the resize
179+
operation. If None, it will be determined by output_shape.
180+
Default: None.
181+
output_shape (tuple(int) | None, optional): The size of the output
182+
image. If None, it will be determined by scale. Note that if
183+
scale is provided, output_shape will not be used.
184+
Default: None.
185+
kernel (str, optional): The kernel for the resize operation.
186+
Currently support 'bicubic' only. Default: 'bicubic'.
187+
kernel_width (float): The kernel width. Currently support 4.0 only.
188+
Default: 4.0.
189+
"""
190+
191+
def __init__(self,
192+
keys=None,
193+
scale=None,
194+
output_shape=None,
195+
kernel='bicubic',
196+
kernel_width=4.0):
197+
198+
if kernel.lower() != 'bicubic':
199+
raise ValueError('Currently support bicubic kernel only.')
200+
201+
if float(kernel_width) != 4.0:
202+
raise ValueError('Current support only width=4 only.')
203+
204+
if scale is None and output_shape is None:
205+
raise ValueError('"scale" and "output_shape" cannot be both None')
206+
207+
self.kernel_func = _cubic
208+
self.keys = keys
209+
self.scale = scale
210+
self.output_shape = output_shape
211+
self.kernel = kernel
212+
self.kernel_width = kernel_width
213+
214+
def resize_img(self, img):
215+
return self._resize(img)
216+
217+
def _resize(self, img):
218+
weights = {}
219+
indices = {}
220+
221+
# compute scale and output_size
222+
if self.scale is not None:
223+
scale = float(self.scale)
224+
scale = [scale, scale]
225+
output_size = get_size_from_scale(img.shape, scale)
226+
else:
227+
scale = get_scale_from_size(img.shape, self.output_shape)
228+
output_size = list(self.output_shape)
229+
230+
# apply cubic interpolation along two dimensions
231+
order = np.argsort(np.array(scale))
232+
for k in range(2):
233+
key = (img.shape[k], output_size[k], scale[k], self.kernel_func,
234+
self.kernel_width)
235+
weight, index = get_weights_indices(img.shape[k], output_size[k],
236+
scale[k], self.kernel_func,
237+
self.kernel_width)
238+
weights[key] = weight
239+
indices[key] = index
240+
241+
output = np.copy(img)
242+
if output.ndim == 2: # grayscale image
243+
output = output[:, :, np.newaxis]
244+
245+
for k in range(2):
246+
dim = order[k]
247+
key = (img.shape[dim], output_size[dim], scale[dim],
248+
self.kernel_func, self.kernel_width)
249+
output = resize_along_dim(output, weights[key], indices[key], dim)
250+
251+
return output
252+
253+
def __call__(self, results):
254+
for key in self.keys:
255+
is_single_image = False
256+
if isinstance(results[key], np.ndarray):
257+
is_single_image = True
258+
results[key] = [results[key]]
259+
260+
results[key] = [self._resize(img) for img in results[key]]
261+
262+
if is_single_image:
263+
results[key] = results[key][0]
264+
265+
results['scale'] = self.scale
266+
results['output_shape'] = self.output_shape
267+
268+
return results
269+
270+
def __repr__(self):
271+
repr_str = self.__class__.__name__
272+
repr_str += (
273+
f'(keys={self.keys}, scale={self.scale}, '
274+
f'output_shape={self.output_shape}, '
275+
f'kernel={self.kernel}, kernel_width={self.kernel_width})')
276+
return repr_str

python_templateModel.mat

0 Bytes
Binary file not shown.

train.py

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111
from scipy.stats import exponweib
1212
from scipy.optimize import fmin
1313
import time
14+
from matlab_resize import MATLABLikeResize
15+
from tqdm import tqdm
1416

1517
def MyPCA(sampleData, reservedRatio):
1618
principleVectors = []
@@ -218,17 +220,20 @@ def train(data_path):
218220
gaussian_window = matlab_fspecial((5,5),5/6)
219221
gaussian_window = gaussian_window/np.sum(gaussian_window)
220222

221-
trainingFiles = os.listdir(data_path)
223+
trainingFiles = sorted(os.listdir(data_path))
222224

223225
pic_features = []
224226
pic_sharpness = []
225227

226-
for img_file in trainingFiles:
228+
for img_file in tqdm(trainingFiles):
227229
img = cv2.imread(os.path.join(data_path, img_file))
228230
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
229231
img = img.astype(np.float64)
230232
img = img.round()
231-
img = cv2.resize(img, (normalizedWidth, normalizedWidth),interpolation=cv2.INTER_AREA)
233+
# img = cv2.resize(img, (normalizedWidth, normalizedWidth),interpolation=cv2.INTER_AREA)
234+
resize_func = MATLABLikeResize(output_shape=(normalizedWidth, normalizedWidth))
235+
img = resize_func.resize_img(img)
236+
img = np.clip(img, 0.0, 255.0)
232237

233238
h, w, _ = img.shape
234239

@@ -352,7 +357,6 @@ def train(data_path):
352357
distparam = np.concatenate(distparam, axis=1)
353358
pic_features.append(np.array(distparam))
354359
pic_sharpness.append(sharpness)
355-
print(img_file, flush=True)
356360

357361
prisparam = None
358362
for i in range(len(pic_features)):

0 commit comments

Comments
 (0)