Project

General

Profile

Actions

Code » History » Revision 7

« Previous | Revision 7/9 (diff) | Next »
Faiq Sayyidan SETIAWAN, 10/31/2024 02:16 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 · 7 revisions