Project

General

Profile

Actions

Codes

Wiki | About_Us | Project_Overview | UML_Diagram | Codes

main_stitcher.py
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from config import Config
from img_processor import ImgProcessor
from display_img import DisplayImg
import cv2

class MainStitcher:
    """!
    @brief A class responsible for stitching and displaying images in a projection system.

    The class integrates configuration settings from the `Config` class and display properties
    from the `DisplayImg` class to handle the main stitching and display logic.
    """ 

    def __init__(self, dImg):
        """!
        @brief Initializes the MainStitcher with configuration and display image settings.
        @param config An instance of the Config class containing projection configuration.
        @param dImg An instance of the DisplayImg class containing display settings.
        """ 
        self.__displayImg = dImg

    #for single monitor/mirroring
    def singleDisplay(self):
        """!
        @brief use two laptops to project the final processed images.
        """ 
        img = self.__displayImg.getImg()
        side = self.__displayImg.getSide()

        # cv2.namedWindow(side, cv2.WINDOW_NORMAL)
        cv2.imshow(side, img)
        # cv2.setWindowProperty(side, cv2.WND_PROP_FULLSCREEN, cv2.WINDOW_FULLSCREEN)

    #for double extended monitor
    def doubleDisplay(self, monitorWidth):
        """!
        @brief use two laptops to project the final processed images.
        """ 
        img = self.__displayImg.getImg()
        side = self.__displayImg.getSide()

        """ 
        In moveWindow(name, x, y), we can replace x with the window size or something
        """ 
        if(side == "Left"):
            cv2.namedWindow(side, cv2.WINDOW_NORMAL)
            cv2.moveWindow(side, 0, 0)
            cv2.imshow(side, img)
            cv2.resizeWindow(side, monitorWidth, 1080)
            cv2.setWindowProperty(side, cv2.WND_PROP_FULLSCREEN, cv2.WINDOW_FULLSCREEN)

        elif(side == "Right"):
            cv2.namedWindow(side, cv2.WINDOW_NORMAL)
            cv2.moveWindow(side, monitorWidth, 0)
            cv2.imshow(side, img)
            cv2.resizeWindow(side, monitorWidth, 1080)
            cv2.setWindowProperty(side, cv2.WND_PROP_FULLSCREEN, cv2.WINDOW_FULLSCREEN)

    def save(self):
        cv2.imwrite("./img/processed/" + self.__displayImg.getSide() + "_img.png", self.__displayImg.getImg())

def main():
    config = Config.readConfigFile()

    imgProcessor = ImgProcessor(config)

    l_img, r_img = imgProcessor.cropImage()
    l_alpha_processed, r_alpha_processed = imgProcessor.alphaBlend(l_img, r_img)

    l_alpha_gamma_processed = imgProcessor.GammaCorrection(l_alpha_processed)
    r_alpha_gamma_processed = imgProcessor.GammaCorrection(r_alpha_processed)

    l_displayImg = DisplayImg(l_alpha_gamma_processed.shape[1] ,l_alpha_gamma_processed.shape[0], config.getOverlapWidth(), l_alpha_gamma_processed, "Left")
    r_displayImg = DisplayImg(r_alpha_gamma_processed.shape[1] ,r_alpha_gamma_processed.shape[0], config.getOverlapWidth(), r_alpha_gamma_processed, "Right")

    l_stitcher = MainStitcher(l_displayImg)
    r_stitcher = MainStitcher(r_displayImg)

    print(config.getIsDualMonitor())

    if config.getIsDualMonitor():
        l_stitcher.doubleDisplay(config.getMonitorWidth())
        r_stitcher.doubleDisplay(config.getMonitorWidth())
    else:
        if config.getSide().lower() == "right":
            r_stitcher.singleDisplay()
        else:
            l_stitcher.singleDisplay()

    cv2.waitKey(0)
    cv2.destroyAllWindows()

    l_stitcher.save()
    r_stitcher.save()

if __name__ == "__main__":
    main()

config.py
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from distutils.util import strtobool

class Config(object):
    def __init__(self, pnd, prd, w, h, p, gamma, overlapWidth, side, isDual, monitorWidth):
        """!
        Constructor for Config class.

        @param pnd: Projection distance.
        @param prd: Projector distance.
        @param w: Image width.
        @param h: Image height.
        @param p: Path to the image.
        @param gamma: Gamma value for adjustments.
        @param overlapWidth: Width of the overlap area.
        @param side: Side of projection.
        """ 
        self.__projection_distance = pnd
        self.__projector_diatance = prd
        self.__img_width = w
        self.__img_height = h
        self.__img_path = p
        self.__gamma = gamma
        self.__overlapWidth = overlapWidth
        self.__side = side
        self.__isDualMonitor = isDual
        self.__monitorWidth = monitorWidth

    def getProjectionDistance(self):
        """!
        Retrieve the projection distance.

        @return: Projection distance as an integer.
        """ 
        return int(self.__projection_distance)

    def getProjectorDistance(self):
        """!
        Retrieve the projector distance.

        @return: Projector distance as an integer.
        """ 
        return int(self.__projector_diatance)

    def getImgWidth(self):
        """!
        Retrieve the image width.

        @return: Image width as an integer.
        """ 
        return int(self.__img_width)

    def getImgHeight(self):
        """!
        Retrieve the image height.

        @return: Image height as an integer.
        """ 
        return int(self.__img_height)

    def getImgPath(self):
        """!
        Retrieve the image path.

        @return: Image path as a string.
        """ 
        return str(self.__img_path)

    def getGamma(self):
        """!
        Retrieve the gamma value.

        @return: Gamma value as a float.
        """ 
        return float(self.__gamma)

    def getOverlapWidth(self):
        """!
        Retrieve the overlap width.

        @return: Overlap width as an integer.
        """ 
        return int(self.__overlapWidth)

    def getSide(self):
        """!
        Retrieve the side of projection.

        @return: Side as a string.
        """ 
        return str(self.__side)

    def getIsDualMonitor(self):
        return bool(strtobool(self.__isDualMonitor))

    def getMonitorWidth(self):
        return int(self.__monitorWidth)

    @staticmethod
    def readConfigFile():
        """!
        Reads the configuration from a config.ini file and returns a Config object.

        @return: Config object with settings loaded from config.ini.
        """ 
        import configparser
        config = configparser.ConfigParser()
        config.read('config.ini')

        __img_path = config["settings"]["imagePath"]
        __img_width = config["settings"]["imageWidth"]
        __img_height = config["settings"]["imageHeight"]
        __projection_distance = config["settings"]["projectionDistance"]
        __projector_diatance = config["settings"]["projectorDistance"]
        __gamma = config["settings"]["gamma"]
        __overlapWidth = config["settings"]["overlapWidth"]
        __side = config["settings"]["side"]
        __isDualMonitor = config["settings"]["isDualMonitor"]
        __monitorWidth = config["settings"]["monitorWidth"]

        return Config(__projection_distance, __projector_diatance, __img_width, __img_height, __img_path, __gamma, __overlapWidth, __side, __isDualMonitor, __monitorWidth)

