| import gradio as gr |
| import numpy as np |
| from PIL import Image, ImageDraw |
| import cv2 |
| import torch |
| import warnings |
| warnings.filterwarnings('ignore') |
|
|
| class ObjectRemover: |
| """Remove objects from images using inpainting""" |
| |
| def __init__(self): |
| self.methods = { |
| 'simple': self.simple_remove, |
| 'inpaint': self.inpaint_remove, |
| 'clone': self.clone_remove |
| } |
| |
| def simple_remove(self, image: np.ndarray, mask: np.ndarray) -> np.ndarray: |
| """Simple removal by filling with average color""" |
| result = image.copy() |
| |
| |
| kernel = np.ones((5, 5), np.uint8) |
| dilated_mask = cv2.dilate(mask, kernel, iterations=2) |
| |
| |
| border = dilated_mask - mask |
| |
| |
| border_pixels = image[border > 0] |
| if len(border_pixels) > 0: |
| avg_color = np.mean(border_pixels, axis=0) |
| |
| |
| result[mask > 0] = avg_color.astype(np.uint8) |
| |
| return result |
| |
| def inpaint_remove(self, image: np.ndarray, mask: np.ndarray) -> np.ndarray: |
| """Use OpenCV inpainting""" |
| |
| if len(image.shape) == 3 and image.shape[2] == 3: |
| bgr_image = cv2.cvtColor(image, cv2.COLOR_RGB2BGR) |
| else: |
| bgr_image = cv2.cvtColor(image, cv2.COLOR_RGBA2BGRA) |
| |
| |
| result = cv2.inpaint( |
| bgr_image, |
| mask, |
| inpaintRadius=3, |
| flags=cv2.INPAINT_TELEA |
| ) |
| |
| |
| if len(result.shape) == 3 and result.shape[2] == 3: |
| return cv2.cvtColor(result, cv2.COLOR_BGR2RGB) |
| else: |
| return cv2.cvtColor(result, cv2.COLOR_BGRA2RGBA) |
| |
| def clone_remove(self, image: np.ndarray, mask: np.ndarray) -> np.ndarray: |
| """Use OpenCV seamless cloning""" |
| |
| height, width = image.shape[:2] |
| |
| |
| kernel = np.ones((11, 11), np.uint8) |
| dilated_mask = cv2.dilate(mask, kernel, iterations=3) |
| |
| |
| non_masked = 255 - dilated_mask |
| |
| |
| contours, _ = cv2.findContours(non_masked, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) |
| |
| if contours: |
| |
| largest_contour = max(contours, key=cv2.contourArea) |
| x, y, w, h = cv2.boundingRect(largest_contour) |
| |
| |
| source_patch = image[y:y+h, x:x+w] |
| |
| |
| mask_height, mask_width = np.where(mask > 0) |
| if len(mask_height) > 0 and len(mask_width) > 0: |
| min_y, max_y = mask_height.min(), mask_height.max() |
| min_x, max_x = mask_width.min(), mask_width.max() |
| |
| patch_height = max_y - min_y + 1 |
| patch_width = max_x - min_x + 1 |
| |
| if patch_height > 0 and patch_width > 0: |
| source_resized = cv2.resize(source_patch, (patch_width, patch_height)) |
| |
| |
| result = image.copy() |
| result[min_y:max_y+1, min_x:max_x+1] = source_resized |
| return result |
| |
| return self.simple_remove(image, mask) |
| |
| def remove_object(self, image: np.ndarray, mask: np.ndarray, method: str = 'inpaint') -> np.ndarray: |
| """Remove object using specified method""" |
| if method in self.methods: |
| return self.methods[method](image, mask) |
| return self.inpaint_remove(image, mask) |
|
|
| |
| remover = ObjectRemover() |
|
|
| def process_object_removal( |
| image: np.ndarray, |
| brush_size: int, |
| method: str |
| ) -> Tuple[np.ndarray, np.ndarray, np.ndarray]: |
| """Process image with object removal""" |
| try: |
| |
| mask = np.zeros(image.shape[:2], dtype=np.uint8) |
| |
| |
| height, width = image.shape[:2] |
| |
| |
| center_x, center_y = width // 2, height // 2 |
| radius = min(width, height) // 4 |
| |
| |
| cv2.circle(mask, (center_x, center_y), radius, 255, -1) |
| |
| |
| result = remover.remove_object(image, mask, method) |
| |
| |
| visualization = image.copy() |
| visualization[mask > 0] = [255, 0, 0] |
| |
| |
| mask_viz = cv2.cvtColor(mask, cv2.COLOR_GRAY2RGB) |
| |
| return result, visualization, mask_viz |
| |
| except Exception as e: |
| print(f"Error: {e}") |
| return image, image, np.zeros_like(image) |