""" ============================================================================= Bilinear Interpolation for Image Rescaling (Headless Backend Engine) ------------------------------------------------------------------ A pedagogical Python implementation demonstrating: 1. Inverse affine mapping from destination -> source coordinates 2. Area-weighted Lagrange (bilinear) interpolation 3. Nearest-neighbor baseline for comparison Mathematical Formulation ~~~~~~~~~~~~~~~~~~~~~~~~ Given sub-pixel source coordinates (x_i, y_i): x1 = floor(x_i), x2 = x1 + 1 y1 = floor(y_i), y2 = y1 + 1 dx = x_i - x1 (horizontal fractional distance) dy = y_i - y1 (vertical fractional distance) The interpolated intensity is: f(x_i, y_i) = (1-dx)(1-dy) . f(x1,y1) + dx (1-dy) . f(x2,y1) + (1-dx) dy . f(x1,y2) + dx dy . f(x2,y2) Stack : Python 3, NumPy, OpenCV (cv2), tqdm (No Matplotlib) ============================================================================= """ import numpy as np import cv2 from tqdm import tqdm import os import sys # ========================================================================= # 1. BILINEAR INTERPOLATION (core algorithm - from scratch) # ========================================================================= def bilinear_interpolation(src, dst_height, dst_width, disable_tqdm=False): """ Rescale *src* to (dst_height x dst_width) using bilinear interpolation. For every pixel (u, v) in the destination image: 1. Inverse affine mapping: x_i = u / S_x , y_i = v / S_y 2. Four integer neighbours with boundary clamping 3. Fractional distances dx, dy 4. Area-weighted Lagrange formula """ src_h, src_w = src.shape[:2] is_color = (src.ndim == 3) S_x = dst_height / src_h S_y = dst_width / src_w if is_color: dst = np.zeros((dst_height, dst_width, src.shape[2]), dtype=np.float64) else: dst = np.zeros((dst_height, dst_width), dtype=np.float64) print("\n[BILINEAR] Bilinear Interpolation -- processing rows...") for u in tqdm(range(dst_height), desc="Bilinear", unit="row", bar_format="{l_bar}{bar:40}{r_bar}", disable=disable_tqdm): # Step 1 - Inverse mapping: destination row -> source row x_i = u / S_x # Step 2a - Integer neighbours (row) & clamping x1 = int(np.floor(x_i)) x2 = min(x1 + 1, src_h - 1) x1 = min(x1, src_h - 1) # Step 3a - Vertical fractional distance dx = x_i - int(np.floor(x_i)) for v in range(dst_width): # Step 1 - Inverse mapping: destination col -> source col y_i = v / S_y # Step 2b - Integer neighbours (col) & clamping y1 = int(np.floor(y_i)) y2 = min(y1 + 1, src_w - 1) y1 = min(y1, src_w - 1) # Step 3b - Horizontal fractional distance dy = y_i - int(np.floor(y_i)) # Step 4 - Bilinear (area-weighted Lagrange) formula # f(x_i,y_i) = (1-dx)(1-dy).f(x1,y1) + dx(1-dy).f(x2,y1) # + (1-dx)dy.f(x1,y2) + dx.dy.f(x2,y2) f_x1y1 = src[x1, y1].astype(np.float64) f_x2y1 = src[x2, y1].astype(np.float64) f_x1y2 = src[x1, y2].astype(np.float64) f_x2y2 = src[x2, y2].astype(np.float64) dst[u, v] = ( (1 - dx) * (1 - dy) * f_x1y1 + dx * (1 - dy) * f_x2y1 + (1 - dx) * dy * f_x1y2 + dx * dy * f_x2y2 ) return np.clip(dst, 0, 255).astype(np.uint8) # ========================================================================= # 2. NEAREST-NEIGHBOUR BASELINE # ========================================================================= def nearest_neighbor(src, dst_height, dst_width, disable_tqdm=False): """ Rescale using nearest-neighbour: round to closest source pixel. Produces characteristic "blocky" artefacts. """ src_h, src_w = src.shape[:2] is_color = (src.ndim == 3) S_x = dst_height / src_h S_y = dst_width / src_w if is_color: dst = np.zeros((dst_height, dst_width, src.shape[2]), dtype=np.uint8) else: dst = np.zeros((dst_height, dst_width), dtype=np.uint8) print("\n[NN] Nearest-Neighbour -- processing rows...") for u in tqdm(range(dst_height), desc="Nearest ", unit="row", bar_format="{l_bar}{bar:40}{r_bar}", disable=disable_tqdm): x_near = min(int(round(u / S_x)), src_h - 1) for v in range(dst_width): y_near = min(int(round(v / S_y)), src_w - 1) dst[u, v] = src[x_near, y_near] return dst # ========================================================================= # 3. REAL IMAGE RESCALING # ========================================================================= def rescale_image(image_path, scale=2.0): """Load a real image, upscale by *scale*, return (original, nn, bilinear).""" src = cv2.imread(image_path) if src is None: print(f"[WARN] Could not read '{image_path}'. Skipping.") return None, None, None h, w = src.shape[:2] new_h, new_w = int(h * scale), int(w * scale) print("=" * 65) print(f" IMAGE RESCALING : {os.path.basename(image_path)}") print(f" {w}x{h} -> {new_w}x{new_h} (scale = {scale}x)") print("=" * 65) nn_result = nearest_neighbor(src, new_h, new_w) bil_result = bilinear_interpolation(src, new_h, new_w) return src, nn_result, bil_result # ========================================================================= # 4. GENERATE A SAMPLE TEST IMAGE # ========================================================================= def generate_sample_image(path="sample_input.png", size=64): """Create a small colour test image with gradients and shapes.""" img = np.zeros((size, size, 3), dtype=np.uint8) for r in range(size): img[r, :, 2] = int(255 * r / (size - 1)) for c in range(size): img[:, c, 1] = int(255 * c / (size - 1)) for r in range(size): for c in range(size): if abs(r - c) < size // 8: img[r, c, 0] = 200 s = size // 4 img[s:3*s, s:3*s] = [255, 255, 255] cv2.circle(img, (size // 2, size // 2), size // 8, (30, 30, 30), -1) cv2.imwrite(path, img) print(f"[OK] Generated sample image -> {path} ({size}x{size})") return path # ========================================================================= # 5. MAIN ENTRY POINT (Headless execution) # ========================================================================= def main(): """ Execution flow ~~~~~~~~~~~~~~ Optionally rescale a real image with tqdm progress (no UI rendering). """ sample_path = "sample_input.png" if len(sys.argv) > 1 and os.path.isfile(sys.argv[1]): sample_path = sys.argv[1] print(f"\n[INFO] Using user-supplied image: {sample_path}") else: sample_path = generate_sample_image(sample_path, size=64) scale_factor = 3.0 src_img, nn_img, bil_img = rescale_image(sample_path, scale=scale_factor) print("\n[DONE] All math operations executed successfully in headless mode.") # ========================================================================= if __name__ == "__main__": main()