|
1
|
import cv2
|
|
2
|
import numpy as np
|
|
3
|
from scipy.special import erf
|
|
4
|
import config_reader
|
|
5
|
from typing import Tuple, Optional
|
|
6
|
|
|
7
|
|
|
8
|
class ProjectionSplit:
|
|
9
|
"""!
|
|
10
|
@brief Handles splitting images for dual-projector setups with overlap blending.
|
|
11
|
|
|
12
|
This class manages the process of taking a single input image, splitting it into
|
|
13
|
left and right components, and applying edge blending in the overlap region
|
|
14
|
to create a seamless projection when using two projectors.
|
|
15
|
"""
|
|
16
|
|
|
17
|
def __init__(self):
|
|
18
|
"""!
|
|
19
|
@brief Initializes the ProjectionSplit class.
|
|
20
|
|
|
21
|
Sets up the configuration reader and initializes image placeholders.
|
|
22
|
"""
|
|
23
|
self.image_left: Optional[np.ndarray] = None
|
|
24
|
self.image_right: Optional[np.ndarray] = None
|
|
25
|
self.image_main: Optional[np.ndarray] = None
|
|
26
|
self.cfg = config_reader.ConfigReader("config.ini")
|
|
27
|
print("ProjectionSplit initialized (NumPy backend)")
|
|
28
|
|
|
29
|
def _generate_alpha_curves(
|
|
30
|
self, overlap: int, blend_type: str
|
|
31
|
) -> Tuple[np.ndarray, np.ndarray]:
|
|
32
|
"""!
|
|
33
|
@brief Generates alpha blending curves for the overlap region.
|
|
34
|
|
|
35
|
Calculates the alpha values for the left and right images in the overlap area
|
|
36
|
based on the specified blending type (linear, quadratic, or gaussian).
|
|
37
|
|
|
38
|
@param overlap The width of the overlap region in pixels.
|
|
39
|
@param blend_type The type of blending to apply ('linear', 'quadratic', 'gaussian').
|
|
40
|
@return A tuple containing two numpy arrays: (alpha_left, alpha_right).
|
|
41
|
"""
|
|
42
|
x = np.linspace(0, 1, overlap)
|
|
43
|
#gamma = self.cfg.get_gamma() # e.g. 0.4 ~ 1.4
|
|
44
|
gamma = 1
|
|
45
|
|
|
46
|
x = x ** gamma
|
|
47
|
|
|
48
|
if blend_type == "linear":
|
|
49
|
param = self.cfg.get_linear_parameter()
|
|
50
|
left = 1 - param * x
|
|
51
|
right = 1 - param + param * x
|
|
52
|
|
|
53
|
elif blend_type == "quadratic":
|
|
54
|
left = (1 - x) ** 2
|
|
55
|
right = x ** 2
|
|
56
|
|
|
57
|
elif blend_type == "gaussian":
|
|
58
|
sigma = 0.25
|
|
59
|
g = 0.5 * (1 + erf((x - 0.5) / (sigma * np.sqrt(2))))
|
|
60
|
left = 1 - g
|
|
61
|
right = g
|
|
62
|
else:
|
|
63
|
raise ValueError(f"Unsupported blend_type: {blend_type}")
|
|
64
|
|
|
65
|
return left.astype(np.float32), right.astype(np.float32)
|
|
66
|
|
|
67
|
def _apply_blending(self, image: np.ndarray, overlap: int, blend_type: str) -> None:
|
|
68
|
"""!
|
|
69
|
@brief Applies blending to the input image and splits it.
|
|
70
|
|
|
71
|
Splits the image into left and right parts and applies the calculated alpha
|
|
72
|
curves to the overlap region. Saves the resulting images as 'left.png' and 'right.png'.
|
|
73
|
|
|
74
|
@param image The input image as a numpy array.
|
|
75
|
@param overlap The width of the overlap region in pixels.
|
|
76
|
@param blend_type The type of blending to apply.
|
|
77
|
"""
|
|
78
|
if image.shape[2] == 3:
|
|
79
|
image = cv2.cvtColor(image, cv2.COLOR_BGR2BGRA)
|
|
80
|
|
|
81
|
height, width = image.shape[:2]
|
|
82
|
split_x = width // 2
|
|
83
|
|
|
84
|
left_end = split_x + overlap // 2
|
|
85
|
right_start = split_x - overlap // 2
|
|
86
|
|
|
87
|
left_img = image[:, :left_end].astype(np.float32)
|
|
88
|
right_img = image[:, right_start:].astype(np.float32)
|
|
89
|
|
|
90
|
alpha_l, alpha_r = self._generate_alpha_curves(overlap, blend_type)
|
|
91
|
|
|
92
|
# Apply alpha blending (NumPy)
|
|
93
|
left_img[:, -overlap:, 3] *= alpha_l[None, :]
|
|
94
|
right_img[:, :overlap, 3] *= alpha_r[None, :]
|
|
95
|
|
|
96
|
self.image_left = left_img.astype(np.uint8)
|
|
97
|
self.image_right = right_img.astype(np.uint8)
|
|
98
|
|
|
99
|
cv2.imwrite("left.png", self.image_left)
|
|
100
|
cv2.imwrite("right.png", self.image_right)
|
|
101
|
|
|
102
|
def process_images(self, overlap: int = 75, blend_type: str = "linear"):
|
|
103
|
"""!
|
|
104
|
@brief Main processing function to load, split, and blend the image.
|
|
105
|
|
|
106
|
Loads 'input.png', applies the splitting and blending logic, and stores the results.
|
|
107
|
|
|
108
|
@param overlap The width of the overlap region in pixels. Default is 75.
|
|
109
|
@param blend_type The type of blending to apply. Default is 'linear'.
|
|
110
|
@exception FileNotFoundError Raised if 'input.png' is not found.
|
|
111
|
"""
|
|
112
|
image = cv2.imread("input.png", cv2.IMREAD_UNCHANGED)
|
|
113
|
if image is None:
|
|
114
|
raise FileNotFoundError("input.png not found.")
|
|
115
|
self.image_main = image
|
|
116
|
self._apply_blending(image, overlap, blend_type)
|