From 25d94b36c6691866737501008cfc469ac9f12068 Mon Sep 17 00:00:00 2001 From: Pavel Campr Date: Thu, 9 Aug 2012 13:49:26 +0300 Subject: [PATCH 1/4] fix hog.py - orientation and visualization --- skimage/feature/_hog.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/skimage/feature/_hog.py b/skimage/feature/_hog.py index 5206034e..98d8f731 100644 --- a/skimage/feature/_hog.py +++ b/skimage/feature/_hog.py @@ -96,7 +96,7 @@ def hog(image, orientations=9, pixels_per_cell=(8, 8), """ magnitude = sqrt(gx**2 + gy**2) - orientation = arctan2(gy, (gx + 1e-15)) * (180 / pi) + 90 + orientation = arctan2(gy, (gx + 1e-15)) * (180 / pi) % 180 sy, sx = image.shape cx, cy = pixels_per_cell @@ -137,8 +137,8 @@ def hog(image, orientations=9, pixels_per_cell=(8, 8), centre = tuple([y * cy + cy // 2, x * cx + cx // 2]) dx = radius * cos(float(o) / orientations * np.pi) dy = radius * sin(float(o) / orientations * np.pi) - rr, cc = draw.bresenham(centre[0] - dx, centre[1] - dy, - centre[0] + dx, centre[1] + dy) + rr, cc = draw.bresenham(centre[0] - dy, centre[1] - dx, + centre[0] + dy, centre[1] + dx) hog_image[rr, cc] += orientation_histogram[y, x, o] """ From 298c1f189092f37d63528c256d7178fccaf7c798 Mon Sep 17 00:00:00 2001 From: pcampr Date: Sat, 18 Aug 2012 15:50:42 +0200 Subject: [PATCH 2/4] fixing multiple bugs in hog.py, adding two tests to test_hog.py --- skimage/feature/_hog.py | 17 +++-- skimage/feature/tests/test_hog.py | 112 ++++++++++++++++++++++++++++++ 2 files changed, 123 insertions(+), 6 deletions(-) diff --git a/skimage/feature/_hog.py b/skimage/feature/_hog.py index 98d8f731..3bd849ab 100644 --- a/skimage/feature/_hog.py +++ b/skimage/feature/_hog.py @@ -59,7 +59,7 @@ def hog(image, orientations=9, pixels_per_cell=(8, 8), shadowing and illumination variations. """ - if image.ndim > 3: + if image.ndim > 2: raise ValueError("Currently only supports grey-level images") if normalise: @@ -75,6 +75,11 @@ def hog(image, orientations=9, pixels_per_cell=(8, 8), e.g. bar like structures in bicycles and limbs in humans. """ + if image.dtype.kind == 'u': + # convert uint image to float + # to avoid problems with subtracting unsigned numbers in np.diff() + image = image.astype('float') + gx = np.zeros(image.shape) gy = np.zeros(image.shape) gx[:, :-1] = np.diff(image, n=1, axis=1) @@ -96,7 +101,7 @@ def hog(image, orientations=9, pixels_per_cell=(8, 8), """ magnitude = sqrt(gx**2 + gy**2) - orientation = arctan2(gy, (gx + 1e-15)) * (180 / pi) % 180 + orientation = arctan2(gy, gx) * (180 / pi) % 180 sy, sx = image.shape cx, cy = pixels_per_cell @@ -113,11 +118,11 @@ def hog(image, orientations=9, pixels_per_cell=(8, 8), # isolate orientations in this range temp_ori = np.where(orientation < 180 / orientations * (i + 1), - orientation, 0) + orientation, -1) temp_ori = np.where(orientation >= 180 / orientations * i, - temp_ori, 0) + temp_ori, -1) # select magnitudes for those orientations - cond2 = temp_ori > 0 + cond2 = temp_ori > -1 temp_mag = np.where(cond2, magnitude, 0) temp_filt = uniform_filter(temp_mag, size=(cy, cx)) @@ -176,4 +181,4 @@ def hog(image, orientations=9, pixels_per_cell=(8, 8), if visualise: return normalised_blocks.ravel(), hog_image else: - return normalised_blocks.ravel() + return normalised_blocks.ravel() \ No newline at end of file diff --git a/skimage/feature/tests/test_hog.py b/skimage/feature/tests/test_hog.py index 90e08105..ef26d0db 100644 --- a/skimage/feature/tests/test_hog.py +++ b/skimage/feature/tests/test_hog.py @@ -1,6 +1,10 @@ +import numpy as np +from scipy import ndimage from skimage import data from skimage import feature from skimage import img_as_float +from skimage import draw +from numpy.testing import * def test_histogram_of_oriented_gradients(): img = img_as_float(data.lena()[:256, :].mean(axis=2)) @@ -16,6 +20,114 @@ def test_hog_image_size_cell_size_mismatch(): cells_per_block=(1, 1)) assert len(fd) == 9 * (150 // 8) * (200 // 8) +def test_hog_color_image_unsupported_error(): + image = np.zeros((20, 20, 3)) + assert_raises(ValueError, feature.hog, image) + +def test_hog_basic_orientations_and_data_types(): + # scenario: + # 1) create image (with float values) where upper half is filled by zeros, bottom half by 100 + # 2) create unsigned integer version of this image + # 3) calculate feature.hog() for both images, both with 'normalise' option enabled and disabled + # 4) verify that all results are equal where expected + # 5) verify that computed feature vector is as expected + # 6) repeat the scenario for 90, 180 and 270 degrees rotated images + + # size of testing image + width = height = 35 + + image0 = np.zeros((height, width), dtype='float') + image0[height / 2:] = 100 + + for rot in range(4): + # rotate by 0, 90, 180 and 270 degrees + image_float = np.rot90(image0, rot) + + # create uint8 image from image_float + image_uint8 = image_float.astype('uint8') + + (hog_float, hog_img_float) = feature.hog(image_float, orientations=4, pixels_per_cell=(8, 8), + cells_per_block=(1, 1), visualise=True, normalise=False) + (hog_uint8, hog_img_uint8) = feature.hog(image_uint8, orientations=4, pixels_per_cell=(8, 8), + cells_per_block=(1, 1), visualise=True, normalise=False) + (hog_float_norm, hog_img_float_norm) = feature.hog(image_float, orientations=4, pixels_per_cell=(8, 8), + cells_per_block=(1, 1), visualise=True, normalise=True) + (hog_uint8_norm, hog_img_uint8_norm) = feature.hog(image_uint8, orientations=4, pixels_per_cell=(8, 8), + cells_per_block=(1, 1), visualise=True, normalise=True) + + # set to True to enable manual debugging with graphical output, + # must be False for automatic testing + if False: + from pylab import * + plt.figure() + plt.subplot(2, 3, 1); plt.imshow(image_float); plt.colorbar(); plt.title('image') + plt.subplot(2, 3, 2); plt.imshow(hog_img_float); plt.colorbar(); plt.title('HOG result visualisation (float img)') + plt.subplot(2, 3, 5); plt.imshow(hog_img_uint8); plt.colorbar(); plt.title('HOG result visualisation (uint8 img)') + plt.subplot(2, 3, 3); plt.imshow(hog_img_float_norm); plt.colorbar(); plt.title('HOG result (normalise) visualisation (float img)') + plt.subplot(2, 3, 6); plt.imshow(hog_img_uint8_norm); plt.colorbar(); plt.title('HOG result (normalise) visualisation (uint8 img)') + plt.show() + + # results (features and visualisation) for float and uint8 images must be almost equal + assert_almost_equal(hog_float, hog_uint8) + assert_almost_equal(hog_img_float, hog_img_uint8) + + # resulting features should be almost equal when 'normalise' is enabled or disabled (for current simple testing image) + assert_almost_equal(hog_float, hog_float_norm, decimal=4) + assert_almost_equal(hog_float, hog_uint8_norm, decimal=4) + + # reshape resulting feature vector to matrix with 4 columns (each corresponding to one of 4 directions), + # only one direction should contain nonzero values (this is manually determined for testing image) + actual = np.max(hog_float.reshape(-1, 4), axis=0) + + if rot in [0, 2]: + # image is rotated by 0 and 180 degrees + desired = [0, 0, 1, 0] + elif rot in [1, 3]: + # image is rotated by 90 and 270 degrees + desired = [1, 0, 0, 0] + + assert_almost_equal(actual, desired, decimal=2) + +def test_hog_orientations_circle(): + # scenario: + # 1) create image with blurred circle in the middle + # 2) calculate feature.hog() + # 3) verify that the resulting feature vector contains uniformly distributed values for all orientations, + # i.e. no orientation is lost or emphasized + # 4) repeat the scenario for other 'orientations' option + + # size of testing image + width = height = 100 + + image = np.zeros((height, width)) + rr, cc = draw.circle(height/2, width/2, width/3) + image[rr, cc] = 100 + image = ndimage.gaussian_filter(image, 2) + + for orientations in range(2, 15): + (hog, hog_img) = feature.hog(image, orientations=orientations, pixels_per_cell=(8, 8), + cells_per_block=(1, 1), visualise=True, normalise=False) + + # set to True to enable manual debugging with graphical output, + # must be False for automatic testing + if False: + from pylab import * + + plt.figure() + plt.subplot(1, 2, 1); plt.imshow(image); plt.colorbar(); plt.title('image_float') + plt.subplot(1, 2, 2); plt.imshow(hog_img); plt.colorbar(); plt.title('HOG result visualisation, orientations=%d' % (orientations)) + plt.show() + + # reshape resulting feature vector to matrix with N columns (each column corresponds to one direction), + hog_matrix = hog.reshape(-1, orientations) + + # compute mean values in the resulting feature vector for each direction, + # these values should be almost equal to the global mean value (since the image contains a circle), + # i.e. all directions have same contribution to the result + actual = np.mean(hog_matrix, axis=0) + desired = np.mean(hog_matrix) + assert_almost_equal(actual, desired, decimal=1) + if __name__ == '__main__': from numpy.testing import run_module_suite run_module_suite() From 6761f131adc29800f0b12eeb0859759401112e5e Mon Sep 17 00:00:00 2001 From: pcampr Date: Sat, 18 Aug 2012 16:25:14 +0200 Subject: [PATCH 3/4] fixed imports --- skimage/feature/tests/test_hog.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/skimage/feature/tests/test_hog.py b/skimage/feature/tests/test_hog.py index ef26d0db..8cf45fb8 100644 --- a/skimage/feature/tests/test_hog.py +++ b/skimage/feature/tests/test_hog.py @@ -3,7 +3,7 @@ from scipy import ndimage from skimage import data from skimage import feature from skimage import img_as_float -from skimage import draw +from skimage.draw import draw from numpy.testing import * def test_histogram_of_oriented_gradients(): @@ -32,6 +32,8 @@ def test_hog_basic_orientations_and_data_types(): # 4) verify that all results are equal where expected # 5) verify that computed feature vector is as expected # 6) repeat the scenario for 90, 180 and 270 degrees rotated images + # + # author: Pavel Campr # size of testing image width = height = 35 @@ -58,7 +60,7 @@ def test_hog_basic_orientations_and_data_types(): # set to True to enable manual debugging with graphical output, # must be False for automatic testing if False: - from pylab import * + import matplotlib.pyplot as plt plt.figure() plt.subplot(2, 3, 1); plt.imshow(image_float); plt.colorbar(); plt.title('image') plt.subplot(2, 3, 2); plt.imshow(hog_img_float); plt.colorbar(); plt.title('HOG result visualisation (float img)') @@ -85,6 +87,8 @@ def test_hog_basic_orientations_and_data_types(): elif rot in [1, 3]: # image is rotated by 90 and 270 degrees desired = [1, 0, 0, 0] + else: + raise Exception('Result is not determined for this rotation.') assert_almost_equal(actual, desired, decimal=2) @@ -95,6 +99,8 @@ def test_hog_orientations_circle(): # 3) verify that the resulting feature vector contains uniformly distributed values for all orientations, # i.e. no orientation is lost or emphasized # 4) repeat the scenario for other 'orientations' option + # + # author: Pavel Campr # size of testing image width = height = 100 @@ -111,8 +117,7 @@ def test_hog_orientations_circle(): # set to True to enable manual debugging with graphical output, # must be False for automatic testing if False: - from pylab import * - + import matplotlib.pyplot as plt plt.figure() plt.subplot(1, 2, 1); plt.imshow(image); plt.colorbar(); plt.title('image_float') plt.subplot(1, 2, 2); plt.imshow(hog_img); plt.colorbar(); plt.title('HOG result visualisation, orientations=%d' % (orientations)) From 673d4ec212d78e9a8823bf415e43446ee45ca798 Mon Sep 17 00:00:00 2001 From: pcampr Date: Mon, 20 Aug 2012 22:34:58 +0200 Subject: [PATCH 4/4] several fixes and 3 new tests for Histograms of Oriented Gradients --- CONTRIBUTORS.txt | 3 +++ skimage/feature/_hog.py | 2 +- skimage/feature/tests/test_hog.py | 4 ---- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/CONTRIBUTORS.txt b/CONTRIBUTORS.txt index 5961407d..735bffa9 100644 --- a/CONTRIBUTORS.txt +++ b/CONTRIBUTORS.txt @@ -108,3 +108,6 @@ Adaptive thresholding Implementation of Matlab's `regionprops` Estimation of geometric transformation parameters + +- Pavel Campr + Fixes and tests for Histograms of Oriented Gradients. diff --git a/skimage/feature/_hog.py b/skimage/feature/_hog.py index 3bd849ab..ce7db462 100644 --- a/skimage/feature/_hog.py +++ b/skimage/feature/_hog.py @@ -181,4 +181,4 @@ def hog(image, orientations=9, pixels_per_cell=(8, 8), if visualise: return normalised_blocks.ravel(), hog_image else: - return normalised_blocks.ravel() \ No newline at end of file + return normalised_blocks.ravel() diff --git a/skimage/feature/tests/test_hog.py b/skimage/feature/tests/test_hog.py index 8cf45fb8..c9449028 100644 --- a/skimage/feature/tests/test_hog.py +++ b/skimage/feature/tests/test_hog.py @@ -32,8 +32,6 @@ def test_hog_basic_orientations_and_data_types(): # 4) verify that all results are equal where expected # 5) verify that computed feature vector is as expected # 6) repeat the scenario for 90, 180 and 270 degrees rotated images - # - # author: Pavel Campr # size of testing image width = height = 35 @@ -99,8 +97,6 @@ def test_hog_orientations_circle(): # 3) verify that the resulting feature vector contains uniformly distributed values for all orientations, # i.e. no orientation is lost or emphasized # 4) repeat the scenario for other 'orientations' option - # - # author: Pavel Campr # size of testing image width = height = 100