How can I get the intersection points of lines down using opencv Hough lines algorithm?
Here is my code:
import cv2
import numpy as np
import imutils
Here is a complete solution written in python 2.7.x using OpenCV 2.4.
It uses the solution from alkasm in this thread, which was incomplete. Also the returned value from HoughLines() and the syntax for kmeans() has changed from OpenCV 2.x to 3.x
Result 1: A piece of paper on a desk
https://i.ibb.co/VBSY7V7/paper-on-desk-intersection-points.jpg
This answers the original question, however using k-means clustering with k = 2,3,4 does not segment the piece of paper. You would need a different approach to find the corners of the paper
e.g. filtering for parallel lines.
Result 2: Sudoku grid
https://i.ibb.co/b6thfgr/sudoku-intersection-points.jpg
Code: https://pastiebin.com/5f36425b7ae3d
"""
Find the intersection points of lines.
"""
import numpy as np
import cv2
from collections import defaultdict
import sys
img = cv2.imread("paper_on_desk.jpg")
#img = cv2.imread("sudoku.jpg")
def segment_by_angle_kmeans(lines, k=2, **kwargs):
"""
Group lines by their angle using k-means clustering.
Code from here:
https://stackoverflow.com/a/46572063/1755401
"""
# Define criteria = (type, max_iter, epsilon)
default_criteria_type = cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER
criteria = kwargs.get('criteria', (default_criteria_type, 10, 1.0))
flags = kwargs.get('flags', cv2.KMEANS_RANDOM_CENTERS)
attempts = kwargs.get('attempts', 10)
# Get angles in [0, pi] radians
angles = np.array([line[0][1] for line in lines])
# Multiply the angles by two and find coordinates of that angle on the Unit Circle
pts = np.array([[np.cos(2*angle), np.sin(2*angle)] for angle in angles], dtype=np.float32)
# Run k-means
if sys.version_info[0] == 2:
# python 2.x
ret, labels, centers = cv2.kmeans(pts, k, criteria, attempts, flags)
else:
# python 3.x, syntax has changed.
labels, centers = cv2.kmeans(pts, k, None, criteria, attempts, flags)[1:]
labels = labels.reshape(-1) # Transpose to row vector
# Segment lines based on their label of 0 or 1
segmented = defaultdict(list)
for i, line in zip(range(len(lines)), lines):
segmented[labels[i]].append(line)
segmented = list(segmented.values())
print("Segmented lines into two groups: %d, %d" % (len(segmented[0]), len(segmented[1])))
return segmented
def intersection(line1, line2):
"""
Find the intersection of two lines
specified in Hesse normal form.
Returns closest integer pixel locations.
See here:
https://stackoverflow.com/a/383527/5087436
"""
rho1, theta1 = line1[0]
rho2, theta2 = line2[0]
A = np.array([[np.cos(theta1), np.sin(theta1)],
[np.cos(theta2), np.sin(theta2)]])
b = np.array([[rho1], [rho2]])
x0, y0 = np.linalg.solve(A, b)
x0, y0 = int(np.round(x0)), int(np.round(y0))
return [[x0, y0]]
def segmented_intersections(lines):
"""
Find the intersection between groups of lines.
"""
intersections = []
for i, group in enumerate(lines[:-1]):
for next_group in lines[i+1:]:
for line1 in group:
for line2 in next_group:
intersections.append(intersection(line1, line2))
return intersections
def drawLines(img, lines, color=(0,0,255)):
"""
Draw lines on an image
"""
for line in lines:
for rho,theta in line:
a = np.cos(theta)
b = np.sin(theta)
x0 = a*rho
y0 = b*rho
x1 = int(x0 + 1000*(-b))
y1 = int(y0 + 1000*(a))
x2 = int(x0 - 1000*(-b))
y2 = int(y0 - 1000*(a))
cv2.line(img, (x1,y1), (x2,y2), color, 1)
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
blur = cv2.medianBlur(gray, 5)
# Make binary image
adapt_type = cv2.ADAPTIVE_THRESH_GAUSSIAN_C
thresh_type = cv2.THRESH_BINARY_INV
bin_img = cv2.adaptiveThreshold(blur, 255, adapt_type, thresh_type, 11, 2)
cv2.imshow("binary", bin_img)
cv2.waitKey()
# Detect lines
rho = 2
theta = np.pi/180
thresh = 350
lines = cv2.HoughLines(bin_img, rho, theta, thresh)
if sys.version_info[0] == 2:
# python 2.x
# Re-shape from 1xNx2 to Nx1x2
temp_lines = []
N = lines.shape[1]
for i in range(N):
rho = lines[0,i,0]
theta = lines[0,i,1]
temp_lines.append( np.array([[rho,theta]]) )
lines = temp_lines
print("Found lines: %d" % (len(lines)))
# Draw all Hough lines in red
img_with_all_lines = np.copy(img)
drawLines(img_with_all_lines, lines)
cv2.imshow("Hough lines", img_with_all_lines)
cv2.waitKey()
cv2.imwrite("all_lines.jpg", img_with_all_lines)
# Cluster line angles into 2 groups (vertical and horizontal)
segmented = segment_by_angle_kmeans(lines, 2)
# Find the intersections of each vertical line with each horizontal line
intersections = segmented_intersections(segmented)
img_with_segmented_lines = np.copy(img)
# Draw vertical lines in green
vertical_lines = segmented[1]
img_with_vertical_lines = np.copy(img)
drawLines(img_with_segmented_lines, vertical_lines, (0,255,0))
# Draw horizontal lines in yellow
horizontal_lines = segmented[0]
img_with_horizontal_lines = np.copy(img)
drawLines(img_with_segmented_lines, horizontal_lines, (0,255,255))
# Draw intersection points in magenta
for point in intersections:
pt = (point[0][0], point[0][1])
length = 5
cv2.line(img_with_segmented_lines, (pt[0], pt[1]-length), (pt[0], pt[1]+length), (255, 0, 255), 1) # vertical line
cv2.line(img_with_segmented_lines, (pt[0]-length, pt[1]), (pt[0]+length, pt[1]), (255, 0, 255), 1)
cv2.imshow("Segmented lines", img_with_segmented_lines)
cv2.waitKey()
cv2.imwrite("intersection_points.jpg", img_with_segmented_lines)