secure_pixelation/deblur/symetric_kernel.py
Hazel Noack 54a2138746 stuff
2025-05-07 16:48:07 +02:00

252 lines
8.1 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import sys
import numpy as np
import cv2
from PyQt5.QtWidgets import (
QApplication, QWidget, QLabel, QSlider, QVBoxLayout,
QHBoxLayout, QGridLayout, QPushButton, QFileDialog
)
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.
Parameters:
- radius: int, the radius of the kernel (size will be 2*radius + 1)
- sigma: float (optional), standard deviation of the Gaussian. If None, sigma = radius / 3
Returns:
- kernel: 2D numpy array of shape (2*radius+1, 2*radius+1)
"""
size = 2 * radius + 1
if sigma is None:
sigma = radius / 3.0 # Common default choice
print(f"radius: {radius}, sigma: {sigma}")
# Create a grid of (x,y) coordinates
ax = np.arange(-radius, radius + 1)
xx, yy = np.meshgrid(ax, ax)
# Apply the 2D Gaussian formula
kernel = np.exp(-(xx**2 + yy**2) / (2 * sigma**2))
kernel /= 2 * np.pi * sigma**2 # Normalize based on Gaussian PDF
kernel /= kernel.sum() # Normalize to sum to 1
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__()
self.setWindowTitle("Gaussian Kernel Visualizer")
self.image = None
self.deconvolved = None
self.load_button = QPushButton("Load Image")
self.load_button.clicked.connect(self.load_image)
self.radius_slider = QSlider(Qt.Horizontal)
self.radius_slider.setRange(1, 100)
self.radius_slider.setValue(5)
self.sigma_slider = QSlider(Qt.Horizontal)
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.kernel_fig = Figure(figsize=(3, 3))
self.kernel_canvas = FigureCanvas(self.kernel_fig)
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.apply_kernel)
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)
layout.addWidget(QLabel("Original and Deconvolved Image:"))
layout.addWidget(self.image_canvas)
self.setLayout(layout)
if image_path:
self.load_image(image_path)
else:
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 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.image = cv2.resize(self.image, (200, 200))
self.update_visualization()
def apply_kernel(self):
radius = self.radius_slider.value()
sigma = self.sigma_slider.value() / 10.0
iterations = self.iter_slider.value()
kernel = generate_kernel(radius, sigma)
self.deconvolved = richardson_lucy(self.image, kernel, iterations=iterations)
self.update_visualization()
def update_visualization(self):
radius = self.radius_slider.value()
sigma = self.sigma_slider.value() / 10.0 * (radius / 3)
kernel = generate_kernel(radius, sigma)
iterations = self.iter_slider.value()
# Kernel Visualization
self.kernel_fig.clear()
ax = self.kernel_fig.add_subplot(111)
cax = ax.imshow(kernel, cmap='hot')
self.kernel_fig.colorbar(cax, ax=ax)
ax.set_title(f"Kernel (r={radius}, σ={sigma:.2f})")
self.kernel_canvas.draw()
if self.image is not None:
self.image_fig.clear()
ax1 = self.image_fig.add_subplot(131)
ax1.imshow(self.image, cmap='gray')
ax1.set_title("Original")
ax1.axis('off')
if self.deconvolved is not None:
ax3 = self.image_fig.add_subplot(133)
ax3.imshow(self.deconvolved, cmap='gray')
ax3.set_title(f"Deconvolved (RL, {iterations} iter)")
ax3.axis('off')
self.image_canvas.draw()
else:
self.image_fig.clear()
ax = self.image_fig.add_subplot(111)
ax.text(0.5, 0.5, "No image loaded", fontsize=14, ha='center', va='center')
ax.axis('off')
self.image_canvas.draw()
if __name__ == "__main__":
image_path = None
if len(sys.argv) > 1:
image_path = sys.argv[1] # Get image path from command-line argument
print(image_path)
app = QApplication(sys.argv)
viewer = KernelVisualizer(image_path=image_path)
viewer.show()
sys.exit(app.exec_())