From 37a5da37b0094eadb7981c33f579179701e03600 Mon Sep 17 00:00:00 2001 From: Hazel Noack Date: Wed, 7 May 2025 16:11:39 +0200 Subject: [PATCH] changed deconvolution algorythm --- deblur/symetric_kernel.py | 131 +++++++++++++++++++++++++++++++------- 1 file changed, 108 insertions(+), 23 deletions(-) diff --git a/deblur/symetric_kernel.py b/deblur/symetric_kernel.py index c3c8d5f..aab130b 100644 --- a/deblur/symetric_kernel.py +++ b/deblur/symetric_kernel.py @@ -9,11 +9,24 @@ from PyQt5.QtCore import Qt from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas from matplotlib.figure import Figure import scipy.signal +from scipy.signal import convolve2d import os os.environ.pop("QT_QPA_PLATFORM_PLUGIN_PATH", None) +def generate_box_kernel(size): + return np.ones((size, size), dtype=np.float32) / (size * size) + +def generate_disk_kernel(radius): + size = 2 * radius + 1 + y, x = np.ogrid[-radius:radius+1, -radius:radius+1] + mask = x**2 + y**2 <= radius**2 + kernel = np.zeros((size, size), dtype=np.float32) + kernel[mask] = 1 + kernel /= kernel.sum() + return kernel + def generate_kernel(radius, sigma=None): """ Generate a 2D Gaussian kernel with a given radius. @@ -43,6 +56,58 @@ def generate_kernel(radius, sigma=None): return kernel +def wiener_deconvolution(blurred, kernel, K=0.1): + """ + Perform Wiener deconvolution on a 2D image. + + Parameters: + - blurred: 2D numpy array (blurred image) + - kernel: 2D numpy array (PSF / blur kernel) + - K: float, estimated noise-to-signal ratio + + Returns: + - deconvolved: 2D numpy array (deblurred image) + """ + # Pad kernel to image size + kernel /= np.sum(kernel) + pad = [(0, blurred.shape[0] - kernel.shape[0]), + (0, blurred.shape[1] - kernel.shape[1])] + kernel_padded = np.pad(kernel, pad, 'constant') + + # FFT of image and kernel + H = np.fft.fft2(kernel_padded) + G = np.fft.fft2(blurred) + + # Avoid division by zero + H_conj = np.conj(H) + denominator = H_conj * H + K + F_hat = H_conj / denominator * G + + # Inverse FFT to get result + deconvolved = np.fft.ifft2(F_hat) + deconvolved = np.abs(deconvolved) + deconvolved = np.clip(deconvolved, 0, 255) + return deconvolved.astype(np.uint8) + + +def richardson_lucy(image, psf, iterations=30, clip=True): + image = image.astype(np.float32) + 1e-6 + psf = psf / psf.sum() + estimate = np.full(image.shape, 0.5, dtype=np.float32) + + psf_mirror = psf[::-1, ::-1] + + for _ in range(iterations): + conv = convolve2d(estimate, psf, mode='same', boundary='wrap') + relative_blur = image / (conv + 1e-6) + estimate *= convolve2d(relative_blur, psf_mirror, mode='same', boundary='wrap') + + if clip: + estimate = np.clip(estimate, 0, 255) + + return estimate + + class KernelVisualizer(QWidget): def __init__(self, image_path=None): super().__init__() @@ -53,15 +118,15 @@ class KernelVisualizer(QWidget): self.load_button.clicked.connect(self.load_image) self.radius_slider = QSlider(Qt.Horizontal) - self.radius_slider.setRange(1, 30) + self.radius_slider.setRange(1, 100) self.radius_slider.setValue(5) self.sigma_slider = QSlider(Qt.Horizontal) - self.sigma_slider.setRange(1, 100) + self.sigma_slider.setRange(1, 300) self.sigma_slider.setValue(15) - self.radius_slider.valueChanged.connect(self.update_visualization) - self.sigma_slider.valueChanged.connect(self.update_visualization) + # self.radius_slider.valueChanged.connect(self.update_visualization) + # self.sigma_slider.valueChanged.connect(self.update_visualization) self.kernel_fig = Figure(figsize=(3, 3)) self.kernel_canvas = FigureCanvas(self.kernel_fig) @@ -69,15 +134,27 @@ class KernelVisualizer(QWidget): self.image_fig = Figure(figsize=(6, 3)) self.image_canvas = FigureCanvas(self.image_fig) + self.iter_slider = QSlider(Qt.Horizontal) + self.iter_slider.setRange(1, 50) + self.iter_slider.setValue(10) + + self.apply_button = QPushButton("Do Deconvolution.") + self.apply_button.clicked.connect(self.update_visualization) + layout = QVBoxLayout() layout.addWidget(self.load_button) + sliders_layout = QGridLayout() sliders_layout.addWidget(QLabel("Radius:"), 0, 0) sliders_layout.addWidget(self.radius_slider, 0, 1) sliders_layout.addWidget(QLabel("Sigma:"), 1, 0) sliders_layout.addWidget(self.sigma_slider, 1, 1) + sliders_layout.addWidget(QLabel("Iterations:"), 2, 0) + sliders_layout.addWidget(self.iter_slider, 2, 1) + sliders_layout.addWidget(self.apply_button, 3, 1) + layout.addLayout(sliders_layout) layout.addWidget(QLabel("Kernel Visualization:")) layout.addWidget(self.kernel_canvas) @@ -102,9 +179,20 @@ class KernelVisualizer(QWidget): self.image = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) self.update_visualization() + def load_image(self, image_path=None): + if not image_path: + fname, _ = QFileDialog.getOpenFileName(self, "Open Image", "", "Images (*.png *.jpg *.bmp *.jpeg)") + image_path = fname + + if image_path: + img = cv2.imread(image_path) + if img is not None: + self.image = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) + self.update_visualization() + def update_visualization(self): radius = self.radius_slider.value() - sigma = self.sigma_slider.value() / 10.0 + sigma = self.sigma_slider.value() / 10.0 * (radius / 3) kernel = generate_kernel(radius, sigma) # Kernel Visualization @@ -116,34 +204,31 @@ class KernelVisualizer(QWidget): self.kernel_canvas.draw() if self.image is not None: - kernel_size = 2 * radius + 1 + radius = self.radius_slider.value() + sigma = self.sigma_slider.value() / 10.0 + iterations = self.iter_slider.value() - # Apply row-by-row deconvolution - deconvolved_image = np.zeros_like(self.image) + kernel = generate_kernel(radius, sigma) + blurred = cv2.filter2D(self.image, -1, kernel) - # Perform row-wise deconvolution with padded kernel - for i in range(self.image.shape[0]): - padded_kernel = np.pad(kernel, ((0, self.image.shape[1] - kernel_size), (0, 0)), mode='constant') - deconvolved_image[i, :], _ = scipy.signal.deconvolve(self.image[i, :], padded_kernel[i, :]) - - # Perform column-wise deconvolution with padded kernel - for j in range(self.image.shape[1]): - padded_kernel = np.pad(kernel, ((0, self.image.shape[0] - kernel_size), (0, 0)), mode='constant') - deconvolved_image[:, j], _ = scipy.signal.deconvolve(self.image[:, j], padded_kernel[:, j]) - - deconvolved_image = np.clip(deconvolved_image, 0, 255).astype(np.uint8) # Ensure valid range + deconvolved = richardson_lucy(blurred, kernel, iterations=iterations) self.image_fig.clear() - ax1 = self.image_fig.add_subplot(121) + ax1 = self.image_fig.add_subplot(131) ax1.imshow(self.image, cmap='gray') ax1.set_title("Original") ax1.axis('off') - ax2 = self.image_fig.add_subplot(122) - ax2.imshow(deconvolved_image, cmap='gray') - ax2.set_title("Deconvolved") + ax2 = self.image_fig.add_subplot(132) + ax2.imshow(blurred, cmap='gray') + ax2.set_title("Blurred") ax2.axis('off') + ax3 = self.image_fig.add_subplot(133) + ax3.imshow(deconvolved, cmap='gray') + ax3.set_title(f"Deconvolved (RL, {iterations} iter)") + ax3.axis('off') + self.image_canvas.draw() else: self.image_fig.clear()