Project

General

Profile

Files » alpha_blending.py

Haolun DING, 10/17/2024 01:18 PM

 
1
#!/usr/bin/env python3
2
# -*- coding: utf-8 -*-
3

    
4
import configparser
5
import cv2
6
import numpy as np
7
import os
8
import logging
9

    
10
# Setup logging
11
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
12

    
13

    
14
# ConfigReader class definition
15
class ConfigReader:
16
    def __init__(self, config_path):
17
        self.config_parser = configparser.ConfigParser()
18
        self.config_parser.read(config_path)
19

    
20
    def getImageName(self):
21
        return str(self.config_parser['DEFAULT']['image_name'])
22

    
23
    def getProjectedImageWidth(self):
24
        return int(self.config_parser['DEFAULT']['projected_image_width'])
25

    
26
    def getProjectedOverlapWidth(self):
27
        return int(self.config_parser['DEFAULT']['projected_overlap_width'])
28

    
29
    def getGamma(self):
30
        return float(self.config_parser['DEFAULT']['gamma'])
31

    
32
    def getImageSide(self):
33
        return int(self.config_parser['DEFAULT']['image_side'])
34

    
35
    def getTransparencyFactor(self):
36
        return float(self.config_parser['DEFAULT'].get('transparency_factor', 1.0))
37

    
38

    
39
# MainDisplay class definition
40
class MainDisplay:
41
    def readImage(self, image_path):
42
        # Loads an image from the specified path with 3 channels (BGR)
43
        image = cv2.imread(image_path, cv2.IMREAD_COLOR)
44
        if image is None:
45
            logging.error(f"Error loading image at {image_path}")
46
        else:
47
            logging.info(f"Loaded image {image_path} with shape: {image.shape}")
48
        return image
49

    
50
    def setImage(self, image):
51
        # Placeholder for image processing if necessary
52
        # Here you can modify or prepare the image before displaying
53
        logging.info(f"Setting image with shape: {image.shape}")
54
        return image
55

    
56

    
57
# MaskCreator class definition
58
class MaskCreator:
59
    def __init__(self, image, transparency_factor):
60
        self.__image = image
61
        self.__alpha_gradient = None
62
        self.__gamma_corrected = None
63
        self.result_image = None
64
        self.__mask = None
65
        self.transparency_factor = transparency_factor
66

    
67
    def smoothstep(self, edge0, edge1, x):
68
        # Smoothstep function for smoother transition (non-linear gradient)
69
        x = np.clip((x - edge0) / (edge1 - edge0), 0, 1)
70
        return x * x * (3 - 2 * x)
71

    
72
    def create_mask(self, image_side, mask_width, image_width):
73
        self.__mask = self.__image.shape[1] * mask_width // image_width
74
        gradient = np.linspace(0, 1, self.__mask)
75

    
76
        # Apply smoothstep to create a smoother gradient
77
        smooth_gradient = self.smoothstep(0, 1, gradient)
78

    
79
        # Apply transparency factor to adjust the strength of the blending
80
        smooth_gradient = smooth_gradient * self.transparency_factor
81

    
82
        if image_side == 1:
83
            # Gradient from transparent to opaque (middle to right)
84
            self.__alpha_gradient = smooth_gradient
85
        elif image_side == 0:
86
            # Gradient from opaque to transparent (left to middle)
87
            self.__alpha_gradient = smooth_gradient[::-1]  # Reverse for the left side
88

    
89
    def gammaCorrection(self, gamma):
90
        # Apply gamma correction
91
        inv_gamma = 1.0 / gamma
92
        table = np.array([((i / 255.0) ** inv_gamma) * 255
93
                          for i in np.arange(256)]).astype("uint8")
94
        self.__gamma_corrected = cv2.LUT(self.__image, table)
95
        logging.info(f"Applied gamma correction with gamma={gamma}")
96

    
97
        # Save gamma corrected image for inspection
98
        cv2.imwrite("gamma_corrected.png", self.__gamma_corrected)
99

    
100
    def alpha_blending(self, image_side):
101
        # Ensure the result image is a black background before blending
102
        if image_side == 1:  # Right side
103
            # Set the area where blending will happen to black
104
            self.result_image[:, :self.__mask] = 0  # Set the ROI to black
105

    
106
            roi = self.result_image[:, :self.__mask].astype(np.float32)
107
            gamma_corrected = self.__gamma_corrected[:, :self.__mask].astype(np.float32)
108
            alpha = self.__alpha_gradient.reshape(1, -1, 1).astype(np.float32)
109
            blended = (alpha * gamma_corrected + (1 - alpha) * roi)
