如何在 HoughLinesP 之后合并行?
How to merge lines after HoughLinesP?
我的任务是找到直线(startX、startY、endX、endY)和矩形(4 条直线)的坐标。这是输入文件:
我使用下一个代码:
img = cv2.imread(image_src)
gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
ret, thresh1 = cv2.threshold(gray,127,255,cv2.THRESH_BINARY)
edges = cv2.Canny(thresh1,50,150,apertureSize = 3)
minLineLength = 100
maxLineGap = 10
lines = cv2.HoughLinesP(edges,1,np.pi/180,10,minLineLength,maxLineGap)
print(len(lines))
for line in lines:
cv2.line(img,(line[0][0],line[0][1]),(line[0][2],line[0][3]),(0,0,255),6)
我得到下一个结果:
从最后一张图片可以看到大量的小红线。
问题:
- 合并小行的最佳方法是什么?
- 为什么有很多
HoughLinesP 未检测到的小部分?
我终于完成了流水线:
- 修复了不正确的参数(正如 Dan 所建议的)
- 开发了我自己的 'merging line segments' 算法。当 I implemented TAVARES and PADILHA algorithm(正如安德鲁所建议的那样)时,我的结果很糟糕。
- 我跳过了 Canny 并获得了更好的结果(正如 Alexander 所建议的)
请查找代码和结果:
def get_lines(lines_in):
if cv2.__version__ < '3.0':
return lines_in[0]
return [l[0] for l in lines_in]
def process_lines(image_src):
img = mpimg.imread(image_src)
gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
ret, thresh1 = cv2.threshold(gray,127,255,cv2.THRESH_BINARY)
thresh1 = cv2.bitwise_not(thresh1)
edges = cv2.Canny(thresh1, threshold1=50, threshold2=200, apertureSize = 3)
lines = cv2.HoughLinesP(thresh1, rho=1, theta=np.pi/180, threshold=50,
minLineLength=50, maxLineGap=30)
# l[0] - line; l[1] - angle
for line in get_lines(lines):
leftx, boty, rightx, topy = line
cv2.line(img, (leftx, boty), (rightx,topy), (0,0,255), 6)
# merge lines
#------------------
# prepare
_lines = []
for _line in get_lines(lines):
_lines.append([(_line[0], _line[1]),(_line[2], _line[3])])
# sort
_lines_x = []
_lines_y = []
for line_i in _lines:
orientation_i = math.atan2((line_i[0][1]-line_i[1][1]),(line_i[0][0]-line_i[1][0]))
if (abs(math.degrees(orientation_i)) > 45) and abs(math.degrees(orientation_i)) < (90+45):
_lines_y.append(line_i)
else:
_lines_x.append(line_i)
_lines_x = sorted(_lines_x, key=lambda _line: _line[0][0])
_lines_y = sorted(_lines_y, key=lambda _line: _line[0][1])
merged_lines_x = merge_lines_pipeline_2(_lines_x)
merged_lines_y = merge_lines_pipeline_2(_lines_y)
merged_lines_all = []
merged_lines_all.extend(merged_lines_x)
merged_lines_all.extend(merged_lines_y)
print("process groups lines", len(_lines), len(merged_lines_all))
img_merged_lines = mpimg.imread(image_src)
for line in merged_lines_all:
cv2.line(img_merged_lines, (line[0][0], line[0][1]), (line[1][0],line[1][1]), (0,0,255), 6)
cv2.imwrite('prediction/lines_gray.jpg',gray)
cv2.imwrite('prediction/lines_thresh.jpg',thresh1)
cv2.imwrite('prediction/lines_edges.jpg',edges)
cv2.imwrite('prediction/lines_lines.jpg',img)
cv2.imwrite('prediction/merged_lines.jpg',img_merged_lines)
return merged_lines_all
def merge_lines_pipeline_2(lines):
super_lines_final = []
super_lines = []
min_distance_to_merge = 30
min_angle_to_merge = 30
for line in lines:
create_new_group = True
group_updated = False
for group in super_lines:
for line2 in group:
if get_distance(line2, line) < min_distance_to_merge:
# check the angle between lines
orientation_i = math.atan2((line[0][1]-line[1][1]),(line[0][0]-line[1][0]))
orientation_j = math.atan2((line2[0][1]-line2[1][1]),(line2[0][0]-line2[1][0]))
if int(abs(abs(math.degrees(orientation_i)) - abs(math.degrees(orientation_j)))) < min_angle_to_merge:
#print("angles", orientation_i, orientation_j)
#print(int(abs(orientation_i - orientation_j)))
group.append(line)
create_new_group = False
group_updated = True
break
if group_updated:
break
if (create_new_group):
new_group = []
new_group.append(line)
for idx, line2 in enumerate(lines):
# check the distance between lines
if get_distance(line2, line) < min_distance_to_merge:
# check the angle between lines
orientation_i = math.atan2((line[0][1]-line[1][1]),(line[0][0]-line[1][0]))
orientation_j = math.atan2((line2[0][1]-line2[1][1]),(line2[0][0]-line2[1][0]))
if int(abs(abs(math.degrees(orientation_i)) - abs(math.degrees(orientation_j)))) < min_angle_to_merge:
#print("angles", orientation_i, orientation_j)
#print(int(abs(orientation_i - orientation_j)))
new_group.append(line2)
# remove line from lines list
#lines[idx] = False
# append new group
super_lines.append(new_group)
for group in super_lines:
super_lines_final.append(merge_lines_segments1(group))
return super_lines_final
def merge_lines_segments1(lines, use_log=False):
if(len(lines) == 1):
return lines[0]
line_i = lines[0]
# orientation
orientation_i = math.atan2((line_i[0][1]-line_i[1][1]),(line_i[0][0]-line_i[1][0]))
points = []
for line in lines:
points.append(line[0])
points.append(line[1])
if (abs(math.degrees(orientation_i)) > 45) and abs(math.degrees(orientation_i)) < (90+45):
#sort by y
points = sorted(points, key=lambda point: point[1])
if use_log:
print("use y")
else:
#sort by x
points = sorted(points, key=lambda point: point[0])
if use_log:
print("use x")
return [points[0], points[len(points)-1]]
# https://docs.scipy.org/doc/scipy/reference/generated/scipy.spatial.distance.cdist.html
#
def lines_close(line1, line2):
dist1 = math.hypot(line1[0][0] - line2[0][0], line1[0][0] - line2[0][1])
dist2 = math.hypot(line1[0][2] - line2[0][0], line1[0][3] - line2[0][1])
dist3 = math.hypot(line1[0][0] - line2[0][2], line1[0][0] - line2[0][3])
dist4 = math.hypot(line1[0][2] - line2[0][2], line1[0][3] - line2[0][3])
if (min(dist1,dist2,dist3,dist4) < 100):
return True
else:
return False
def lineMagnitude (x1, y1, x2, y2):
lineMagnitude = math.sqrt(math.pow((x2 - x1), 2)+ math.pow((y2 - y1), 2))
return lineMagnitude
#Calc minimum distance from a point and a line segment (i.e. consecutive vertices in a polyline).
# https://nodedangles.wordpress.com/2010/05/16/measuring-distance-from-a-point-to-a-line-segment/
# http://paulbourke.net/geometry/pointlineplane/
def DistancePointLine(px, py, x1, y1, x2, y2):
#http://local.wasp.uwa.edu.au/~pbourke/geometry/pointline/source.vba
LineMag = lineMagnitude(x1, y1, x2, y2)
if LineMag < 0.00000001:
DistancePointLine = 9999
return DistancePointLine
u1 = (((px - x1) * (x2 - x1)) + ((py - y1) * (y2 - y1)))
u = u1 / (LineMag * LineMag)
if (u < 0.00001) or (u > 1):
#// closest point does not fall within the line segment, take the shorter distance
#// to an endpoint
ix = lineMagnitude(px, py, x1, y1)
iy = lineMagnitude(px, py, x2, y2)
if ix > iy:
DistancePointLine = iy
else:
DistancePointLine = ix
else:
# Intersecting point is on the line, use the formula
ix = x1 + u * (x2 - x1)
iy = y1 + u * (y2 - y1)
DistancePointLine = lineMagnitude(px, py, ix, iy)
return DistancePointLine
def get_distance(line1, line2):
dist1 = DistancePointLine(line1[0][0], line1[0][1],
line2[0][0], line2[0][1], line2[1][0], line2[1][1])
dist2 = DistancePointLine(line1[1][0], line1[1][1],
line2[0][0], line2[0][1], line2[1][0], line2[1][1])
dist3 = DistancePointLine(line2[0][0], line2[0][1],
line1[0][0], line1[0][1], line1[1][0], line1[1][1])
dist4 = DistancePointLine(line2[1][0], line2[1][1],
line1[0][0], line1[0][1], line1[1][0], line1[1][1])
return min(dist1,dist2,dist3,dist4)
还有572行。在我的 "merging line segments" 之后我们只有 89 行
重写了上面的代码,速度提高了 30%,更短,恕我直言,更易于理解:
class HoughBundler:
'''Clasterize and merge each cluster of cv2.HoughLinesP() output
a = HoughBundler()
foo = a.process_lines(houghP_lines, binary_image)
'''
def get_orientation(self, line):
'''get orientation of a line, using its length
https://en.wikipedia.org/wiki/Atan2
'''
orientation = math.atan2(abs((line[0] - line[2])), abs((line[1] - line[3])))
return math.degrees(orientation)
def checker(self, line_new, groups, min_distance_to_merge, min_angle_to_merge):
'''Check if line have enough distance and angle to be count as similar
'''
for group in groups:
# walk through existing line groups
for line_old in group:
# check distance
if self.get_distance(line_old, line_new) < min_distance_to_merge:
# check the angle between lines
orientation_new = self.get_orientation(line_new)
orientation_old = self.get_orientation(line_old)
# if all is ok -- line is similar to others in group
if abs(orientation_new - orientation_old) < min_angle_to_merge:
group.append(line_new)
return False
# if it is totally different line
return True
def DistancePointLine(self, point, line):
"""Get distance between point and line
http://local.wasp.uwa.edu.au/~pbourke/geometry/pointline/source.vba
"""
px, py = point
x1, y1, x2, y2 = line
def lineMagnitude(x1, y1, x2, y2):
'Get line (aka vector) length'
lineMagnitude = math.sqrt(math.pow((x2 - x1), 2) + math.pow((y2 - y1), 2))
return lineMagnitude
LineMag = lineMagnitude(x1, y1, x2, y2)
if LineMag < 0.00000001:
DistancePointLine = 9999
return DistancePointLine
u1 = (((px - x1) * (x2 - x1)) + ((py - y1) * (y2 - y1)))
u = u1 / (LineMag * LineMag)
if (u < 0.00001) or (u > 1):
#// closest point does not fall within the line segment, take the shorter distance
#// to an endpoint
ix = lineMagnitude(px, py, x1, y1)
iy = lineMagnitude(px, py, x2, y2)
if ix > iy:
DistancePointLine = iy
else:
DistancePointLine = ix
else:
# Intersecting point is on the line, use the formula
ix = x1 + u * (x2 - x1)
iy = y1 + u * (y2 - y1)
DistancePointLine = lineMagnitude(px, py, ix, iy)
return DistancePointLine
def get_distance(self, a_line, b_line):
"""Get all possible distances between each dot of two lines and second line
return the shortest
"""
dist1 = self.DistancePointLine(a_line[:2], b_line)
dist2 = self.DistancePointLine(a_line[2:], b_line)
dist3 = self.DistancePointLine(b_line[:2], a_line)
dist4 = self.DistancePointLine(b_line[2:], a_line)
return min(dist1, dist2, dist3, dist4)
def merge_lines_pipeline_2(self, lines):
'Clusterize (group) lines'
groups = [] # all lines groups are here
# Parameters to play with
min_distance_to_merge = 30
min_angle_to_merge = 30
# first line will create new group every time
groups.append([lines[0]])
# if line is different from existing gropus, create a new group
for line_new in lines[1:]:
if self.checker(line_new, groups, min_distance_to_merge, min_angle_to_merge):
groups.append([line_new])
return groups
def merge_lines_segments1(self, lines):
"""Sort lines cluster and return first and last coordinates
"""
orientation = self.get_orientation(lines[0])
# special case
if(len(lines) == 1):
return [lines[0][:2], lines[0][2:]]
# [[1,2,3,4],[]] to [[1,2],[3,4],[],[]]
points = []
for line in lines:
points.append(line[:2])
points.append(line[2:])
# if vertical
if 45 < orientation < 135:
#sort by y
points = sorted(points, key=lambda point: point[1])
else:
#sort by x
points = sorted(points, key=lambda point: point[0])
# return first and last point in sorted group
# [[x,y],[x,y]]
return [points[0], points[-1]]
def process_lines(self, lines, img):
'''Main function for lines from cv.HoughLinesP() output merging
for OpenCV 3
lines -- cv.HoughLinesP() output
img -- binary image
'''
lines_x = []
lines_y = []
# for every line of cv2.HoughLinesP()
for line_i in [l[0] for l in lines]:
orientation = self.get_orientation(line_i)
# if vertical
if 45 < orientation < 135:
lines_y.append(line_i)
else:
lines_x.append(line_i)
lines_y = sorted(lines_y, key=lambda line: line[1])
lines_x = sorted(lines_x, key=lambda line: line[0])
merged_lines_all = []
# for each cluster in vertical and horizantal lines leave only one line
for i in [lines_x, lines_y]:
if len(i) > 0:
groups = self.merge_lines_pipeline_2(i)
merged_lines = []
for group in groups:
merged_lines.append(self.merge_lines_segments1(group))
merged_lines_all.extend(merged_lines)
return merged_lines_all
计算距离的部分可以改成
def distance_to_line(self, point, line):
"""Get distance between point and line
"""
px, py = point
x1, y1, x2, y2 = line
x_diff = x2 - x1
y_diff = y2 - y1
num = abs(y_diff * px - x_diff * py + x2 * y1 - y2 * x1)
den = math.sqrt(y_diff**2 + x_diff**2)
return num / den
def get_distance(self, a_line, b_line):
"""Get all possible distances between each dot of two lines and second line
return the shortest
"""
dist1 = self.distance_to_line(a_line[:2], b_line)
dist2 = self.distance_to_line(a_line[2:], b_line)
dist3 = self.distance_to_line(b_line[:2], a_line)
dist4 = self.distance_to_line(b_line[2:], a_line)
return min(dist1, dist2, dist3, dist4)
但最后你会看到略有不同的线条。
我写了一个简单的算法,在合并两条线时,将两条线的重心和投影到预测线。当合并阈值超过时,算法 returns None
。只需将 HoughLinesP 返回的行转换为 Line
对象并在 LineMerger
中调用 merge_lines(line1, line2)
import numpy as np
import math
class Line:
def __init__(self, x1, y1, x2, y2):
if x1 < x2:
self.x1 = x1
self.x2 = x2
self.y1 = y1
self.y2 = y2
else:
self.x1 = x2
self.x2 = x1
self.y1 = y2
self.y2 = y1
dx = self.x2 - self.x1
if dx == 0:
dx = 0.000000000000000001
dy = self.y2 - self.y1
m = dy / dx
self.theta = np.arctan(m)
if self.theta < 0:
self.theta = 2 * np.pi + self.theta
self.rho = np.abs(m * self.x1 - self.y1) / np.sqrt(1 + m * m)
self.length = math.sqrt(dx * dx + dy * dy)
def point1(self):
return self.x1, self.y1
def point2(self):
return self.x2, self.y2
class LineMerger:
def __init__(self):
self.THETA_THRESHOLD = np.pi / 36
self.MAX_DISTANCE = 5
pass
def merge_lines(self, line1, line2):
theta_r = (line1.theta * line1.length + line2.theta * line2.length) / (line1.length + line2.length)
if np.average([abs(theta_r - line1.theta), abs(theta_r - line2.theta)]) < self.THETA_THRESHOLD:
# get gradients
m = math.tan(theta_r)
if m == 0:
m = 0.00000000000001
md = 1 / m
# find center of gravity
cx = ((line1.x1 + line1.x2) * line1.length + (line2.x1 + line2.x2) * line2.length) * 0.5 / (
line1.length + line2.length)
cy = ((line1.y1 + line1.y2) * line1.length + (line2.y1 + line2.y2) * line2.length) * 0.5 / (
line1.length + line2.length)
# find projection points
# x, y, d
r0 = self.get_projection_point(line1.point1(), (cx, cy), m, md)
r1 = self.get_projection_point(line1.point2(), (cx, cy), m, md)
r2 = self.get_projection_point(line2.point1(), (cx, cy), m, md)
r3 = self.get_projection_point(line2.point2(), (cx, cy), m, md)
l0 = self.get_distance(r0[:2], r2[:2])
l1 = self.get_distance(r0[:2], r3[:2])
l2 = self.get_distance(r1[:2], r2[:2])
l3 = self.get_distance(r1[:2], r3[:2])
l4 = line1.length
l5 = line2.length
max_len_index = np.argmax([l0, l1, l2, l3, l4, l5])
max_len = np.max([l0, l1, l2, l3, l4, l5])
if max_len - (line1.length + line2.length) < self.MAX_DISTANCE:
point1 = None
point2 = None
if max_len_index == 0:
point1, point2 = r0[:2], r2[:2]
elif max_len_index == 1:
point1, point2 = r0[:2], r3[:2]
elif max_len_index == 2:
point1, point2 = r1[:2], r2[:2]
elif max_len_index == 3:
point1, point2 = r1[:2], r3[:2]
elif max_len_index == 4:
point1, point2 = r0[:2], r1[:2]
elif max_len_index == 5:
point1, point2 = r2[:2], r3[:2]
if point1 and point2:
x1, y1 = point1
x2, y2 = point2
return int(x1), int(y1), int(x2), int(y2)
return None
@staticmethod
def get_projection_point(external_point, center_point, m, md):
x0, y0 = external_point
cx, cy = center_point
c = cy - m * cx
cd = y0 + md * x0
mm1 = (m * m + 1)
x = m * (cd - c) / mm1
y = (m * m * cd + c) / mm1
xd = x - x0
yd = y - y0
d = math.sqrt(xd * xd + yd * yd)
return x, y, d
@staticmethod
def get_distance(point1, point2):
x1, y1 = point1
x2, y2 = point2
dx = x1 - x2
dy = y1 - y2
return math.sqrt(dx * dx + dy * dy)
转换为Line
def convert_lines(lines_p) -> [Line]:
lines = []
for line in lines_p:
ln = line[0]
lines.append(Line(ln[0], ln[1], ln[2], ln[3]))
return lines
这是我解决这个问题的尝试。我认为它可以在各种情况下工作。这是想法:
- 将直线转换为极坐标形式 (r, alpha),如霍夫变换。
- 对于列表中的每一行,将其与列表中的另一行进行比较。
- 如果两条线彼此靠近(它们的 r 和 alpha 接近),则它们是同一条线,但它们可能重叠或不重叠。如果它们不重叠,则将它们视为分隔线。检查 2 行是否重叠的算法:
伪运行:
line1 尚不在任何线路组中,因此请使用 line1 创建一个新组
坐标 (*)
line1 用 line2....lineN
测试
line1和line2不近,跳过
1号线和3号线不近,略过....
1号线(AB)和7号线(CD)很近。好的,它们重叠了吗?是的 -> 他们是
同一行,将它们合并为 1 行(例如 AD)。使用此新坐标更新线组 (*)。 ....
line1 和 lineN 不接近,跳过并用 line2 vs (line3....lineN) 重复上述过程,除了已经合并的行。
'''python
import numpy as np
def check_overlap(line1, line2):
combination = np.array([line1,
line2,
[line1[0], line1[1], line2[0], line2[1]],
[line1[0], line1[1], line2[2], line2[3]],
[line1[2], line1[3], line2[0], line2[1]],
[line1[2], line1[3], line2[2], line2[3]]])
distance = np.sqrt((combination[:,0] - combination[:,2])**2 + (combination[:,1] - combination[:,3])**2)
max = np.amax(distance)
overlap = distance[0] + distance[1] - max
endpoint = combination[np.argmax(distance)]
return (overlap >= 0), endpoint #replace 0 with the value of distance between 2 collinear lines
def mergeLine(line_list):
#convert (x1, y1, x2, y2) formm to (r, alpha) form
A = line_list[:,1] - line_list[:,3]
B = line_list[:,2] - line_list[:,0]
C = line_list[:,0]*line_list[:,3] - line_list[:,2]*line_list[:,1]
r = np.divide(np.abs(C), np.sqrt(A*A+B*B))
alpha = (np.arctan2(-B,-A) + math.pi) % (2*math.pi) - math.pi
r_alpha = np.column_stack((r, alpha))
#prepare some variables to keep track of lines looping
r_bin_size = 10 #maximum distance to treat 2 lines as one
alpha_bin_size = 0.15 #maximum angle (radian) to treat 2 lines as one
merged = np.zeros(len(r_alpha), dtype=np.uint8)
line_group = np.empty((0,4), dtype=np.int32)
group_count = 0
for line_index in range(len(r_alpha)):
if merged[line_index] == 0: #if line hasn't been merged yet
merged[line_index] = 1
line_group = np.append(line_group, [line_list[line_index]], axis=0)
for line_index2 in range(line_index+1,len(r_alpha)):
if merged[line_index2] == 0:
#calculate the differences between 2 lines by r and alpha
dr = abs(r_alpha[line_index,0] - r_alpha[line_index2,0])
dalpha = abs(r_alpha[line_index,1] - r_alpha[line_index2,1])
if (dr<r_bin_size) and (dalpha<alpha_bin_size): #if they are close, they are the same line, so check if they are overlap
overlap, endpoints = check_overlap(line_group[group_count], line_list[line_index2])
if overlap:
line_group[group_count] = endpoints
merged[line_index2] = 1
group_count += 1
return line_group
'''
来自 banderlog013 的重写 Python 代码在方向处理和线段合并方面仍然存在问题。以下代码解决了这些问题,可以直接与 OpenCV 的 HoughLinesP 输出一起使用。
class HoughBundler:
def __init__(self,min_distance=5,min_angle=2):
self.min_distance = min_distance
self.min_angle = min_angle
def get_orientation(self, line):
orientation = math.atan2(abs((line[3] - line[1])), abs((line[2] - line[0])))
return math.degrees(orientation)
def check_is_line_different(self, line_1, groups, min_distance_to_merge, min_angle_to_merge):
for group in groups:
for line_2 in group:
if self.get_distance(line_2, line_1) < min_distance_to_merge:
orientation_1 = self.get_orientation(line_1)
orientation_2 = self.get_orientation(line_2)
if abs(orientation_1 - orientation_2) < min_angle_to_merge:
group.append(line_1)
return False
return True
def distance_point_to_line(self, point, line):
px, py = point
x1, y1, x2, y2 = line
def line_magnitude(x1, y1, x2, y2):
line_magnitude = math.sqrt(math.pow((x2 - x1), 2) + math.pow((y2 - y1), 2))
return line_magnitude
lmag = line_magnitude(x1, y1, x2, y2)
if lmag < 0.00000001:
distance_point_to_line = 9999
return distance_point_to_line
u1 = (((px - x1) * (x2 - x1)) + ((py - y1) * (y2 - y1)))
u = u1 / (lmag * lmag)
if (u < 0.00001) or (u > 1):
#// closest point does not fall within the line segment, take the shorter distance
#// to an endpoint
ix = line_magnitude(px, py, x1, y1)
iy = line_magnitude(px, py, x2, y2)
if ix > iy:
distance_point_to_line = iy
else:
distance_point_to_line = ix
else:
# Intersecting point is on the line, use the formula
ix = x1 + u * (x2 - x1)
iy = y1 + u * (y2 - y1)
distance_point_to_line = line_magnitude(px, py, ix, iy)
return distance_point_to_line
def get_distance(self, a_line, b_line):
dist1 = self.distance_point_to_line(a_line[:2], b_line)
dist2 = self.distance_point_to_line(a_line[2:], b_line)
dist3 = self.distance_point_to_line(b_line[:2], a_line)
dist4 = self.distance_point_to_line(b_line[2:], a_line)
return min(dist1, dist2, dist3, dist4)
def merge_lines_into_groups(self, lines):
groups = [] # all lines groups are here
# first line will create new group every time
groups.append([lines[0]])
# if line is different from existing gropus, create a new group
for line_new in lines[1:]:
if self.check_is_line_different(line_new, groups, self.min_distance, self.min_angle):
groups.append([line_new])
return groups
def merge_line_segments(self, lines):
orientation = self.get_orientation(lines[0])
if(len(lines) == 1):
return np.block([[lines[0][:2], lines[0][2:]]])
points = []
for line in lines:
points.append(line[:2])
points.append(line[2:])
if 45 < orientation <= 90:
#sort by y
points = sorted(points, key=lambda point: point[1])
else:
#sort by x
points = sorted(points, key=lambda point: point[0])
return np.block([[points[0],points[-1]]])
def process_lines(self, lines):
lines_horizontal = []
lines_vertical = []
for line_i in [l[0] for l in lines]:
orientation = self.get_orientation(line_i)
# if vertical
if 45 < orientation <= 90:
lines_vertical.append(line_i)
else:
lines_horizontal.append(line_i)
lines_vertical = sorted(lines_vertical , key=lambda line: line[1])
lines_horizontal = sorted(lines_horizontal , key=lambda line: line[0])
merged_lines_all = []
# for each cluster in vertical and horizantal lines leave only one line
for i in [lines_horizontal, lines_vertical]:
if len(i) > 0:
groups = self.merge_lines_into_groups(i)
merged_lines = []
for group in groups:
merged_lines.append(self.merge_line_segments(group))
merged_lines_all.extend(merged_lines)
return np.asarray(merged_lines_all)
# Usage:
lines = cv2.HoughLinesP(edges, 1, np.pi / 180, 50, None, 50, 10)
bundler = HoughBundler(min_distance=10,min_angle=5)
lines = bundler.process_lines(lines)
我的任务是找到直线(startX、startY、endX、endY)和矩形(4 条直线)的坐标。这是输入文件:
我使用下一个代码:
img = cv2.imread(image_src)
gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
ret, thresh1 = cv2.threshold(gray,127,255,cv2.THRESH_BINARY)
edges = cv2.Canny(thresh1,50,150,apertureSize = 3)
minLineLength = 100
maxLineGap = 10
lines = cv2.HoughLinesP(edges,1,np.pi/180,10,minLineLength,maxLineGap)
print(len(lines))
for line in lines:
cv2.line(img,(line[0][0],line[0][1]),(line[0][2],line[0][3]),(0,0,255),6)
我得到下一个结果:
从最后一张图片可以看到大量的小红线。
问题:
- 合并小行的最佳方法是什么?
- 为什么有很多 HoughLinesP 未检测到的小部分?
我终于完成了流水线:
- 修复了不正确的参数(正如 Dan 所建议的)
- 开发了我自己的 'merging line segments' 算法。当 I implemented TAVARES and PADILHA algorithm(正如安德鲁所建议的那样)时,我的结果很糟糕。
- 我跳过了 Canny 并获得了更好的结果(正如 Alexander 所建议的)
请查找代码和结果:
def get_lines(lines_in):
if cv2.__version__ < '3.0':
return lines_in[0]
return [l[0] for l in lines_in]
def process_lines(image_src):
img = mpimg.imread(image_src)
gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
ret, thresh1 = cv2.threshold(gray,127,255,cv2.THRESH_BINARY)
thresh1 = cv2.bitwise_not(thresh1)
edges = cv2.Canny(thresh1, threshold1=50, threshold2=200, apertureSize = 3)
lines = cv2.HoughLinesP(thresh1, rho=1, theta=np.pi/180, threshold=50,
minLineLength=50, maxLineGap=30)
# l[0] - line; l[1] - angle
for line in get_lines(lines):
leftx, boty, rightx, topy = line
cv2.line(img, (leftx, boty), (rightx,topy), (0,0,255), 6)
# merge lines
#------------------
# prepare
_lines = []
for _line in get_lines(lines):
_lines.append([(_line[0], _line[1]),(_line[2], _line[3])])
# sort
_lines_x = []
_lines_y = []
for line_i in _lines:
orientation_i = math.atan2((line_i[0][1]-line_i[1][1]),(line_i[0][0]-line_i[1][0]))
if (abs(math.degrees(orientation_i)) > 45) and abs(math.degrees(orientation_i)) < (90+45):
_lines_y.append(line_i)
else:
_lines_x.append(line_i)
_lines_x = sorted(_lines_x, key=lambda _line: _line[0][0])
_lines_y = sorted(_lines_y, key=lambda _line: _line[0][1])
merged_lines_x = merge_lines_pipeline_2(_lines_x)
merged_lines_y = merge_lines_pipeline_2(_lines_y)
merged_lines_all = []
merged_lines_all.extend(merged_lines_x)
merged_lines_all.extend(merged_lines_y)
print("process groups lines", len(_lines), len(merged_lines_all))
img_merged_lines = mpimg.imread(image_src)
for line in merged_lines_all:
cv2.line(img_merged_lines, (line[0][0], line[0][1]), (line[1][0],line[1][1]), (0,0,255), 6)
cv2.imwrite('prediction/lines_gray.jpg',gray)
cv2.imwrite('prediction/lines_thresh.jpg',thresh1)
cv2.imwrite('prediction/lines_edges.jpg',edges)
cv2.imwrite('prediction/lines_lines.jpg',img)
cv2.imwrite('prediction/merged_lines.jpg',img_merged_lines)
return merged_lines_all
def merge_lines_pipeline_2(lines):
super_lines_final = []
super_lines = []
min_distance_to_merge = 30
min_angle_to_merge = 30
for line in lines:
create_new_group = True
group_updated = False
for group in super_lines:
for line2 in group:
if get_distance(line2, line) < min_distance_to_merge:
# check the angle between lines
orientation_i = math.atan2((line[0][1]-line[1][1]),(line[0][0]-line[1][0]))
orientation_j = math.atan2((line2[0][1]-line2[1][1]),(line2[0][0]-line2[1][0]))
if int(abs(abs(math.degrees(orientation_i)) - abs(math.degrees(orientation_j)))) < min_angle_to_merge:
#print("angles", orientation_i, orientation_j)
#print(int(abs(orientation_i - orientation_j)))
group.append(line)
create_new_group = False
group_updated = True
break
if group_updated:
break
if (create_new_group):
new_group = []
new_group.append(line)
for idx, line2 in enumerate(lines):
# check the distance between lines
if get_distance(line2, line) < min_distance_to_merge:
# check the angle between lines
orientation_i = math.atan2((line[0][1]-line[1][1]),(line[0][0]-line[1][0]))
orientation_j = math.atan2((line2[0][1]-line2[1][1]),(line2[0][0]-line2[1][0]))
if int(abs(abs(math.degrees(orientation_i)) - abs(math.degrees(orientation_j)))) < min_angle_to_merge:
#print("angles", orientation_i, orientation_j)
#print(int(abs(orientation_i - orientation_j)))
new_group.append(line2)
# remove line from lines list
#lines[idx] = False
# append new group
super_lines.append(new_group)
for group in super_lines:
super_lines_final.append(merge_lines_segments1(group))
return super_lines_final
def merge_lines_segments1(lines, use_log=False):
if(len(lines) == 1):
return lines[0]
line_i = lines[0]
# orientation
orientation_i = math.atan2((line_i[0][1]-line_i[1][1]),(line_i[0][0]-line_i[1][0]))
points = []
for line in lines:
points.append(line[0])
points.append(line[1])
if (abs(math.degrees(orientation_i)) > 45) and abs(math.degrees(orientation_i)) < (90+45):
#sort by y
points = sorted(points, key=lambda point: point[1])
if use_log:
print("use y")
else:
#sort by x
points = sorted(points, key=lambda point: point[0])
if use_log:
print("use x")
return [points[0], points[len(points)-1]]
# https://docs.scipy.org/doc/scipy/reference/generated/scipy.spatial.distance.cdist.html
#
def lines_close(line1, line2):
dist1 = math.hypot(line1[0][0] - line2[0][0], line1[0][0] - line2[0][1])
dist2 = math.hypot(line1[0][2] - line2[0][0], line1[0][3] - line2[0][1])
dist3 = math.hypot(line1[0][0] - line2[0][2], line1[0][0] - line2[0][3])
dist4 = math.hypot(line1[0][2] - line2[0][2], line1[0][3] - line2[0][3])
if (min(dist1,dist2,dist3,dist4) < 100):
return True
else:
return False
def lineMagnitude (x1, y1, x2, y2):
lineMagnitude = math.sqrt(math.pow((x2 - x1), 2)+ math.pow((y2 - y1), 2))
return lineMagnitude
#Calc minimum distance from a point and a line segment (i.e. consecutive vertices in a polyline).
# https://nodedangles.wordpress.com/2010/05/16/measuring-distance-from-a-point-to-a-line-segment/
# http://paulbourke.net/geometry/pointlineplane/
def DistancePointLine(px, py, x1, y1, x2, y2):
#http://local.wasp.uwa.edu.au/~pbourke/geometry/pointline/source.vba
LineMag = lineMagnitude(x1, y1, x2, y2)
if LineMag < 0.00000001:
DistancePointLine = 9999
return DistancePointLine
u1 = (((px - x1) * (x2 - x1)) + ((py - y1) * (y2 - y1)))
u = u1 / (LineMag * LineMag)
if (u < 0.00001) or (u > 1):
#// closest point does not fall within the line segment, take the shorter distance
#// to an endpoint
ix = lineMagnitude(px, py, x1, y1)
iy = lineMagnitude(px, py, x2, y2)
if ix > iy:
DistancePointLine = iy
else:
DistancePointLine = ix
else:
# Intersecting point is on the line, use the formula
ix = x1 + u * (x2 - x1)
iy = y1 + u * (y2 - y1)
DistancePointLine = lineMagnitude(px, py, ix, iy)
return DistancePointLine
def get_distance(line1, line2):
dist1 = DistancePointLine(line1[0][0], line1[0][1],
line2[0][0], line2[0][1], line2[1][0], line2[1][1])
dist2 = DistancePointLine(line1[1][0], line1[1][1],
line2[0][0], line2[0][1], line2[1][0], line2[1][1])
dist3 = DistancePointLine(line2[0][0], line2[0][1],
line1[0][0], line1[0][1], line1[1][0], line1[1][1])
dist4 = DistancePointLine(line2[1][0], line2[1][1],
line1[0][0], line1[0][1], line1[1][0], line1[1][1])
return min(dist1,dist2,dist3,dist4)
还有572行。在我的 "merging line segments" 之后我们只有 89 行
重写了上面的代码,速度提高了 30%,更短,恕我直言,更易于理解:
class HoughBundler:
'''Clasterize and merge each cluster of cv2.HoughLinesP() output
a = HoughBundler()
foo = a.process_lines(houghP_lines, binary_image)
'''
def get_orientation(self, line):
'''get orientation of a line, using its length
https://en.wikipedia.org/wiki/Atan2
'''
orientation = math.atan2(abs((line[0] - line[2])), abs((line[1] - line[3])))
return math.degrees(orientation)
def checker(self, line_new, groups, min_distance_to_merge, min_angle_to_merge):
'''Check if line have enough distance and angle to be count as similar
'''
for group in groups:
# walk through existing line groups
for line_old in group:
# check distance
if self.get_distance(line_old, line_new) < min_distance_to_merge:
# check the angle between lines
orientation_new = self.get_orientation(line_new)
orientation_old = self.get_orientation(line_old)
# if all is ok -- line is similar to others in group
if abs(orientation_new - orientation_old) < min_angle_to_merge:
group.append(line_new)
return False
# if it is totally different line
return True
def DistancePointLine(self, point, line):
"""Get distance between point and line
http://local.wasp.uwa.edu.au/~pbourke/geometry/pointline/source.vba
"""
px, py = point
x1, y1, x2, y2 = line
def lineMagnitude(x1, y1, x2, y2):
'Get line (aka vector) length'
lineMagnitude = math.sqrt(math.pow((x2 - x1), 2) + math.pow((y2 - y1), 2))
return lineMagnitude
LineMag = lineMagnitude(x1, y1, x2, y2)
if LineMag < 0.00000001:
DistancePointLine = 9999
return DistancePointLine
u1 = (((px - x1) * (x2 - x1)) + ((py - y1) * (y2 - y1)))
u = u1 / (LineMag * LineMag)
if (u < 0.00001) or (u > 1):
#// closest point does not fall within the line segment, take the shorter distance
#// to an endpoint
ix = lineMagnitude(px, py, x1, y1)
iy = lineMagnitude(px, py, x2, y2)
if ix > iy:
DistancePointLine = iy
else:
DistancePointLine = ix
else:
# Intersecting point is on the line, use the formula
ix = x1 + u * (x2 - x1)
iy = y1 + u * (y2 - y1)
DistancePointLine = lineMagnitude(px, py, ix, iy)
return DistancePointLine
def get_distance(self, a_line, b_line):
"""Get all possible distances between each dot of two lines and second line
return the shortest
"""
dist1 = self.DistancePointLine(a_line[:2], b_line)
dist2 = self.DistancePointLine(a_line[2:], b_line)
dist3 = self.DistancePointLine(b_line[:2], a_line)
dist4 = self.DistancePointLine(b_line[2:], a_line)
return min(dist1, dist2, dist3, dist4)
def merge_lines_pipeline_2(self, lines):
'Clusterize (group) lines'
groups = [] # all lines groups are here
# Parameters to play with
min_distance_to_merge = 30
min_angle_to_merge = 30
# first line will create new group every time
groups.append([lines[0]])
# if line is different from existing gropus, create a new group
for line_new in lines[1:]:
if self.checker(line_new, groups, min_distance_to_merge, min_angle_to_merge):
groups.append([line_new])
return groups
def merge_lines_segments1(self, lines):
"""Sort lines cluster and return first and last coordinates
"""
orientation = self.get_orientation(lines[0])
# special case
if(len(lines) == 1):
return [lines[0][:2], lines[0][2:]]
# [[1,2,3,4],[]] to [[1,2],[3,4],[],[]]
points = []
for line in lines:
points.append(line[:2])
points.append(line[2:])
# if vertical
if 45 < orientation < 135:
#sort by y
points = sorted(points, key=lambda point: point[1])
else:
#sort by x
points = sorted(points, key=lambda point: point[0])
# return first and last point in sorted group
# [[x,y],[x,y]]
return [points[0], points[-1]]
def process_lines(self, lines, img):
'''Main function for lines from cv.HoughLinesP() output merging
for OpenCV 3
lines -- cv.HoughLinesP() output
img -- binary image
'''
lines_x = []
lines_y = []
# for every line of cv2.HoughLinesP()
for line_i in [l[0] for l in lines]:
orientation = self.get_orientation(line_i)
# if vertical
if 45 < orientation < 135:
lines_y.append(line_i)
else:
lines_x.append(line_i)
lines_y = sorted(lines_y, key=lambda line: line[1])
lines_x = sorted(lines_x, key=lambda line: line[0])
merged_lines_all = []
# for each cluster in vertical and horizantal lines leave only one line
for i in [lines_x, lines_y]:
if len(i) > 0:
groups = self.merge_lines_pipeline_2(i)
merged_lines = []
for group in groups:
merged_lines.append(self.merge_lines_segments1(group))
merged_lines_all.extend(merged_lines)
return merged_lines_all
计算距离的部分可以改成
def distance_to_line(self, point, line):
"""Get distance between point and line
"""
px, py = point
x1, y1, x2, y2 = line
x_diff = x2 - x1
y_diff = y2 - y1
num = abs(y_diff * px - x_diff * py + x2 * y1 - y2 * x1)
den = math.sqrt(y_diff**2 + x_diff**2)
return num / den
def get_distance(self, a_line, b_line):
"""Get all possible distances between each dot of two lines and second line
return the shortest
"""
dist1 = self.distance_to_line(a_line[:2], b_line)
dist2 = self.distance_to_line(a_line[2:], b_line)
dist3 = self.distance_to_line(b_line[:2], a_line)
dist4 = self.distance_to_line(b_line[2:], a_line)
return min(dist1, dist2, dist3, dist4)
但最后你会看到略有不同的线条。
我写了一个简单的算法,在合并两条线时,将两条线的重心和投影到预测线。当合并阈值超过时,算法 returns None
。只需将 HoughLinesP 返回的行转换为 Line
对象并在 LineMerger
merge_lines(line1, line2)
import numpy as np
import math
class Line:
def __init__(self, x1, y1, x2, y2):
if x1 < x2:
self.x1 = x1
self.x2 = x2
self.y1 = y1
self.y2 = y2
else:
self.x1 = x2
self.x2 = x1
self.y1 = y2
self.y2 = y1
dx = self.x2 - self.x1
if dx == 0:
dx = 0.000000000000000001
dy = self.y2 - self.y1
m = dy / dx
self.theta = np.arctan(m)
if self.theta < 0:
self.theta = 2 * np.pi + self.theta
self.rho = np.abs(m * self.x1 - self.y1) / np.sqrt(1 + m * m)
self.length = math.sqrt(dx * dx + dy * dy)
def point1(self):
return self.x1, self.y1
def point2(self):
return self.x2, self.y2
class LineMerger:
def __init__(self):
self.THETA_THRESHOLD = np.pi / 36
self.MAX_DISTANCE = 5
pass
def merge_lines(self, line1, line2):
theta_r = (line1.theta * line1.length + line2.theta * line2.length) / (line1.length + line2.length)
if np.average([abs(theta_r - line1.theta), abs(theta_r - line2.theta)]) < self.THETA_THRESHOLD:
# get gradients
m = math.tan(theta_r)
if m == 0:
m = 0.00000000000001
md = 1 / m
# find center of gravity
cx = ((line1.x1 + line1.x2) * line1.length + (line2.x1 + line2.x2) * line2.length) * 0.5 / (
line1.length + line2.length)
cy = ((line1.y1 + line1.y2) * line1.length + (line2.y1 + line2.y2) * line2.length) * 0.5 / (
line1.length + line2.length)
# find projection points
# x, y, d
r0 = self.get_projection_point(line1.point1(), (cx, cy), m, md)
r1 = self.get_projection_point(line1.point2(), (cx, cy), m, md)
r2 = self.get_projection_point(line2.point1(), (cx, cy), m, md)
r3 = self.get_projection_point(line2.point2(), (cx, cy), m, md)
l0 = self.get_distance(r0[:2], r2[:2])
l1 = self.get_distance(r0[:2], r3[:2])
l2 = self.get_distance(r1[:2], r2[:2])
l3 = self.get_distance(r1[:2], r3[:2])
l4 = line1.length
l5 = line2.length
max_len_index = np.argmax([l0, l1, l2, l3, l4, l5])
max_len = np.max([l0, l1, l2, l3, l4, l5])
if max_len - (line1.length + line2.length) < self.MAX_DISTANCE:
point1 = None
point2 = None
if max_len_index == 0:
point1, point2 = r0[:2], r2[:2]
elif max_len_index == 1:
point1, point2 = r0[:2], r3[:2]
elif max_len_index == 2:
point1, point2 = r1[:2], r2[:2]
elif max_len_index == 3:
point1, point2 = r1[:2], r3[:2]
elif max_len_index == 4:
point1, point2 = r0[:2], r1[:2]
elif max_len_index == 5:
point1, point2 = r2[:2], r3[:2]
if point1 and point2:
x1, y1 = point1
x2, y2 = point2
return int(x1), int(y1), int(x2), int(y2)
return None
@staticmethod
def get_projection_point(external_point, center_point, m, md):
x0, y0 = external_point
cx, cy = center_point
c = cy - m * cx
cd = y0 + md * x0
mm1 = (m * m + 1)
x = m * (cd - c) / mm1
y = (m * m * cd + c) / mm1
xd = x - x0
yd = y - y0
d = math.sqrt(xd * xd + yd * yd)
return x, y, d
@staticmethod
def get_distance(point1, point2):
x1, y1 = point1
x2, y2 = point2
dx = x1 - x2
dy = y1 - y2
return math.sqrt(dx * dx + dy * dy)
转换为Line
def convert_lines(lines_p) -> [Line]:
lines = []
for line in lines_p:
ln = line[0]
lines.append(Line(ln[0], ln[1], ln[2], ln[3]))
return lines
这是我解决这个问题的尝试。我认为它可以在各种情况下工作。这是想法:
- 将直线转换为极坐标形式 (r, alpha),如霍夫变换。
- 对于列表中的每一行,将其与列表中的另一行进行比较。
- 如果两条线彼此靠近(它们的 r 和 alpha 接近),则它们是同一条线,但它们可能重叠或不重叠。如果它们不重叠,则将它们视为分隔线。检查 2 行是否重叠的算法:
伪运行:
line1 尚不在任何线路组中,因此请使用 line1 创建一个新组 坐标 (*)
line1 用 line2....lineN
测试line1和line2不近,跳过
1号线和3号线不近,略过....
1号线(AB)和7号线(CD)很近。好的,它们重叠了吗?是的 -> 他们是 同一行,将它们合并为 1 行(例如 AD)。使用此新坐标更新线组 (*)。 ....
line1 和 lineN 不接近,跳过并用 line2 vs (line3....lineN) 重复上述过程,除了已经合并的行。
'''python
import numpy as np
def check_overlap(line1, line2):
combination = np.array([line1,
line2,
[line1[0], line1[1], line2[0], line2[1]],
[line1[0], line1[1], line2[2], line2[3]],
[line1[2], line1[3], line2[0], line2[1]],
[line1[2], line1[3], line2[2], line2[3]]])
distance = np.sqrt((combination[:,0] - combination[:,2])**2 + (combination[:,1] - combination[:,3])**2)
max = np.amax(distance)
overlap = distance[0] + distance[1] - max
endpoint = combination[np.argmax(distance)]
return (overlap >= 0), endpoint #replace 0 with the value of distance between 2 collinear lines
def mergeLine(line_list):
#convert (x1, y1, x2, y2) formm to (r, alpha) form
A = line_list[:,1] - line_list[:,3]
B = line_list[:,2] - line_list[:,0]
C = line_list[:,0]*line_list[:,3] - line_list[:,2]*line_list[:,1]
r = np.divide(np.abs(C), np.sqrt(A*A+B*B))
alpha = (np.arctan2(-B,-A) + math.pi) % (2*math.pi) - math.pi
r_alpha = np.column_stack((r, alpha))
#prepare some variables to keep track of lines looping
r_bin_size = 10 #maximum distance to treat 2 lines as one
alpha_bin_size = 0.15 #maximum angle (radian) to treat 2 lines as one
merged = np.zeros(len(r_alpha), dtype=np.uint8)
line_group = np.empty((0,4), dtype=np.int32)
group_count = 0
for line_index in range(len(r_alpha)):
if merged[line_index] == 0: #if line hasn't been merged yet
merged[line_index] = 1
line_group = np.append(line_group, [line_list[line_index]], axis=0)
for line_index2 in range(line_index+1,len(r_alpha)):
if merged[line_index2] == 0:
#calculate the differences between 2 lines by r and alpha
dr = abs(r_alpha[line_index,0] - r_alpha[line_index2,0])
dalpha = abs(r_alpha[line_index,1] - r_alpha[line_index2,1])
if (dr<r_bin_size) and (dalpha<alpha_bin_size): #if they are close, they are the same line, so check if they are overlap
overlap, endpoints = check_overlap(line_group[group_count], line_list[line_index2])
if overlap:
line_group[group_count] = endpoints
merged[line_index2] = 1
group_count += 1
return line_group
'''
来自 banderlog013 的重写 Python 代码在方向处理和线段合并方面仍然存在问题。以下代码解决了这些问题,可以直接与 OpenCV 的 HoughLinesP 输出一起使用。
class HoughBundler:
def __init__(self,min_distance=5,min_angle=2):
self.min_distance = min_distance
self.min_angle = min_angle
def get_orientation(self, line):
orientation = math.atan2(abs((line[3] - line[1])), abs((line[2] - line[0])))
return math.degrees(orientation)
def check_is_line_different(self, line_1, groups, min_distance_to_merge, min_angle_to_merge):
for group in groups:
for line_2 in group:
if self.get_distance(line_2, line_1) < min_distance_to_merge:
orientation_1 = self.get_orientation(line_1)
orientation_2 = self.get_orientation(line_2)
if abs(orientation_1 - orientation_2) < min_angle_to_merge:
group.append(line_1)
return False
return True
def distance_point_to_line(self, point, line):
px, py = point
x1, y1, x2, y2 = line
def line_magnitude(x1, y1, x2, y2):
line_magnitude = math.sqrt(math.pow((x2 - x1), 2) + math.pow((y2 - y1), 2))
return line_magnitude
lmag = line_magnitude(x1, y1, x2, y2)
if lmag < 0.00000001:
distance_point_to_line = 9999
return distance_point_to_line
u1 = (((px - x1) * (x2 - x1)) + ((py - y1) * (y2 - y1)))
u = u1 / (lmag * lmag)
if (u < 0.00001) or (u > 1):
#// closest point does not fall within the line segment, take the shorter distance
#// to an endpoint
ix = line_magnitude(px, py, x1, y1)
iy = line_magnitude(px, py, x2, y2)
if ix > iy:
distance_point_to_line = iy
else:
distance_point_to_line = ix
else:
# Intersecting point is on the line, use the formula
ix = x1 + u * (x2 - x1)
iy = y1 + u * (y2 - y1)
distance_point_to_line = line_magnitude(px, py, ix, iy)
return distance_point_to_line
def get_distance(self, a_line, b_line):
dist1 = self.distance_point_to_line(a_line[:2], b_line)
dist2 = self.distance_point_to_line(a_line[2:], b_line)
dist3 = self.distance_point_to_line(b_line[:2], a_line)
dist4 = self.distance_point_to_line(b_line[2:], a_line)
return min(dist1, dist2, dist3, dist4)
def merge_lines_into_groups(self, lines):
groups = [] # all lines groups are here
# first line will create new group every time
groups.append([lines[0]])
# if line is different from existing gropus, create a new group
for line_new in lines[1:]:
if self.check_is_line_different(line_new, groups, self.min_distance, self.min_angle):
groups.append([line_new])
return groups
def merge_line_segments(self, lines):
orientation = self.get_orientation(lines[0])
if(len(lines) == 1):
return np.block([[lines[0][:2], lines[0][2:]]])
points = []
for line in lines:
points.append(line[:2])
points.append(line[2:])
if 45 < orientation <= 90:
#sort by y
points = sorted(points, key=lambda point: point[1])
else:
#sort by x
points = sorted(points, key=lambda point: point[0])
return np.block([[points[0],points[-1]]])
def process_lines(self, lines):
lines_horizontal = []
lines_vertical = []
for line_i in [l[0] for l in lines]:
orientation = self.get_orientation(line_i)
# if vertical
if 45 < orientation <= 90:
lines_vertical.append(line_i)
else:
lines_horizontal.append(line_i)
lines_vertical = sorted(lines_vertical , key=lambda line: line[1])
lines_horizontal = sorted(lines_horizontal , key=lambda line: line[0])
merged_lines_all = []
# for each cluster in vertical and horizantal lines leave only one line
for i in [lines_horizontal, lines_vertical]:
if len(i) > 0:
groups = self.merge_lines_into_groups(i)
merged_lines = []
for group in groups:
merged_lines.append(self.merge_line_segments(group))
merged_lines_all.extend(merged_lines)
return np.asarray(merged_lines_all)
# Usage:
lines = cv2.HoughLinesP(edges, 1, np.pi / 180, 50, None, 50, 10)
bundler = HoughBundler(min_distance=10,min_angle=5)
lines = bundler.process_lines(lines)