Process the output of cv2.HoughLines
There are a lot of articles that show how to detect lines using cv2.HoughLines. In this post I will present some functions that will help you process the lines.
Polar to Cartesian coordinate system
cv2.HoughLines returns lines in Polar coordinate system. Converting them to Cartesian coordinate system helps in calculating points on the line and point of intersection between two lines.
y = m * x + b … [1]
def polar2cartesian(rho: float, theta_rad: float, rotate90: bool = False):
"""
Converts line equation from polar to cartesian coordinates
Args:
rho: input line rho
theta_rad: input line theta
rotate90: output line perpendicular to the input line
Returns:
m: slope of the line
For horizontal line: m = 0
For vertical line: m = np.nan
b: intercept when x=0
"""
x = np.cos(theta_rad) * rho
y = np.sin(theta_rad) * rho
m = np.nan
if not np.isclose(x, 0.0):
m = y / x
if rotate90:
if m is np.nan:
m = 0.0
elif np.isclose(m, 0.0):
m = np.nan
else:
m = -1.0 / m
b = 0.0
if m is not np.nan:
b = y - m * x
return m, b
The Cartesian line equations can be used to find points on the edge of the image that lie on the line detected by cv2.HoughLines.
In equation [1], substituting x equal to 0 and the width of the image will get us the y coordinate. Similarly, setting y equal to 0 and the height of the image will get us x coordinates of the edge.
def line_end_points_on_image(rho: float, theta: float, image_shape: tuple):
"""
Returns end points of the line on the end of the image
Args:
rho: input line rho
theta: input line theta
image_shape: shape of the image
Returns:
list: [(x1, y1), (x2, y2)]
"""
m, b = polar2cartesian(rho, theta, True)
end_pts = []
if not np.isclose(m, 0.0):
x = int(0)
y = int(solve4y(x, m, b))
if point_on_image(x, y, image_shape):
end_pts.append((x, y))
x = int(image_shape[1] - 1)
y = int(solve4y(x, m, b))
if point_on_image(x, y, image_shape):
end_pts.append((x, y))
if m is not np.nan:
y = int(0)
x = int(solve4x(y, m, b))
if point_on_image(x, y, image_shape):
end_pts.append((x, y))
y = int(image_shape[0] - 1)
x = int(solve4x(y, m, b))
if point_on_image(x, y, image_shape):
end_pts.append((x, y))
return end_ptsdef solve4x(y: float, m: float, b: float):
"""
From y = m * x + b
x = (y - b) / m
"""
if np.isclose(m, 0.0):
return 0.0
if m is np.nan:
return b
return (y - b) / m
def solve4y(x: float, m: float, b: float):
"""
y = m * x + b
"""
if m is np.nan:
return b
return m * x + b
To check if the points lie on the image we use this.
def point_on_image(x: int, y: int, image_shape: tuple):
"""
Returns true is x and y are on the image
"""
return 0 <= y < image_shape[0] and 0 <= x < image_shape[1]
Point of Intersection
Using the line equation in Cartesian coordinates the point of intersection of the lines can be found by considering y’s are equal and solving for x.
m1 * x + b1 = m2 * x + b2
x = (b2 — b1)/(m1 — m2)
Then substituting the value of x in [1] and solving for y.
def intersection(m1: float, b1: float, m2: float, b2: float):
# Consider y to be equal and solve for x
# Solve:
# m1 * x + b1 = m2 * x + b2
x = (b2 - b1) / (m1 - m2)
# Use the value of x to calculate y
y = m1 * x + b1
return int(round(x)), int(round(y))
The above functions can be used to find end points of the Hough lines on the image edge and points of intersection of all possible combinations of lines returned from cv2.HoughLines.
def hough_lines_end_points(lines: np.array, image_shape: tuple):
"""
Returns end points of the lines on the edge of the image
"""
if len(lines.shape) == 3 and \
lines.shape[1] == 1 and lines.shape[2] == 2:
lines = np.squeeze(lines)
end_pts = []
for line in lines:
rho, theta = line
end_pts.append(
line_end_points_on_image(rho, theta, image_shape))
return end_ptsdef hough_lines_intersection(lines: np.array, image_shape: tuple):
"""
Returns the intersection points that lie on the image
for all combinations of the lines
"""
if len(lines.shape) == 3 and \
lines.shape[1] == 1 and lines.shape[2] == 2:
lines = np.squeeze(lines)
lines_count = len(lines)
intersect_pts = []
for i in range(lines_count - 1):
for j in range(i + 1, lines_count):
m1, b1 = polar2cartesian(lines[i][0], lines[i][1], True)
m2, b2 = polar2cartesian(lines[j][0], lines[j][1], True)
x, y = intersection(m1, b1, m2, b2)
if point_on_image(x, y, image_shape):
intersect_pts.append([x, y])
return np.array(intersect_pts, dtype=int)
The functions that you see in this post can be found in this gist to use as a python module in your project.
Hope this module helps you in some way. Please let me know if you would like to see more functions that would be helpful in processing the output from cv2.HoughLines.
Here is an application to straighten a page image using the code discussed above.