Project

General

Profile

Codes » History » Version 32

Mitsuki EIKI, 01/16/2025 02:23 PM

1 1 Mitsuki EIKI
h1. Codes
2 2 Mitsuki EIKI
3 3 Man Mong CHAN
[[Wiki]] | [[About_Us]] | [[Project_Overview]] | [[UML_Diagram]] | [[Codes]]
4 4 Kentaro HARATAKE
5 31 Mitsuki EIKI
h1. main_stitcher.py
6 6 Mitsuki EIKI
7 30 Mitsuki EIKI
<pre><code class="python">
8
#!/usr/bin/env python
9
# -*- coding: utf-8 -*-
10
from config import Config
11
from img_processor import ImgProcessor
12
from display_img import DisplayImg
13
import cv2
14
15
class MainStitcher:
16
    """!
17
    @brief A class responsible for stitching and displaying images in a projection system.
18
19
    The class integrates configuration settings from the `Config` class and display properties
20
    from the `DisplayImg` class to handle the main stitching and display logic.
21
    """
22
23
    def __init__(self, dImg):
24
        """!
25
        @brief Initializes the MainStitcher with configuration and display image settings.
26
        @param config An instance of the Config class containing projection configuration.
27
        @param dImg An instance of the DisplayImg class containing display settings.
28
        """
29
        self.__displayImg = dImg
30
31
32
    #for single monitor/mirroring
33
    def singleDisplay(self):
34
        """!
35
        @brief use two laptops to project the final processed images.
36
        """
37
        img = self.__displayImg.getImg()
38
        side = self.__displayImg.getSide()
39
40
        # cv2.namedWindow(side, cv2.WINDOW_NORMAL)
41
        cv2.imshow(side, img)
42
        # cv2.setWindowProperty(side, cv2.WND_PROP_FULLSCREEN, cv2.WINDOW_FULLSCREEN)
43
44
45
    #for double extended monitor
46
    def doubleDisplay(self, monitorWidth):
47
        """!
48
        @brief use two laptops to project the final processed images.
49
        """
50
        img = self.__displayImg.getImg()
51
        side = self.__displayImg.getSide()
52
53
        """
54
        In moveWindow(name, x, y), we can replace x with the window size or something
55
        """
56
        if(side == "Left"):
57
            cv2.namedWindow(side, cv2.WINDOW_NORMAL)
58
            cv2.moveWindow(side, 0, 0)
59
            cv2.imshow(side, img)
60
            cv2.resizeWindow(side, monitorWidth, 1080)
61
            cv2.setWindowProperty(side, cv2.WND_PROP_FULLSCREEN, cv2.WINDOW_FULLSCREEN)
62
63
        elif(side == "Right"):
64
            cv2.namedWindow(side, cv2.WINDOW_NORMAL)
65
            cv2.moveWindow(side, monitorWidth, 0)
66
            cv2.imshow(side, img)
67
            cv2.resizeWindow(side, monitorWidth, 1080)
68
            cv2.setWindowProperty(side, cv2.WND_PROP_FULLSCREEN, cv2.WINDOW_FULLSCREEN)
69
70
    def save(self):
71
        cv2.imwrite("./img/processed/" + self.__displayImg.getSide() + "_img.png", self.__displayImg.getImg())
72
73
74
def main():
75
    config = Config.readConfigFile()
76
77
    imgProcessor = ImgProcessor(config)
78
79
    l_img, r_img = imgProcessor.cropImage()
80
    l_alpha_processed, r_alpha_processed = imgProcessor.alphaBlend(l_img, r_img)
81
82
83
    l_alpha_gamma_processed = imgProcessor.GammaCorrection(l_alpha_processed)
84
    r_alpha_gamma_processed = imgProcessor.GammaCorrection(r_alpha_processed)
85
86
    l_displayImg = DisplayImg(l_alpha_gamma_processed.shape[1] ,l_alpha_gamma_processed.shape[0], config.getOverlapWidth(), l_alpha_gamma_processed, "Left")
