Source code for xrprimer.calibration.mview_fisheye_calibrator

import logging
import os
import shutil
from typing import List, Tuple, Union

import cv2
import numpy as np

from xrprimer.data_structure.camera import FisheyeCameraParameter
from xrprimer.transform.camera.distortion import undistort_images
from xrprimer.utils.path_utils import (
    Existence,
    check_path_existence,
    prepare_output_path,
)
from .mview_pinhole_calibrator import MviewPinholeCalibrator
from .sview_fisheye_calibrator import SviewFisheyeDistortionCalibrator


[docs]class MviewFisheyeCalibrator(MviewPinholeCalibrator): """Multi-view extrinsic calibrator for distorted fisheye cameras.""" def __init__(self, chessboard_width: int, chessboard_height: int, chessboard_square_size: int, work_dir: str, calibrate_intrinsic: bool = False, calibrate_distortion: bool = False, calibrate_extrinsic: bool = True, logger: Union[None, str, logging.Logger] = None) -> None: """Initialization for MviewFisheyeCalibrator class. Args: chessboard_width (int): How many internal corners along the horizontal edge of the chessboard. chessboard_height (int): How many internal corners along the vertical edge of the chessboard. chessboard_square_size (int): The edge length of a unit square in millimeter. work_dir (str): A path to an empty dir or nonexistent dir, undistorted frames will be saved there. calibrate_intrinsic (bool, optional): Whether to calibrate intrinsic. Defaults to False. calibrate_distortion (bool, optional): Whether to calibrate distortion coefficients. Defaults to False. calibrate_extrinsic (bool, optional): Whether to calibrate extrinsic. Defaults to True. logger (Union[None, str, logging.Logger], optional): Logger for logging. If None, root logger will be selected. Defaults to None. """ MviewPinholeCalibrator.__init__( self, chessboard_width=chessboard_width, chessboard_height=chessboard_height, chessboard_square_size=chessboard_square_size, logger=logger) self.work_dir = work_dir if calibrate_intrinsic: self.logger.error('Intrinsic calibration not implemented yet.') raise NotImplementedError if (calibrate_intrinsic or calibrate_distortion or calibrate_extrinsic) is False: self.logger.error( 'Please select at least one parameter to calibrate.') raise ValueError self.calibrate_intrinsic = calibrate_intrinsic self.calibrate_distortion = calibrate_distortion self.calibrate_extrinsic = calibrate_extrinsic
[docs] def calibrate( self, frames: List[List[str]], fisheye_param_list: List[FisheyeCameraParameter], ) -> List[FisheyeCameraParameter]: """Calibrate multi-FisheyeCameraParameters with a chessboard. It takes intrinsics and distortion coefficients from fisheye_param_list, calibrates only extrinsics on undistorted frames. Args: frames (List[List[str]]): A nested list of distorted image paths. The shape is [n_frame, n_view], and each element is the path to an image file. '' stands for an empty image. fisheye_param_list (List[FisheyeCameraParameter]): A list of FisheyeCameraParameters. Intrinsic matrix and distortion coefficients are necessary for calibration. Returns: List[FisheyeCameraParameter]: A list of calibrated fisheye cameras, name, logger, resolution will be kept. """ prepare_output_path( output_path=self.work_dir, path_type='dir', overwrite=False, logger=self.logger) if len(frames) <= 0: self.logger.error('Frames are necessary for fisheye extrinsic' + ' calibration.') raise ValueError if len(frames[0]) != len(fisheye_param_list): self.logger.error( 'n_view of frames must be equal to len(fisheye_param_list).') raise ValueError ret_list = [fisheye_param for fisheye_param in fisheye_param_list] if self.calibrate_distortion: sview_distortion_calibrator = SviewFisheyeDistortionCalibrator( chessboard_width=self.chessboard_width, chessboard_height=self.chessboard_height, logger=self.logger) for view_idx, fisheye_param in enumerate(ret_list): sview_frames = [] for mview_frames in frames: if len(mview_frames[view_idx]) > 0: sview_frames.append(mview_frames[view_idx]) calibrated_fisheye_param = \ sview_distortion_calibrator.calibrate( frames=sview_frames, fisheye_param=fisheye_param) ret_list[view_idx] = calibrated_fisheye_param if self.calibrate_extrinsic: undistorted_frames, pinhole_param_list = \ self.__prepare_undistorted_frames__( frames=frames, fisheye_param_list=ret_list) pinhole_param_list = MviewPinholeCalibrator.calibrate( self, frames=undistorted_frames, pinhole_param_list=pinhole_param_list, ) self.__remove_work_dir__() for view_idx, pinhole_param in enumerate(pinhole_param_list): fisheye_param = ret_list[view_idx].clone() fisheye_param.convention = 'opencv' fisheye_param.set_KRT( R=pinhole_param.get_extrinsic_r(), T=pinhole_param.get_extrinsic_t(), world2cam=False) ret_list[view_idx] = fisheye_param return ret_list
def __prepare_undistorted_frames__( self, frames: List[List[str]], fisheye_param_list: List[FisheyeCameraParameter], ) -> Tuple[List[List[str]], List[FisheyeCameraParameter]]: """Undistort images in frames, return paths to undistorted images and undistorted pinhole cameras. Args: frames (List[List[str]]): A nested list of distorted image paths. The shape is [n_frame, n_view], and each element is the path to an image file. '' stands for an empty image. fisheye_param_list (List[FisheyeCameraParameter]): A list of FisheyeCameraParameters. Intrinsic matrix and distortion coefficients are necessary for calibration. Returns: undistorted_frames (List[List[str]]): A nested list of undistorted image paths. pinhole_param_list (List[FisheyeCameraParameter]): A list of PinholeCameraParameters """ n_view = len(fisheye_param_list) pinhole_param_list = [] undistorted_frames = [[] for _ in range(len(frames))] for view_idx in range(n_view): sview_path_list = [] default_shape = None for frame_idx, mview_paths in enumerate(frames): img_path = mview_paths[view_idx] if default_shape is None and len(img_path) > 0: default_shape = cv2.imread(img_path).shape sview_path_list.append(img_path) sview_img_list = [] sview_idx_list = [] for frame_idx, img_path in enumerate(sview_path_list): if len(img_path) > 0: img = cv2.imread(img_path) sview_img_list.append(img) sview_idx_list.append(frame_idx) sview_img_arr = np.asarray(sview_img_list) pinhole_param, undist_img_arr = undistort_images( distorted_cam=fisheye_param_list[view_idx], image_array=sview_img_arr) pinhole_param_list.append(pinhole_param) for img, frame_idx in zip(sview_img_list, sview_idx_list): path = os.path.join( self.work_dir, f'frame_{frame_idx:06d}_view_{view_idx:03d}.png') cv2.imwrite(path, img) for frame_idx, img_path in enumerate(sview_path_list): path = os.path.join( self.work_dir, f'frame_{frame_idx:06d}_view_{view_idx:03d}.png') if len(img_path) > 0: undistorted_frames[frame_idx].append(path) else: undistorted_frames[frame_idx].append('') return undistorted_frames, pinhole_param_list def __remove_work_dir__(self): """Remove temp files in self.work_dir.""" if check_path_existence(self.work_dir) == \ Existence.DirectoryExistEmpty or \ check_path_existence(self.work_dir) == \ Existence.DirectoryExistNotEmpty: shutil.rmtree(self.work_dir)