File size: 3,309 Bytes
03e312d 6dc74d5 | 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 | def preprocess_image(image):
from PIL import Image
import cv2
import numpy as np
from skimage.filters import threshold_sauvola
####### crop #######
padding=40
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
# Strong Blur to merge text strokes and smooth paper ripples
blurred = cv2.GaussianBlur(gray, (7, 7), 0)
# Adaptive Threshold (Block size 21, Constant 10)
# ignores large shadows and focuses on local contrast
thresh = cv2.adaptiveThreshold(blurred, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY_INV, 21, 10)
# clean up: Remove small dots (salt & pepper)
kernel = np.ones((3,3), np.uint8)
# Opening removes small white noise from the background
thresh = cv2.morphologyEx(thresh, cv2.MORPH_OPEN, kernel, iterations=1)
# Dilation makes the text "thick" so we don't miss thin strokes
thresh = cv2.dilate(thresh, kernel, iterations=2)
# Find Contours and filter by area
contours, _ = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
if contours:
# Filter out very small noise (area < 100 pixels)
valid_cnts = [cnt for cnt in contours if cv2.contourArea(cnt) > 100]
if valid_cnts:
# Get one big bounding box for all valid text pieces
all_pts = np.concatenate(valid_cnts)
x, y, w, h = cv2.boundingRect(all_pts)
# Apply Padding
h_img, w_img = image.shape[:2]
x1 = max(0, x - padding)
y1 = max(0, y - padding)
x2 = min(w_img, x + w + padding)
y2 = min(h_img, y + h + padding)
image = image[y1:y2, x1:x2]
####### binarize #######
image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
thresh = threshold_sauvola(image, window_size=25, k=0.2)
image = ((image > thresh) * 255).astype(np.uint8)
####### deskew #######
gray = image.copy()
gray = cv2.bitwise_not(gray)
# Threshold to make text white on black background
thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU)[1]
# Find all coordinates where pixels are "text" (white)
coords = np.column_stack(np.where(thresh > 0))
# find the minimum area rectangle that encloses all points
# This rectangle will be tilted at the same angle as the text
angle = cv2.minAreaRect(coords)[-1]
# Handle OpenCV's angle logic
if angle < -45:
angle = -(90 + angle)
else:
angle = -angle
# Rotate the image to deskew
(h, w) = image.shape[:2]
center = (w // 2, h // 2)
M = cv2.getRotationMatrix2D(center, angle, 1.0)
image = cv2.warpAffine(image, M, (w, h), flags=cv2.INTER_CUBIC, borderMode=cv2.BORDER_REPLICATE)
####### resize #######
image = Image.fromarray(image).convert("RGB")
target_h = 102
target_w = 512
w, h = image.size
scale = target_h / h
new_w = int(w * scale)
new_h = target_h
image = image.resize((new_w, new_h), Image.Resampling.LANCZOS)
canvas = Image.new("RGB", (target_w, target_h), 255)
if new_w <= target_w:
canvas.paste(image, (0, 0))
else:
image = image.resize((target_w, new_h), Image.Resampling.LANCZOS)
canvas.paste(image, (0, 0))
return canvas.convert("L").convert("RGB") |