Project

General

Profile

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>