Download segmentation.py from q6/N: direct link, hf CLI and curl.
- Browser
- Download file 3.2 kB
-
https://huggingface.co/spaces/q6/N/resolve/main/segmentation.py
- Command line
-
hf download hf://spaces/q6/N/segmentation.py
-
curl -L -o segmentation.py https://huggingface.co/spaces/q6/N/resolve/main/segmentation.py
3.2 kB
| import cv2 | |
| import numpy as np | |
| MASK_VALUE = 255 | |
| ADAPTIVE_BLOCK_SIZE = 35 | |
| ADAPTIVE_OFFSET = 8 | |
| BACKGROUND_RING_WIDTH = 3 | |
| MIN_COMPONENT_AREA = 2 | |
| DEFAULT_MARGIN = 2 | |
| MAX_MARGIN = 10 | |
| POLARITY_AUTO = "Auto" | |
| POLARITY_DARK = "Dark text" | |
| POLARITY_LIGHT = "Light text" | |
| POLARITY_CHOICES = [POLARITY_AUTO, POLARITY_DARK, POLARITY_LIGHT] | |
| def character_mask(rgb_image, polygons, margin=DEFAULT_MARGIN, polarity=POLARITY_AUTO): | |
| margin = int(margin) | |
| if not 0 <= margin <= MAX_MARGIN: | |
| raise ValueError(f"Margin must be between 0 and {MAX_MARGIN}") | |
| if polarity not in POLARITY_CHOICES: | |
| raise ValueError("Invalid text polarity") | |
| gray = cv2.cvtColor(rgb_image, cv2.COLOR_RGB2GRAY) | |
| height, width = gray.shape | |
| mask = np.zeros_like(gray) | |
| padding = max(ADAPTIVE_BLOCK_SIZE // 2, BACKGROUND_RING_WIDTH) | |
| ring_size = BACKGROUND_RING_WIDTH * 2 + 1 | |
| ring_kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (ring_size, ring_size)) | |
| for polygon in polygons: | |
| points = np.asarray(polygon, dtype=np.float32).reshape(-1, 2) | |
| if len(points) < 3 or not np.isfinite(points).all(): | |
| continue | |
| points = np.rint(points).astype(np.int32) | |
| x, y, box_width, box_height = cv2.boundingRect(points) | |
| left, top = max(0, x - padding), max(0, y - padding) | |
| right = min(width, x + box_width + padding) | |
| bottom = min(height, y + box_height + padding) | |
| if left >= right or top >= bottom: | |
| continue | |
| crop = gray[top:bottom, left:right] | |
| region = np.zeros_like(crop) | |
| cv2.fillPoly(region, [points - (left, top)], MASK_VALUE) | |
| inside = region != 0 | |
| if not np.any(inside): | |
| continue | |
| selected_polarity = polarity | |
| if selected_polarity == POLARITY_AUTO: | |
| ring = (cv2.dilate(region, ring_kernel) != 0) & ~inside | |
| background = np.median(crop[ring] if np.any(ring) else crop[inside]) | |
| split, _ = cv2.threshold( | |
| crop[inside], 0, MASK_VALUE, cv2.THRESH_BINARY | cv2.THRESH_OTSU | |
| ) | |
| selected_polarity = POLARITY_DARK if background > split else POLARITY_LIGHT | |
| dark_text = selected_polarity == POLARITY_DARK | |
| threshold_type = cv2.THRESH_BINARY_INV if dark_text else cv2.THRESH_BINARY | |
| offset = ADAPTIVE_OFFSET if dark_text else -ADAPTIVE_OFFSET | |
| foreground = cv2.adaptiveThreshold( | |
| crop, | |
| MASK_VALUE, | |
| cv2.ADAPTIVE_THRESH_GAUSSIAN_C, | |
| threshold_type, | |
| ADAPTIVE_BLOCK_SIZE, | |
| offset, | |
| ) | |
| foreground = cv2.bitwise_and(foreground, region) | |
| count, labels, stats, _ = cv2.connectedComponentsWithStats(foreground, connectivity=8) | |
| keep = np.zeros(count, dtype=np.uint8) | |
| keep[1:] = (stats[1:, cv2.CC_STAT_AREA] >= MIN_COMPONENT_AREA) * MASK_VALUE | |
| cleaned = keep[labels] | |
| target = mask[top:bottom, left:right] | |
| np.maximum(target, cleaned, out=target) | |
| if margin: | |
| kernel_size = margin * 2 + 1 | |
| kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (kernel_size, kernel_size)) | |
| mask = cv2.dilate(mask, kernel) | |
| return mask | |