From 945ede743d0d53c947e861fb155e0da4a0dd5473 Mon Sep 17 00:00:00 2001 From: Brian Holt Date: Sat, 17 Sep 2011 18:35:03 +0100 Subject: [PATCH] pep8 + addressing some of Stefan's comments --- CONTRIBUTORS.txt | 2 + scikits/image/feature/__init__.py | 3 +- scikits/image/feature/hog.py | 231 ++++++++++++------------ scikits/image/feature/tests/test_hog.py | 24 +-- 4 files changed, 129 insertions(+), 131 deletions(-) diff --git a/CONTRIBUTORS.txt b/CONTRIBUTORS.txt index 5feb5777..3365b0df 100644 --- a/CONTRIBUTORS.txt +++ b/CONTRIBUTORS.txt @@ -68,3 +68,5 @@ - Andreas Mueller Example data set loader. +- Brian Holt + Histograms of Oriented Gradients diff --git a/scikits/image/feature/__init__.py b/scikits/image/feature/__init__.py index 47e14c9d..eb6faa62 100644 --- a/scikits/image/feature/__init__.py +++ b/scikits/image/feature/__init__.py @@ -1,2 +1 @@ - -from hog import histogram_of_oriented_gradients \ No newline at end of file +from hog import hog \ No newline at end of file diff --git a/scikits/image/feature/hog.py b/scikits/image/feature/hog.py index 7cfc0fa1..8d9dc4f7 100644 --- a/scikits/image/feature/hog.py +++ b/scikits/image/feature/hog.py @@ -1,24 +1,22 @@ -"""Extract Histogram of Oriented Gradients feature from image.""" - -# Authors: Brian Holt -# -# License: BSD +""" +:author: Brian Holt, 2011 +:license: modified BSD +""" import numpy as np from scipy import sqrt, pi, arctan, cos, sin -def histogram_of_oriented_gradients(image, n_orientations=9, ppc=(8,8), - cpb=(3,3), visualise=False, - apply_normalisation=False): - """ Histogram of oriented gradients (HOG) for a given image. +def hog(image, n_orientations=9, pixels_per_cell=(8, 8), + cells_per_block=(3, 3), visualise=False, normalise=False): + """ Extract Histogram of Oriented Gradients (HOG) for a given image. Compute a Histogram of Oriented Gradients (HOG) by 1) (optional) global image normalisation 2) computing the gradient image in x and y 3) computing gradient histograms 3) normalise across blocks - 4) flatten into a feature vector + 4) flatten into a feature vector Parameters ---------- @@ -28,86 +26,91 @@ def histogram_of_oriented_gradients(image, n_orientations=9, ppc=(8,8), n_orientations : int number of orientation bins - ppc : 2 tuple (int,int) + pixels_per_cell : 2 tuple (int,int) pixels per cell, size in pixels of a cell - cpb : 2 tuple (int,int) + cells_per_block : 2 tuple (int,int) cells per block, number of cells in each block - visualise : bool + visualise : bool, optional return an image of the HOG - apply_normalisation : bool - apply the initial optional global normalisation - + normalise : bool, optional + apply power law compression to normalise the image before + processing + Returns ------- - newarr : ndarray + newarr : ndarray HOG for the image as a 1D (flattened) array. + hog_image : PIL Image (if visualise=True) + A visualisation of the HOG image + References ---------- - * http://en.wikipedia.org/wiki/Histogram_of_oriented_gradients - - * Dalal, N and Triggs, B, Histograms of Oriented Gradients for Human Detection, - IEEE Computer Society Conference on Computer Vision and Pattern Recognition 2005 - San Diego, CA, USA + * http://en.wikipedia.org/wiki/Histogram_of_oriented_gradients + + * Dalal, N and Triggs, B, Histograms of Oriented Gradients for + Human Detection, IEEE Computer Society Conference on Computer + Vision and Pattern Recognition 2005 San Diego, CA, USA """ - - image = np.atleast_2d(image) - + + image = np.atleast_2d(image) + """ - The first stage applies an optional global image normalisation - equalisation that is designed to reduce the influence of illumination - effects. In practice we use gamma (power law) compression, either + The first stage applies an optional global image normalisation + equalisation that is designed to reduce the influence of illumination + effects. In practice we use gamma (power law) compression, either computing the square root or the log of each colour channel. - Image texture strength is typically proportional to the local surface - illumination so this compression helps to reduce the effects of local + Image texture strength is typically proportional to the local surface + illumination so this compression helps to reduce the effects of local shadowing and illumination variations. """ - - if apply_normalisation: - image = sqrt(image) - - """ - The second stage computes first order image gradients. These capture - contour, silhouette and some texture information, while providing - further resistance to illumination variations. The locally dominant - colour channel is used, which provides colour invariance to a large extent. - Variant methods may also include second order image derivatives, which - act as primitive bar detectors - a useful feature for capturing, - e.g. bar like structures in bicycles and limbs in humans - """ - + if image.ndim == 3: # replace RGB with locally dominant colour channel - pass # TODO + pass # TODO + if normalise: + image = sqrt(image) + + """ + The second stage computes first order image gradients. These capture + contour, silhouette and some texture information, while providing + further resistance to illumination variations. The locally dominant + colour channel is used, which provides colour invariance to a large + extent. Variant methods may also include second order image derivatives, + which act as primitive bar detectors - a useful feature for capturing, + e.g. bar like structures in bicycles and limbs in humans. + """ + gx = np.zeros(image.shape) - gy = np.zeros(image.shape) + gy = np.zeros(image.shape) gx[:-1, :-1] = image[:-1,:-1]-image[:-1,1:] gy[:-1, :-1] = image[:-1,:-1]-image[1:,:-1] + #gx[:-1, :-1] = np.diff(image, n=1, axis=0) + #gy[:-1, :-1] = np.diff(image, n=1, axis=1) #import Image #Image.fromarray(gx).show() - #Image.fromarray(gy).show() - + #Image.fromarray(gy).show() """ - The third stage aims to produce an encoding that is sensitive to - local image content while remaining resistant to small changes in pose - or appearance. The adopted method pools gradient orientation information - locally in the same way as the SIFT [Lowe 2004] feature. The image window + The third stage aims to produce an encoding that is sensitive to + local image content while remaining resistant to small changes in pose + or appearance. The adopted method pools gradient orientation information + locally in the same way as the SIFT [Lowe 2004] feature. The image window is divided into small spatial regions, called "cells". For each cell we - accumulate a local 1-D histogram of gradient or edge orientations over - all the pixels in the cell. This combined cell-level 1-D histogram - forms the basic "orientation histogram" representation. Each orientation - histogram divides the gradient angle range into a fixed number of - predetermined bins. The gradient magnitudes of the pixels in the cell - are used to vote into the orientation histogram. + accumulate a local 1-D histogram of gradient or edge orientations over + all the pixels in the cell. This combined cell-level 1-D histogram + forms the basic "orientation histogram" representation. Each orientation + histogram divides the gradient angle range into a fixed number of + predetermined bins. The gradient magnitudes of the pixels in the cell + are used to vote into the orientation histogram. """ - - magnitude = sqrt(gx**2+gy**2) - orientation = arctan(gy/(gx+1e-15))*(180/pi)+90 + + magnitude = sqrt(gx ** 2 + gy ** 2) + orientation = arctan(gy / (gx + 1e-15)) * (180 / pi) + 90 # compute n_orientations integral images integral_images = [] @@ -115,85 +118,89 @@ def histogram_of_oriented_gradients(image, n_orientations=9, ppc=(8,8), #create new integral image for this orientation # isolate orientations in this range - temp_ori = np.where(orientation < 180/n_orientations*(i+1), + temp_ori = np.where(orientation < 180 / n_orientations * (i + 1), orientation, 0) - temp_ori = np.where(orientation >= 180/n_orientations*i, + temp_ori = np.where(orientation >= 180 / n_orientations * i, temp_ori, 0) # select magnitudes for those orientations cond2 = temp_ori > 0 - temp_mag = np.where(cond2, magnitude, 0) + temp_mag = np.where(cond2, magnitude, 0) #compute integral image integral = np.cumsum(np.cumsum(temp_mag, axis=0, dtype=float), - axis=1, dtype=float) + axis=1, dtype=float) integral_images.append(integral) - - sx,sy = image.shape - cx, cy = ppc - bx, by = cpb - - n_cellsx = int(np.floor(sx//cx)) # number of cells in x - n_cellsy = int(np.floor(sy//cy)) # number of cells in y - + + sx, sy = image.shape + cx, cy = pixels_per_cell + bx, by = cells_per_block + + n_cellsx = int(np.floor(sx // cx)) # number of cells in x + n_cellsy = int(np.floor(sy // cy)) # number of cells in y + # now for each cell, compute the histogram orientation_histogram = np.zeros((n_cellsx, n_cellsy, n_orientations)) - + radius = min(cx, cy) // 2 - 1 hog_image = None if visualise: - import Image, ImageDraw - hog_image = Image.new("F", (sy,sx)) + import Image + import ImageDraw + hog_image = Image.new("F", (sy, sx)) draw = ImageDraw.Draw(hog_image) - + for x in range(0, n_cellsx): for y in range(0, n_cellsy): for o in range(0, n_orientations): # compute the histogram from integral image #print x, y, o - A = integral_images[o][x*cx, y*cy] - B = integral_images[o][(x+1)*cx-1, y*cy] - C = integral_images[o][(x+1)*cx-1, (y+1)*cy-1] - D = integral_images[o][x*cx, (y+1)*cy-1] + A = integral_images[o][x * cx, y * cy] + B = integral_images[o][(x + 1) * cx - 1, y * cy] + C = integral_images[o][(x + 1) * cx - 1, (y + 1) * cy - 1] + D = integral_images[o][x * cx, (y + 1) * cy - 1] orientation_histogram[x, y, o] = A + C - D - B - - if visualise: - centre = tuple([y*cy + cy//2 , x*cx + cx//2]) - dx = radius*cos(1.0*o/n_orientations*np.pi) - dy = radius*sin(1.0*o/n_orientations*np.pi) - draw.line([(centre[0]-dx, centre[1]-dy), - (centre[0]+dx, centre[1]+dy)], + + if visualise: + centre = tuple([y * cy + cy // 2, x * cx + cx // 2]) + dx = radius * cos(float(o) / n_orientations * np.pi) + dy = radius * sin(float(o) / n_orientations * np.pi) + draw.line([(centre[0] - dx, centre[1] - dy), + (centre[0] + dx, centre[1] + dy)], fill=orientation_histogram[x, y, o]) - + """ - The fourth stage computes normalisation, which takes local groups of - cells and contrast normalises their overall responses before passing - to next stage. Normalisation introduces better invariance to illumination, - shadowing, and edge contrast. It is performed by accumulating a measure - of local histogram "energy" over local groups of cells that we call - "blocks". The result is used to normalise each cell in the block. - Typically each individual cell is shared between several blocks, but - its normalisations are block dependent and thus different. The cell - thus appears several times in the final output vector with different - normalisations. This may seem redundant but it improves the performance. - We refer to the normalised block descriptors as Histogram of Oriented + The fourth stage computes normalisation, which takes local groups of + cells and contrast normalises their overall responses before passing + to next stage. Normalisation introduces better invariance to illumination, + shadowing, and edge contrast. It is performed by accumulating a measure + of local histogram "energy" over local groups of cells that we call + "blocks". The result is used to normalise each cell in the block. + Typically each individual cell is shared between several blocks, but + its normalisations are block dependent and thus different. The cell + thus appears several times in the final output vector with different + normalisations. This may seem redundant but it improves the performance. + We refer to the normalised block descriptors as Histogram of Oriented Gradient (HOG) descriptors. - """ - + """ + n_blocksx = (n_cellsx - bx) + 1 - n_blocksy = (n_cellsy - by) + 1 - normalised_blocks = np.zeros((n_blocksx, n_blocksy, + n_blocksy = (n_cellsy - by) + 1 + normalised_blocks = np.zeros((n_blocksx, n_blocksy, bx, by, n_orientations)) - + for x in range(0, n_blocksx): for y in range(0, n_blocksy): - block = orientation_histogram[x:x+bx, y:y+by, :] + block = orientation_histogram[x:x + bx, y:y + by, :] eps = 1e-5 - normalised_blocks[x, y, :] = block / sqrt(block.sum()**2 + eps) - + normalised_blocks[x, y, :] = block / sqrt(block.sum() ** 2 + eps) + """ - The final step collects the HOG descriptors from all blocks of a dense - overlapping grid of blocks covering the detection window into a combined + The final step collects the HOG descriptors from all blocks of a dense + overlapping grid of blocks covering the detection window into a combined feature vector for use in the window classifier """ - - return normalised_blocks.ravel(), hog_image + + if visualise: + return normalised_blocks.ravel(), hog_image + else: + return normalised_blocks.ravel() diff --git a/scikits/image/feature/tests/test_hog.py b/scikits/image/feature/tests/test_hog.py index ba3de467..b37e0e98 100644 --- a/scikits/image/feature/tests/test_hog.py +++ b/scikits/image/feature/tests/test_hog.py @@ -1,25 +1,15 @@ -# Authors: Brian Holt -# -# License: BSD - import numpy as np -import scipy as sp -from scipy import ndimage +import scipy -from numpy.testing import assert_raises - -from scikits.image.feature import histogram_of_oriented_gradients +from scikits.image.feature import hog def test_histogram_of_oriented_gradients(): - img = sp.lena().astype(np.int8) + img = scipy.lena().astype(np.int8) - fd, hog_image = histogram_of_oriented_gradients(img, - n_orientations=9, - ppc=(8,8), - cpb=(1,1), - visualise=False) + fd = hog(img, n_orientations=9, pixels_per_cell=(8, 8), + cells_per_block=(1, 1)) assert len(fd) == 9 * (512//8) ** 2 if __name__ == '__main__': - import nose - nose.runmodule() + from numpy.testing import run_module_suite + run_module_suite()