mirror of
https://github.com/wassname/scikit-image.git
synced 2026-07-07 16:01:08 +08:00
Merge pull request #480 from sciunto/hl_review
Hough transoform: review
This commit is contained in:
@@ -2,13 +2,14 @@ import numpy as np
|
||||
import matplotlib.pyplot as plt
|
||||
|
||||
from skimage.transform import hough_line
|
||||
from skimage.draw import line
|
||||
|
||||
img = np.zeros((100, 150), dtype=bool)
|
||||
img[30, :] = 1
|
||||
img[:, 65] = 1
|
||||
img[35:45, 35:50] = 1
|
||||
for i in range(90):
|
||||
img[i, i] = 1
|
||||
rr, cc = line(60, 130, 80, 10)
|
||||
img[rr, cc] = 1
|
||||
img += np.random.random(img.shape) > 0.95
|
||||
|
||||
out, angles, d = hough_line(img)
|
||||
@@ -20,8 +21,8 @@ plt.title('Input image')
|
||||
|
||||
plt.subplot(1, 2, 2)
|
||||
plt.imshow(out, cmap=plt.cm.bone,
|
||||
extent=(np.rad2deg(angles[0]), np.rad2deg(angles[-1]),
|
||||
d[0], d[-1]))
|
||||
extent=(np.rad2deg(angles[-1]), np.rad2deg(angles[0]),
|
||||
d[-1], d[0]))
|
||||
plt.title('Hough transform')
|
||||
plt.xlabel('Angle (degree)')
|
||||
plt.ylabel('Distance (pixel)')
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
from ._hough_transform import (hough_circle,
|
||||
hough_line,
|
||||
probabilistic_hough_line)
|
||||
from .hough_transform import *
|
||||
from .radon_transform import *
|
||||
from .finite_radon_transform import *
|
||||
|
||||
@@ -20,9 +20,9 @@ cdef inline Py_ssize_t round(double r):
|
||||
return <Py_ssize_t>((r + 0.5) if (r > 0.0) else (r - 0.5))
|
||||
|
||||
|
||||
def _hough_circle(cnp.ndarray img,
|
||||
cnp.ndarray[ndim=1, dtype=cnp.intp_t] radius,
|
||||
char normalize=True):
|
||||
def hough_circle(cnp.ndarray img,
|
||||
cnp.ndarray[ndim=1, dtype=cnp.intp_t] radius,
|
||||
char normalize=True):
|
||||
"""Perform a circular Hough transform.
|
||||
|
||||
Parameters
|
||||
@@ -88,8 +88,52 @@ def _hough_circle(cnp.ndarray img,
|
||||
return acc
|
||||
|
||||
|
||||
def _hough(cnp.ndarray img, cnp.ndarray[ndim=1, dtype=cnp.double_t] theta=None):
|
||||
def hough_line(cnp.ndarray img, cnp.ndarray[ndim=1, dtype=cnp.double_t] theta=None):
|
||||
"""Perform a straight line Hough transform.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
img : (M, N) ndarray
|
||||
Input image with nonzero values representing edges.
|
||||
theta : 1D ndarray of double
|
||||
Angles at which to compute the transform, in radians.
|
||||
Defaults to -pi/2 .. pi/2
|
||||
|
||||
Returns
|
||||
-------
|
||||
H : 2-D ndarray of uint64
|
||||
Hough transform accumulator.
|
||||
theta : ndarray
|
||||
Angles at which the transform was computed, in radians.
|
||||
distances : ndarray
|
||||
Distance values.
|
||||
|
||||
Notes
|
||||
-----
|
||||
The origin is the top left corner of the original image.
|
||||
X and Y axis are horizontal and vertical edges respectively.
|
||||
The distance is the minimal algebraic distance from the origin
|
||||
to the detected line.
|
||||
|
||||
Examples
|
||||
--------
|
||||
Generate a test image:
|
||||
|
||||
>>> img = np.zeros((100, 150), dtype=bool)
|
||||
>>> img[30, :] = 1
|
||||
>>> img[:, 65] = 1
|
||||
>>> img[35:45, 35:50] = 1
|
||||
>>> for i in range(90):
|
||||
... img[i, i] = 1
|
||||
>>> img += np.random.random(img.shape) > 0.95
|
||||
|
||||
Apply the Hough transform:
|
||||
|
||||
>>> out, angles, d = hough_line(img)
|
||||
|
||||
.. plot:: hough_tf.py
|
||||
|
||||
"""
|
||||
if img.ndim != 2:
|
||||
raise ValueError('The input image must be 2D.')
|
||||
|
||||
@@ -98,7 +142,7 @@ def _hough(cnp.ndarray img, cnp.ndarray[ndim=1, dtype=cnp.double_t] theta=None):
|
||||
cdef cnp.ndarray[ndim=1, dtype=cnp.double_t] stheta
|
||||
|
||||
if theta is None:
|
||||
theta = np.linspace(PI_2, NEG_PI_2, 180)
|
||||
theta = np.linspace(NEG_PI_2, PI_2, 180)
|
||||
|
||||
ctheta = np.cos(theta)
|
||||
stheta = np.sin(theta)
|
||||
@@ -120,7 +164,7 @@ def _hough(cnp.ndarray img, cnp.ndarray[ndim=1, dtype=cnp.double_t] theta=None):
|
||||
|
||||
# finally, run the transform
|
||||
cdef Py_ssize_t nidxs, nthetas, i, j, x, y, accum_idx
|
||||
nidxs = y_idxs.shape[0] # x and y are the same shape
|
||||
nidxs = y_idxs.shape[0] # x and y are the same shape
|
||||
nthetas = theta.shape[0]
|
||||
for i in range(nidxs):
|
||||
x = x_idxs[i]
|
||||
@@ -131,10 +175,38 @@ def _hough(cnp.ndarray img, cnp.ndarray[ndim=1, dtype=cnp.double_t] theta=None):
|
||||
return accum, theta, bins
|
||||
|
||||
|
||||
def _probabilistic_hough(cnp.ndarray img, int value_threshold,
|
||||
int line_length, int line_gap,
|
||||
cnp.ndarray[ndim=1, dtype=cnp.double_t] theta=None):
|
||||
def probabilistic_hough_line(cnp.ndarray img, int threshold=10,
|
||||
int line_length=50, int line_gap=10,
|
||||
cnp.ndarray[ndim=1, dtype=cnp.double_t] theta=None):
|
||||
"""Return lines from a progressive probabilistic line Hough transform.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
img : (M, N) ndarray
|
||||
Input image with nonzero values representing edges.
|
||||
threshold : int, optional (default 10)
|
||||
Threshold
|
||||
line_length : int, optional (default 50)
|
||||
Minimum accepted length of detected lines.
|
||||
Increase the parameter to extract longer lines.
|
||||
line_gap : int, optional, (default 10)
|
||||
Maximum gap between pixels to still form a line.
|
||||
Increase the parameter to merge broken lines more aggresively.
|
||||
theta : 1D ndarray, dtype=double, optional, default (-pi/2 .. pi/2)
|
||||
Angles at which to compute the transform, in radians.
|
||||
|
||||
Returns
|
||||
-------
|
||||
lines : list
|
||||
List of lines identified, lines in format ((x0, y0), (x1, y0)), indicating
|
||||
line start and end.
|
||||
|
||||
References
|
||||
----------
|
||||
.. [1] C. Galamhos, J. Matas and J. Kittler, "Progressive probabilistic
|
||||
Hough transform for line detection", in IEEE Computer Society
|
||||
Conference on Computer Vision and Pattern Recognition, 1999.
|
||||
"""
|
||||
if img.ndim != 2:
|
||||
raise ValueError('The input image must be 2D.')
|
||||
|
||||
@@ -196,7 +268,7 @@ def _probabilistic_hough(cnp.ndarray img, int value_threshold,
|
||||
continue
|
||||
|
||||
value = 0
|
||||
max_value = value_threshold - 1
|
||||
max_value = threshold - 1
|
||||
max_theta = -1
|
||||
|
||||
# apply hough transform on point
|
||||
@@ -207,7 +279,7 @@ def _probabilistic_hough(cnp.ndarray img, int value_threshold,
|
||||
if value > max_value:
|
||||
max_value = value
|
||||
max_theta = j
|
||||
if max_value < value_threshold:
|
||||
if max_value < threshold:
|
||||
continue
|
||||
|
||||
# from the random point walk in opposite directions and find line
|
||||
|
||||
@@ -1,192 +1,49 @@
|
||||
__all__ = ['hough', 'hough_line', 'hough_circle', 'hough_peaks', 'probabilistic_hough']
|
||||
|
||||
from itertools import izip as zip
|
||||
__all__ = ['hough_line_peaks']
|
||||
|
||||
import numpy as np
|
||||
from scipy import ndimage
|
||||
from ._hough_transform import _probabilistic_hough
|
||||
from skimage import measure, morphology
|
||||
|
||||
|
||||
def _hough(img, theta=None):
|
||||
if img.ndim != 2:
|
||||
raise ValueError('The input image must be 2-D')
|
||||
|
||||
if theta is None:
|
||||
theta = np.linspace(-np.pi / 2, np.pi / 2, 180)
|
||||
|
||||
# compute the vertical bins (the distances)
|
||||
d = np.ceil(np.hypot(*img.shape))
|
||||
nr_bins = 2 * d
|
||||
bins = np.linspace(-d, d, nr_bins)
|
||||
|
||||
# allocate the output image
|
||||
out = np.zeros((nr_bins, len(theta)), dtype=np.uint64)
|
||||
|
||||
# precompute the sin and cos of the angles
|
||||
cos_theta = np.cos(theta)
|
||||
sin_theta = np.sin(theta)
|
||||
|
||||
# find the indices of the non-zero values in
|
||||
# the input image
|
||||
y, x = np.nonzero(img)
|
||||
|
||||
# x and y can be large, so we can't just broadcast to 2D
|
||||
# arrays as we may run out of memory. Instead we process
|
||||
# one vertical slice at a time.
|
||||
for i, (cT, sT) in enumerate(zip(cos_theta, sin_theta)):
|
||||
|
||||
# compute the base distances
|
||||
distances = x * cT + y * sT
|
||||
|
||||
# round the distances to the nearest integer
|
||||
# and shift them to a nonzero bin
|
||||
shifted = np.round(distances) - bins[0]
|
||||
|
||||
# cast the shifted values to ints to use as indices
|
||||
indices = shifted.astype(np.int)
|
||||
|
||||
# use bin count to accumulate the coefficients
|
||||
bincount = np.bincount(indices)
|
||||
|
||||
# finally assign the proper values to the out array
|
||||
out[:len(bincount), i] = bincount
|
||||
|
||||
return out, theta, bins
|
||||
|
||||
_py_hough = _hough
|
||||
|
||||
# try to import and use the faster Cython version if it exists
|
||||
try:
|
||||
from ._hough_transform import _hough
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
|
||||
def probabilistic_hough(img, threshold=10, line_length=50, line_gap=10,
|
||||
theta=None):
|
||||
"""Return lines from a progressive probabilistic line Hough transform.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
img : (M, N) ndarray
|
||||
Input image with nonzero values representing edges.
|
||||
threshold : int
|
||||
Threshold
|
||||
line_length : int, optional (default 50)
|
||||
Minimum accepted length of detected lines.
|
||||
Increase the parameter to extract longer lines.
|
||||
line_gap : int, optional, (default 10)
|
||||
Maximum gap between pixels to still form a line.
|
||||
Increase the parameter to merge broken lines more aggresively.
|
||||
theta : 1D ndarray, dtype=double, optional, default (-pi/2 .. pi/2)
|
||||
Angles at which to compute the transform, in radians.
|
||||
|
||||
Returns
|
||||
-------
|
||||
lines : list
|
||||
List of lines identified, lines in format ((x0, y0), (x1, y0)), indicating
|
||||
line start and end.
|
||||
|
||||
References
|
||||
----------
|
||||
.. [1] C. Galamhos, J. Matas and J. Kittler, "Progressive probabilistic
|
||||
Hough transform for line detection", in IEEE Computer Society
|
||||
Conference on Computer Vision and Pattern Recognition, 1999.
|
||||
"""
|
||||
return _probabilistic_hough(img, threshold, line_length, line_gap, theta)
|
||||
|
||||
from ._hough_transform import hough_line, probabilistic_hough_line
|
||||
from skimage._shared.utils import deprecated
|
||||
|
||||
|
||||
@deprecated('hough_line')
|
||||
def hough(img, theta=None):
|
||||
return hough_line(img, theta)
|
||||
|
||||
from ._hough_transform import _hough_circle
|
||||
|
||||
def hough_line(img, theta=None):
|
||||
"""Perform a straight line Hough transform.
|
||||
@deprecated('probabilistic_hough_line')
|
||||
def probabilistic_hough(img, threshold=10, line_length=50, line_gap=10,
|
||||
theta=None):
|
||||
return probabilistic_hough_line(img, threshold=threshold,
|
||||
line_length=line_length, line_gap=line_gap,
|
||||
theta=theta)
|
||||
|
||||
Parameters
|
||||
----------
|
||||
img : (M, N) ndarray
|
||||
Input image with nonzero values representing edges.
|
||||
theta : 1D ndarray of double
|
||||
Angles at which to compute the transform, in radians.
|
||||
Defaults to -pi/2 .. pi/2
|
||||
|
||||
Returns
|
||||
-------
|
||||
H : 2-D ndarray of uint64
|
||||
Hough transform accumulator.
|
||||
theta : ndarray
|
||||
Angles at which the transform was computed.
|
||||
distances : ndarray
|
||||
Distance values.
|
||||
|
||||
Notes
|
||||
-----
|
||||
The origin is the top left corner of the original image.
|
||||
The angle is counted clockwise from 9 o'clock.
|
||||
The distance is the minimal algebraic distance from this origin to the line.
|
||||
|
||||
Examples
|
||||
--------
|
||||
Generate a test image:
|
||||
|
||||
>>> img = np.zeros((100, 150), dtype=bool)
|
||||
>>> img[30, :] = 1
|
||||
>>> img[:, 65] = 1
|
||||
>>> img[35:45, 35:50] = 1
|
||||
>>> for i in range(90):
|
||||
... img[i, i] = 1
|
||||
>>> img += np.random.random(img.shape) > 0.95
|
||||
|
||||
Apply the Hough transform:
|
||||
|
||||
>>> out, angles, d = hough_line(img)
|
||||
|
||||
.. plot:: hough_tf.py
|
||||
|
||||
"""
|
||||
return _hough(img, theta)
|
||||
|
||||
def hough_circle(img, radius, normalize=True):
|
||||
"""Perform a circular Hough transform.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
img : (M, N) ndarray
|
||||
Input image with nonzero values representing edges.
|
||||
radius : ndarray
|
||||
Radii at which to compute the Hough transform.
|
||||
normalize : boolean, optional
|
||||
Normalize the accumulator with the number
|
||||
of pixels used to draw the radius
|
||||
|
||||
Returns
|
||||
-------
|
||||
H : 3D ndarray (radius index, (M, N) ndarray)
|
||||
Hough transform accumulator for each radius
|
||||
|
||||
"""
|
||||
return _hough_circle(img, radius.astype(np.intp), normalize)
|
||||
|
||||
@deprecated('hough_line_peaks')
|
||||
def hough_peaks(hspace, angles, dists, min_distance=10, min_angle=10,
|
||||
threshold=None, num_peaks=np.inf):
|
||||
return hough_line_peaks(hspace, angles, dists, min_distance, min_angle,
|
||||
threshold, num_peaks)
|
||||
|
||||
|
||||
def hough_line_peaks(hspace, angles, dists, min_distance=9, min_angle=10,
|
||||
threshold=None, num_peaks=np.inf):
|
||||
"""Return peaks in hough transform.
|
||||
|
||||
Identifies most prominent lines separated by a certain angle and distance in
|
||||
a hough transform. Non-maximum suppression with different sizes is applied
|
||||
separately in the first (distances) and second (angles) dimension of the
|
||||
hough space to identify peaks.
|
||||
Identifies most prominent lines separated by a certain angle and distance
|
||||
in a hough transform. Non-maximum suppression with different sizes is
|
||||
applied separately in the first (distances) and second (angles) dimension
|
||||
of the hough space to identify peaks.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
hspace : (N, M) array
|
||||
Hough space returned by the `hough_line` function.
|
||||
angles : (M,) array
|
||||
Angles returned by the `hough_line` function. Assumed to be continuous
|
||||
Angles returned by the `hough_line` function. Assumed to be continuous.
|
||||
(`angles[-1] - angles[0] == PI`).
|
||||
dists : (N, ) array
|
||||
Distances returned by the `hough_line` function.
|
||||
|
||||
@@ -2,9 +2,7 @@ import numpy as np
|
||||
from numpy.testing import *
|
||||
|
||||
import skimage.transform as tf
|
||||
import skimage.transform.hough_transform as ht
|
||||
from skimage.transform import probabilistic_hough
|
||||
from skimage.draw import circle_perimeter
|
||||
from skimage.draw import circle_perimeter, line
|
||||
|
||||
|
||||
def append_desc(func, description):
|
||||
@@ -16,11 +14,11 @@ def append_desc(func, description):
|
||||
return func
|
||||
|
||||
|
||||
def test_hough():
|
||||
def test_hough_line():
|
||||
# Generate a test image
|
||||
img = np.zeros((100, 100), dtype=int)
|
||||
for i in range(25, 75):
|
||||
img[100 - i, i] = 1
|
||||
img = np.zeros((100, 150), dtype=int)
|
||||
rr, cc = line(60, 130, 80, 10)
|
||||
img[rr, cc] = 1
|
||||
|
||||
out, angles, d = tf.hough_line(img)
|
||||
|
||||
@@ -28,11 +26,11 @@ def test_hough():
|
||||
dist = d[y[0]]
|
||||
theta = angles[x[0]]
|
||||
|
||||
assert_equal(dist > 70, dist < 72)
|
||||
assert_equal(theta > 0.78, theta < 0.79)
|
||||
assert_almost_equal(dist, 80.723, 1)
|
||||
assert_almost_equal(theta, 1.41, 1)
|
||||
|
||||
|
||||
def test_hough_angles():
|
||||
def test_hough_line_angles():
|
||||
img = np.zeros((10, 10))
|
||||
img[0, 0] = 1
|
||||
|
||||
@@ -41,15 +39,6 @@ def test_hough_angles():
|
||||
assert_equal(len(angles), 10)
|
||||
|
||||
|
||||
def test_py_hough():
|
||||
ht._hough, fast_hough = ht._py_hough, ht._hough
|
||||
|
||||
yield append_desc(test_hough, '_python')
|
||||
yield append_desc(test_hough_angles, '_python')
|
||||
|
||||
tf._hough = fast_hough
|
||||
|
||||
|
||||
def test_probabilistic_hough():
|
||||
# Generate a test image
|
||||
img = np.zeros((100, 100), dtype=int)
|
||||
@@ -59,8 +48,8 @@ def test_probabilistic_hough():
|
||||
# decrease default theta sampling because similar orientations may confuse
|
||||
# as mentioned in article of Galambos et al
|
||||
theta = np.linspace(0, np.pi, 45)
|
||||
lines = probabilistic_hough(img, theta=theta, threshold=10, line_length=10,
|
||||
line_gap=1)
|
||||
lines = tf.probabilistic_hough_line(img, threshold=10, line_length=10,
|
||||
line_gap=1, theta=theta)
|
||||
# sort the lines according to the x-axis
|
||||
sorted_lines = []
|
||||
for line in lines:
|
||||
@@ -71,45 +60,59 @@ def test_probabilistic_hough():
|
||||
assert([(25, 25), (74, 74)] in sorted_lines)
|
||||
|
||||
|
||||
def test_hough_peaks_dist():
|
||||
def test_hough_line_peaks():
|
||||
img = np.zeros((100, 150), dtype=int)
|
||||
rr, cc = line(60, 130, 80, 10)
|
||||
img[rr, cc] = 1
|
||||
|
||||
out, angles, d = tf.hough_line(img)
|
||||
|
||||
out, theta, dist = tf.hough_line_peaks(out, angles, d)
|
||||
|
||||
assert_equal(len(dist), 1)
|
||||
assert_almost_equal(dist[0], 80.723, 1)
|
||||
assert_almost_equal(theta[0], 1.41, 1)
|
||||
|
||||
|
||||
def test_hough_line_peaks_dist():
|
||||
img = np.zeros((100, 100), dtype=np.bool_)
|
||||
img[:, 30] = True
|
||||
img[:, 40] = True
|
||||
hspace, angles, dists = tf.hough_line(img)
|
||||
assert len(tf.hough_peaks(hspace, angles, dists, min_distance=5)[0]) == 2
|
||||
assert len(tf.hough_peaks(hspace, angles, dists, min_distance=15)[0]) == 1
|
||||
assert len(tf.hough_line_peaks(hspace, angles, dists, min_distance=5)[0]) == 2
|
||||
assert len(tf.hough_line_peaks(hspace, angles, dists, min_distance=15)[0]) == 1
|
||||
|
||||
|
||||
def test_hough_peaks_angle():
|
||||
def test_hough_line_peaks_angle():
|
||||
img = np.zeros((100, 100), dtype=np.bool_)
|
||||
img[:, 0] = True
|
||||
img[0, :] = True
|
||||
|
||||
hspace, angles, dists = tf.hough_line(img)
|
||||
assert len(tf.hough_peaks(hspace, angles, dists, min_angle=45)[0]) == 2
|
||||
assert len(tf.hough_peaks(hspace, angles, dists, min_angle=90)[0]) == 1
|
||||
assert len(tf.hough_line_peaks(hspace, angles, dists, min_angle=45)[0]) == 2
|
||||
assert len(tf.hough_line_peaks(hspace, angles, dists, min_angle=90)[0]) == 1
|
||||
|
||||
theta = np.linspace(0, np.pi, 100)
|
||||
hspace, angles, dists = tf.hough_line(img, theta)
|
||||
assert len(tf.hough_peaks(hspace, angles, dists, min_angle=45)[0]) == 2
|
||||
assert len(tf.hough_peaks(hspace, angles, dists, min_angle=90)[0]) == 1
|
||||
assert len(tf.hough_line_peaks(hspace, angles, dists, min_angle=45)[0]) == 2
|
||||
assert len(tf.hough_line_peaks(hspace, angles, dists, min_angle=90)[0]) == 1
|
||||
|
||||
theta = np.linspace(np.pi / 3, 4. / 3 * np.pi, 100)
|
||||
hspace, angles, dists = tf.hough_line(img, theta)
|
||||
assert len(tf.hough_peaks(hspace, angles, dists, min_angle=45)[0]) == 2
|
||||
assert len(tf.hough_peaks(hspace, angles, dists, min_angle=90)[0]) == 1
|
||||
assert len(tf.hough_line_peaks(hspace, angles, dists, min_angle=45)[0]) == 2
|
||||
assert len(tf.hough_line_peaks(hspace, angles, dists, min_angle=90)[0]) == 1
|
||||
|
||||
|
||||
def test_hough_peaks_num():
|
||||
def test_hough_line_peaks_num():
|
||||
img = np.zeros((100, 100), dtype=np.bool_)
|
||||
img[:, 30] = True
|
||||
img[:, 40] = True
|
||||
hspace, angles, dists = tf.hough_line(img)
|
||||
assert len(tf.hough_peaks(hspace, angles, dists, min_distance=0,
|
||||
min_angle=0, num_peaks=1)[0]) == 1
|
||||
assert len(tf.hough_line_peaks(hspace, angles, dists, min_distance=0,
|
||||
min_angle=0, num_peaks=1)[0]) == 1
|
||||
|
||||
|
||||
def test_houghcircle():
|
||||
def test_hough_circle():
|
||||
# Prepare picture
|
||||
img = np.zeros((120, 100), dtype=int)
|
||||
radius = 20
|
||||
|
||||
Reference in New Issue
Block a user