diff --git a/deblur/deblur_2d.py b/deblur/deblur_2d.py index 4285c62..3aca925 100644 --- a/deblur/deblur_2d.py +++ b/deblur/deblur_2d.py @@ -4,6 +4,7 @@ from scipy.sparse import lil_matrix from scipy.sparse.linalg import spsolve import cv2 import matplotlib.pyplot as plt +from pathlib import Path """ https://setosa.io/ev/image-kernels/ @@ -16,61 +17,183 @@ def show(img): cv2.destroyAllWindows() -# Define 2D image and kernel -image = cv2.imread('assets/omas.png', 0) -image = cv2.resize(image, (200, 200), interpolation= cv2.INTER_LINEAR) +def demo(image_file): + # Define 2D image and kernel + image = cv2.imread(image_file, 0) + image = cv2.resize(image, (200, 200), interpolation= cv2.INTER_LINEAR) -kernel = np.array([ - [1, 2, 1], - [2, 4, 2], - [1, 2, 1] -], dtype=np.float32) -kernel /= kernel.sum() # Normalize + kernel = np.array([ + [1, 2, 1], + [2, 4, 2], + [1, 2, 1] + ], dtype=np.float32) + kernel /= kernel.sum() # Normalize -print(kernel) + print(kernel) -# Perform 2D convolution (blurring) -blurred = convolve2d(image, kernel, mode="same", boundary="fill", fillvalue=0) + # Perform 2D convolution (blurring) + blurred = convolve2d(image, kernel, mode="same", boundary="fill", fillvalue=0) -h, w = image.shape -kh, kw = kernel.shape -pad_h, pad_w = kh // 2, kw // 2 + h, w = image.shape + kh, kw = kernel.shape + pad_h, pad_w = kh // 2, kw // 2 -show(image) -show(blurred) + show(image) + show(blurred) -print("Original image:\n", image) -print("\nBlurred image:\n", blurred) + print("Original image:\n", image) + print("\nBlurred image:\n", blurred) -print("\nBuilding linear system for deconvolution...") + print("\nBuilding linear system for deconvolution...") -# Step 2: Build sparse matrix A -N = h * w -A = lil_matrix((N, N), dtype=np.float32) -b = blurred.flatten() + # Step 2: Build sparse matrix A + N = h * w + A = lil_matrix((N, N), dtype=np.float32) + b = blurred.flatten() -def index(y, x): - return y * w + x + def index(y, x): + return y * w + x -for y in range(h): - for x in range(w): - row_idx = index(y, x) - for ky in range(kh): - for kx in range(kw): - iy = y + ky - pad_h - ix = x + kx - pad_w - if 0 <= iy < h and 0 <= ix < w: - col_idx = index(iy, ix) - A[row_idx, col_idx] += kernel[ky, kx] + for y in range(h): + for x in range(w): + row_idx = index(y, x) + for ky in range(kh): + for kx in range(kw): + iy = y + ky - pad_h + ix = x + kx - pad_w + if 0 <= iy < h and 0 <= ix < w: + col_idx = index(iy, ix) + A[row_idx, col_idx] += kernel[ky, kx] -# Step 3: Solve the sparse system A * x = b -x = spsolve(A.tocsr(), b) -deblurred = x.reshape((h, w)) + # Step 3: Solve the sparse system A * x = b + x = spsolve(A.tocsr(), b) + deblurred = x.reshape((h, w)) -print("\nDeblurred image:\n", np.round(deblurred, 2)) + print("\nDeblurred image:\n", np.round(deblurred, 2)) -show(deblurred) + show(deblurred) + + +def get_mask(image_file): + mask_file = Path(image_file) + mask_file = mask_file.with_name("mask_" + mask_file.name) + + if mask_file.exists(): + return cv2.imread(str(mask_file), 0) + + drawing = False # True when mouse is pressed + brush_size = 5 + image = cv2.imread(image_file) + mask = np.zeros(image.shape[:2], dtype=np.uint8) + clone = image.copy() + + def draw_mask(event, x, y, flags, param): + nonlocal drawing, mask, brush_size + + if event == cv2.EVENT_LBUTTONDOWN: + drawing = True + elif event == cv2.EVENT_MOUSEMOVE: + if drawing: + cv2.circle(mask, (x, y), brush_size, 255, -1) + cv2.circle(image, (x, y), brush_size, (0, 0, 255), -1) + elif event == cv2.EVENT_LBUTTONUP: + drawing = False + + + cv2.namedWindow("Draw Mask") + cv2.setMouseCallback("Draw Mask", draw_mask) + + while True: + display = image.copy() + cv2.putText(display, f'Brush size: {brush_size}', (10, 30), cv2.FONT_HERSHEY_SIMPLEX, 1, (0,255,0), 2) + cv2.imshow("Draw Mask", display) + key = cv2.waitKey(1) & 0xFF + if key == 13: # Enter to finish + break + elif key == ord('+') or key == ord('='): # `=` for some keyboard layouts + brush_size = min(100, brush_size + 1) + elif key == ord('-') or key == ord('_'): + brush_size = max(1, brush_size - 1) + + + cv2.destroyAllWindows() + + cv2.imwrite(str(mask_file), mask) + + # Apply mask + masked_image = cv2.bitwise_and(clone, clone, mask=mask) + + cv2.imshow("Masked Image", masked_image) + cv2.waitKey(0) + cv2.destroyAllWindows() + +def deconvolution(image_file): + + image = cv2.imread(image_file, 0) + # image = cv2.resize(image, (200, 200), interpolation= cv2.INTER_LINEAR) + mask = get_mask(image_file) + + + # Define 2D image and kernel + + + kernel = np.array([ + [1, 1, 1], + [1, 1, 1], + [1, 1, 1] + ], dtype=np.float32) + kernel /= kernel.sum() # Normalize + + print(kernel) + return + + # Perform 2D convolution (blurring) + + h, w = image.shape + kh, kw = kernel.shape + pad_h, pad_w = kh // 2, kw // 2 + + + show(image) + + print("Original image:\n", image) + print("\nBlurred image:\n", image) + + print("\nBuilding linear system for deconvolution...") + + # Step 2: Build sparse matrix A + N = h * w + A = lil_matrix((N, N), dtype=np.float32) + b = image.flatten() + + def index(y, x): + return y * w + x + + for y in range(h): + for x in range(w): + row_idx = index(y, x) + for ky in range(kh): + for kx in range(kw): + iy = y + ky - pad_h + ix = x + kx - pad_w + if 0 <= iy < h and 0 <= ix < w: + col_idx = index(iy, ix) + A[row_idx, col_idx] += kernel[ky, kx] + + # Step 3: Solve the sparse system A * x = b + x = spsolve(A.tocsr(), b) + deblurred = x.reshape((h, w)) + + print("\nDeblurred image:\n", np.round(deblurred, 2)) + + show(deblurred) + +if __name__ == "__main__": + img_file = "assets/real_test.jpg" + + #demo("assets/omas.png") + deconvolution(img_file)