87
    r_displayImg = DisplayImg(r_alpha_gamma_processed.shape[1] ,r_alpha_gamma_processed.shape[0], config.getOverlapWidth(), r_alpha_gamma_processed, "Right")
88
89
    l_stitcher = MainStitcher(l_displayImg)
90
    r_stitcher = MainStitcher(r_displayImg)
91
92
93
    print(config.getIsDualMonitor())
94
95
    if config.getIsDualMonitor():
96
        l_stitcher.doubleDisplay(config.getMonitorWidth())
97
        r_stitcher.doubleDisplay(config.getMonitorWidth())
98
    else:
99
        if config.getSide().lower() == "right":
100
            r_stitcher.singleDisplay()
101
        else:
102
            l_stitcher.singleDisplay()
103
104
    cv2.waitKey(0)
105
    cv2.destroyAllWindows()
106
107
108
    l_stitcher.save()
109
    r_stitcher.save()
110
111
if __name__ == "__main__":
112
    main()
113
</code></pre>
114 1 Mitsuki EIKI
115
116 31 Mitsuki EIKI
117
h1. config.py
118
119 29 Mitsuki EIKI
<pre><code class="python">
120 5 Mitsuki EIKI
#!/usr/bin/env python
121 1 Mitsuki EIKI
# -*- coding: utf-8 -*-
122
from distutils.util import strtobool
123 26 Mitsuki EIKI
124 1 Mitsuki EIKI
class Config(object):
125 26 Mitsuki EIKI
    def __init__(self, pnd, prd, w, h, p, gamma, overlapWidth, side, isDual, monitorWidth):
126 1 Mitsuki EIKI
        """!
127
        Constructor for Config class.
128 8 Mitsuki EIKI
129
        @param pnd: Projection distance.
130
        @param prd: Projector distance.
131
        @param w: Image width.
132
        @param h: Image height.
133
        @param p: Path to the image.
134
        @param gamma: Gamma value for adjustments.
135 1 Mitsuki EIKI
        @param overlapWidth: Width of the overlap area.
136
        @param side: Side of projection.
137 24 Mitsuki EIKI
        """
138 1 Mitsuki EIKI
        self.__projection_distance = pnd
139
        self.__projector_diatance = prd
140
        self.__img_width = w
141 8 Mitsuki EIKI
        self.__img_height = h
142
        self.__img_path = p
143 1 Mitsuki EIKI
        self.__gamma = gamma
144 8 Mitsuki EIKI
        self.__overlapWidth = overlapWidth
145 24 Mitsuki EIKI
        self.__side = side
146 25 Mitsuki EIKI
        self.__isDualMonitor = isDual
147 8 Mitsuki EIKI
        self.__monitorWidth = monitorWidth
148
149 7 Mitsuki EIKI
    def getProjectionDistance(self):
150 5 Mitsuki EIKI
        """!
151
        Retrieve the projection distance.
152 8 Mitsuki EIKI
153
        @return: Projection distance as an integer.
154 5 Mitsuki EIKI
        """
155
        return int(self.__projection_distance)
156 1 Mitsuki EIKI
157
    def getProjectorDistance(self):
158
        """!
159 5 Mitsuki EIKI
        Retrieve the projector distance.
160 8 Mitsuki EIKI
161
        @return: Projector distance as an integer.
162 1 Mitsuki EIKI
        """
163
        return int(self.__projector_diatance)
164
165 8 Mitsuki EIKI
    def getImgWidth(self):
166
        """!
167
        Retrieve the image width.
168 1 Mitsuki EIKI
169 8 Mitsuki EIKI
        @return: Image width as an integer.
170
        """
171
        return int(self.__img_width)
172
173
    def getImgHeight(self):
174
        """!
175
        Retrieve the image height.
176
177
        @return: Image height as an integer.
178
        """
