Project

General

Profile

Codes » History » Version 33

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