110
            blended = np.clip(blended, 0, 255).astype(np.uint8)
111
            self.result_image[:, :self.__mask] = blended
112
            logging.info(f"Applied alpha blending on the right side with mask width {self.__mask}")
113
            # Save blended region for debugging
114
            cv2.imwrite("blended_right_side.png", blended)
115

    
116
        elif image_side == 0:  # Left side
117
            # Set the area where blending will happen to black
118
            self.result_image[:, -self.__mask:] = 0  # Set the ROI to black
119

    
120
            roi = self.result_image[:, -self.__mask:].astype(np.float32)
121
            gamma_corrected = self.__gamma_corrected[:, -self.__mask:].astype(np.float32)
122
            alpha = self.__alpha_gradient.reshape(1, -1, 1).astype(np.float32)
123
            blended = (alpha * gamma_corrected + (1 - alpha) * roi)
124
            blended = np.clip(blended, 0, 255).astype(np.uint8)
125
            self.result_image[:, -self.__mask:] = blended
126
            logging.info(f"Applied alpha blending on the left side with mask width {self.__mask}")
127
            # Save blended region for debugging
128
            cv2.imwrite("blended_left_side.png", blended)
129

    
130

    
131
def process_image(config_path, main_display):
132
    """
133
    Processes an image based on the provided configuration.
134

    
135
    Args:
136
        config_path (str): Path to the configuration file.
137
        main_display (MainDisplay): Instance of MainDisplay for image operations.
138

    
139
    Returns:
140
        tuple: Processed image and its corresponding name.
141
    """
142
    # Load configuration
143
    config_reader = ConfigReader(config_path)
144

    
145
    # Retrieve configuration parameters
146
    mask_width = config_reader.getProjectedOverlapWidth()
147
    image_width = config_reader.getProjectedImageWidth()
148
    gamma = config_reader.getGamma()
149
    image_side = config_reader.getImageSide()
150
    image_path = config_reader.getImageName()
151
    transparency_factor = config_reader.getTransparencyFactor()
152

    
153
    # Determine image side name
154
    if image_side == 0:
155
        image_name = 'left'
156
    elif image_side == 1:
157
        image_name = 'right'
158
    else:
159
        logging.error(f"Invalid ImageSide value in {config_path}. Use 0 for left image, 1 for right image.")
160
        return None, None
161

    
162
    # Load image
163
    image = main_display.readImage(image_path)
164
    if image is None:
165
        logging.error(f"Image loading failed for {image_path}. Skipping...")
166
        return None, None
167

    
168
    # Initialize result image
169
    result_image = main_display.setImage(image).copy()
170
    cv2.imwrite("initial_result_image.png", result_image)  # Save initial image
171

    
172
    # Initialize MaskCreator
173
    mask_creator = MaskCreator(image, transparency_factor)
174

    
175
    # Apply image modifications
176
    mask_creator.create_mask(image_side, mask_width, image_width)
177
    mask_creator.gammaCorrection(gamma)
178
    mask_creator.result_image = result_image
179
    mask_creator.alpha_blending(image_side)
180

    
181
    # Save final result for inspection
182
    cv2.imwrite("final_result_image.png", mask_creator.result_image)
183

    
184
    return mask_creator.result_image, image_name
185

    
186

    
187
def save_image(image, name):
188
    """
189
    Save the processed image to the project folder.
190

    
191
    Args:
192
        image (np.ndarray): The image to be saved.
193
        name (str): Name of the image (left or right).
194
    """
195
    # Define the output path
196
    output_dir = os.path.join(os.getcwd(), "processed_images")
197
    os.makedirs(output_dir, exist_ok=True)
198

    
199
    # Create the output file name
200
    output_path = os.path.join(output_dir, f"processed_image_{name}.png")
201

    
202
    # Save the image
203
    cv2.imwrite(output_path, image)
204
    logging.info(f"Saved processed image: {output_path}")
205

    
206

    
207
def main():
208
    # Initialize MainDisplay
209
    main_display = MainDisplay()
210

    
211
    # Define configuration files for left and right images
212
    config_files = ["config_left.ini", "config_right.ini"]
213

    
214
    # Dictionary to store processed images
215
    processed_images = {}
216

    
217
    # Process each configuration
218
    for config_file in config_files:
219
        processed_image, image_name = process_image(config_file, main_display)
220
        if processed_image is not None and image_name is not None:
221
            processed_images[image_name] = processed_image
222

    
223
    # Save all processed images to the project folder
224
    for name, img in processed_images.items():
225
        save_image(img, name)
226

    
227

    
228
if __name__ == "__main__":
229
    main()
(1-1/2)