How to merge lines after HoughLinesP?

后端 未结 2 1893
终归单人心
终归单人心 2020-12-08 17:22

My task is to find coordinates of lines (startX, startY, endX, endY) and rectangles (4 lines). Here is input file:

I use the next code:

img = cv2.imr         


        
2条回答
  •  广开言路
    2020-12-08 17:35

    Rewritten code above, it is 30% faster, shorter and, IMHO, more understandable:

    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
    

    The part with distance calculation could be changed to

    def distance_to_line(self, point, line):
        """Get distance between point and line
        https://stackoverflow.com/questions/40970478/python-3-5-2-distance-from-a-point-to-a-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)
    

    But you'll get slightly different lines at the end.

提交回复
热议问题