Project

General

Profile

Actions

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 saveConfig(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 createMask(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 alphaBlending(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 processImage(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.createMask(image_side, mask_width, image_width)

    mask_creator.gammaCorrection(gamma)

    mask_creator.result_image = result_image

    mask_creator.alphaBlending(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 saveImage(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.setupLeftTab()

        self.setupRightTab()

        self.setupSettingsTab()

        self.setupPreviewTab()

        # Process and Save Button

        self.process_button = tk.Button(root, text="Process and Save Images", command=self.processAndSave)

        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 setupLeftTab(self):

        frame = self.tab_left

        # Select Image Button

        btn_select = tk.Button(frame, text="Select Left Image", command=self.selectLeftImage)

        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 setupRightTab(self):

        frame = self.tab_right

        # Select Image Button

        btn_select = tk.Button(frame, text="Select Right Image", command=self.selectRightImage)

        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 setupSettingsTab(self):

        frame = self.tab_settings

        # Configurations can be saved or loaded here

        saveConfig_btn = tk.Button(frame, text="Save Configuration", command=self.saveConfiguration)

        saveConfig_btn.pack(pady=10)

        load_config_btn = tk.Button(frame, text="Load Configuration", command=self.loadConfiguration)

        load_config_btn.pack(pady=10)

    def setupPreviewTab(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 selectLeftImage(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 selectRightImage(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 saveConfiguration(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.saveConfig(save_path)

            messagebox.showinfo("Success", f"Configuration saved to {save_path}")

            logging.info(f"Configuration saved to {save_path}")

    def loadConfiguration(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 processAndSave(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 = processImage(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

            saveImage(left_processed, left_name)

            self.progress_bar["value"] += 50

            self.root.update_idletasks()

        # Process right image

        right_processed, right_name = processImage(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

            saveImage(right_processed, right_name)

            self.progress_bar["value"] += 50

            self.root.update_idletasks()

        # Display images

        self.displayPreview()

        # Finalize progress

        self.progress_bar["value"] = 100

        self.root.update_idletasks()

        messagebox.showinfo("Processing Complete", "Images processed and saved successfully.")

    def displayPreview(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 displayImage(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 · 9 revisions