| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| import argparse |
|
|
| import numpy as np |
| import open3d |
| from read_write_model import qvec2rotmat, read_model |
|
|
|
|
| class Model: |
| def __init__(self): |
| self.cameras = [] |
| self.images = [] |
| self.points3D = [] |
| self.__vis = None |
|
|
| def read_model(self, path, ext=""): |
| self.cameras, self.images, self.points3D = read_model(path, ext) |
|
|
| def add_points(self, min_track_len=3, remove_statistical_outlier=True): |
| pcd = open3d.geometry.PointCloud() |
|
|
| xyz = [] |
| rgb = [] |
| for point3D in self.points3D.values(): |
| track_len = len(point3D.point2D_idxs) |
| if track_len < min_track_len: |
| continue |
| xyz.append(point3D.xyz) |
| rgb.append(point3D.rgb / 255) |
|
|
| pcd.points = open3d.utility.Vector3dVector(xyz) |
| pcd.colors = open3d.utility.Vector3dVector(rgb) |
|
|
| |
| if remove_statistical_outlier: |
| [pcd, _] = pcd.remove_statistical_outlier( |
| nb_neighbors=20, std_ratio=2.0 |
| ) |
|
|
| |
| self.__vis.add_geometry(pcd) |
| self.__vis.poll_events() |
| self.__vis.update_renderer() |
|
|
| def add_cameras(self, scale=1): |
| frames = [] |
| for img in self.images.values(): |
| |
| R = qvec2rotmat(img.qvec) |
|
|
| |
| t = img.tvec |
|
|
| |
| t = -R.T @ t |
| R = R.T |
|
|
| |
| cam = self.cameras[img.camera_id] |
|
|
| if cam.model in ("SIMPLE_PINHOLE", "SIMPLE_RADIAL", "RADIAL"): |
| fx = fy = cam.params[0] |
| cx = cam.params[1] |
| cy = cam.params[2] |
| elif cam.model in ( |
| "PINHOLE", |
| "OPENCV", |
| "OPENCV_FISHEYE", |
| "FULL_OPENCV", |
| ): |
| fx = cam.params[0] |
| fy = cam.params[1] |
| cx = cam.params[2] |
| cy = cam.params[3] |
| else: |
| raise Exception("Camera model not supported") |
|
|
| |
| K = np.identity(3) |
| K[0, 0] = fx |
| K[1, 1] = fy |
| K[0, 2] = cx |
| K[1, 2] = cy |
|
|
| |
| cam_model = draw_camera(K, R, t, cam.width, cam.height, scale) |
| frames.extend(cam_model) |
|
|
| |
| for i in frames: |
| self.__vis.add_geometry(i) |
|
|
| def create_window(self): |
| self.__vis = open3d.visualization.Visualizer() |
| self.__vis.create_window() |
|
|
| def show(self): |
| self.__vis.poll_events() |
| self.__vis.update_renderer() |
| self.__vis.run() |
| self.__vis.destroy_window() |
|
|
|
|
| def draw_camera(K, R, t, w, h, scale=1, color=[0.8, 0.2, 0.8]): |
| """Create axis, plane and pyramed geometries in Open3D format. |
| :param K: calibration matrix (camera intrinsics) |
| :param R: rotation matrix |
| :param t: translation |
| :param w: image width |
| :param h: image height |
| :param scale: camera model scale |
| :param color: color of the image plane and pyramid lines |
| :return: camera model geometries (axis, plane and pyramid) |
| """ |
|
|
| |
| K = K.copy() / scale |
| Kinv = np.linalg.inv(K) |
|
|
| |
| T = np.column_stack((R, t)) |
| T = np.vstack((T, (0, 0, 0, 1))) |
|
|
| |
| axis = open3d.geometry.TriangleMesh.create_coordinate_frame( |
| size=0.5 * scale |
| ) |
| axis.transform(T) |
|
|
| |
| points_pixel = [ |
| [0, 0, 0], |
| [0, 0, 1], |
| [w, 0, 1], |
| [0, h, 1], |
| [w, h, 1], |
| ] |
|
|
| |
| points = [Kinv @ p for p in points_pixel] |
|
|
| |
| width = abs(points[1][0]) + abs(points[3][0]) |
| height = abs(points[1][1]) + abs(points[3][1]) |
| plane = open3d.geometry.TriangleMesh.create_box(width, height, depth=1e-6) |
| plane.paint_uniform_color(color) |
| plane.translate([points[1][0], points[1][1], scale]) |
| plane.transform(T) |
|
|
| |
| points_in_world = [(R @ p + t) for p in points] |
| lines = [ |
| [0, 1], |
| [0, 2], |
| [0, 3], |
| [0, 4], |
| ] |
| colors = [color for i in range(len(lines))] |
| line_set = open3d.geometry.LineSet( |
| points=open3d.utility.Vector3dVector(points_in_world), |
| lines=open3d.utility.Vector2iVector(lines), |
| ) |
| line_set.colors = open3d.utility.Vector3dVector(colors) |
|
|
| |
| return [axis, plane, line_set] |
|
|
|
|
| def parse_args(): |
| parser = argparse.ArgumentParser( |
| description="Visualize COLMAP binary and text models" |
| ) |
| parser.add_argument( |
| "--input_model", required=True, help="path to input model folder" |
| ) |
| parser.add_argument( |
| "--input_format", |
| choices=[".bin", ".txt"], |
| help="input model format", |
| default="", |
| ) |
| args = parser.parse_args() |
| return args |
|
|
|
|
| def main(): |
| args = parse_args() |
|
|
| |
| model = Model() |
| model.read_model(args.input_model, ext=args.input_format) |
|
|
| print("num_cameras:", len(model.cameras)) |
| print("num_images:", len(model.images)) |
| print("num_points3D:", len(model.points3D)) |
|
|
| |
| model.create_window() |
| model.add_points() |
| model.add_cameras(scale=0.25) |
| model.show() |
|
|
|
|
| if __name__ == "__main__": |
| main() |
|
|