179
        return int(self.__img_height)
180
181
    def getImgPath(self):
182
        """!
183
        Retrieve the image path.
184
185
        @return: Image path as a string.
186
        """
187
        return str(self.__img_path)
188
189
    def getGamma(self):
190
        """!
191
        Retrieve the gamma value.
192
193
        @return: Gamma value as a float.
194
        """
195
        return float(self.__gamma)
196
197
    def getOverlapWidth(self):
198
        """!
199
        Retrieve the overlap width.
200
201
        @return: Overlap width as an integer.
202
        """
203
        return int(self.__overlapWidth)
204
205
    def getSide(self):
206 1 Mitsuki EIKI
        """!
207 8 Mitsuki EIKI
        Retrieve the side of projection.
208
209 1 Mitsuki EIKI
        @return: Side as a string.
210 8 Mitsuki EIKI
        """
211
        return str(self.__side)
212 24 Mitsuki EIKI
    
213 8 Mitsuki EIKI
    def getIsDualMonitor(self):
214
        return bool(strtobool(self.__isDualMonitor))
215
216
    def getMonitorWidth(self):
217
        return int(self.__monitorWidth)
218
219 1 Mitsuki EIKI
    @staticmethod
220
    def readConfigFile():
221
        """!
222
        Reads the configuration from a config.ini file and returns a Config object.
223 8 Mitsuki EIKI
224
        @return: Config object with settings loaded from config.ini.
225 1 Mitsuki EIKI
        """
226
        import configparser
227 8 Mitsuki EIKI
        config = configparser.ConfigParser()
228
        config.read('config.ini')
229
230
        __img_path = config["settings"]["imagePath"]
231
        __img_width = config["settings"]["imageWidth"]
232
        __img_height = config["settings"]["imageHeight"]
233 1 Mitsuki EIKI
        __projection_distance = config["settings"]["projectionDistance"]
234
        __projector_diatance = config["settings"]["projectorDistance"]
235
        __gamma = config["settings"]["gamma"]
236 8 Mitsuki EIKI
        __overlapWidth = config["settings"]["overlapWidth"]
237 1 Mitsuki EIKI
        __side = config["settings"]["side"]
238
        __isDualMonitor = config["settings"]["isDualMonitor"]
239 26 Mitsuki EIKI
        __monitorWidth = config["settings"]["monitorWidth"]
240
        
241 1 Mitsuki EIKI
        return Config(__projection_distance, __projector_diatance, __img_width, __img_height, __img_path, __gamma, __overlapWidth, __side, __isDualMonitor, __monitorWidth)
242
</code></pre>
243
244 31 Mitsuki EIKI
245
246
247
h1. display_img.py
248 32 Mitsuki EIKI
249 31 Mitsuki EIKI
<pre><code class="python">
250
#!/usr/bin/env python
251
# -*- coding: utf-8 -*-
252
253
class DisplayImg:
254
    """!
255
    @brief A class to represent and manage image display properties.
256
    """
257
258
    def __init__(self, w, h,overlap, img, side):
259
        """!
260
        @brief Constructor for the DisplayImg class.
261
        @param w The width of the display area.
262
        @param h The height of the display area.
263
        @param overlap The overlap percentage for the display area.
264
        @param img The image to be displayed.
265
        @param side The side or orientation of the display.
266
        """
267
        self.__p_width = w  
268
        self.__p_height = h
269
        self.__p_overlap = overlap
270
        self.__img = img
271
        self.__side = side
272
273
    def getImg(self):
274
        """!
275
        @brief Getter for the image.
276
        @return The image associated with the display.
277
        """
278
        return self.__img
279
280
    def getWidth(self):
281
        """!
282
        @brief Getter for the width of the display.
283
        @return The width of the display area.
284
        """
285
        return self.__p_width
286
287
    def getHeight(self):
288
        """!
289
        @brief Getter for the height of the display.
290
        @return The height of the display area.
291
        """
