Code » History » Revision 7
Revision 6 (Faiq Sayyidan SETIAWAN, 10/24/2024 01:23 PM) → Revision 7/9 (Faiq Sayyidan SETIAWAN, 10/31/2024 02:16 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 config_path): 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, process_image(config_path, main_display): """ Processes an image based on the provided parameters. configuration. Args: image_path config_path (str): Path to the image configuration file. params (dict): Parameters for image processing. 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 from params dictionary mask_width = params.get('projected_overlap_width') config_reader.getProjectedOverlapWidth() image_width = params.get('projected_image_width') config_reader.getProjectedImageWidth() gamma = params.get('gamma') config_reader.getGamma() image_side = params.get('image_side') config_reader.getImageSide() image_path = config_reader.getImageName() transparency_factor = params.get('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. 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") # Initialize variables self.left_image_path UI elements self.config_left = "" self.right_image_path self.config_right = "" self.left_params = {} self.right_params = {} self.processed_images = {} # Create notebook (tabs) self.notebook self.left_image_label = ttk.Notebook(root) self.notebook.pack(expand=True, fill='both') tk.Label(root, text="Left Image: None", anchor="w") # Create frames for each tab self.tab_left self.left_image_label.pack(fill="x", padx=5, pady=5) self.right_image_label = 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) tk.Label(root, text="Right Image: None", anchor="w") 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') self.right_image_label.pack(fill="x", padx=5, pady=5) # 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 self.select_left_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) Config", command=self.select_left_config) # 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) self.select_left_button.pack(pady=5) # Parameters for left image params_frame self.select_right_button = 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, tk.Button(root, text="Select Right Image", command=self.select_right_image) btn_select.pack(pady=5) Config", command=self.select_right_config) # 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) self.select_right_button.pack(pady=5) # Parameters for right image params_frame self.process_button = tk.LabelFrame(frame, text="Right Image Parameters", padx=10, pady=10) params_frame.pack(padx=10, pady=10, fill="x") tk.Button(root, text="Process Images", command=self.process_images, state="disabled") # 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") self.process_button.pack(pady=10) # Projected Overlap Width tk.Label(params_frame, text="Projected Overlap Width:").grid(row=1, column=0, sticky="e") self.right_projected_overlap_width self.progress_bar = 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") ttk.Progressbar(root, orient="horizontal", length=300, mode="determinate") # 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") self.progress_bar.pack(pady=10) # Image Side tk.Label(params_frame, text="Image Side:").grid(row=3, column=0, sticky="e") self.right_image_side self.image_display = 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") tk.Label(root) # 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") self.image_display.pack() def setup_settings_tab(self): frame = self.tab_settings select_left_config(self): # Configurations can be saved or loaded here save_config_btn self.config_left = 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 Config File", filetypes=[("INI files", "*.png *.jpg *.jpeg *.bmp")]) "*.ini")]) if path: self.left_image_path = path self.left_image_label.config(text=os.path.basename(path)) logging.info(f"Selected left image: {path}") 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_image(self): path select_right_config(self): self.config_right = filedialog.askopenfilename(title="Select Right Image", filetypes=[("Image Config File", filetypes=[("INI 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() "*.ini")]) # 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 self.config_right: self.right_image_label.config(text=f"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}") Config: {os.path.basename(self.config_right)}") self.enable_process_button() def load_configuration(self): load_path = filedialog.askopenfilename(title="Load Configuration", filetypes=[("INI files", "*.ini")]) enable_process_button(self): if load_path self.config_left and os.path.exists(load_path): config = ConfigReader(load_path) self.config_right: # 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.") self.process_button.config(state="normal") 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 process_images(self): # Collect parameters for left image left_params config_files = { '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()) } [self.config_left, self.config_right] # Collect parameters for right image right_params processed_images = { '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 for i, config_file in enumerate(config_files): image, name = process_image(self.left_image_path, left_params, main_display) process_image(config_file, MainDisplay()) if left_processed image is not None and left_name name is not None: self.processed_images[left_name] processed_images[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() for name, img in processed_images.items(): save_image(img, name) # Finalize progress 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_preview(self): # Display the first processed image as preview if self.processed_images: # For simplicity, display the first image display_image(self, processed_images): for name, img = next(iter(self.processed_images.items())) in 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 self.image_display.config(image=img_tk) self.image_display.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>