Project

General

Profile

Codes » History » Version 31

Mitsuki EIKI, 01/16/2025 02:16 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
<pre><code class="python">
249
#!/usr/bin/env python
250
# -*- coding: utf-8 -*-
251
252
class DisplayImg:
253
    """!
254
    @brief A class to represent and manage image display properties.
255
    """
256
257
    def __init__(self, w, h,overlap, img, side):
258
        """!
259
        @brief Constructor for the DisplayImg class.
260
        @param w The width of the display area.
261
        @param h The height of the display area.
262
        @param overlap The overlap percentage for the display area.
263
        @param img The image to be displayed.
264
        @param side The side or orientation of the display.
265
        """
266
        self.__p_width = w  
267
        self.__p_height = h
268
        self.__p_overlap = overlap
269
        self.__img = img
270
        self.__side = side
271
272
    def getImg(self):
273
        """!
274
        @brief Getter for the image.
275
        @return The image associated with the display.
276
        """
277
        return self.__img
278
279
    def getWidth(self):
280
        """!
281
        @brief Getter for the width of the display.
282
        @return The width of the display area.
283
        """
284
        return self.__p_width
285
286
    def getHeight(self):
287
        """!
288
        @brief Getter for the height of the display.
289
        @return The height of the display area.
290
        """
291
        return self.__p_height
292
293
    def getOverlap(self):
294
        """!
295
        @brief Getter for the overlap percentage.
296
        @return The overlap percentage of the display area.
297
        """
298
        return self.__p_overlap
299
300
    def getSide(self):
301
        """!
302
        @brief Getter for the side/orientation of the display.
303
        @return The side or orientation of the display.
304
        """
305
        return self.__side
306
307
</code></pre>
308
309
310
311
312
h1. img_processor.py
313
<pre><code class="python">
314
#!/usr/bin/env python
315
# -*- coding: utf-8 -*-
316
317
from config import Config
318
from display_img import DisplayImg
319
import numpy as np
320
import cv2
321
import os
322
323
class ImgProcessor:
324
    """!
325
    @brief Performs alpha blending and gamma correction.
326
327
    This class provides methods for image processing, including alpha blending 
328
    of overlapping image regions and gamma correction for brightness adjustment.
329
    """
330
331
    def __init__(self, config):
332
        """!
333
        @brief Initializes the ImgProcessor object.
334
335
        Sets the configuration object to retrieve parameters like overlap width 
336
        and gamma values.
337
338
        @param config Configuration object containing parameters.
339
        """
340
        self.__config = config
341
342
    def alphaBlend(self, l_img, r_img):
343
        """!
344
        @brief Performs alpha blending on overlapping regions of two images.
345
346
        Blends the overlapping regions of the left and right images using 
347
        a linear gradient alpha mask.
348
349
        @param l_img Left image.
350
        @param r_img Right image.
351
        @return Tuple containing processed left and right images.
352
        """
353
        overlap_width = self.__config.getOverlapWidth()
354
        
355
        l_overlap = l_img[:, -overlap_width:, :].copy()
356
        r_overlap = r_img[:, :overlap_width, :].copy()
357
358
        height = l_overlap.shape[0]
359
360
        alpha = np.linspace(1, 0, overlap_width).reshape(1, overlap_width, 1)
361
        alpha = np.tile(alpha, (height, 1, 3))
362
363
        l_overlap = (alpha * l_overlap).astype(np.uint8)
364
        r_overlap = ((1 - alpha) * r_overlap).astype(np.uint8)
365
366
        l_common = l_img[:, :-overlap_width, :]
367
        r_common = r_img[:, overlap_width:, :]
368
        l_processed = np.concatenate([l_common, l_overlap], axis=1)
369
        r_processed = np.concatenate([r_overlap, r_common], axis=1)
370
371
        return l_processed, r_processed
372
373
    def GammaCorrection(self, img):
374
        """!
375
        @brief Applies gamma correction to an image.
376
377
        Adjusts the brightness of the image using gamma correction based 
378
        on the gamma value in the configuration.
379
380
        @param img Input image.
381
        @return Gamma-corrected image.
382
        """
383
        gamma = self.__config.getGamma()
384
        inv_gamma = 1.0 / gamma
385
        lookup_table = np.array([((i / 255.0) ** inv_gamma) * 255 for i in range(256)]).astype("uint8")
386
        corrected_image = cv2.LUT(img, lookup_table)
387
        return corrected_image
388
389
    def cropImage(self):
390
        """!
391
        @brief Crops the left and right halves of an image with overlap.
392
393
        Reads the image from the specified path, splits it into left and 
394
        right halves with the overlap region, and returns the cropped images.
395
396
        @return Tuple containing cropped left and right images.
397
        """
398
        path = str(os.getcwd() + self.__config.getImgPath())
399
        overlap_width = self.__config.getOverlapWidth()
400
        image = cv2.imread(path)
401
        width = image.shape[1]
402
        l_img = image[:, :width // 2 + overlap_width, :]
403
        r_img = image[:, width // 2 - overlap_width:, :]
404
        return l_img, r_img
405
406
</code></pre>