def untransform(self, points): points = ensure_homogeneous(points, d=3) if len(self.shuffle_indices) > 0: inv_shuffle_indices = list(range(len(self.shuffle_indices))) for i, j in enumerate(self.shuffle_indices): inv_shuffle_indices[j] = i points = points[..., inv_shuffle_indices, :] return points @ cast_array(np.linalg.inv(self.matrix).T, points)
def from_ccd_params(cls, alpha_x, alpha_y, x_0, y_0): """Create a CameraIntrinsics instance from CCD parameters (4 DOF).""" matrix = cast_array([ [alpha_x, 0.0, x_0, 0.0], [0.0, alpha_y, y_0, 0.0], [0.0, 0.0, 1.0, 0.0], ], np.float64) return cls(matrix)
def untransform(self, camera: CameraIntrinsics): camera = camera.clone() x_0, y_0 = camera.x_0, camera.y_0 camera.x_0, camera.y_0 = 0, 0 camera.matrix = cast_array(np.linalg.inv(self.matrix), camera.matrix) @ camera.matrix camera.x_0, camera.y_0 = x_0 / self.sx, y_0 / self.sy return camera
def denormalise_points(norm_points, z_ref, intrinsics, height, width): """Denormalise a set of points, adding scale and z offset. Args: norm_points (np.ndarray): The points to denormalise. z_ref (float): The depth of the plane that z=0 should map to. intrinsics (CameraIntrinsics): The camera which projects 3D points onto the 2D image. height (float): The image height. width (float): The image width. Returns: np.ndarray: The denormalised points. """ m_proj = cast_array( camera_to_clip_matrix(z_ref, intrinsics, height, width), norm_points) return ndc_to_camera_space(norm_points, m_proj)
def normalise_points(denorm_points, z_ref, intrinsics, height, width): """Normalise a set of points, removing scale and z offset. Points within the image frame should have coordinate values between -1 and 1. Args: denorm_points (np.ndarray): The points to normalise. z_ref (float): The depth of the plane which will become z=0. intrinsics (CameraIntrinsics): The camera which projects 3D points onto the 2D image. height (float): The image height. width (float): The image width. Returns: np.ndarray: The normalised points. """ m_proj = cast_array( camera_to_clip_matrix(z_ref, intrinsics, height, width), denorm_points) return camera_space_to_ndc(denorm_points, m_proj)
def _transform_image(self, image, inverse=False): matrix = self.centred_matrix if inverse: matrix = np.linalg.inv(matrix) output_size = cast_array([self.orig_width, self.orig_height], np.int) else: output_size = self.dest_size.round().astype(np.int) # Scale up matrix = self._mm(mat3.scale(self.msaa), matrix) # Apply affine image transformation inv_matrix = np.linalg.inv(matrix) image = image.transform(tuple(output_size * self.msaa), Image.AFFINE, tuple(inv_matrix[0:2].ravel()), Image.BILINEAR) # Scale down to output size if self.msaa != 1: image = image.resize(tuple(output_size), Image.ANTIALIAS) return image
def camera_to_clip_matrix(z_ref, intrinsics, height, width): """Build a matrix that projects from camera space into clip space. Args: z_ref (float): The reference depth (will become z=0). intrinsics (CameraIntrinsics): The camera object specifying focal length and optical centre. height (float): The image height. width (float): The image width. Returns: np.ndarray: The projection matrix. """ # Set the z-size (depth) of the viewing frustum to be equal to the # size of the portion of the XY plane at z_ref which projects # onto the image. size = z_ref * max(width / intrinsics.alpha_x, height / intrinsics.alpha_y) # Set near and far planes such that: # a) z_ref will correspond to z=0 after normalisation # $z_ref = 2fn/(f+n)$ # b) The distance from z=-1 to z=1 (normalised) will correspond # to `size` in camera space # $f - n = size$ far = 0.5 * (sqrt(z_ref**2 + size**2) + z_ref - size) near = 0.5 * (sqrt(z_ref**2 + size**2) + z_ref + size) # Construct the perspective projection matrix. m_proj = cast_array([ [intrinsics.alpha_x / intrinsics.x_0, 0, 0, 0], [0, intrinsics.alpha_y / intrinsics.y_0, 0, 0], [0, 0, -(far + near) / (far - near), 2 * far * near / (far - near)], [0, 0, 1, 0], ], intrinsics.matrix) return m_proj
def back_project(self, coords): """Project points from image space to camera space (ideal points).""" assert coords.shape[ -1] == 3, 'expected homogeneous coordinates in 2D space' return coords @ cast_array(np.linalg.pinv(self.matrix).T, coords)
def project(self, coords): """Project points from camera space to image space.""" assert coords.shape[ -1] == 4, 'expected homogeneous coordinates in 3D space' return coords @ cast_array(self.matrix.T, coords)
def __init__(self, matrix): matrix = cast_array(matrix, np.float64) if matrix.shape == (3, 3): matrix = np.concatenate([matrix, np.zeros((3, 1))], 1) assert matrix.shape == (3, 4), 'intrinsic matrix must be 3x4' self.matrix = matrix
def transform(self, camera: CameraIntrinsics): camera = camera.clone() camera.matrix = cast_array(self.get_centred_matrix(camera), camera.matrix) @ camera.matrix return camera
def _mm(self, a, b): a = cast_array(a, self.matrix) b = cast_array(b, self.matrix) return a @ b
def transform(self, points): points = ensure_homogeneous(points, d=3) if len(self.shuffle_indices) > 0: points = points[..., self.shuffle_indices, :] return points @ cast_array(self.matrix.T, points)
def set_output_size(self, width, height): self.dest_size = cast_array([width, height], self.dest_size) return self.dest_size