display_img.py
#!/usr/bin/env python
# -*- coding: utf-8 -*-

class DisplayImg:
    """!
    @brief A class to represent and manage image display properties.
    """ 

    def __init__(self, w, h,overlap, img, side):
        """!
        @brief Constructor for the DisplayImg class.
        @param w The width of the display area.
        @param h The height of the display area.
        @param overlap The overlap percentage for the display area.
        @param img The image to be displayed.
        @param side The side or orientation of the display.
        """ 
        self.__p_width = w  
        self.__p_height = h
        self.__p_overlap = overlap
        self.__img = img
        self.__side = side

    def getImg(self):
        """!
        @brief Getter for the image.
        @return The image associated with the display.
        """ 
        return self.__img

    def getWidth(self):
        """!
        @brief Getter for the width of the display.
        @return The width of the display area.
        """ 
        return self.__p_width

    def getHeight(self):
        """!
        @brief Getter for the height of the display.
        @return The height of the display area.
        """ 
        return self.__p_height

    def getOverlap(self):
        """!
        @brief Getter for the overlap percentage.
        @return The overlap percentage of the display area.
        """ 
        return self.__p_overlap

    def getSide(self):
        """!
        @brief Getter for the side/orientation of the display.
        @return The side or orientation of the display.
        """ 
        return self.__side

img_processor.py
#!/usr/bin/env python
# -*- coding: utf-8 -*-

from config import Config
from display_img import DisplayImg
import numpy as np
import cv2
import os

class ImgProcessor:
    """!
    @brief Performs alpha blending and gamma correction.

    This class provides methods for image processing, including alpha blending 
    of overlapping image regions and gamma correction for brightness adjustment.
    """ 

    def __init__(self, config):
        """!
        @brief Initializes the ImgProcessor object.

        Sets the configuration object to retrieve parameters like overlap width 
        and gamma values.

        @param config Configuration object containing parameters.
        """ 
        self.__config = config

    def alphaBlend(self, l_img, r_img):
        """!
        @brief Performs alpha blending on overlapping regions of two images.

        Blends the overlapping regions of the left and right images using 
        a linear gradient alpha mask.

        @param l_img Left image.
        @param r_img Right image.
        @return Tuple containing processed left and right images.
        """ 
        overlap_width = self.__config.getOverlapWidth()

        l_overlap = l_img[:, -overlap_width:, :].copy()
        r_overlap = r_img[:, :overlap_width, :].copy()

        height = l_overlap.shape[0]

        alpha = np.linspace(1, 0, overlap_width).reshape(1, overlap_width, 1)
        alpha = np.tile(alpha, (height, 1, 3))

        l_overlap = (alpha * l_overlap).astype(np.uint8)
        r_overlap = ((1 - alpha) * r_overlap).astype(np.uint8)

        l_common = l_img[:, :-overlap_width, :]
        r_common = r_img[:, overlap_width:, :]
        l_processed = np.concatenate([l_common, l_overlap], axis=1)
        r_processed = np.concatenate([r_overlap, r_common], axis=1)

        return l_processed, r_processed

    def GammaCorrection(self, img):
        """!
        @brief Applies gamma correction to an image.

        Adjusts the brightness of the image using gamma correction based 
        on the gamma value in the configuration.

        @param img Input image.
        @return Gamma-corrected image.
        """ 
        gamma = self.__config.getGamma()
        inv_gamma = 1.0 / gamma
        lookup_table = np.array([((i / 255.0) ** inv_gamma) * 255 for i in range(256)]).astype("uint8")
        corrected_image = cv2.LUT(img, lookup_table)
        return corrected_image

    def cropImage(self):
        """!
        @brief Crops the left and right halves of an image with overlap.

        Reads the image from the specified path, splits it into left and 
        right halves with the overlap region, and returns the cropped images.

        @return Tuple containing cropped left and right images.
        """ 
        path = str(os.getcwd() + self.__config.getImgPath())
        overlap_width = self.__config.getOverlapWidth()
        image = cv2.imread(path)
        width = image.shape[1]
        l_img = image[:, :width // 2 + overlap_width, :]
        r_img = image[:, width // 2 - overlap_width:, :]
        return l_img, r_img

Updated by Mitsuki EIKI 4 months ago · 33 revisions