Project

General

Profile

Actions

Code » History » Revision 5

« Previous | Revision 5/9 (diff) | Next »
Faiq Sayyidan SETIAWAN, 10/24/2024 01:23 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):

        self.config_parser = configparser.ConfigParser()

        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))

# 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(config_path, main_display):

    """ 

    Processes an image based on the provided configuration.

    Args:

        config_path (str): Path to the configuration file.

        main_display (MainDisplay): Instance of MainDisplay for image operations.

    Returns:

        tuple: Processed image and its corresponding name.

    """ 

    # Load configuration

    config_reader = ConfigReader(config_path)

    # Retrieve configuration parameters

    mask_width = config_reader.getProjectedOverlapWidth()

    image_width = config_reader.getProjectedImageWidth()

    gamma = config_reader.getGamma()

    image_side = config_reader.getImageSide()

    image_path = config_reader.getImageName()

    transparency_factor = config_reader.getTransparencyFactor()

    # 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 in {config_path}. 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")

        # UI elements

        self.config_left = "" 

        self.config_right = "" 

        self.left_image_label = tk.Label(root, text="Left Image: None", anchor="w")

        self.left_image_label.pack(fill="x", padx=5, pady=5)

        self.right_image_label = tk.Label(root, text="Right Image: None", anchor="w")

        self.right_image_label.pack(fill="x", padx=5, pady=5)

        self.select_left_button = tk.Button(root, text="Select Left Config", command=self.select_left_config)

        self.select_left_button.pack(pady=5)

        self.select_right_button = tk.Button(root, text="Select Right Config", command=self.select_right_config)

        self.select_right_button.pack(pady=5)

        self.process_button = tk.Button(root, text="Process Images", command=self.process_images, state="disabled")

        self.process_button.pack(pady=10)

        self.progress_bar = ttk.Progressbar(root, orient="horizontal", length=300, mode="determinate")

        self.progress_bar.pack(pady=10)

        self.image_display = tk.Label(root)

        self.image_display.pack()

    def select_left_config(self):

        self.config_left = filedialog.askopenfilename(title="Select Left Config File",

                                                      filetypes=[("INI files", "*.ini")])

        if self.config_left:

            self.left_image_label.config(text=f"Left Image Config: {os.path.basename(self.config_left)}")

            self.enable_process_button()

    def select_right_config(self):

        self.config_right = filedialog.askopenfilename(title="Select Right Config File",

                                                       filetypes=[("INI files", "*.ini")])

        if self.config_right:

            self.right_image_label.config(text=f"Right Image Config: {os.path.basename(self.config_right)}")

            self.enable_process_button()

    def enable_process_button(self):

        if self.config_left and self.config_right:

            self.process_button.config(state="normal")

    def process_images(self):

        config_files = [self.config_left, self.config_right]

        processed_images = {}

        self.progress_bar["value"] = 0

        self.root.update_idletasks()

        for i, config_file in enumerate(config_files):

            image, name = process_image(config_file, MainDisplay())

            if image is not None and name is not None:

                processed_images[name] = image

            self.progress_bar["value"] += 50

            self.root.update_idletasks()

        for name, img in processed_images.items():

            save_image(img, name)

        self.display_image(processed_images)

        self.progress_bar["value"] = 100

        self.root.update_idletasks()

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

    def display_image(self, processed_images):

        for name, img in processed_images.items():

            img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)

            pil_img = Image.fromarray(img_rgb)

            img_tk = ImageTk.PhotoImage(pil_img)

            self.image_display.config(image=img_tk)

            self.image_display.image = img_tk

def main():

    root = tk.Tk()

    app = ImageProcessingApp(root)

    root.mainloop()

if __name__ == "__main__":

    main()

Updated by Faiq Sayyidan SETIAWAN 6 months ago · 5 revisions