import cv2 import numpy as np linelen = lambda l: np.sqrt((l[0]-l[2])**2 + (l[1]-l[3])**2) import math 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]) p0 = np.array(points[:2]).mean(axis=0) p1 = np.array(points[-2:]).mean(axis=0) return np.block([[p0,p1]]).astype(int) # 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) def get_needle_line(im)->list: # fn = "./0.png" # # load grayscale image # im = cv2.imread(fn) gray_im = cv2.cvtColor(im, cv2.COLOR_RGB2GRAY) sz = 640 h,w,_ = im.shape _hf = h/sz _wf = w/sz gray_im = cv2.resize(gray_im, (sz,sz)) blur = cv2.GaussianBlur(gray_im, (0,0), 5) # edges = cv2.Canny(blur, 50, 100) edges = cv2.Canny(blur, 20, 60) rectKernel = cv2.getStructuringElement(cv2.MORPH_RECT, (11, 19)) hat = cv2.morphologyEx(edges, cv2.MORPH_BLACKHAT, rectKernel) # hat = cv2.morphologyEx(edges, cv2.MORPH_TOPHAT, rectKernel) # k = cv2.getStructuringElement(cv2.MORPH_ERODE, (13,13)) k = None hat = cv2.erode(hat,k, iterations=2) minLineLength = 60 maxLineGap = 10 plines = cv2.HoughLinesP(image=edges, rho=3, theta=np.pi / 180, threshold=10,minLineLength=minLineLength, maxLineGap=maxLineGap) # rho is set to 3 to detect more lines, easier to get more then filter them out later if len(plines)<=1: return plines.squeeze() * [_wf, _hf, _wf, _hf] bundler = HoughBundler(min_distance=120,min_angle=5) clines = bundler.process_lines(plines) return clines.squeeze() * [_wf, _hf, _wf, _hf]