Tesseract / utils /connectivity.py
yansari's picture
Deploy: floorplan rendered as DOM element, edges always visible
1e3df84
Raw
History Blame Contribute Delete
14.4 kB
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):
# Convert target_color to a numpy array for element-wise operations
target_color = np.array(target_color)
# Calculate the lower and upper bounds
lower_bound = np.clip(target_color - tolerance, 0, 255)
upper_bound = np.clip(target_color + tolerance, 0, 255)
# Create the mask using the color range
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): # Skip background (label 0)
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): # Skip background (label 0)
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)
# Create wall, corridor, and outdoor masks
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)
# Clean and label components for corridor and outdoor
_, 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)
# Detect the color #FFCC66 (RGB: [255, 204, 102])
room_color = [255, 204, 102]
room_mask = create_color_mask(image_array, room_color, tolerance)
# Extract room pixels and convert to list of [y, x] format
room_pixels = [[[y, x] for y, x in np.argwhere(room_mask == 255)]]
# Return the image array along with the corridor, outdoor, and room pixels
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]] # Start from the top-left pixel
while queue:
x, y = queue.pop(0)
if (x, y) in visited:
continue
visited.add((x, y))
marked_pixels.append((x, y))
# Plot the pixel as a circle
circle = Circle((y, x), radius=3, color=color, fill=True)
ax.add_patch(circle)
# Add neighbors that are `distance` away
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))
# Grow and plot for each corridor and outdoor blob
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 # Unpack point
keep_point = True
# Check each bounding box
for bbox in door_bboxes:
#y1, x1, y2, x2 = bbox
x1, y1, x2, y2 = bbox
# Expand the bounding box by 3 pixels on all sides
y1_expanded = max(0, y1 - 3)
x1_expanded = max(0, x1 - 3)
y2_expanded = y2 + 3
x2_expanded = x2 + 3
# Check if the point lies within the expanded bounding box
if y1_expanded <= y <= y2_expanded and x1_expanded <= x <= x2_expanded:
keep_point = False
break # If the point is inside any bounding box, skip it
# If the point is not inside any bounding box, add it to the refined list
if keep_point:
refined_points.append(point)
# Return the refined points as a numpy array in the same format as input
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])) # Sort by top-left corner
while door_bboxes:
# Take the first bounding box from the list
current_bbox = door_bboxes.pop(0)
x1, y1, x2, y2 = current_bbox
merged = False
# Check for overlap with other bounding boxes
for i, (bx1, by1, bx2, by2) in enumerate(door_bboxes):
# Check if the bounding boxes overlap
if not (x2 < bx1 or x1 > bx2 or y2 < by1 or y1 > by2): # If they overlap
# Merge the bounding boxes
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 no overlaps, just add the current bbox as is
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"):
# Load the image
image = cv2.imread(image_path)
if image is None:
raise FileNotFoundError(f"Image not found: {image_path}")
# Convert to RGB for easier visualization with matplotlib
image_rgb = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
# Step 1: Paint corridor, outdoor, and room pixels in the image
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] # Light green
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] # Light blue
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] # Light orange
# Save the first image with colorized pixels
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()
# Step 2: Draw doors and extended bounding boxes
print("Drawing doors and extended bounding boxes...")
# Create a copy of the image for overlay
overlay_image = image_rgb.copy()
for bbox in door_bboxes:
x1, y1, x2, y2 = bbox
# Create 4 extended bounding boxes
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]
# Draw the extended bounding boxes in red (1px thick)
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) # Red color
# Draw the original door bbox in blue (2px thick)
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) # Blue color
# Save the image with doors and extended bounding boxes
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()
# Step 3: Categorize and plot door bounding boxes based on extended bbox analysis
print("Categorizing door bounding boxes...")
door_image = image_rgb.copy()
for bbox in door_bboxes:
x1, y1, x2, y2 = bbox
# Create opposite extended bounding boxes
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]
# Check pixel content in the extended bounding boxes
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):
# Wardrobe door: color pink
cv2.rectangle(door_image, (x1, y1), (x2, y2), (255, 105, 180), 2) # Pink color
else:
# Default: color blue
cv2.rectangle(door_image, (x1, y1), (x2, y2), (0, 0, 255), 2) # Blue color
# Save the categorized door image
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.")