| import numpy as np |
| import cv2 |
| from PIL import Image |
| import matplotlib.pyplot as plt |
| from matplotlib.patches import Circle |
| from matplotlib.patches import Rectangle |
|
|
| def create_wall_mask(image_array, tolerance=240, proximity=70): |
| black_color = np.array([0, 0, 0]) |
| lower_bound = np.clip(black_color - tolerance, 0, 255) |
| upper_bound = np.clip(black_color + tolerance, 0, 255) |
| wall_mask = cv2.inRange(image_array, lower_bound, upper_bound) |
| kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (2 * proximity + 1, 2 * proximity + 1)) |
| expanded_wall_mask = cv2.dilate(wall_mask, kernel) |
| return expanded_wall_mask |
|
|
| def create_color_mask(image_array, target_color, tolerance=20): |
| |
| target_color = np.array(target_color) |
| |
| |
| lower_bound = np.clip(target_color - tolerance, 0, 255) |
| upper_bound = np.clip(target_color + tolerance, 0, 255) |
| |
| |
| mask = cv2.inRange(image_array, lower_bound, upper_bound) |
| |
| return mask |
|
|
| def extract_pixels_per_blob(labels, num_labels): |
| blobs = [] |
| for i in range(1, num_labels): |
| blob_pixels = np.argwhere(labels == i) |
| blobs.append([tuple(coord) for coord in blob_pixels]) |
| return blobs |
|
|
| def clean_and_label_components(mask, wall_mask, area_threshold=1000): |
| cleaned_mask = cv2.bitwise_and(mask, cv2.bitwise_not(wall_mask)) |
| num_labels, labels, stats, _ = cv2.connectedComponentsWithStats(cleaned_mask) |
|
|
| filtered_mask = np.zeros_like(cleaned_mask) |
| valid_labels = [] |
| for i in range(1, num_labels): |
| if stats[i, cv2.CC_STAT_AREA] >= area_threshold: |
| filtered_mask[labels == i] = 255 |
| valid_labels.append(i) |
|
|
| num_filtered_labels, filtered_labels, _, _ = cv2.connectedComponentsWithStats(filtered_mask) |
|
|
| blobs = extract_pixels_per_blob(filtered_labels, num_filtered_labels) |
|
|
| return filtered_labels, num_filtered_labels - 1, blobs |
|
|
| def process_image(image_path, corridor_color, outdoor_color, tolerance=20, wall_tolerance=240, proximity=5, area_threshold=1000): |
| image = Image.open(image_path) |
| image_rgb = image.convert("RGB") |
| image_array = np.array(image_rgb) |
|
|
| |
| wall_mask = create_wall_mask(image_array, wall_tolerance, proximity) |
| corridor_mask = create_color_mask(image_array, corridor_color, tolerance) |
| outdoor_mask = create_color_mask(image_array, outdoor_color, tolerance) |
|
|
| |
| _, corridor_count, corridor_pixels = clean_and_label_components(corridor_mask, wall_mask, area_threshold) |
| _, outdoor_count, outdoor_pixels = clean_and_label_components(outdoor_mask, wall_mask, area_threshold) |
|
|
| |
| room_color = [255, 204, 102] |
| room_mask = create_color_mask(image_array, room_color, tolerance) |
| |
| |
| room_pixels = [[[y, x] for y, x in np.argwhere(room_mask == 255)]] |
| |
| |
| return image_array, corridor_pixels, outdoor_pixels, room_pixels |
| |
| def grow_regions(image_array, corridor_pixels, outdoor_pixels, distance=20, connect_img_dir=""): |
| """ |
| Grows regions starting from the top-left corner of each blob, selecting pixels |
| that are `distance` pixels apart. Plots the result with red circles on the original image. |
| |
| Args: |
| image_array (np.ndarray): The original image array. |
| corridor_pixels (List[List[Tuple[int, int]]]): List of corridor blobs. |
| outdoor_pixels (List[List[Tuple[int, int]]]): List of outdoor blobs. |
| distance (int): Distance in pixels for growing regions (default: 20). |
| connect_img_dir (str): Directory to save the resulting image. |
| |
| Returns: |
| np.ndarray: Array of all marked pixel coordinates. |
| """ |
| height, width, _ = image_array.shape |
| print(f"Plot dimensions: {width}x{height}") |
|
|
| fig, ax = plt.subplots(figsize=(width / 100, height / 100), dpi=100) |
| ax.imshow(image_array) |
|
|
| marked_pixels = [] |
|
|
| def grow_blob(blob, color='red'): |
| nonlocal marked_pixels |
| visited = set() |
| queue = [blob[0]] |
| while queue: |
| x, y = queue.pop(0) |
| if (x, y) in visited: |
| continue |
| visited.add((x, y)) |
| marked_pixels.append((x, y)) |
| |
| circle = Circle((y, x), radius=3, color=color, fill=True) |
| ax.add_patch(circle) |
|
|
| |
| for dx in range(-distance, distance + 1, distance): |
| for dy in range(-distance, distance + 1, distance): |
| if dx == 0 and dy == 0: |
| continue |
| nx, ny = x + dx, y + dy |
| if (nx, ny) in blob and (nx, ny) not in visited: |
| queue.append((nx, ny)) |
|
|
| |
| for i, corridor_blob in enumerate(corridor_pixels, start=1): |
| print(f"Processing corridor blob {i}/{len(corridor_pixels)}") |
| grow_blob(corridor_blob, color='red') |
|
|
| ax.set_xlim(0, width) |
| ax.set_ylim(height, 0) |
| ax.axis('off') |
|
|
| if connect_img_dir: |
| save_path = f"{connect_img_dir}/grown_regions.png" |
| plt.savefig(save_path, dpi=300, bbox_inches='tight', pad_inches=0) |
| print(f"Image saved at: {save_path}") |
|
|
| plt.close(fig) |
| return np.array(marked_pixels) |
|
|
| def plot_first_ten_pixels(image_path, marked_pixels, results_dir): |
| """ |
| Plots the first ten marked pixels on the image with their coordinates displayed as text. |
| Args: |
| image_array (np.ndarray): The original image array. |
| marked_pixels (np.ndarray): Array of all marked pixel coordinates. |
| """ |
| image = Image.open(image_path) |
| image_rgb = image.convert("RGB") |
| image_array = np.array(image_rgb) |
|
|
| fig, ax = plt.subplots(figsize=(10, 10)) |
| ax.imshow(image_array) |
|
|
| for i, (x, y) in enumerate(marked_pixels[:60]): |
| ax.scatter(y, x, color='blue', s=10) |
| if i%3==0: |
| ax.text(y + 5, x, f'({x}, {y})', color='red', fontsize=8) |
|
|
| ax.set_xlim(0, image_array.shape[1]) |
| ax.set_ylim(image_array.shape[0], 0) |
| ax.set_xlabel("Width (pixels)") |
| ax.set_ylabel("Height (pixels)") |
| ax.set_title("First Ten Marked Pixels") |
| save_path = f"{results_dir}/10_checker.png" |
| plt.savefig(save_path, dpi=300, bbox_inches='tight', pad_inches=0) |
| plt.close() |
| |
|
|
| def refine_marked_points(marked_points, door_bboxes): |
| """ |
| Refine the list of marked points by removing those within the expanded bounding boxes. |
| |
| Args: |
| marked_points (np.ndarray): Array of points in (y, x) format, shape (N, 2). |
| door_bboxes (np.ndarray): Array of bounding boxes in [y1, x1, y2, x2] format. |
| |
| Returns: |
| np.ndarray: Refined array of marked points that are outside the expanded bounding boxes, |
| in the same format as the input (N, 2). |
| """ |
| refined_points = [] |
|
|
| for point in marked_points: |
| y, x = point |
| keep_point = True |
| |
| |
| for bbox in door_bboxes: |
| |
| x1, y1, x2, y2 = bbox |
|
|
| |
| y1_expanded = max(0, y1 - 3) |
| x1_expanded = max(0, x1 - 3) |
| y2_expanded = y2 + 3 |
| x2_expanded = x2 + 3 |
|
|
| |
| if y1_expanded <= y <= y2_expanded and x1_expanded <= x <= x2_expanded: |
| keep_point = False |
| break |
| |
| |
| if keep_point: |
| refined_points.append(point) |
| |
| |
| return np.array(refined_points) |
|
|
| def merge_overlapping_bboxes(door_bboxes): |
| """ |
| Merge overlapping door bounding boxes into larger bounding boxes. |
| |
| Args: |
| door_bboxes (List[List[int]]): List of bounding boxes in the form [xmin, ymin, xmax, ymax]. |
| |
| Returns: |
| List[List[int]]: List of merged bounding boxes. |
| """ |
| merged_bboxes = [] |
| door_bboxes = sorted(door_bboxes, key=lambda bbox: (bbox[0], bbox[1])) |
| |
| while door_bboxes: |
| |
| current_bbox = door_bboxes.pop(0) |
| x1, y1, x2, y2 = current_bbox |
| merged = False |
| |
| |
| for i, (bx1, by1, bx2, by2) in enumerate(door_bboxes): |
| |
| if not (x2 < bx1 or x1 > bx2 or y2 < by1 or y1 > by2): |
| |
| new_bbox = [min(x1, bx1), min(y1, by1), max(x2, bx2), max(y2, by2)] |
| door_bboxes.pop(i) |
| door_bboxes.append(new_bbox) |
| merged = True |
| break |
| |
| if not merged: |
| merged_bboxes.append(current_bbox) |
| |
| |
| if not merged: |
| merged_bboxes.append(current_bbox) |
| |
| return merged_bboxes |
|
|
|
|
| def paint_and_overlay_doors(image_path, corridor_pixels, outdoor_pixels, room_pixels, door_bboxes, buffer_size=20, save_path="overlay1.png"): |
| |
| image = cv2.imread(image_path) |
| if image is None: |
| raise FileNotFoundError(f"Image not found: {image_path}") |
|
|
| |
| image_rgb = cv2.cvtColor(image, cv2.COLOR_BGR2RGB) |
|
|
| |
| print("Painting corridor pixels (light green)...") |
| for pixels in corridor_pixels: |
| for y, x in pixels: |
| if 0 <= x < image.shape[1] and 0 <= y < image.shape[0]: |
| image_rgb[y, x] = [144, 238, 144] |
|
|
| print("Painting outdoor pixels (light blue)...") |
| for pixels in outdoor_pixels: |
| for y, x in pixels: |
| if 0 <= x < image.shape[1] and 0 <= y < image.shape[0]: |
| image_rgb[y, x] = [173, 216, 230] |
|
|
| print("Painting room pixels (light orange)...") |
| for pixels in room_pixels: |
| for y, x in pixels: |
| if 0 <= x < image.shape[1] and 0 <= y < image.shape[0]: |
| image_rgb[y, x] = [252, 224, 169] |
|
|
| |
| plt.imshow(image_rgb) |
| plt.axis('off') |
| plt.savefig(save_path, bbox_inches='tight', pad_inches=0, dpi=image.shape[1]/plt.gcf().get_size_inches()[0]) |
| plt.close() |
|
|
| |
| print("Drawing doors and extended bounding boxes...") |
|
|
| |
| overlay_image = image_rgb.copy() |
|
|
| for bbox in door_bboxes: |
| x1, y1, x2, y2 = bbox |
|
|
| |
| top_bbox = [x1, y1 - buffer_size, x2, y1] |
| bottom_bbox = [x1, y2, x2, y2 + buffer_size] |
| left_bbox = [x1 - buffer_size, y1, x1, y2] |
| right_bbox = [x2, y1, x2 + buffer_size, y2] |
|
|
| |
| for extended_bbox in [top_bbox, bottom_bbox, left_bbox, right_bbox]: |
| ex1, ey1, ex2, ey2 = extended_bbox |
| if 0 <= ex1 < overlay_image.shape[1] and 0 <= ey1 < overlay_image.shape[0]: |
| cv2.rectangle(overlay_image, (ex1, ey1), (ex2, ey2), (255, 0, 0), 1) |
|
|
| |
| if 0 <= x1 < overlay_image.shape[1] and 0 <= y1 < overlay_image.shape[0]: |
| cv2.rectangle(overlay_image, (x1, y1), (x2, y2), (0, 0, 255), 2) |
|
|
| |
| plt.imshow(overlay_image) |
| plt.axis('off') |
| plt.savefig("overlay_with_doors.png", bbox_inches='tight', pad_inches=0, dpi=image.shape[1]/plt.gcf().get_size_inches()[0]) |
| plt.close() |
|
|
| |
| print("Categorizing door bounding boxes...") |
|
|
| door_image = image_rgb.copy() |
|
|
| for bbox in door_bboxes: |
| x1, y1, x2, y2 = bbox |
|
|
| |
| top_bbox = [x1, y1 - buffer_size, x2, y1] |
| bottom_bbox = [x1, y2, x2, y2 + buffer_size] |
| left_bbox = [x1 - buffer_size, y1, x1, y2] |
| right_bbox = [x2, y1, x2 + buffer_size, y2] |
|
|
| |
| top_pixels = image_rgb[max(0, y1 - buffer_size):y1, x1:x2] |
| bottom_pixels = image_rgb[y2:min(image_rgb.shape[0], y2 + buffer_size), x1:x2] |
| left_pixels = image_rgb[y1:y2, max(0, x1 - buffer_size):x1] |
| right_pixels = image_rgb[y1:y2, x2:min(image_rgb.shape[1], x2 + buffer_size)] |
|
|
| top_majority_white = np.mean(np.all(top_pixels == [255, 255, 255], axis=-1)) > 0.5 |
| bottom_majority_orange = np.mean(np.all(bottom_pixels == [252, 224, 169], axis=-1)) > 0.5 |
| left_majority_white = np.mean(np.all(left_pixels == [255, 255, 255], axis=-1)) > 0.5 |
| right_majority_orange = np.mean(np.all(right_pixels == [252, 224, 169], axis=-1)) > 0.5 |
|
|
| if (top_majority_white and bottom_majority_orange) or (left_majority_white and right_majority_orange): |
| |
| cv2.rectangle(door_image, (x1, y1), (x2, y2), (255, 105, 180), 2) |
| else: |
| |
| cv2.rectangle(door_image, (x1, y1), (x2, y2), (0, 0, 255), 2) |
|
|
| |
| plt.imshow(door_image) |
| plt.axis('off') |
| plt.savefig("categorized_doors.png", bbox_inches='tight', pad_inches=0, dpi=image.shape[1]/plt.gcf().get_size_inches()[0]) |
| plt.close() |
|
|
| print("Categorized door image saved.") |
|
|