Code » History » Version 4
Faiq Sayyidan SETIAWAN, 10/24/2024 01:18 PM
1 | 1 | Faiq Sayyidan SETIAWAN | |
---|---|---|---|
2 | --- |
||
3 | |||
4 | *[[Wiki]]* | *[[Team Members]]* | *[[Project Description]]* | *[[Code]]* | *[[UML Diagrams]]* | *[[Results]]* | |
||
5 | |||
6 | --- |
||
7 | 2 | Faiq Sayyidan SETIAWAN | |
8 | h1=. <pre> |
||
9 | 3 | Faiq Sayyidan SETIAWAN | Code </pre> |
10 | 4 | Faiq Sayyidan SETIAWAN | |
11 | h2. alpha_blending_v2.py: everything |
||
12 | |||
13 | <pre><code class="python"> |
||
14 | import tkinter as tk |
||
15 | |||
16 | from tkinter import filedialog, messagebox |
||
17 | |||
18 | from tkinter import ttk |
||
19 | |||
20 | import configparser |
||
21 | |||
22 | import cv2 |
||
23 | |||
24 | import numpy as np |
||
25 | |||
26 | import os |
||
27 | |||
28 | import logging |
||
29 | |||
30 | from PIL import Image, ImageTk |
||
31 | |||
32 | # Setup logging |
||
33 | |||
34 | logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s') |
||
35 | |||
36 | # ConfigReader class definition |
||
37 | |||
38 | class ConfigReader: |
||
39 | |||
40 | def __init__(self, config_path): |
||
41 | |||
42 | self.config_parser = configparser.ConfigParser() |
||
43 | |||
44 | self.config_parser.read(config_path) |
||
45 | |||
46 | def getImageName(self): |
||
47 | |||
48 | return str(self.config_parser['DEFAULT']['image_name']) |
||
49 | |||
50 | def getProjectedImageWidth(self): |
||
51 | |||
52 | return int(self.config_parser['DEFAULT']['projected_image_width']) |
||
53 | |||
54 | def getProjectedOverlapWidth(self): |
||
55 | |||
56 | return int(self.config_parser['DEFAULT']['projected_overlap_width']) |
||
57 | |||
58 | def getGamma(self): |
||
59 | |||
60 | return float(self.config_parser['DEFAULT']['gamma']) |
||
61 | |||
62 | def getImageSide(self): |
||
63 | |||
64 | return int(self.config_parser['DEFAULT']['image_side']) |
||
65 | |||
66 | def getTransparencyFactor(self): |
||
67 | |||
68 | return float(self.config_parser['DEFAULT'].get('transparency_factor', 1.0)) |
||
69 | |||
70 | # MainDisplay class definition |
||
71 | |||
72 | class MainDisplay: |
||
73 | |||
74 | def readImage(self, image_path): |
||
75 | |||
76 | # Loads an image from the specified path with 3 channels (BGR) |
||
77 | |||
78 | image = cv2.imread(image_path, cv2.IMREAD_COLOR) |
||
79 | |||
80 | if image is None: |
||
81 | |||
82 | logging.error(f"Error loading image at {image_path}") |
||
83 | |||
84 | else: |
||
85 | |||
86 | logging.info(f"Loaded image {image_path} with shape: {image.shape}") |
||
87 | |||
88 | return image |
||
89 | |||
90 | def setImage(self, image): |
||
91 | |||
92 | # Placeholder for image processing if necessary |
||
93 | |||
94 | # Here you can modify or prepare the image before displaying |
||
95 | |||
96 | logging.info(f"Setting image with shape: {image.shape}") |
||
97 | |||
98 | return image |
||
99 | |||
100 | # MaskCreator class definition |
||
101 | |||
102 | class MaskCreator: |
||
103 | |||
104 | def __init__(self, image, transparency_factor): |
||
105 | |||
106 | self.__image = image |
||
107 | |||
108 | self.__alpha_gradient = None |
||
109 | |||
110 | self.__gamma_corrected = None |
||
111 | |||
112 | self.result_image = None |
||
113 | |||
114 | self.__mask = None |
||
115 | |||
116 | self.transparency_factor = transparency_factor |
||
117 | |||
118 | def smoothstep(self, edge0, edge1, x): |
||
119 | |||
120 | # Smoothstep function for smoother transition (non-linear gradient) |
||
121 | |||
122 | x = np.clip((x - edge0) / (edge1 - edge0), 0, 1) |
||
123 | |||
124 | return x * x * (3 - 2 * x) |
||
125 | |||
126 | def create_mask(self, image_side, mask_width, image_width): |
||
127 | |||
128 | self.__mask = self.__image.shape[1] * mask_width // image_width |
||
129 | |||
130 | gradient = np.linspace(0, 1, self.__mask) |
||
131 | |||
132 | # Apply smoothstep to create a smoother gradient |
||
133 | |||
134 | smooth_gradient = self.smoothstep(0, 1, gradient) |
||
135 | |||
136 | # Apply transparency factor to adjust the strength of the blending |
||
137 | |||
138 | smooth_gradient = smooth_gradient * self.transparency_factor |
||
139 | |||
140 | if image_side == 1: |
||
141 | |||
142 | # Gradient from transparent to opaque (middle to right) |
||
143 | |||
144 | self.__alpha_gradient = smooth_gradient |
||
145 | |||
146 | elif image_side == 0: |
||
147 | |||
148 | # Gradient from opaque to transparent (left to middle) |
||
149 | |||
150 | self.__alpha_gradient = smooth_gradient[::-1] # Reverse for the left side |
||
151 | |||
152 | def gammaCorrection(self, gamma): |
||
153 | |||
154 | # Apply gamma correction |
||
155 | |||
156 | inv_gamma = 1.0 / gamma |
||
157 | |||
158 | table = np.array([((i / 255.0) ** inv_gamma) * 255 |
||
159 | |||
160 | for i in np.arange(256)]).astype("uint8") |
||
161 | |||
162 | self.__gamma_corrected = cv2.LUT(self.__image, table) |
||
163 | |||
164 | logging.info(f"Applied gamma correction with gamma={gamma}") |
||
165 | |||
166 | # Save gamma corrected image for inspection |
||
167 | |||
168 | cv2.imwrite("gamma_corrected.png", self.__gamma_corrected) |
||
169 | |||
170 | def alpha_blending(self, image_side): |
||
171 | |||
172 | """ |
||
173 | |||
174 | Applies alpha blending on the gamma-corrected image. |
||
175 | |||
176 | Combines the gamma-corrected part of the image with a black background using the alpha gradient mask. |
||
177 | |||
178 | """ |
||
179 | |||
180 | # Initialize result_image to be the gamma-corrected image |
||
181 | |||
182 | self.result_image = self.__gamma_corrected.copy() |
||
183 | |||
184 | if image_side == 1: # Right side |
||
185 | |||
186 | # Create a region of interest (ROI) where blending will occur (right side of the image) |
||
187 | |||
188 | roi = self.result_image[:, :self.__mask].astype(np.float32) |
||
189 | |||
190 | # Create black background for blending |
||
191 | |||
192 | black_background = np.zeros_like(roi, dtype=np.float32) |
||
193 | |||
194 | # Apply the alpha mask to blend gamma-corrected image with black background |
||
195 | |||
196 | alpha = self.__alpha_gradient.reshape(1, -1, 1).astype(np.float32) |
||
197 | |||
198 | blended = (alpha * roi + (1 - alpha) * black_background) |
||
199 | |||
200 | blended = np.clip(blended, 0, 255).astype(np.uint8) |
||
201 | |||
202 | # Place the blended region back in the result image |
||
203 | |||
204 | self.result_image[:, :self.__mask] = blended |
||
205 | |||
206 | logging.info(f"Applied alpha blending on the right side with mask width {self.__mask}") |
||
207 | |||
208 | # Save blended region for debugging |
||
209 | |||
210 | cv2.imwrite("blended_right_side.png", blended) |
||
211 | |||
212 | elif image_side == 0: # Left side |
||
213 | |||
214 | # Create a region of interest (ROI) where blending will occur (left side of the image) |
||
215 | |||
216 | roi = self.result_image[:, -self.__mask:].astype(np.float32) |
||
217 | |||
218 | # Create black background for blending |
||
219 | |||
220 | black_background = np.zeros_like(roi, dtype=np.float32) |
||
221 | |||
222 | # Apply the alpha mask to blend gamma-corrected image with black background |
||
223 | |||
224 | alpha = self.__alpha_gradient.reshape(1, -1, 1).astype(np.float32) |
||
225 | |||
226 | blended = (alpha * roi + (1 - alpha) * black_background) |
||
227 | |||
228 | blended = np.clip(blended, 0, 255).astype(np.uint8) |
||
229 | |||
230 | # Place the blended region back in the result image |
||
231 | |||
232 | self.result_image[:, -self.__mask:] = blended |
||
233 | |||
234 | logging.info(f"Applied alpha blending on the left side with mask width {self.__mask}") |
||
235 | |||
236 | # Save blended region for debugging |
||
237 | |||
238 | cv2.imwrite("blended_left_side.png", blended) |
||
239 | |||
240 | def process_image(config_path, main_display): |
||
241 | |||
242 | """ |
||
243 | |||
244 | Processes an image based on the provided configuration. |
||
245 | |||
246 | Args: |
||
247 | |||
248 | config_path (str): Path to the configuration file. |
||
249 | |||
250 | main_display (MainDisplay): Instance of MainDisplay for image operations. |
||
251 | |||
252 | Returns: |
||
253 | |||
254 | tuple: Processed image and its corresponding name. |
||
255 | |||
256 | """ |
||
257 | |||
258 | # Load configuration |
||
259 | |||
260 | config_reader = ConfigReader(config_path) |
||
261 | |||
262 | # Retrieve configuration parameters |
||
263 | |||
264 | mask_width = config_reader.getProjectedOverlapWidth() |
||
265 | |||
266 | image_width = config_reader.getProjectedImageWidth() |
||
267 | |||
268 | gamma = config_reader.getGamma() |
||
269 | |||
270 | image_side = config_reader.getImageSide() |
||
271 | |||
272 | image_path = config_reader.getImageName() |
||
273 | |||
274 | transparency_factor = config_reader.getTransparencyFactor() |
||
275 | |||
276 | # Determine image side name |
||
277 | |||
278 | if image_side == 0: |
||
279 | |||
280 | image_name = 'left' |
||
281 | |||
282 | elif image_side == 1: |
||
283 | |||
284 | image_name = 'right' |
||
285 | |||
286 | else: |
||
287 | |||
288 | logging.error(f"Invalid ImageSide value in {config_path}. Use 0 for left image, 1 for right image.") |
||
289 | |||
290 | return None, None |
||
291 | |||
292 | # Load image |
||
293 | |||
294 | image = main_display.readImage(image_path) |
||
295 | |||
296 | if image is None: |
||
297 | |||
298 | logging.error(f"Image loading failed for {image_path}. Skipping...") |
||
299 | |||
300 | return None, None |
||
301 | |||
302 | # Initialize result image |
||
303 | |||
304 | result_image = main_display.setImage(image).copy() |
||
305 | |||
306 | cv2.imwrite("initial_result_image.png", result_image) # Save initial image |
||
307 | |||
308 | # Initialize MaskCreator |
||
309 | |||
310 | mask_creator = MaskCreator(image, transparency_factor) |
||
311 | |||
312 | # Apply image modifications |
||
313 | |||
314 | mask_creator.create_mask(image_side, mask_width, image_width) |
||
315 | |||
316 | mask_creator.gammaCorrection(gamma) |
||
317 | |||
318 | mask_creator.result_image = result_image |
||
319 | |||
320 | mask_creator.alpha_blending(image_side) |
||
321 | |||
322 | # Save final result for inspection |
||
323 | |||
324 | cv2.imwrite("final_result_image.png", mask_creator.result_image) |
||
325 | |||
326 | return mask_creator.result_image, image_name |
||
327 | |||
328 | def save_image(image, name): |
||
329 | |||
330 | """ |
||
331 | |||
332 | Save the processed image to the project folder. |
||
333 | |||
334 | Args: |
||
335 | |||
336 | image (np.ndarray): The image to be saved. |
||
337 | |||
338 | name (str): Name of the image (left or right). |
||
339 | |||
340 | """ |
||
341 | |||
342 | # Define the output path |
||
343 | |||
344 | output_dir = os.path.join(os.getcwd(), "processed_images") |
||
345 | |||
346 | os.makedirs(output_dir, exist_ok=True) |
||
347 | |||
348 | # Create the output file name |
||
349 | |||
350 | output_path = os.path.join(output_dir, f"processed_image_{name}.png") |
||
351 | |||
352 | # Save the image |
||
353 | |||
354 | cv2.imwrite(output_path, image) |
||
355 | |||
356 | logging.info(f"Saved processed image: {output_path}") |
||
357 | |||
358 | # GUI Application class definition |
||
359 | |||
360 | class ImageProcessingApp: |
||
361 | |||
362 | def __init__(self, root): |
||
363 | |||
364 | self.root = root |
||
365 | |||
366 | self.root.title("Image Processing Application") |
||
367 | |||
368 | # UI elements |
||
369 | |||
370 | self.config_left = "" |
||
371 | |||
372 | self.config_right = "" |
||
373 | |||
374 | self.left_image_label = tk.Label(root, text="Left Image: None", anchor="w") |
||
375 | |||
376 | self.left_image_label.pack(fill="x", padx=5, pady=5) |
||
377 | |||
378 | self.right_image_label = tk.Label(root, text="Right Image: None", anchor="w") |
||
379 | |||
380 | self.right_image_label.pack(fill="x", padx=5, pady=5) |
||
381 | |||
382 | self.select_left_button = tk.Button(root, text="Select Left Config", command=self.select_left_config) |
||
383 | |||
384 | self.select_left_button.pack(pady=5) |
||
385 | |||
386 | self.select_right_button = tk.Button(root, text="Select Right Config", command=self.select_right_config) |
||
387 | |||
388 | self.select_right_button.pack(pady=5) |
||
389 | |||
390 | self.process_button = tk.Button(root, text="Process Images", command=self.process_images, state="disabled") |
||
391 | |||
392 | self.process_button.pack(pady=10) |
||
393 | |||
394 | self.progress_bar = ttk.Progressbar(root, orient="horizontal", length=300, mode="determinate") |
||
395 | |||
396 | self.progress_bar.pack(pady=10) |
||
397 | |||
398 | self.image_display = tk.Label(root) |
||
399 | |||
400 | self.image_display.pack() |
||
401 | |||
402 | def select_left_config(self): |
||
403 | |||
404 | self.config_left = filedialog.askopenfilename(title="Select Left Config File", |
||
405 | |||
406 | filetypes=[("INI files", "*.ini")]) |
||
407 | |||
408 | if self.config_left: |
||
409 | |||
410 | self.left_image_label.config(text=f"Left Image Config: {os.path.basename(self.config_left)}") |
||
411 | |||
412 | self.enable_process_button() |
||
413 | |||
414 | def select_right_config(self): |
||
415 | |||
416 | self.config_right = filedialog.askopenfilename(title="Select Right Config File", |
||
417 | |||
418 | filetypes=[("INI files", "*.ini")]) |
||
419 | |||
420 | if self.config_right: |
||
421 | |||
422 | self.right_image_label.config(text=f"Right Image Config: {os.path.basename(self.config_right)}") |
||
423 | |||
424 | self.enable_process_button() |
||
425 | |||
426 | def enable_process_button(self): |
||
427 | |||
428 | if self.config_left and self.config_right: |
||
429 | |||
430 | self.process_button.config(state="normal") |
||
431 | |||
432 | def process_images(self): |
||
433 | |||
434 | config_files = [self.config_left, self.config_right] |
||
435 | |||
436 | processed_images = {} |
||
437 | |||
438 | self.progress_bar["value"] = 0 |
||
439 | |||
440 | self.root.update_idletasks() |
||
441 | |||
442 | for i, config_file in enumerate(config_files): |
||
443 | |||
444 | image, name = process_image(config_file, MainDisplay()) |
||
445 | |||
446 | if image is not None and name is not None: |
||
447 | |||
448 | processed_images[name] = image |
||
449 | |||
450 | self.progress_bar["value"] += 50 |
||
451 | |||
452 | self.root.update_idletasks() |
||
453 | |||
454 | for name, img in processed_images.items(): |
||
455 | |||
456 | save_image(img, name) |
||
457 | |||
458 | self.display_image(processed_images) |
||
459 | |||
460 | self.progress_bar["value"] = 100 |
||
461 | |||
462 | self.root.update_idletasks() |
||
463 | |||
464 | messagebox.showinfo("Processing Complete", "Images processed and saved successfully.") |
||
465 | |||
466 | def display_image(self, processed_images): |
||
467 | |||
468 | for name, img in processed_images.items(): |
||
469 | |||
470 | img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) |
||
471 | |||
472 | pil_img = Image.fromarray(img_rgb) |
||
473 | |||
474 | img_tk = ImageTk.PhotoImage(pil_img) |
||
475 | |||
476 | self.image_display.config(image=img_tk) |
||
477 | |||
478 | self.image_display.image = img_tk |
||
479 | |||
480 | def main(): |
||
481 | |||
482 | root = tk.Tk() |
||
483 | |||
484 | app = ImageProcessingApp(root) |
||
485 | |||
486 | root.mainloop() |
||
487 | |||
488 | if __name__ == "__main__": |
||
489 | |||
490 | main() |
||
491 | |||
492 | </code></pre> |