import cv2 import numpy as np import utils def solve_maze(image, debug=False): original_width, original_height = image.shape[1], image.shape[0] image = cv2.resize(image, (4000, 3000)) def debug_image(label, image, *args, **kwargs): if debug: utils.display_image(label, image, *args, **kwargs) debug_image("Image", image) # convert the image to grayscale image_gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) # extract the tags tag_registry = utils.extract_tags_dict_single(image_gray) missing_tags = {1, 2, 3, 4} - set(tag_registry.keys()) if len(missing_tags) > 0: raise Exception(f"Missing tags in given image: {missing_tags}") # extract the foreground normalized_image = utils.extract_foreground(image_gray) debug_image("Normalized", normalized_image) # draw a black rectangle around the detected tags for r in tag_registry.values(): (ptA, ptB, ptC, ptD) = r.corners # get min_x, min_y, max_x, max_y min_x = min(ptA[0], ptB[0], ptC[0], ptD[0]) min_y = min(ptA[1], ptB[1], ptC[1], ptD[1]) max_x = max(ptA[0], ptB[0], ptC[0], ptD[0]) max_y = max(ptA[1], ptB[1], ptC[1], ptD[1]) width = max_x - min_x height = max_y - min_y # enlarge the rectangle min_x -= (width / 7) * 1.5 min_y -= (height * 2 / 7) * 1.5 max_x += (width / 7) * 1.5 max_y += (height / 7) * 1.5 # draw the rectangle cv2.rectangle(normalized_image, (int(min_x), int(min_y)), (int(max_x), int(max_y)), (255, 255, 255), -1) # cv2.fillPoly(image, [np.int32(r.corners)], (0, 0, 0)) # erode the image eroded = cv2.erode(normalized_image, np.ones((7, 7), np.uint8)) # eroded = cv2.GaussianBlur(eroded, (15, 15), 0) debug_image("Eroded", eroded) # binarize the image _, thresh = cv2.threshold(eroded, 150, 255, cv2.THRESH_BINARY) thresh = cv2.bitwise_not(thresh) debug_image("Threshold", thresh) dilated = cv2.dilate(thresh, None, iterations=8) debug_image("Dilated", dilated) # crop the maze region using tag 1 and 2 as bounding box tag_1_center = tag_registry[1].center tag_2_center = tag_registry[2].center min_x = min(tag_1_center[0], tag_2_center[0]) min_y = min(tag_1_center[1], tag_2_center[1]) max_x = max(tag_1_center[0], tag_2_center[0]) max_y = max(tag_1_center[1], tag_2_center[1]) def transform_1(x, y): return x - min_x, y - min_y cropped_image = dilated[int(min_y) : int(max_y), int(min_x) : int(max_x)] cropped_image = cv2.cvtColor(cropped_image, cv2.COLOR_GRAY2BGR) cv2.circle(cropped_image, np.int32(transform_1(*tag_registry[3].center)), 10, (0, 0, 255), -1) cv2.circle(cropped_image, np.int32(transform_1(*tag_registry[4].center)), 10, (0, 255, 0), -1) debug_image("Cropped", cropped_image) def transform_2(x, y): return int((x - min_x) * 256 / (max_x - min_x)), int((y - min_y) * 256 / (max_y - min_y)) maze_image_small = cv2.resize(cropped_image, (256, 256), interpolation=cv2.INTER_NEAREST) debug_image("Maze", maze_image_small, interpolation=cv2.INTER_NEAREST) # convert the maze image to a numpy bitmap maze_bitmap = np.zeros((256, 256), dtype=np.uint8) for x in range(256): for y in range(256): if maze_image_small[y, x, 0] > 0: maze_bitmap[y, x] = 1 utils.display_image("Maze Bitmap", maze_bitmap * 255, interpolation=cv2.INTER_NEAREST) # use networkx to solve the maze import networkx as nx from networkx import Graph # def transform_3(x, y): # pt = transform_2(x, y) # return pt[0] // 2, pt[1] // 2 G: Graph = nx.grid_2d_graph(256, 256) G.add_edges_from( [((x, y), (x + 1, y + 1)) for x in range(255) for y in range(255)] + [((x + 1, y), (x, y + 1)) for x in range(255) for y in range(255)], weight=1.4, ) for x in range(256): for y in range(256): if maze_bitmap[y, x] == 1: G.remove_node((x, y)) start_node = transform_2(*tag_registry[3].center) end_node = transform_2(*tag_registry[4].center) print(f"Start: {start_node}, End: {end_node}") def dist(a, b): (x1, y1) = a (x2, y2) = b return ((x1 - x2) ** 2 + (y1 - y2) ** 2) ** 0.5 try: path = nx.astar_path(G, start_node, end_node, heuristic=dist) except nx.NetworkXNoPath: raise Exception("No path found") print(f"Shortest path length: {len(path)}") # draw the path on the maze image for x, y in path: maze_image_small[y, x] = [0, 255, 0] debug_image("Maze Path", maze_image_small, interpolation=cv2.INTER_NEAREST) def un_transform(x, y): out_x = int(x * (max_x - min_x) / 256 + min_x) out_y = int(y * (max_y - min_y) / 256 + min_y) return int(out_x * original_width / 4000), int(out_y * original_height / 3000) path = [un_transform(x, y) for x, y in path] return path