Convert a reconstruction built from the rig of perspective virtual cameras back to one equirectangular camera/image per input panorama. The output reconstruction references the original panorama images with the native EQUIRECTANGULAR camera model. Frame poses, 3D points, and
(
self, reconstruction: pycolmap.Reconstruction
)
| 305 | raise ValueError(f"Unknown virtual camera for image {image_name!r}.") |
| 306 | |
| 307 | def convert_to_equirectangular( |
| 308 | self, reconstruction: pycolmap.Reconstruction |
| 309 | ) -> pycolmap.Reconstruction: |
| 310 | """Convert a reconstruction built from the rig of perspective virtual |
| 311 | cameras back to one equirectangular camera/image per input panorama. |
| 312 | |
| 313 | The output reconstruction references the original panorama images with |
| 314 | the native EQUIRECTANGULAR camera model. Frame poses, 3D points, and |
| 315 | all keypoints (including those without a 3D observation) are carried |
| 316 | over by re-projecting the perspective keypoints onto the panorama |
| 317 | through the same spherical mapping used for rendering, so the result is |
| 318 | a valid, bundle-adjustable reconstruction. |
| 319 | """ |
| 320 | if self._camera is None or self._pano_size is None: |
| 321 | raise RuntimeError("No panorama was rendered yet.") |
| 322 | pano_width, pano_height = self._pano_size |
| 323 | |
| 324 | equirect = pycolmap.Reconstruction() |
| 325 | equirect_camera = pycolmap.Camera.create_from_model_id( |
| 326 | camera_id=1, |
| 327 | model=pycolmap.CameraModelId.EQUIRECTANGULAR, |
| 328 | focal_length=0.0, |
| 329 | width=pano_width, |
| 330 | height=pano_height, |
| 331 | ) |
| 332 | equirect.add_camera_with_trivial_rig(equirect_camera) |
| 333 | |
| 334 | # The rig reference sensor is virtual camera 0 (see |
| 335 | # create_pano_rig_config), so rig_from_world == cam0_from_world and |
| 336 | # pano_from_world = pano_from_cam0 @ cam0_from_world. The virtual |
| 337 | # cameras share the panorama center, hence the zero translation. |
| 338 | pano_from_ref = pycolmap.Rigid3d( |
| 339 | pycolmap.Rotation3d( |
| 340 | cast(NDArray3x3, self.cams_from_pano_rotation[0]) |
| 341 | ), |
| 342 | cast(NDArray3x1, np.zeros((3, 1), dtype=np.float64)), |
| 343 | ).inverse() |
| 344 | |
| 345 | # Group the registered virtual cameras by frame. All virtual cameras of |
| 346 | # a frame observe the same panorama and share its pose, so we can |
| 347 | # accumulate their keypoints into a single equirectangular image. |
| 348 | images_by_frame: dict[int, list[pycolmap.Image]] = ( |
| 349 | collections.defaultdict(list) |
| 350 | ) |
| 351 | for image in reconstruction.images.values(): |
| 352 | if image.has_pose: |
| 353 | images_by_frame[image.frame_id].append(image) |
| 354 | |
| 355 | # Maps an image_id of a virtual camera to a dict from its old point2D |
| 356 | # index to the (new point2D index, pano_name) in the equirectangular |
| 357 | # image, so we can later rebuild the 3D point tracks. |
| 358 | old_to_new_point2D: dict[int, dict[int, tuple[int, str]]] = {} |
| 359 | pano_to_image_id: dict[str, int] = {} |
| 360 | |
| 361 | frame_images = sorted( |
| 362 | images_by_frame.values(), |
| 363 | key=lambda images: self.split_image_name(images[0].name)[1], |
| 364 | ) |
no test coverage detected