File size: 4,961 Bytes
57276d4
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
import cv2
import numpy as np


class Perspective:
    r"""Convert perspective image to equirectangular image.
    Args:
        img_name (str or np.ndarray): The name of the image file or the image array.
        FOV (float): The field of view of the image in degrees.
        THETA (float): The left/right angle in degrees.
        PHI (float): The up/down angle in degrees.
        img_width (int): The width of the output equirectangular image.
        img_height (int): The height of the output equirectangular image.
        crop_bound (bool): Whether to crop the boundary area of the image proportionally.
    """
    def __init__(self, img_name=None, FOV=None, THETA=None, PHI=None, img_width=512, img_height=512, crop_bound=False):
        # either img_name is provided, or img_width/img_height and img in function GetEquirec is provided
        self.crop_bound = crop_bound
        if img_name is not None:
            # Load the image
            if isinstance(img_name, str):
                self._img = cv2.imread(img_name, cv2.IMREAD_COLOR)
            elif isinstance(img_name, np.ndarray):
                self._img = img_name
            [self._height, self._width, _] = self._img.shape
            # Crop the boundary area of the image proportionally
            if self.crop_bound:
                self._img = self._img[int(
                    self._height*0.05):int(self._height*0.95), int(self._width*0.05):int(self._width*0.95), :]

                [self._height, self._width, _] = self._img.shape
        else:
            self._img = None
            self._height = img_height
            self._width = img_width

        self.THETA = THETA
        self.PHI = PHI
        if self._width > self._height:
            self.wFOV = FOV
            self.hFOV = (float(self._height) / self._width) * FOV
        else:
            self.wFOV = (float(self._width) / self._height) * FOV
            self.hFOV = FOV

        self.w_len = np.tan(np.radians(self.wFOV / 2.0))
        self.h_len = np.tan(np.radians(self.hFOV / 2.0))

    def GetEquirec(self, height, width, img=None):
        #
        # THETA is left/right angle, PHI is up/down angle, both in degree
        #
        if self._img is None:
            self._img = img
        # Calculate the equirectangular coordinates
        x, y = np.meshgrid(np.linspace(-180, 180, width),
                           np.linspace(90, -90, height))
        # Convert spherical coordinates to Cartesian coordinates
        x_map = np.cos(np.radians(x)) * np.cos(np.radians(y))
        y_map = np.sin(np.radians(x)) * np.cos(np.radians(y))
        z_map = np.sin(np.radians(y))
        # Stack the coordinates to form a 3D array
        xyz = np.stack((x_map, y_map, z_map), axis=2)
        # Reshape the coordinates to match the image dimensions
        y_axis = np.array([0.0, 1.0, 0.0], np.float32)
        z_axis = np.array([0.0, 0.0, 1.0], np.float32)
        # Calculate the rotation matrices
        [R1, _] = cv2.Rodrigues(z_axis * np.radians(self.THETA))
        [R2, _] = cv2.Rodrigues(np.dot(R1, y_axis) * np.radians(-self.PHI))
        # Invert rotations to transform from equirectangular to perspective
        R1 = np.linalg.inv(R1)
        R2 = np.linalg.inv(R2)
        # Apply rotations
        xyz = xyz.reshape([height * width, 3]).T
        xyz = np.dot(R2, xyz)
        xyz = np.dot(R1, xyz).T
        xyz = xyz.reshape([height, width, 3])
        # Create mask for valid forward-facing points (x > 0)
        inverse_mask = np.where(xyz[:, :, 0] > 0, 1, 0)
        # Normalize coordinates by x-component (perspective division)
        xyz[:, :] = xyz[:, :] / \
            np.repeat(xyz[:, :, 0][:, :, np.newaxis], 3, axis=2)
        # Map 3D points back to 2D perspective image coordinates
        lon_map = np.where(
            (-self.w_len < xyz[:, :, 1]) & (xyz[:, :, 1] < self.w_len) \
                & (-self.h_len < xyz[:, :, 2]) & (xyz[:, :, 2] < self.h_len),
            (xyz[:, :, 1]+self.w_len)/2/self.w_len*self._width,
            0)
        lat_map = np.where(
            (-self.w_len < xyz[:, :, 1]) & (xyz[:, :, 1] < self.w_len) \
                & (-self.h_len < xyz[:, :, 2]) & (xyz[:, :, 2] < self.h_len),
            (-xyz[:, :, 2]+self.h_len) /
            2/self.h_len*self._height,
            0)
        mask = np.where(
            (-self.w_len < xyz[:, :, 1]) & (xyz[:, :, 1] < self.w_len) \
                & (-self.h_len < xyz[:, :, 2]) & (xyz[:, :, 2] < self.h_len),
            1,
            0)
        # Remap the image using the longitude and latitude maps
        persp = cv2.remap(self._img, lon_map.astype(np.float32), lat_map.astype(
            np.float32), cv2.INTER_CUBIC, borderMode=cv2.BORDER_WRAP)  # BORDER_CONSTANT) #))
        # Apply the mask to the equirectangular image
        mask = mask * inverse_mask
        mask = np.repeat(mask[:, :, np.newaxis], 3, axis=2)
        persp = persp * mask

        return persp, mask