Exemplo n.º 1
0
def find_endpoints_in_area(lines_list: list, start_index: int, x: int, y: int, radius: float, main_angle: float) \
        -> np.ndarray:
    """
    Find all endpoints in circle area (center and radius given)
    Also calculate angle difference between each line containing endpoint in area and main endpoint line

    :param lines_list: list of lines (tuples of 2 endpoints)
    :param start_index: starting index for search in lines list
    :param x: center coordinate
    :param y: center coordinate
    :param radius: determines area of search
    :param main_angle: angle of line that search point is in
    :return: numpy array of endpoint descriptions containing: (if no endpoints found empty list is returned)
        line_index - line index in list
        point_index - endpoint index in line (0 or 1)
        delta - difference of angles between main line, and lines containing in_area endpoints
    """
    in_area_list = []
    endpoint = np.array([x, y])
    for i in range(start_index, len(lines_list)):
        if lines_list[i] is None:
            continue
        for j in range(0, 2):
            tmp_endpoint = np.array([lines_list[i][j][0], lines_list[i][j][1]])
            if distance_L2(endpoint, tmp_endpoint) <= radius:  # calculate distance
                other_endpoint = np.array([lines_list[i][(j+1) % 2][0], lines_list[i][(j+1) % 2][1]])
                tmp_angle = vector_angle(tmp_endpoint, other_endpoint)
                diff = abs(main_angle - tmp_angle)
                delta = diff if diff <= 180 else 360 - diff
                in_area_list.append([i, j, delta])
    ret_arr = np.array(in_area_list) if in_area_list else None
    return ret_arr
Exemplo n.º 2
0
def link_nearby_endpoints(lines_list: list, backend: np.ndarray, search_radius: float, angle_threshold: float) \
        -> (list, np.ndarray):
    """
    Link edge segments (lines) that are close to each other and go at a similar angle. This operation is performed
    because sometimes, one edge is separated into multiple lines (e.g. when intersections occur)

    :param lines_list: list of lines (tuples of 2 endpoints)
    :param backend: source image with visualised topology recognition backend
    :param search_radius: radius determining area around endpoint that other endpoint of other line has to be in to
        be considered a "nearby" endpoint
    :param angle_threshold: threshold that describes upper limit for angle (in degrees)
        that lines of 2 nearby endpoints have to be at to be linked
    :return: list of lines, where nearby line segments are connected into single lines also visualised results
    """
    # sort lines by descending length - longer lines have higher priority (occur closer to the beginning of the list)
    lines_list = sorted(lines_list, key=functools.cmp_to_key(lines_lengths_compare), reverse=True)
    i = 0
    while i < len(lines_list):
        if lines_list[i] is None:  # skip already linked lines
            i += 1
            continue
        line = lines_list[i]
        line_len = distance_L2(line[0], line[1])
        search_radius = search_radius if search_radius <= line_len*1.2 else line_len*1.2
        for j in range(0, len(line)):  # for each one of 2 endpoints
            main_point = line[j]  # search around this endpoint for endpoints from different lines
            other_point = line[(j+1) % 2]  # other endpoint from the same line
            cv.circle(backend, (main_point[0], main_point[1]), int(search_radius), Color.PINK, 1)
            cv.line(backend, (main_point[0]-int(search_radius), main_point[1]),
                    (main_point[0]+int(search_radius), main_point[1]), Color.PINK, 1)
            if main_point is None or other_point is None:
                break
            else:
                main_angle = vector_angle(other_point, main_point)
                in_area_list = find_endpoints_in_area(lines_list, i+1, main_point[0], main_point[1], search_radius,
                                                      main_angle)  # find all endpoints in area of main point
                if in_area_list is not None:
                    deltas = in_area_list[:, 2]  # extract angle differences into separate array
                    min_delta = np.min(deltas)
                    if min_delta <= angle_threshold:  # check if lines are going at a similar angle
                        min_index = np.argmin(deltas)
                        k, l = (int(in_area_list[min_index][0]), int(in_area_list[min_index][1]))
                        # create linked line by assigning new value to main_point place in list
                        lines_list[i][j] = lines_list[k][(l + 1) % 2]  # find new endpoint for main line
                        lines_list[k] = None  # mark line as linked
                        i -= 1  # take another iteration over current line since it has just changed
                        break  # start new iteration with new linked line
        i += 1
    final_lines_list = []
    i = 0
    for line in lines_list:
        i += 1
        if line is None:  # remove fields in list that remained after they were linked to other lines
            continue
        else:  # print lines
            final_lines_list.append(line)
            pt1, pt2 = line
            cv.line(backend, (pt1[0], pt1[1]), (pt2[0], pt2[1]), Color.ORANGE, 2)

    return final_lines_list, backend
Exemplo n.º 3
0
def extract_circle_area(image: np.ndarray, x: int, y: int,
                        r: int) -> np.ndarray:
    """
    Extract circular area from image into numpy array of pixels

    :param image: input image
    :param x: x coordinate of circle center
    :param y: y coordinate of circle center
    :param r: circle radius
    :return:
    """
    pixel_list = []
    if x >= 0 and y >= 0:
        # calculate square area boundaries that contains circle area
        top = y - r if ((y - r) >= 0) else 0
        bottom = y + r if ((y + r) <= image.shape[0]) else image.shape[0]
        left = x - r if ((x - r) >= 0) else 0
        right = x + r if ((x + r) <= image.shape[1]) else image.shape[1]

        # in inner circle area count black and white pixels to find dominant color
        for y_iter in range(top, bottom):
            for x_iter in range(left, right):
                distance = round(distance_L2([x, y], [x_iter, y_iter]))
                if distance <= r:
                    pixel_list.append(image[y_iter][x_iter])
    return np.array(pixel_list) if len(pixel_list) > 0 else None
