generated from Hazel/python-project
changed deconvolution algorythm
This commit is contained in:
parent
edd8096030
commit
37a5da37b0
@ -9,11 +9,24 @@ from PyQt5.QtCore import Qt
|
|||||||
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
|
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
|
||||||
from matplotlib.figure import Figure
|
from matplotlib.figure import Figure
|
||||||
import scipy.signal
|
import scipy.signal
|
||||||
|
from scipy.signal import convolve2d
|
||||||
|
|
||||||
import os
|
import os
|
||||||
os.environ.pop("QT_QPA_PLATFORM_PLUGIN_PATH", None)
|
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):
|
def generate_kernel(radius, sigma=None):
|
||||||
"""
|
"""
|
||||||
Generate a 2D Gaussian kernel with a given radius.
|
Generate a 2D Gaussian kernel with a given radius.
|
||||||
@ -43,6 +56,58 @@ def generate_kernel(radius, sigma=None):
|
|||||||
return kernel
|
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):
|
class KernelVisualizer(QWidget):
|
||||||
def __init__(self, image_path=None):
|
def __init__(self, image_path=None):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
@ -53,15 +118,15 @@ class KernelVisualizer(QWidget):
|
|||||||
self.load_button.clicked.connect(self.load_image)
|
self.load_button.clicked.connect(self.load_image)
|
||||||
|
|
||||||
self.radius_slider = QSlider(Qt.Horizontal)
|
self.radius_slider = QSlider(Qt.Horizontal)
|
||||||
self.radius_slider.setRange(1, 30)
|
self.radius_slider.setRange(1, 100)
|
||||||
self.radius_slider.setValue(5)
|
self.radius_slider.setValue(5)
|
||||||
|
|
||||||
self.sigma_slider = QSlider(Qt.Horizontal)
|
self.sigma_slider = QSlider(Qt.Horizontal)
|
||||||
self.sigma_slider.setRange(1, 100)
|
self.sigma_slider.setRange(1, 300)
|
||||||
self.sigma_slider.setValue(15)
|
self.sigma_slider.setValue(15)
|
||||||
|
|
||||||
self.radius_slider.valueChanged.connect(self.update_visualization)
|
# self.radius_slider.valueChanged.connect(self.update_visualization)
|
||||||
self.sigma_slider.valueChanged.connect(self.update_visualization)
|
# self.sigma_slider.valueChanged.connect(self.update_visualization)
|
||||||
|
|
||||||
self.kernel_fig = Figure(figsize=(3, 3))
|
self.kernel_fig = Figure(figsize=(3, 3))
|
||||||
self.kernel_canvas = FigureCanvas(self.kernel_fig)
|
self.kernel_canvas = FigureCanvas(self.kernel_fig)
|
||||||
@ -69,15 +134,27 @@ class KernelVisualizer(QWidget):
|
|||||||
self.image_fig = Figure(figsize=(6, 3))
|
self.image_fig = Figure(figsize=(6, 3))
|
||||||
self.image_canvas = FigureCanvas(self.image_fig)
|
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 = QVBoxLayout()
|
||||||
layout.addWidget(self.load_button)
|
layout.addWidget(self.load_button)
|
||||||
|
|
||||||
|
|
||||||
sliders_layout = QGridLayout()
|
sliders_layout = QGridLayout()
|
||||||
sliders_layout.addWidget(QLabel("Radius:"), 0, 0)
|
sliders_layout.addWidget(QLabel("Radius:"), 0, 0)
|
||||||
sliders_layout.addWidget(self.radius_slider, 0, 1)
|
sliders_layout.addWidget(self.radius_slider, 0, 1)
|
||||||
sliders_layout.addWidget(QLabel("Sigma:"), 1, 0)
|
sliders_layout.addWidget(QLabel("Sigma:"), 1, 0)
|
||||||
sliders_layout.addWidget(self.sigma_slider, 1, 1)
|
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.addLayout(sliders_layout)
|
||||||
layout.addWidget(QLabel("Kernel Visualization:"))
|
layout.addWidget(QLabel("Kernel Visualization:"))
|
||||||
layout.addWidget(self.kernel_canvas)
|
layout.addWidget(self.kernel_canvas)
|
||||||
@ -102,9 +179,20 @@ class KernelVisualizer(QWidget):
|
|||||||
self.image = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
|
self.image = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
|
||||||
self.update_visualization()
|
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):
|
def update_visualization(self):
|
||||||
radius = self.radius_slider.value()
|
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 = generate_kernel(radius, sigma)
|
||||||
|
|
||||||
# Kernel Visualization
|
# Kernel Visualization
|
||||||
@ -116,34 +204,31 @@ class KernelVisualizer(QWidget):
|
|||||||
self.kernel_canvas.draw()
|
self.kernel_canvas.draw()
|
||||||
|
|
||||||
if self.image is not None:
|
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
|
kernel = generate_kernel(radius, sigma)
|
||||||
deconvolved_image = np.zeros_like(self.image)
|
blurred = cv2.filter2D(self.image, -1, kernel)
|
||||||
|
|
||||||
# Perform row-wise deconvolution with padded kernel
|
deconvolved = richardson_lucy(blurred, kernel, iterations=iterations)
|
||||||
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
|
|
||||||
|
|
||||||
self.image_fig.clear()
|
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.imshow(self.image, cmap='gray')
|
||||||
ax1.set_title("Original")
|
ax1.set_title("Original")
|
||||||
ax1.axis('off')
|
ax1.axis('off')
|
||||||
|
|
||||||
ax2 = self.image_fig.add_subplot(122)
|
ax2 = self.image_fig.add_subplot(132)
|
||||||
ax2.imshow(deconvolved_image, cmap='gray')
|
ax2.imshow(blurred, cmap='gray')
|
||||||
ax2.set_title("Deconvolved")
|
ax2.set_title("Blurred")
|
||||||
ax2.axis('off')
|
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()
|
self.image_canvas.draw()
|
||||||
else:
|
else:
|
||||||
self.image_fig.clear()
|
self.image_fig.clear()
|
||||||
|
Loading…
x
Reference in New Issue
Block a user