Spaces:
Sleeping
Sleeping
File size: 3,850 Bytes
fc895f4 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 | """
binarizer.py
------------
Converts a grayscale floor plan image into a clean binary (black/white) image.
Pipeline:
1. Gaussian blur → reduce sensor/scan noise
2. Adaptive threshold → handle uneven lighting across the page
3. Morphological close → fill tiny gaps in wall lines
4. Morphological open → remove isolated specks
"""
import cv2
import numpy as np
def binarize(
img: np.ndarray,
blur_kernel: int = 5,
block_size: int = 25,
c_offset: int = 10,
morph_kernel: int = 3,
) -> np.ndarray:
"""
Convert a grayscale image to a clean binary image.
Args:
img: Grayscale uint8 numpy array.
blur_kernel: Gaussian blur kernel size (must be odd).
block_size: Neighbourhood size for adaptive threshold (must be odd, ≥3).
c_offset: Constant subtracted from the mean in adaptive threshold.
Higher = more aggressive (removes faint lines too).
morph_kernel: Kernel size for morphological cleanup.
Returns:
Binary image (0 = background, 255 = foreground/walls), uint8.
"""
_validate_grayscale(img)
# 1. Gaussian blur to suppress scan noise
blurred = cv2.GaussianBlur(img, (blur_kernel, blur_kernel), 0)
# 2. Adaptive threshold — handles uneven illumination better than Otsu
# THRESH_BINARY_INV: walls/lines become white (255) on black background
binary = cv2.adaptiveThreshold(
blurred,
maxValue=255,
adaptiveMethod=cv2.ADAPTIVE_THRESH_GAUSSIAN_C,
thresholdType=cv2.THRESH_BINARY_INV,
blockSize=block_size,
C=c_offset,
)
# 3. Morphological closing: fills small breaks in wall lines
close_kernel = cv2.getStructuringElement(
cv2.MORPH_RECT, (morph_kernel, morph_kernel)
)
binary = cv2.morphologyEx(binary, cv2.MORPH_CLOSE, close_kernel)
# 4. Morphological opening: removes isolated noise specks
open_kernel = cv2.getStructuringElement(
cv2.MORPH_RECT, (morph_kernel, morph_kernel)
)
binary = cv2.morphologyEx(binary, cv2.MORPH_OPEN, open_kernel)
return binary
def remove_small_components(
binary: np.ndarray, min_area: int = 100
) -> np.ndarray:
"""
Remove connected components smaller than min_area pixels.
Useful for eliminating text fragments and scan artifacts.
Args:
binary: Binary image (uint8, values 0 or 255).
min_area: Components with fewer pixels than this are removed.
Returns:
Cleaned binary image.
"""
_validate_grayscale(binary)
num_labels, labels, stats, _ = cv2.connectedComponentsWithStats(
binary, connectivity=8
)
# Background is label 0 — skip it
cleaned = np.zeros_like(binary)
for label in range(1, num_labels):
area = stats[label, cv2.CC_STAT_AREA]
if area >= min_area:
cleaned[labels == label] = 255
return cleaned
def enhance_contrast(img: np.ndarray) -> np.ndarray:
"""
Apply CLAHE (Contrast Limited Adaptive Histogram Equalization).
Improves visibility of faint lines before thresholding.
Args:
img: Grayscale uint8 numpy array.
Returns:
Contrast-enhanced grayscale image.
"""
_validate_grayscale(img)
clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8, 8))
return clahe.apply(img)
def _validate_grayscale(img: np.ndarray) -> None:
if img is None or not isinstance(img, np.ndarray):
raise TypeError("Input must be a numpy ndarray.")
if len(img.shape) != 2:
raise ValueError(
f"Expected a grayscale (2D) image, got shape {img.shape}. "
"Convert to grayscale first."
)
|