-
-
Notifications
You must be signed in to change notification settings - Fork 46.1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
03a4251
commit 48bba17
Showing
1 changed file
with
175 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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() |