Project

General

Profile

Code » History » Revision 8

Revision 7 (Faiq Sayyidan SETIAWAN, 10/31/2024 02:16 PM) → Revision 8/9 (Faiq Sayyidan SETIAWAN, 10/31/2024 03:35 PM)

--- 

 *[[/|Home]]*     |     *[[Team Members]]*     |     *[[Project Description]]*     |     *[[Code]]*     |     *[[UML Diagrams]]*     |     *[[Results]]*     | 

 --- 

 h1=. <pre> 
 Code </pre> 

 h2. alpha_blending_v2.py: everything 

 <pre><code class="python"> 
 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() 


 

 </code></pre>