Exemplo n.º 4
0
def lines_lengths_compare(line1: ([int, int], [int, int]), line2: ([int, int], [int, int])) -> int:
    """
    Compare 2 lines in terms of length

    :param line1: first line (2 endpoints)
    :param line2: second line --||--
    :return:
         1 - first line is longer than second one
         0 - lines lengths are equal
        -1 - second line is longer than first one
    """
    len1 = distance_L2(line1[0], line1[1])
    len2 = distance_L2(line2[0], line2[1])
    if len1 > len2:
        return 1
    elif len1 == len2:
        return 0
    else:
        return -1
Exemplo n.º 5
0
def point_within_radius(point: np.ndarray, vertex: Vertex, radius_factor: float) -> bool:
    """
    Check if point is within vertex area with radius modified by factor (based on euclidean distance - L2)

    :param point: x and y coordinates
    :param vertex: vertex which area is considered
    :param radius_factor: factor to increase/decrease radius and therefore area
    :return: True if point is within radius, and False if it is not
    """
    radius = vertex.r * radius_factor
    return True if distance_L2(point, [vertex.x, vertex.y]) <= radius else False
Exemplo n.º 6
0
def find_nearest_vertex(point: np.ndarray, vertices_list: list) -> int:
    """
    Find vertex nearest to a given point (based on euclidean distance - L2)

    :param point: x and y coordinates from which distance to vertices is measured
    :param vertices_list: list of detected vertices in segmentation phase
    :return nearest_index: index in vertices list of vertex nearest to the given point
    """
    # Initialise values with first on list
    nearest_index = 0
    current_vertex = vertices_list[nearest_index]
    current_center = np.array([current_vertex.x, current_vertex.y])
    min_distance = distance_L2(point, current_center)
    for i in range(0, len(vertices_list)):
        current_vertex = vertices_list[i]
        current_center = np.array([current_vertex.x, current_vertex.y])
        distance = distance_L2(point, current_center)
        if distance < min_distance:     # Found vertex closer to a point
            nearest_index = i
            min_distance = distance
    return nearest_index
Exemplo n.º 7
0
def remove_margins(binary_image: np.ndarray) -> np.ndarray:
    """
    Remove vertical and horizontal margins (lines that go the whole way through a binary image).

    :param binary_image: input transformed image with notebook margins remaining
    :return: image with margins removed
    """
    margin_lines = []
    # detect horizontal margin lines
    width = binary_image.shape[1]
    structure = cv.getStructuringElement(cv.MORPH_RECT, (width // 50, 1))
    horizontal = cv.morphologyEx(binary_image,
                                 cv.MORPH_OPEN,
                                 structure,
                                 iterations=1)
    horizontal = cv.dilate(horizontal, Kernel.k3, iterations=1)
    margin_lines.append(cv.HoughLines(horizontal, 1, np.pi / 180, 500))

    # detect vertical margin lines
    height = binary_image.shape[0]
    structure = cv.getStructuringElement(cv.MORPH_RECT, (1, height // 50))
    vertical = cv.morphologyEx(binary_image,
                               cv.MORPH_OPEN,
                               structure,
                               iterations=1)
    vertical = cv.dilate(vertical, Kernel.k3, iterations=1)
    margin_lines.append(cv.HoughLines(vertical, 1, np.pi / 180, 400))

    # remove detected margins
    processed = binary_image.copy()
    diagonal_len = distance_L2([0, 0],
                               [processed.shape[1], processed.shape[0]])
    for lines in margin_lines:
        if lines is not None:
            for line in lines:  # convert each line from Polar to Cartesian coordinate system
                rho, theta = line[0]
                a, b = (np.cos(theta), np.sin(theta))
                x0, y0 = (a * rho, b * rho)
                pt1 = (int(x0 + diagonal_len * (-b)),
                       int(y0 + diagonal_len * a))
                pt2 = (int(x0 - diagonal_len * (-b)),
                       int(y0 - diagonal_len * a))
                cv.line(processed, pt1, pt2, Color.BG,
                        6)  # mask margin line in the image

    return processed
Exemplo n.º 8
0
def lines_from_contours(preprocessed: np.ndarray, backend: np.ndarray, min_line_length: float = 10) \
        -> (list, np.ndarray):
    """
    From image with removed vertices approximate each contour with straight line

    :param preprocessed: input preprocessed image with removed vertices
    :param backend: image with visualisation of topology recognition backend
    :param min_line_length: all approximated lines of smaller length than this value will be considered noise and not
        added to the list of lines
    :return: List of lines (tuples of 2 endpoints) and image with lines visualised
    """
    lines_list = []
    contours, hierarchy = cv.findContours(preprocessed, cv.RETR_CCOMP, cv.CHAIN_APPROX_SIMPLE)
    cv.drawContours(backend, contours, -1, Color.YELLOW, 1)
    for i in range(0, len(contours)):
        if hierarchy[0][i][3] == -1:  # outer contours only
            cnt = contours[i]
            pt1, pt2 = fit_line(cnt)
            # if line has been fitted take lines that are long enough to be an edge
            if pt1 is not None and pt2 is not None and distance_L2(pt1, pt2) >= min_line_length:
                cv.circle(backend, (pt1[0], pt1[1]), 4, Color.CYAN, cv.FILLED)
                cv.circle(backend, (pt2[0], pt2[1]), 4, Color.CYAN, cv.FILLED)
                lines_list.append([pt1, pt2])
    return lines_list, backend