scikit-image
360 строк · 11.4 Кб
1import numpy as np
2import pytest
3from numpy.testing import assert_almost_equal
4
5from skimage import color, data, draw, feature, img_as_float
6from skimage._shared import filters
7from skimage._shared.testing import fetch
8from skimage._shared.utils import _supported_float_type
9
10
11def test_hog_output_size():
12img = img_as_float(data.astronaut()[:256, :].mean(axis=2))
13
14fd = feature.hog(
15img,
16orientations=9,
17pixels_per_cell=(8, 8),
18cells_per_block=(1, 1),
19block_norm='L1',
20)
21
22assert len(fd) == 9 * (256 // 8) * (512 // 8)
23
24
25@pytest.mark.parametrize('dtype', [np.float32, np.float64])
26def test_hog_output_correctness_l1_norm(dtype):
27img = color.rgb2gray(data.astronaut()).astype(dtype=dtype, copy=False)
28correct_output = np.load(fetch('data/astronaut_GRAY_hog_L1.npy'))
29
30output = feature.hog(
31img,
32orientations=9,
33pixels_per_cell=(8, 8),
34cells_per_block=(3, 3),
35block_norm='L1',
36feature_vector=True,
37transform_sqrt=False,
38visualize=False,
39)
40float_dtype = _supported_float_type(dtype)
41assert output.dtype == float_dtype
42decimal = 7 if float_dtype == np.float64 else 5
43assert_almost_equal(output, correct_output, decimal=decimal)
44
45
46@pytest.mark.parametrize('dtype', [np.float32, np.float64])
47def test_hog_output_correctness_l2hys_norm(dtype):
48img = color.rgb2gray(data.astronaut()).astype(dtype=dtype, copy=False)
49correct_output = np.load(fetch('data/astronaut_GRAY_hog_L2-Hys.npy'))
50
51output = feature.hog(
52img,
53orientations=9,
54pixels_per_cell=(8, 8),
55cells_per_block=(3, 3),
56block_norm='L2-Hys',
57feature_vector=True,
58transform_sqrt=False,
59visualize=False,
60)
61float_dtype = _supported_float_type(dtype)
62assert output.dtype == float_dtype
63decimal = 7 if float_dtype == np.float64 else 5
64assert_almost_equal(output, correct_output, decimal=decimal)
65
66
67def test_hog_image_size_cell_size_mismatch():
68image = data.camera()[:150, :200]
69fd = feature.hog(
70image,
71orientations=9,
72pixels_per_cell=(8, 8),
73cells_per_block=(1, 1),
74block_norm='L1',
75)
76assert len(fd) == 9 * (150 // 8) * (200 // 8)
77
78
79def test_hog_odd_cell_size():
80img = np.zeros((3, 3))
81img[2, 2] = 1
82
83correct_output = np.zeros((9,))
84correct_output[0] = 0.5
85correct_output[4] = 0.5
86
87output = feature.hog(
88img, pixels_per_cell=(3, 3), cells_per_block=(1, 1), block_norm='L1'
89)
90
91assert_almost_equal(output, correct_output, decimal=1)
92
93
94def test_hog_basic_orientations_and_data_types():
95# scenario:
96# 1) create image (with float values) where upper half is filled by
97# zeros, bottom half by 100
98# 2) create unsigned integer version of this image
99# 3) calculate feature.hog() for both images, both with 'transform_sqrt'
100# option enabled and disabled
101# 4) verify that all results are equal where expected
102# 5) verify that computed feature vector is as expected
103# 6) repeat the scenario for 90, 180 and 270 degrees rotated images
104
105# size of testing image
106width = height = 35
107
108image0 = np.zeros((height, width), dtype='float')
109image0[height // 2 :] = 100
110
111for rot in range(4):
112# rotate by 0, 90, 180 and 270 degrees
113image_float = np.rot90(image0, rot)
114
115# create uint8 image from image_float
116image_uint8 = image_float.astype('uint8')
117
118(hog_float, hog_img_float) = feature.hog(
119image_float,
120orientations=4,
121pixels_per_cell=(8, 8),
122cells_per_block=(1, 1),
123visualize=True,
124transform_sqrt=False,
125block_norm='L1',
126)
127(hog_uint8, hog_img_uint8) = feature.hog(
128image_uint8,
129orientations=4,
130pixels_per_cell=(8, 8),
131cells_per_block=(1, 1),
132visualize=True,
133transform_sqrt=False,
134block_norm='L1',
135)
136(hog_float_norm, hog_img_float_norm) = feature.hog(
137image_float,
138orientations=4,
139pixels_per_cell=(8, 8),
140cells_per_block=(1, 1),
141visualize=True,
142transform_sqrt=True,
143block_norm='L1',
144)
145(hog_uint8_norm, hog_img_uint8_norm) = feature.hog(
146image_uint8,
147orientations=4,
148pixels_per_cell=(8, 8),
149cells_per_block=(1, 1),
150visualize=True,
151transform_sqrt=True,
152block_norm='L1',
153)
154
155# set to True to enable manual debugging with graphical output,
156# must be False for automatic testing
157if False:
158import matplotlib.pyplot as plt
159
160plt.figure()
161plt.subplot(2, 3, 1)
162plt.imshow(image_float)
163plt.colorbar()
164plt.title('image')
165plt.subplot(2, 3, 2)
166plt.imshow(hog_img_float)
167plt.colorbar()
168plt.title('HOG result visualisation (float img)')
169plt.subplot(2, 3, 5)
170plt.imshow(hog_img_uint8)
171plt.colorbar()
172plt.title('HOG result visualisation (uint8 img)')
173plt.subplot(2, 3, 3)
174plt.imshow(hog_img_float_norm)
175plt.colorbar()
176plt.title('HOG result (transform_sqrt) visualisation (float img)')
177plt.subplot(2, 3, 6)
178plt.imshow(hog_img_uint8_norm)
179plt.colorbar()
180plt.title('HOG result (transform_sqrt) visualisation (uint8 img)')
181plt.show()
182
183# results (features and visualisation) for float and uint8 images must
184# be almost equal
185assert_almost_equal(hog_float, hog_uint8)
186assert_almost_equal(hog_img_float, hog_img_uint8)
187
188# resulting features should be almost equal
189# when 'transform_sqrt' is enabled
190# or disabled (for current simple testing image)
191assert_almost_equal(hog_float, hog_float_norm, decimal=4)
192assert_almost_equal(hog_float, hog_uint8_norm, decimal=4)
193
194# reshape resulting feature vector to matrix with 4 columns (each
195# corresponding to one of 4 directions); only one direction should
196# contain nonzero values (this is manually determined for testing
197# image)
198actual = np.max(hog_float.reshape(-1, 4), axis=0)
199
200if rot in [0, 2]:
201# image is rotated by 0 and 180 degrees
202desired = [0, 0, 1, 0]
203elif rot in [1, 3]:
204# image is rotated by 90 and 270 degrees
205desired = [1, 0, 0, 0]
206else:
207raise Exception('Result is not determined for this rotation.')
208
209assert_almost_equal(actual, desired, decimal=2)
210
211
212def test_hog_orientations_circle():
213# scenario:
214# 1) create image with blurred circle in the middle
215# 2) calculate feature.hog()
216# 3) verify that the resulting feature vector contains uniformly
217# distributed values for all orientations, i.e. no orientation is
218# lost or emphasized
219# 4) repeat the scenario for other 'orientations' option
220
221# size of testing image
222width = height = 100
223
224image = np.zeros((height, width))
225rr, cc = draw.disk((int(height / 2), int(width / 2)), int(width / 3))
226image[rr, cc] = 100
227image = filters.gaussian(image, sigma=2, mode='reflect')
228
229for orientations in range(2, 15):
230(hog, hog_img) = feature.hog(
231image,
232orientations=orientations,
233pixels_per_cell=(8, 8),
234cells_per_block=(1, 1),
235visualize=True,
236transform_sqrt=False,
237block_norm='L1',
238)
239
240# set to True to enable manual debugging with graphical output,
241# must be False for automatic testing
242if False:
243import matplotlib.pyplot as plt
244
245plt.figure()
246plt.subplot(1, 2, 1)
247plt.imshow(image)
248plt.colorbar()
249plt.title('image_float')
250plt.subplot(1, 2, 2)
251plt.imshow(hog_img)
252plt.colorbar()
253plt.title('HOG result visualisation, ' f'orientations={orientations}')
254plt.show()
255
256# reshape resulting feature vector to matrix with N columns (each
257# column corresponds to one direction),
258hog_matrix = hog.reshape(-1, orientations)
259
260# compute mean values in the resulting feature vector for each
261# direction, these values should be almost equal to the global mean
262# value (since the image contains a circle), i.e., all directions have
263# same contribution to the result
264actual = np.mean(hog_matrix, axis=0)
265desired = np.mean(hog_matrix)
266assert_almost_equal(actual, desired, decimal=1)
267
268
269def test_hog_visualization_orientation():
270"""Test that the visualization produces a line with correct orientation
271
272The hog visualization is expected to draw line segments perpendicular to
273the midpoints of orientation bins. This example verifies that when
274orientations=3 and the gradient is entirely in the middle bin (bisected
275by the y-axis), the line segment drawn by the visualization is horizontal.
276"""
277
278width = height = 11
279
280image = np.zeros((height, width), dtype='float')
281image[height // 2 :] = 1
282
283_, hog_image = feature.hog(
284image,
285orientations=3,
286pixels_per_cell=(width, height),
287cells_per_block=(1, 1),
288visualize=True,
289block_norm='L1',
290)
291
292middle_index = height // 2
293indices_excluding_middle = [x for x in range(height) if x != middle_index]
294
295assert (hog_image[indices_excluding_middle, :] == 0).all()
296assert (hog_image[middle_index, 1:-1] > 0).all()
297
298
299def test_hog_block_normalization_incorrect_error():
300img = np.eye(4)
301with pytest.raises(ValueError):
302feature.hog(img, block_norm='Linf')
303
304
305@pytest.mark.parametrize(
306"shape,channel_axis",
307[
308((3, 3, 3), None),
309((3, 3), -1),
310((3, 3, 3, 3), -1),
311],
312)
313def test_hog_incorrect_dimensions(shape, channel_axis):
314img = np.zeros(shape)
315with pytest.raises(ValueError):
316feature.hog(img, channel_axis=channel_axis, block_norm='L1')
317
318
319def test_hog_output_equivariance_deprecated_multichannel():
320img = data.astronaut()
321img[:, :, (1, 2)] = 0
322hog_ref = feature.hog(img, channel_axis=-1, block_norm='L1')
323
324for n in (1, 2):
325hog_fact = feature.hog(
326np.roll(img, n, axis=2), channel_axis=-1, block_norm='L1'
327)
328assert_almost_equal(hog_ref, hog_fact)
329
330
331@pytest.mark.parametrize('channel_axis', [0, 1, -1, -2])
332def test_hog_output_equivariance_channel_axis(channel_axis):
333img = data.astronaut()[:64, :32]
334img[:, :, (1, 2)] = 0
335img = np.moveaxis(img, -1, channel_axis)
336hog_ref = feature.hog(img, channel_axis=channel_axis, block_norm='L1')
337
338for n in (1, 2):
339hog_fact = feature.hog(
340np.roll(img, n, axis=channel_axis),
341channel_axis=channel_axis,
342block_norm='L1',
343)
344assert_almost_equal(hog_ref, hog_fact)
345
346
347def test_hog_small_image():
348"""Test that an exception is thrown whenever the input image is
349too small for the given parameters.
350"""
351img = np.zeros((24, 24))
352feature.hog(img, pixels_per_cell=(8, 8), cells_per_block=(3, 3))
353
354img = np.zeros((23, 23))
355with pytest.raises(ValueError, match=".*image is too small given"):
356feature.hog(
357img,
358pixels_per_cell=(8, 8),
359cells_per_block=(3, 3),
360)
361