mirror of
https://github.com/TheAlgorithms/Python.git
synced 2024-11-23 21:11:08 +00:00
add hough transform
This commit is contained in:
parent
03a42510b0
commit
48bba17c9b
175
computer_vision/hough_transform.py
Normal file
175
computer_vision/hough_transform.py
Normal file
|
@ -0,0 +1,175 @@
|
|||
"""
|
||||
The Hough transform can be used to detect lines, circles or
|
||||
other parametric curves. It works by transforming
|
||||
the image edge map (obtained using a Sobel Filter) to Polar coordinates
|
||||
and then selecting local maxima in the Parametric space as lines based on
|
||||
majority voting.
|
||||
|
||||
References:
|
||||
https://en.wikipedia.org/wiki/Hough_transform
|
||||
https://www.cs.cmu.edu/~16385/s17/Slides/5.3_Hough_Transform.pdf
|
||||
https://www.uio.no/studier/emner/matnat/ifi/INF4300/h09/undervisningsmateriale/hough09.pdf
|
||||
|
||||
Requirements (pip):
|
||||
- matplotlib
|
||||
- cv2
|
||||
"""
|
||||
|
||||
import cv2
|
||||
import matplotlib.pyplot as plt
|
||||
import numpy as np
|
||||
|
||||
from digital_image_processing.edge_detection import canny
|
||||
|
||||
|
||||
def generate_accumulator(edges: np.ndarray) -> np.ndarray:
|
||||
"""
|
||||
- Generates an accumulator by transforming edge coordinates from Cartesian
|
||||
to polar coordinates (Hough space).
|
||||
|
||||
- The accumulator array can be indexed as `accumulator[p][theta]`
|
||||
|
||||
Params:
|
||||
------
|
||||
edges (np.ndarray): The edge-detected binary image (single-channel).
|
||||
|
||||
Returns:
|
||||
------
|
||||
np.ndarray: The accumulator array with votes for line candidates.
|
||||
|
||||
Example:
|
||||
------
|
||||
>>> img = np.array([[1, 0, 0,], [1, 0, 0,], [1, 0, 0,],])
|
||||
>>> np.sum(generate_accumulator(img))
|
||||
np.float64(540.0)
|
||||
"""
|
||||
n, m = edges.shape
|
||||
theta_min, theta_max = 0, 180
|
||||
p_min, p_max = 0, int(n * np.sqrt(2) + 1)
|
||||
accumulator = np.zeros((int(theta_max - theta_min), int(p_max - p_min)))
|
||||
for x in range(n):
|
||||
for y in range(m):
|
||||
if edges[x][y]:
|
||||
for theta in range(theta_min, theta_max):
|
||||
p = int(
|
||||
x * np.cos(np.deg2rad(theta)) + y * np.sin(np.deg2rad(theta))
|
||||
)
|
||||
accumulator[theta][p] += 1
|
||||
return accumulator
|
||||
|
||||
|
||||
def hough_transform(
|
||||
img: np.ndarray, threshold: int = 30, max_num_lines: int = 5
|
||||
) -> list[tuple[int, int, np.float64]]:
|
||||
"""
|
||||
Performs the Hough transform to detect lines in the input image.
|
||||
|
||||
Params:
|
||||
------
|
||||
img (np.ndarray): Single-channel grayscale image.
|
||||
threshold (int): Minimum vote count in the accumulator to consider a line.
|
||||
max_num_lines (int): Maximum number of lines to return.
|
||||
|
||||
Returns:
|
||||
------
|
||||
list[tuple[int, int, int]]: List of detected lines in (theta, p, votes) format.
|
||||
|
||||
Raises:
|
||||
------
|
||||
AssertionError: If the image is not square or single-channel.
|
||||
|
||||
Example:
|
||||
------
|
||||
>>> img = np.vstack([np.zeros((30, 50)),np.ones((1, 50)),np.zeros((19, 50))])
|
||||
>>> hough_transform(img, 30, 1)
|
||||
[(0, 28, np.float64(48.0))]
|
||||
"""
|
||||
assert img.shape[0] == img.shape[1], "image must have equal dimensions"
|
||||
assert len(img.shape) == 2, "image should be single-channel"
|
||||
|
||||
# Obtain edge map for image
|
||||
edges = canny.canny(img)
|
||||
|
||||
# Transform to Polar Coordinates
|
||||
n, _ = img.shape
|
||||
theta_min, theta_max = 0, 180
|
||||
p_min, p_max = 0, int(n * np.sqrt(2) + 1)
|
||||
accumulator = generate_accumulator(edges)
|
||||
|
||||
# Select maxima in Polar space
|
||||
res = []
|
||||
for theta in range(theta_min, theta_max):
|
||||
for p in range(p_min, p_max):
|
||||
if accumulator[theta][p] > threshold:
|
||||
res.append((theta, p, accumulator[theta][p]))
|
||||
|
||||
res = sorted(res, key=lambda x: x[2], reverse=True)[:max_num_lines]
|
||||
return res
|
||||
|
||||
|
||||
def draw_hough_lines(
|
||||
img: np.ndarray,
|
||||
lines: list[tuple],
|
||||
thickness: int = 1,
|
||||
color: tuple[int, int, int] = (255, 0, 0),
|
||||
) -> None:
|
||||
"""
|
||||
Draws detected Hough lines on the image.
|
||||
|
||||
Params:
|
||||
------
|
||||
img (np.ndarray): The input image to draw lines on.
|
||||
lines (list[tuple[int, int, int]]):
|
||||
List of (theta, p, votes) for detected lines.
|
||||
thickness (int): Line thickness.
|
||||
color (tuple[int, int, int]): BGR color of the lines.
|
||||
|
||||
Example:
|
||||
------
|
||||
>>> draw_hough_lines(create_dummy_img(), [(50, 0),])
|
||||
"""
|
||||
for line in lines:
|
||||
theta, p = line[0], line[1]
|
||||
a = np.sin(np.deg2rad(theta))
|
||||
b = np.cos(np.deg2rad(theta))
|
||||
x0, y0 = a * p, b * p
|
||||
x1, y1 = int(x0 + 100 * (-b)), int(y0 + 100 * (a))
|
||||
x2, y2 = int(x0 - 100 * (-b)), int(y0 - 100 * (a))
|
||||
cv2.line(img, (x1, y1), (x2, y2), color, thickness)
|
||||
|
||||
|
||||
def create_dummy_img(height: int = 50, width: int = 50) -> np.ndarray:
|
||||
"""
|
||||
Test function to create dummy 3-channel image of specified width and height
|
||||
Example:
|
||||
------
|
||||
>>> create_dummy_img(100, 120).shape
|
||||
(100, 120, 3)
|
||||
"""
|
||||
img = np.zeros((height, width), dtype=np.uint8)
|
||||
cv2.line(img, (10, 10), (int(0.6 * height), int(0.8 * width)), 255, 1) # type: ignore[call-overload]
|
||||
img = cv2.cvtColor(img, cv2.COLOR_GRAY2BGR) # type: ignore[assignment]
|
||||
return img
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import doctest
|
||||
|
||||
# Run doctests
|
||||
doctest.testmod()
|
||||
|
||||
img = create_dummy_img(60, 80)
|
||||
# Preprocess Image
|
||||
img = cv2.resize(img, (64, 64), interpolation=cv2.INTER_AREA)
|
||||
gray_image = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
|
||||
plt.imshow(img)
|
||||
plt.show()
|
||||
# Accumulator
|
||||
accumulator = generate_accumulator(canny.canny(gray_image))
|
||||
plt.imshow(accumulator)
|
||||
plt.show()
|
||||
# Hough Transform
|
||||
res = hough_transform(gray_image, 30, 1)
|
||||
draw_hough_lines(img, res)
|
||||
plt.imshow(img)
|
||||
plt.show()
|
Loading…
Reference in New Issue
Block a user