mirror of
https://github.com/wassname/scikit-image.git
synced 2026-06-30 05:41:30 +08:00
Merge pull request #1758 from JDWarner/hough_test
TST/STY: Hough transform regression test & Cython wrappers
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
from ._hough_transform import (hough_ellipse, hough_line,
|
||||
probabilistic_hough_line)
|
||||
from .hough_transform import hough_circle, hough_line_peaks
|
||||
from .hough_transform import (hough_line, hough_line_peaks,
|
||||
probabilistic_hough_line, hough_circle,
|
||||
hough_ellipse)
|
||||
from .radon_transform import radon, iradon, iradon_sart
|
||||
from .finite_radon_transform import frt2, ifrt2
|
||||
from .integral import integral_image, integrate
|
||||
|
||||
@@ -12,9 +12,6 @@ from libc.stdlib cimport rand
|
||||
|
||||
from ..draw import circle_perimeter
|
||||
|
||||
cdef double PI_2 = 1.5707963267948966
|
||||
cdef double NEG_PI_2 = -PI_2
|
||||
|
||||
from .._shared.interpolation cimport round
|
||||
|
||||
|
||||
@@ -278,16 +275,10 @@ def hough_line(cnp.ndarray img,
|
||||
.. plot:: hough_tf.py
|
||||
|
||||
"""
|
||||
if img.ndim != 2:
|
||||
raise ValueError('The input image must be 2D.')
|
||||
|
||||
# Compute the array of angles and their sine and cosine
|
||||
cdef cnp.ndarray[ndim=1, dtype=cnp.double_t] ctheta
|
||||
cdef cnp.ndarray[ndim=1, dtype=cnp.double_t] stheta
|
||||
|
||||
if theta is None:
|
||||
theta = np.linspace(NEG_PI_2, PI_2, 180)
|
||||
|
||||
ctheta = np.cos(theta)
|
||||
stheta = np.sin(theta)
|
||||
|
||||
@@ -354,12 +345,6 @@ def probabilistic_hough_line(cnp.ndarray img, int threshold=10,
|
||||
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.')
|
||||
|
||||
if theta is None:
|
||||
theta = PI_2 - np.arange(180) / 180.0 * 2 * PI_2
|
||||
|
||||
cdef Py_ssize_t height = img.shape[0]
|
||||
cdef Py_ssize_t width = img.shape[1]
|
||||
|
||||
|
||||
@@ -1,7 +1,67 @@
|
||||
import numpy as np
|
||||
from scipy import ndimage as ndi
|
||||
from .. import measure, morphology
|
||||
from ._hough_transform import _hough_circle
|
||||
from .. import measure
|
||||
from ._hough_transform import (_hough_circle,
|
||||
hough_ellipse as _hough_ellipse,
|
||||
hough_line as _hough_line,
|
||||
probabilistic_hough_line as _prob_hough_line)
|
||||
|
||||
|
||||
# Wrapper for Cython allows function signature introspection
|
||||
def hough_line(img, 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 `img` must be 2D.')
|
||||
|
||||
if theta is None:
|
||||
# These values are approximations of pi/2
|
||||
theta = np.linspace(-np.pi / 2, np.pi / 2, 180)
|
||||
|
||||
return _hough_line(img, theta=theta)
|
||||
|
||||
|
||||
def hough_line_peaks(hspace, angles, dists, min_distance=9, min_angle=10,
|
||||
@@ -73,7 +133,10 @@ def hough_line_peaks(hspace, angles, dists, min_distance=9, min_angle=10,
|
||||
|
||||
label_hspace = measure.label(hspace_t)
|
||||
props = measure.regionprops(label_hspace, hspace_max)
|
||||
props = sorted(props, key= lambda x: x.max_intensity)[::-1]
|
||||
|
||||
# Sort the list of peaks by intensity, not left-right, so larger peaks
|
||||
# in Hough space cannot be arbitrarily suppressed by smaller neighbors
|
||||
props = sorted(props, key=lambda x: x.max_intensity)[::-1]
|
||||
coords = np.array([np.round(p.centroid) for p in props], dtype=int)
|
||||
|
||||
hspace_peaks = []
|
||||
@@ -126,6 +189,48 @@ def hough_line_peaks(hspace, angles, dists, min_distance=9, min_angle=10,
|
||||
return hspace_peaks, angle_peaks, dist_peaks
|
||||
|
||||
|
||||
# Wrapper for Cython allows function signature introspection
|
||||
def probabilistic_hough_line(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, 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 `img` must be 2D.')
|
||||
|
||||
if theta is None:
|
||||
theta = np.pi / 2 - np.arange(180) / 180.0 * np.pi
|
||||
|
||||
return _prob_hough_line(img, threshold=threshold, line_length=line_length,
|
||||
line_gap=line_gap, theta=theta)
|
||||
|
||||
|
||||
def hough_circle(image, radius, normalize=True, full_output=False):
|
||||
"""Perform a circular Hough transform.
|
||||
|
||||
@@ -169,3 +274,56 @@ def hough_circle(image, radius, normalize=True, full_output=False):
|
||||
radius = np.atleast_1d(np.asarray(radius))
|
||||
return _hough_circle(image, radius.astype(np.intp),
|
||||
normalize=normalize, full_output=full_output)
|
||||
|
||||
|
||||
# Wrapper for Cython allows function signature introspection
|
||||
def hough_ellipse(img, threshold=4, accuracy=1, min_size=4, max_size=None):
|
||||
"""Perform an elliptical Hough transform.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
img : (M, N) ndarray
|
||||
Input image with nonzero values representing edges.
|
||||
threshold: int, optional (default 4)
|
||||
Accumulator threshold value.
|
||||
accuracy : double, optional (default 1)
|
||||
Bin size on the minor axis used in the accumulator.
|
||||
min_size : int, optional (default 4)
|
||||
Minimal major axis length.
|
||||
max_size : int, optional
|
||||
Maximal minor axis length. (default None)
|
||||
If None, the value is set to the half of the smaller
|
||||
image dimension.
|
||||
|
||||
Returns
|
||||
-------
|
||||
result : ndarray with fields [(accumulator, y0, x0, a, b, orientation)]
|
||||
Where ``(yc, xc)`` is the center, ``(a, b)`` the major and minor
|
||||
axes, respectively. The `orientation` value follows
|
||||
`skimage.draw.ellipse_perimeter` convention.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> from skimage.transform import hough_ellipse
|
||||
>>> from skimage.draw import ellipse_perimeter
|
||||
>>> img = np.zeros((25, 25), dtype=np.uint8)
|
||||
>>> rr, cc = ellipse_perimeter(10, 10, 6, 8)
|
||||
>>> img[cc, rr] = 1
|
||||
>>> result = hough_ellipse(img, threshold=8)
|
||||
>>> result.tolist()
|
||||
[(10, 10.0, 10.0, 8.0, 6.0, 0.0)]
|
||||
|
||||
Notes
|
||||
-----
|
||||
The accuracy must be chosen to produce a peak in the accumulator
|
||||
distribution. In other words, a flat accumulator distribution with low
|
||||
values may be caused by a too low bin size.
|
||||
|
||||
References
|
||||
----------
|
||||
.. [1] Xie, Yonghong, and Qiang Ji. "A new efficient ellipse detection
|
||||
method." Pattern Recognition, 2002. Proceedings. 16th International
|
||||
Conference on. Vol. 2. IEEE, 2002
|
||||
"""
|
||||
return _hough_ellipse(img, threshold=threshold, accuracy=accuracy,
|
||||
min_size=min_size, max_size=max_size)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import numpy as np
|
||||
from numpy.testing import assert_almost_equal, assert_equal
|
||||
from numpy.testing import assert_almost_equal, assert_equal, assert_raises
|
||||
|
||||
import skimage.transform as tf
|
||||
from skimage.draw import line, circle_perimeter, ellipse_perimeter
|
||||
@@ -7,15 +7,6 @@ from skimage._shared._warnings import expected_warnings
|
||||
from skimage._shared.testing import test_parallel
|
||||
|
||||
|
||||
def append_desc(func, description):
|
||||
"""Append the test function ``func`` and append
|
||||
``description`` to its name.
|
||||
"""
|
||||
func.description = func.__module__ + '.' + func.__name__ + description
|
||||
|
||||
return func
|
||||
|
||||
|
||||
@test_parallel()
|
||||
def test_hough_line():
|
||||
# Generate a test image
|
||||
@@ -42,12 +33,21 @@ def test_hough_line_angles():
|
||||
assert_equal(len(angles), 10)
|
||||
|
||||
|
||||
def test_hough_line_bad_input():
|
||||
img = np.zeros(100)
|
||||
img[10] = 1
|
||||
|
||||
# Expected error, img must be 2D
|
||||
assert_raises(ValueError, tf.hough_line, img, np.linspace(0, 360, 10))
|
||||
|
||||
|
||||
def test_probabilistic_hough():
|
||||
# Generate a test image
|
||||
img = np.zeros((100, 100), dtype=int)
|
||||
for i in range(25, 75):
|
||||
img[100 - i, i] = 100
|
||||
img[i, i] = 100
|
||||
|
||||
# decrease default theta sampling because similar orientations may confuse
|
||||
# as mentioned in article of Galambos et al
|
||||
theta = np.linspace(0, np.pi, 45)
|
||||
@@ -59,9 +59,21 @@ def test_probabilistic_hough():
|
||||
line = list(line)
|
||||
line.sort(key=lambda x: x[0])
|
||||
sorted_lines.append(line)
|
||||
|
||||
assert([(25, 75), (74, 26)] in sorted_lines)
|
||||
assert([(25, 25), (74, 74)] in sorted_lines)
|
||||
|
||||
# Execute with default theta
|
||||
tf.probabilistic_hough_line(img, line_length=10, line_gap=3)
|
||||
|
||||
|
||||
def test_probabilistic_hough_bad_input():
|
||||
img = np.zeros(100)
|
||||
img[10] = 1
|
||||
|
||||
# Expected error, img must be 2D
|
||||
assert_raises(ValueError, tf.probabilistic_hough_line, img)
|
||||
|
||||
|
||||
def test_hough_line_peaks():
|
||||
img = np.zeros((100, 150), dtype=int)
|
||||
@@ -78,6 +90,22 @@ def test_hough_line_peaks():
|
||||
assert_almost_equal(theta[0], 1.41, 1)
|
||||
|
||||
|
||||
def test_hough_line_peaks_ordered():
|
||||
# Regression test per PR #1421
|
||||
testim = np.zeros((256, 64), dtype=np.bool)
|
||||
|
||||
testim[50:100, 20] = True
|
||||
testim[85:200, 25] = True
|
||||
testim[15:35, 50] = True
|
||||
testim[1:-1, 58] = True
|
||||
|
||||
hough_space, angles, dists = tf.hough_line(testim)
|
||||
|
||||
with expected_warnings(['`background`']):
|
||||
hspace, _, _ = tf.hough_line_peaks(hough_space, angles, dists)
|
||||
assert hspace[0] > hspace[1]
|
||||
|
||||
|
||||
def test_hough_line_peaks_dist():
|
||||
img = np.zeros((100, 100), dtype=np.bool_)
|
||||
img[:, 30] = True
|
||||
|
||||
Reference in New Issue
Block a user