292
        return self.__p_height
293
294
    def getOverlap(self):
295
        """!
296
        @brief Getter for the overlap percentage.
297
        @return The overlap percentage of the display area.
298
        """
299
        return self.__p_overlap
300
301
    def getSide(self):
302
        """!
303
        @brief Getter for the side/orientation of the display.
304
        @return The side or orientation of the display.
305
        """
306
        return self.__side
307
308
</code></pre>
309
310
311
312
313
h1. img_processor.py
314 32 Mitsuki EIKI
315 31 Mitsuki EIKI
<pre><code class="python">
316
#!/usr/bin/env python
317
# -*- coding: utf-8 -*-
318
319
from config import Config
320
from display_img import DisplayImg
321
import numpy as np
322
import cv2
323
import os
324
325
class ImgProcessor:
326
    """!
327
    @brief Performs alpha blending and gamma correction.
328
329
    This class provides methods for image processing, including alpha blending 
330
    of overlapping image regions and gamma correction for brightness adjustment.
331
    """
332
333
    def __init__(self, config):
334
        """!
335
        @brief Initializes the ImgProcessor object.
336
337
        Sets the configuration object to retrieve parameters like overlap width 
338
        and gamma values.
339
340
        @param config Configuration object containing parameters.
341
        """
342
        self.__config = config
343
344
    def alphaBlend(self, l_img, r_img):
345
        """!
346
        @brief Performs alpha blending on overlapping regions of two images.
347
348
        Blends the overlapping regions of the left and right images using 
349
        a linear gradient alpha mask.
350
351
        @param l_img Left image.
352
        @param r_img Right image.
353
        @return Tuple containing processed left and right images.
354
        """
355
        overlap_width = self.__config.getOverlapWidth()
356
        
357
        l_overlap = l_img[:, -overlap_width:, :].copy()
358
        r_overlap = r_img[:, :overlap_width, :].copy()
359
360
        height = l_overlap.shape[0]
361
362
        alpha = np.linspace(1, 0, overlap_width).reshape(1, overlap_width, 1)
363
        alpha = np.tile(alpha, (height, 1, 3))
364
365
        l_overlap = (alpha * l_overlap).astype(np.uint8)
366
        r_overlap = ((1 - alpha) * r_overlap).astype(np.uint8)
367
368
        l_common = l_img[:, :-overlap_width, :]
369
        r_common = r_img[:, overlap_width:, :]
370
        l_processed = np.concatenate([l_common, l_overlap], axis=1)
371
        r_processed = np.concatenate([r_overlap, r_common], axis=1)
372
373
        return l_processed, r_processed
374
375
    def GammaCorrection(self, img):
376
        """!
377
        @brief Applies gamma correction to an image.
378
379
        Adjusts the brightness of the image using gamma correction based 
380
        on the gamma value in the configuration.
381
382
        @param img Input image.
383
        @return Gamma-corrected image.
384
        """
385
        gamma = self.__config.getGamma()
386
        inv_gamma = 1.0 / gamma
387
        lookup_table = np.array([((i / 255.0) ** inv_gamma) * 255 for i in range(256)]).astype("uint8")
388
        corrected_image = cv2.LUT(img, lookup_table)
389
        return corrected_image
390
391
    def cropImage(self):
392
        """!
393
        @brief Crops the left and right halves of an image with overlap.
394
395
        Reads the image from the specified path, splits it into left and 
396
        right halves with the overlap region, and returns the cropped images.
397
398
        @return Tuple containing cropped left and right images.
399
        """
400
        path = str(os.getcwd() + self.__config.getImgPath())
401
        overlap_width = self.__config.getOverlapWidth()
402
        image = cv2.imread(path)
403
        width = image.shape[1]
404
        l_img = image[:, :width // 2 + overlap_width, :]
405
        r_img = image[:, width // 2 - overlap_width:, :]
406
        return l_img, r_img
407
408
</code></pre>