Actions
Code » History » Revision 8
« Previous |
Revision 8/9
(diff)
| Next »
Faiq Sayyidan SETIAWAN, 10/31/2024 03:35 PM
Home | Team Members | Project Description | Code | UML Diagrams | Results |
Code
alpha_blending_v2.py: everything¶
import tkinter as tk
from tkinter import filedialog, messagebox
from tkinter import ttk
import configparser
import cv2
import numpy as np
import os
import logging
from PIL import Image, ImageTk
# Setup logging
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
# ConfigReader class definition
class ConfigReader:
def __init__(self, config_path=None):
# Assume some initialization using config_path
self.config_path = config_path
# Add other initialization logic here
self.config_parser = configparser.ConfigParser()
if config_path is not None and os.path.exists(config_path):
self.config_parser.read(config_path)
def getImageName(self):
return str(self.config_parser['DEFAULT']['image_name'])
def getProjectedImageWidth(self):
return int(self.config_parser['DEFAULT']['projected_image_width'])
def getProjectedOverlapWidth(self):
return int(self.config_parser['DEFAULT']['projected_overlap_width'])
def getGamma(self):
return float(self.config_parser['DEFAULT']['gamma'])
def getImageSide(self):
return int(self.config_parser['DEFAULT']['image_side'])
def getTransparencyFactor(self):
return float(self.config_parser['DEFAULT'].get('transparency_factor', 1.0))
def setParameters(self, parameters):
# The parameters is a dictionary with key-value pairs
if 'DEFAULT' not in self.config_parser:
self.config_parser['DEFAULT'] = {}
for key, value in parameters.items():
self.config_parser['DEFAULT'][key] = str(value)
def save_config(self, path):
with open(path, 'w') as configfile:
self.config_parser.write(configfile)
# MainDisplay class definition
class MainDisplay:
def readImage(self, image_path):
# Loads an image from the specified path with 3 channels (BGR)
image = cv2.imread(image_path, cv2.IMREAD_COLOR)
if image is None:
logging.error(f"Error loading image at {image_path}")
else:
logging.info(f"Loaded image {image_path} with shape: {image.shape}")
return image
def setImage(self, image):
# Placeholder for image processing if necessary
# Here you can modify or prepare the image before displaying
logging.info(f"Setting image with shape: {image.shape}")
return image
# MaskCreator class definition
class MaskCreator:
def __init__(self, image, transparency_factor):
self.__image = image
self.__alpha_gradient = None
self.__gamma_corrected = None
self.result_image = None
self.__mask = None
self.transparency_factor = transparency_factor
def smoothstep(self, edge0, edge1, x):
# Smoothstep function for smoother transition (non-linear gradient)
x = np.clip((x - edge0) / (edge1 - edge0), 0, 1)
return x * x * (3 - 2 * x)
def create_mask(self, image_side, mask_width, image_width):
self.__mask = self.__image.shape[1] * mask_width // image_width
gradient = np.linspace(0, 1, self.__mask)
# Apply smoothstep to create a smoother gradient
smooth_gradient = self.smoothstep(0, 1, gradient)
# Apply transparency factor to adjust the strength of the blending
smooth_gradient = smooth_gradient * self.transparency_factor
if image_side == 1:
# Gradient from transparent to opaque (middle to right)
self.__alpha_gradient = smooth_gradient
elif image_side == 0:
# Gradient from opaque to transparent (left to middle)
self.__alpha_gradient = smooth_gradient[::-1] # Reverse for the left side
def gammaCorrection(self, gamma):
# Apply gamma correction
inv_gamma = 1.0 / gamma
table = np.array([((i / 255.0) ** inv_gamma) * 255
for i in np.arange(256)]).astype("uint8")
self.__gamma_corrected = cv2.LUT(self.__image, table)
logging.info(f"Applied gamma correction with gamma={gamma}")
# Save gamma corrected image for inspection
cv2.imwrite("gamma_corrected.png", self.__gamma_corrected)
def alpha_blending(self, image_side):
"""
Applies alpha blending on the gamma-corrected image.
Combines the gamma-corrected part of the image with a black background using the alpha gradient mask.
"""
# Initialize result_image to be the gamma-corrected image
self.result_image = self.__gamma_corrected.copy()
if image_side == 1: # Right side
# Create a region of interest (ROI) where blending will occur (right side of the image)
roi = self.result_image[:, :self.__mask].astype(np.float32)
# Create black background for blending
black_background = np.zeros_like(roi, dtype=np.float32)
# Apply the alpha mask to blend gamma-corrected image with black background
alpha = self.__alpha_gradient.reshape(1, -1, 1).astype(np.float32)
blended = (alpha * roi + (1 - alpha) * black_background)
blended = np.clip(blended, 0, 255).astype(np.uint8)
# Place the blended region back in the result image
self.result_image[:, :self.__mask] = blended
logging.info(f"Applied alpha blending on the right side with mask width {self.__mask}")
# Save blended region for debugging
cv2.imwrite("blended_right_side.png", blended)
elif image_side == 0: # Left side
# Create a region of interest (ROI) where blending will occur (left side of the image)
roi = self.result_image[:, -self.__mask:].astype(np.float32)
# Create black background for blending
black_background = np.zeros_like(roi, dtype=np.float32)
# Apply the alpha mask to blend gamma-corrected image with black background
alpha = self.__alpha_gradient.reshape(1, -1, 1).astype(np.float32)
blended = (alpha * roi + (1 - alpha) * black_background)
blended = np.clip(blended, 0, 255).astype(np.uint8)
# Place the blended region back in the result image
self.result_image[:, -self.__mask:] = blended
logging.info(f"Applied alpha blending on the left side with mask width {self.__mask}")
# Save blended region for debugging
cv2.imwrite("blended_left_side.png", blended)
def process_image(image_path, params, main_display):
"""
Processes an image based on the provided parameters.
Args:
image_path (str): Path to the image file.
params (dict): Parameters for image processing.
main_display (MainDisplay): Instance of MainDisplay for image operations.
Returns:
tuple: Processed image and its corresponding name.
"""
# Retrieve parameters from params dictionary
mask_width = params.get('projected_overlap_width')
image_width = params.get('projected_image_width')
gamma = params.get('gamma')
image_side = params.get('image_side')
transparency_factor = params.get('transparency_factor')
# Determine image side name
if image_side == 0:
image_name = 'left'
elif image_side == 1:
image_name = 'right'
else:
logging.error(f"Invalid ImageSide value. Use 0 for left image, 1 for right image.")
return None, None
# Load image
image = main_display.readImage(image_path)
if image is None:
logging.error(f"Image loading failed for {image_path}. Skipping...")
return None, None
# Initialize result image
result_image = main_display.setImage(image).copy()
cv2.imwrite("initial_result_image.png", result_image) # Save initial image
# Initialize MaskCreator
mask_creator = MaskCreator(image, transparency_factor)
# Apply image modifications
mask_creator.create_mask(image_side, mask_width, image_width)
mask_creator.gammaCorrection(gamma)
mask_creator.result_image = result_image
mask_creator.alpha_blending(image_side)
# Save final result for inspection
cv2.imwrite("final_result_image.png", mask_creator.result_image)
return mask_creator.result_image, image_name
def save_image(image, name):
"""
Save the processed image to the project folder.
Args:
image (np.ndarray): The image to be saved.
name (str): Name of the image (left or right).
"""
# Define the output path
output_dir = os.path.join(os.getcwd(), "processed_images")
os.makedirs(output_dir, exist_ok=True)
# Create the output file name
output_path = os.path.join(output_dir, f"processed_image_{name}.png")
# Save the image
cv2.imwrite(output_path, image)
logging.info(f"Saved processed image: {output_path}")
# GUI Application class definition
class ImageProcessingApp:
def __init__(self, root):
self.root = root
self.root.title("Image Processing Application")
# Initialize variables
self.left_image_path = ""
self.right_image_path = ""
self.left_params = {}
self.right_params = {}
self.processed_images = {}
# Create notebook (tabs)
self.notebook = ttk.Notebook(root)
self.notebook.pack(expand=True, fill='both')
# Create frames for each tab
self.tab_left = ttk.Frame(self.notebook)
self.tab_right = ttk.Frame(self.notebook)
self.tab_settings = ttk.Frame(self.notebook)
self.tab_preview = ttk.Frame(self.notebook)
self.notebook.add(self.tab_left, text='Left Image')
self.notebook.add(self.tab_right, text='Right Image')
self.notebook.add(self.tab_settings, text='Settings')
self.notebook.add(self.tab_preview, text='Preview')
# Setup each tab
self.setup_left_tab()
self.setup_right_tab()
self.setup_settings_tab()
self.setup_preview_tab()
# Process and Save Button
self.process_button = tk.Button(root, text="Process and Save Images", command=self.process_and_save)
self.process_button.pack(pady=10)
# Progress Bar
self.progress_bar = ttk.Progressbar(root, orient="horizontal", length=400, mode="determinate")
self.progress_bar.pack(pady=10)
def setup_left_tab(self):
frame = self.tab_left
# Select Image Button
btn_select = tk.Button(frame, text="Select Left Image", command=self.select_left_image)
btn_select.pack(pady=5)
# Display selected image path
self.left_image_label = tk.Label(frame, text="No image selected", wraplength=300, anchor="w", justify="left")
self.left_image_label.pack(padx=10, pady=5)
# Parameters for left image
params_frame = tk.LabelFrame(frame, text="Left Image Parameters", padx=10, pady=10)
params_frame.pack(padx=10, pady=10, fill="x")
# Projected Image Width
tk.Label(params_frame, text="Projected Image Width:").grid(row=0, column=0, sticky="e")
self.left_projected_image_width = tk.Entry(params_frame)
self.left_projected_image_width.insert(0, "800")
self.left_projected_image_width.grid(row=0, column=1, pady=2, sticky="w")
# Projected Overlap Width
tk.Label(params_frame, text="Projected Overlap Width:").grid(row=1, column=0, sticky="e")
self.left_projected_overlap_width = tk.Entry(params_frame)
self.left_projected_overlap_width.insert(0, "100")
self.left_projected_overlap_width.grid(row=1, column=1, pady=2, sticky="w")
# Gamma
tk.Label(params_frame, text="Gamma:").grid(row=2, column=0, sticky="e")
self.left_gamma = tk.Entry(params_frame)
self.left_gamma.insert(0, "1.0")
self.left_gamma.grid(row=2, column=1, pady=2, sticky="w")
# Image Side
tk.Label(params_frame, text="Image Side:").grid(row=3, column=0, sticky="e")
self.left_image_side = tk.IntVar(value=0) # Default to left
tk.Radiobutton(params_frame, text="Left", variable=self.left_image_side, value=0).grid(row=3, column=1, sticky="w")
tk.Radiobutton(params_frame, text="Right", variable=self.left_image_side, value=1).grid(row=3, column=1, padx=60, sticky="w")
# Transparency Factor
tk.Label(params_frame, text="Transparency Factor:").grid(row=4, column=0, sticky="e")
self.left_transparency_factor = tk.Entry(params_frame)
self.left_transparency_factor.insert(0, "1.0")
self.left_transparency_factor.grid(row=4, column=1, pady=2, sticky="w")
def setup_right_tab(self):
frame = self.tab_right
# Select Image Button
btn_select = tk.Button(frame, text="Select Right Image", command=self.select_right_image)
btn_select.pack(pady=5)
# Display selected image path
self.right_image_label = tk.Label(frame, text="No image selected", wraplength=300, anchor="w", justify="left")
self.right_image_label.pack(padx=10, pady=5)
# Parameters for right image
params_frame = tk.LabelFrame(frame, text="Right Image Parameters", padx=10, pady=10)
params_frame.pack(padx=10, pady=10, fill="x")
# Projected Image Width
tk.Label(params_frame, text="Projected Image Width:").grid(row=0, column=0, sticky="e")
self.right_projected_image_width = tk.Entry(params_frame)
self.right_projected_image_width.insert(0, "800")
self.right_projected_image_width.grid(row=0, column=1, pady=2, sticky="w")
# Projected Overlap Width
tk.Label(params_frame, text="Projected Overlap Width:").grid(row=1, column=0, sticky="e")
self.right_projected_overlap_width = tk.Entry(params_frame)
self.right_projected_overlap_width.insert(0, "100")
self.right_projected_overlap_width.grid(row=1, column=1, pady=2, sticky="w")
# Gamma
tk.Label(params_frame, text="Gamma:").grid(row=2, column=0, sticky="e")
self.right_gamma = tk.Entry(params_frame)
self.right_gamma.insert(0, "1.0")
self.right_gamma.grid(row=2, column=1, pady=2, sticky="w")
# Image Side
tk.Label(params_frame, text="Image Side:").grid(row=3, column=0, sticky="e")
self.right_image_side = tk.IntVar(value=1) # Default to right
tk.Radiobutton(params_frame, text="Left", variable=self.right_image_side, value=0).grid(row=3, column=1, sticky="w")
tk.Radiobutton(params_frame, text="Right", variable=self.right_image_side, value=1).grid(row=3, column=1, padx=60, sticky="w")
# Transparency Factor
tk.Label(params_frame, text="Transparency Factor:").grid(row=4, column=0, sticky="e")
self.right_transparency_factor = tk.Entry(params_frame)
self.right_transparency_factor.insert(0, "1.0")
self.right_transparency_factor.grid(row=4, column=1, pady=2, sticky="w")
def setup_settings_tab(self):
frame = self.tab_settings
# Configurations can be saved or loaded here
save_config_btn = tk.Button(frame, text="Save Configuration", command=self.save_configuration)
save_config_btn.pack(pady=10)
load_config_btn = tk.Button(frame, text="Load Configuration", command=self.load_configuration)
load_config_btn.pack(pady=10)
def setup_preview_tab(self):
frame = self.tab_preview
# Labels to display images
self.original_image_label = tk.Label(frame, text="Original Image")
self.original_image_label.pack(side="left", padx=10, pady=10)
self.processed_image_label = tk.Label(frame, text="Processed Image")
self.processed_image_label.pack(side="right", padx=10, pady=10)
def select_left_image(self):
path = filedialog.askopenfilename(title="Select Left Image",
filetypes=[("Image files", "*.png *.jpg *.jpeg *.bmp")])
if path:
self.left_image_path = path
self.left_image_label.config(text=os.path.basename(path))
logging.info(f"Selected left image: {path}")
def select_right_image(self):
path = filedialog.askopenfilename(title="Select Right Image",
filetypes=[("Image files", "*.png *.jpg *.jpeg *.bmp")])
if path:
self.right_image_path = path
self.right_image_label.config(text=os.path.basename(path))
logging.info(f"Selected right image: {path}")
def save_configuration(self):
config = ConfigReader()
# Left Image Parameters
if self.left_image_path:
config.setParameters({
'image_name': self.left_image_path,
'projected_image_width': self.left_projected_image_width.get(),
'projected_overlap_width': self.left_projected_overlap_width.get(),
'gamma': self.left_gamma.get(),
'image_side': self.left_image_side.get(),
'transparency_factor': self.left_transparency_factor.get()
})
# Right Image Parameters
if self.right_image_path:
config.setParameters({
'image_name': self.right_image_path,
'projected_image_width': self.right_projected_image_width.get(),
'projected_overlap_width': self.right_projected_overlap_width.get(),
'gamma': self.right_gamma.get(),
'image_side': self.right_image_side.get(),
'transparency_factor': self.right_transparency_factor.get()
})
# Save to file
save_path = filedialog.asksaveasfilename(title="Save Configuration",
defaultextension=".ini",
filetypes=[("INI files", "*.ini")])
if save_path:
config.save_config(save_path)
messagebox.showinfo("Success", f"Configuration saved to {save_path}")
logging.info(f"Configuration saved to {save_path}")
def load_configuration(self):
load_path = filedialog.askopenfilename(title="Load Configuration",
filetypes=[("INI files", "*.ini")])
if load_path and os.path.exists(load_path):
config = ConfigReader(load_path)
# Load left image parameters
self.left_image_path = config.getImageName()
if self.left_image_path and os.path.exists(self.left_image_path):
self.left_image_label.config(text=os.path.basename(self.left_image_path))
self.left_projected_image_width.delete(0, tk.END)
self.left_projected_image_width.insert(0, config.getProjectedImageWidth())
self.left_projected_overlap_width.delete(0, tk.END)
self.left_projected_overlap_width.insert(0, config.getProjectedOverlapWidth())
self.left_gamma.delete(0, tk.END)
self.left_gamma.insert(0, config.getGamma())
self.left_image_side.set(config.getImageSide())
self.left_transparency_factor.delete(0, tk.END)
self.left_transparency_factor.insert(0, config.getTransparencyFactor())
# Load right image parameters
self.right_image_path = config.getImageName()
if self.right_image_path and os.path.exists(self.right_image_path):
self.right_image_label.config(text=os.path.basename(self.right_image_path))
self.right_projected_image_width.delete(0, tk.END)
self.right_projected_image_width.insert(0, config.getProjectedImageWidth())
self.right_projected_overlap_width.delete(0, tk.END)
self.right_projected_overlap_width.insert(0, config.getProjectedOverlapWidth())
self.right_gamma.delete(0, tk.END)
self.right_gamma.insert(0, config.getGamma())
self.right_image_side.set(config.getImageSide())
self.right_transparency_factor.delete(0, tk.END)
self.right_transparency_factor.insert(0, config.getTransparencyFactor())
messagebox.showinfo("Success", f"Configuration loaded from {load_path}")
logging.info(f"Configuration loaded from {load_path}")
else:
messagebox.showerror("Error", "Failed to load configuration.")
def process_and_save(self):
# Validate inputs
if not self.left_image_path or not self.right_image_path:
messagebox.showerror("Error", "Please select both left and right images.")
return
# Collect parameters for left image
left_params = {
'image_name': self.left_image_path,
'projected_image_width': int(self.left_projected_image_width.get()),
'projected_overlap_width': int(self.left_projected_overlap_width.get()),
'gamma': float(self.left_gamma.get()),
'image_side': self.left_image_side.get(),
'transparency_factor': float(self.left_transparency_factor.get())
}
# Collect parameters for right image
right_params = {
'image_name': self.right_image_path,
'projected_image_width': int(self.right_projected_image_width.get()),
'projected_overlap_width': int(self.right_projected_overlap_width.get()),
'gamma': float(self.right_gamma.get()),
'image_side': self.right_image_side.get(),
'transparency_factor': float(self.right_transparency_factor.get())
}
# Initialize main display
main_display = MainDisplay()
# Process left image
self.progress_bar["value"] = 0
self.root.update_idletasks()
left_processed, left_name = process_image(self.left_image_path, left_params, main_display)
if left_processed is not None and left_name is not None:
self.processed_images[left_name] = left_processed
save_image(left_processed, left_name)
self.progress_bar["value"] += 50
self.root.update_idletasks()
# Process right image
right_processed, right_name = process_image(self.right_image_path, right_params, main_display)
if right_processed is not None and right_name is not None:
self.processed_images[right_name] = right_processed
save_image(right_processed, right_name)
self.progress_bar["value"] += 50
self.root.update_idletasks()
# Display images
self.display_preview()
# Finalize progress
self.progress_bar["value"] = 100
self.root.update_idletasks()
messagebox.showinfo("Processing Complete", "Images processed and saved successfully.")
def display_preview(self):
# Display the first processed image as preview
if self.processed_images:
# For simplicity, display the first image
name, img = next(iter(self.processed_images.items()))
img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
pil_img = Image.fromarray(img_rgb)
pil_img = pil_img.resize((400, 300), Image.ANTIALIAS)
img_tk = ImageTk.PhotoImage(pil_img)
self.processed_image_label.config(image=img_tk)
self.processed_image_label.image = img_tk
# Load and display original image
original_img = cv2.imread(self.left_image_path if name == 'left' else self.right_image_path, cv2.IMREAD_COLOR)
if original_img is not None:
original_rgb = cv2.cvtColor(original_img, cv2.COLOR_BGR2RGB)
pil_original = Image.fromarray(original_rgb)
pil_original = pil_original.resize((400, 300), Image.ANTIALIAS)
original_tk = ImageTk.PhotoImage(pil_original)
self.original_image_label.config(image=original_tk)
self.original_image_label.image = original_tk
def display_image(self, processed_images):
# This method can be expanded to display multiple images if needed
pass
def main():
root = tk.Tk()
app = ImageProcessingApp(root)
root.mainloop()
if __name__ == "__main__":
main()
Updated by Faiq Sayyidan SETIAWAN 6 months ago · 8 revisions