CoolFace
Modelpublic

beverley-gorry/colmap-vslamlab

sourceHugging Faceupdated 3mo agoView on Hugging Face
0likes
read_write_model.py606 linesDownload Raw Back to python
1# Copyright (c), ETH Zurich and UNC Chapel Hill.2# All rights reserved.3#4# Redistribution and use in source and binary forms, with or without5# modification, are permitted provided that the following conditions are met:6#7#     * Redistributions of source code must retain the above copyright8#       notice, this list of conditions and the following disclaimer.9#10#     * Redistributions in binary form must reproduce the above copyright11#       notice, this list of conditions and the following disclaimer in the12#       documentation and/or other materials provided with the distribution.13#14#     * Neither the name of ETH Zurich and UNC Chapel Hill nor the names of15#       its contributors may be used to endorse or promote products derived16#       from this software without specific prior written permission.17#18# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"19# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE20# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE21# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE22# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR23# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF24# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS25# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN26# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)27# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE28# POSSIBILITY OF SUCH DAMAGE.29 30 31import argparse32import collections33import os34import struct35 36import numpy as np37 38CameraModel = collections.namedtuple(39    "CameraModel", ["model_id", "model_name", "num_params"]40)41Camera = collections.namedtuple(42    "Camera", ["id", "model", "width", "height", "params"]43)44BaseImage = collections.namedtuple(45    "Image", ["id", "qvec", "tvec", "camera_id", "name", "xys", "point3D_ids"]46)47Point3D = collections.namedtuple(48    "Point3D", ["id", "xyz", "rgb", "error", "image_ids", "point2D_idxs"]49)50 51 52class Image(BaseImage):53    def qvec2rotmat(self):54        return qvec2rotmat(self.qvec)55 56 57CAMERA_MODELS = {58    CameraModel(model_id=0, model_name="SIMPLE_PINHOLE", num_params=3),59    CameraModel(model_id=1, model_name="PINHOLE", num_params=4),60    CameraModel(model_id=2, model_name="SIMPLE_RADIAL", num_params=4),61    CameraModel(model_id=3, model_name="RADIAL", num_params=5),62    CameraModel(model_id=4, model_name="OPENCV", num_params=8),63    CameraModel(model_id=5, model_name="OPENCV_FISHEYE", num_params=8),64    CameraModel(model_id=6, model_name="FULL_OPENCV", num_params=12),65    CameraModel(model_id=7, model_name="FOV", num_params=5),66    CameraModel(model_id=8, model_name="SIMPLE_RADIAL_FISHEYE", num_params=4),67    CameraModel(model_id=9, model_name="RADIAL_FISHEYE", num_params=5),68    CameraModel(model_id=10, model_name="THIN_PRISM_FISHEYE", num_params=12),69}70CAMERA_MODEL_IDS = dict(71    [(camera_model.model_id, camera_model) for camera_model in CAMERA_MODELS]72)73CAMERA_MODEL_NAMES = dict(74    [(camera_model.model_name, camera_model) for camera_model in CAMERA_MODELS]75)76 77 78def read_next_bytes(fid, num_bytes, format_char_sequence, endian_character="<"):79    """Read and unpack the next bytes from a binary file.80    :param fid:81    :param num_bytes: Sum of combination of {2, 4, 8}, e.g. 2, 6, 16, 30, etc.82    :param format_char_sequence: List of {c, e, f, d, h, H, i, I, l, L, q, Q}.83    :param endian_character: Any of {@, =, <, >, !}84    :return: Tuple of read and unpacked values.85    """86    data = fid.read(num_bytes)87    return struct.unpack(endian_character + format_char_sequence, data)88 89 90def write_next_bytes(fid, data, format_char_sequence, endian_character="<"):91    """pack and write to a binary file.92    :param fid:93    :param data: data to send, if multiple elements are sent at the same time,94    they should be encapsuled either in a list or a tuple95    :param format_char_sequence: List of {c, e, f, d, h, H, i, I, l, L, q, Q}.96    should be the same length as the data list or tuple97    :param endian_character: Any of {@, =, <, >, !}98    """99    if isinstance(data, (list, tuple)):100        bytes = struct.pack(endian_character + format_char_sequence, *data)101    else:102        bytes = struct.pack(endian_character + format_char_sequence, data)103    fid.write(bytes)104 105 106def read_cameras_text(path):107    """108    see: src/colmap/scene/reconstruction.cc109        void Reconstruction::WriteCamerasText(const std::string& path)110        void Reconstruction::ReadCamerasText(const std::string& path)111    """112    cameras = {}113    with open(path, "r") as fid:114        while True:115            line = fid.readline()116            if not line:117                break118            line = line.strip()119            if len(line) > 0 and line[0] != "#":120                elems = line.split()121                camera_id = int(elems[0])122                model = elems[1]123                width = int(elems[2])124                height = int(elems[3])125                params = np.array(tuple(map(float, elems[4:])))126                cameras[camera_id] = Camera(127                    id=camera_id,128                    model=model,129                    width=width,130                    height=height,131                    params=params,132                )133    return cameras134 135 136def read_cameras_binary(path_to_model_file):137    """138    see: src/colmap/scene/reconstruction.cc139        void Reconstruction::WriteCamerasBinary(const std::string& path)140        void Reconstruction::ReadCamerasBinary(const std::string& path)141    """142    cameras = {}143    with open(path_to_model_file, "rb") as fid:144        num_cameras = read_next_bytes(fid, 8, "Q")[0]145        for _ in range(num_cameras):146            camera_properties = read_next_bytes(147                fid, num_bytes=24, format_char_sequence="iiQQ"148            )149            camera_id = camera_properties[0]150            model_id = camera_properties[1]151            model_name = CAMERA_MODEL_IDS[camera_properties[1]].model_name152            width = camera_properties[2]153            height = camera_properties[3]154            num_params = CAMERA_MODEL_IDS[model_id].num_params155            params = read_next_bytes(156                fid,157                num_bytes=8 * num_params,158                format_char_sequence="d" * num_params,159            )160            cameras[camera_id] = Camera(161                id=camera_id,162                model=model_name,163                width=width,164                height=height,165                params=np.array(params),166            )167        assert len(cameras) == num_cameras168    return cameras169 170 171def write_cameras_text(cameras, path):172    """173    see: src/colmap/scene/reconstruction.cc174        void Reconstruction::WriteCamerasText(const std::string& path)175        void Reconstruction::ReadCamerasText(const std::string& path)176    """177    HEADER = (178        "# Camera list with one line of data per camera:\n"179        + "#   CAMERA_ID, MODEL, WIDTH, HEIGHT, PARAMS[]\n"180        + "# Number of cameras: {}\n".format(len(cameras))181    )182    with open(path, "w") as fid:183        fid.write(HEADER)184        for _, cam in cameras.items():185            to_write = [cam.id, cam.model, cam.width, cam.height, *cam.params]186            line = " ".join([str(elem) for elem in to_write])187            fid.write(line + "\n")188 189 190def write_cameras_binary(cameras, path_to_model_file):191    """192    see: src/colmap/scene/reconstruction.cc193        void Reconstruction::WriteCamerasBinary(const std::string& path)194        void Reconstruction::ReadCamerasBinary(const std::string& path)195    """196    with open(path_to_model_file, "wb") as fid:197        write_next_bytes(fid, len(cameras), "Q")198        for _, cam in cameras.items():199            model_id = CAMERA_MODEL_NAMES[cam.model].model_id200            camera_properties = [cam.id, model_id, cam.width, cam.height]201            write_next_bytes(fid, camera_properties, "iiQQ")202            for p in cam.params:203                write_next_bytes(fid, float(p), "d")204    return cameras205 206 207def read_images_text(path):208    """209    see: src/colmap/scene/reconstruction.cc210        void Reconstruction::ReadImagesText(const std::string& path)211        void Reconstruction::WriteImagesText(const std::string& path)212    """213    images = {}214    with open(path, "r") as fid:215        while True:216            line = fid.readline()217            if not line:218                break219            line = line.strip()220            if len(line) > 0 and line[0] != "#":221                elems = line.split()222                image_id = int(elems[0])223                qvec = np.array(tuple(map(float, elems[1:5])))224                tvec = np.array(tuple(map(float, elems[5:8])))225                camera_id = int(elems[8])226                image_name = elems[9]227                elems = fid.readline().split()228                xys = np.column_stack(229                    [230                        tuple(map(float, elems[0::3])),231                        tuple(map(float, elems[1::3])),232                    ]233                )234                point3D_ids = np.array(tuple(map(int, elems[2::3])))235                images[image_id] = Image(236                    id=image_id,237                    qvec=qvec,238                    tvec=tvec,239                    camera_id=camera_id,240                    name=image_name,241                    xys=xys,242                    point3D_ids=point3D_ids,243                )244    return images245 246 247def read_images_binary(path_to_model_file):248    """249    see: src/colmap/scene/reconstruction.cc250        void Reconstruction::ReadImagesBinary(const std::string& path)251        void Reconstruction::WriteImagesBinary(const std::string& path)252    """253    images = {}254    with open(path_to_model_file, "rb") as fid:255        num_reg_images = read_next_bytes(fid, 8, "Q")[0]256        for _ in range(num_reg_images):257            binary_image_properties = read_next_bytes(258                fid, num_bytes=64, format_char_sequence="idddddddi"259            )260            image_id = binary_image_properties[0]261            qvec = np.array(binary_image_properties[1:5])262            tvec = np.array(binary_image_properties[5:8])263            camera_id = binary_image_properties[8]264            binary_image_name = b""265            current_char = read_next_bytes(fid, 1, "c")[0]266            while current_char != b"\x00":  # look for the ASCII 0 entry267                binary_image_name += current_char268                current_char = read_next_bytes(fid, 1, "c")[0]269            image_name = binary_image_name.decode("utf-8")270            num_points2D = read_next_bytes(271                fid, num_bytes=8, format_char_sequence="Q"272            )[0]273            x_y_id_s = read_next_bytes(274                fid,275                num_bytes=24 * num_points2D,276                format_char_sequence="ddq" * num_points2D,277            )278            xys = np.column_stack(279                [280                    tuple(map(float, x_y_id_s[0::3])),281                    tuple(map(float, x_y_id_s[1::3])),282                ]283            )284            point3D_ids = np.array(tuple(map(int, x_y_id_s[2::3])))285            images[image_id] = Image(286                id=image_id,287                qvec=qvec,288                tvec=tvec,289                camera_id=camera_id,290                name=image_name,291                xys=xys,292                point3D_ids=point3D_ids,293            )294    return images295 296 297def write_images_text(images, path):298    """299    see: src/colmap/scene/reconstruction.cc300        void Reconstruction::ReadImagesText(const std::string& path)301        void Reconstruction::WriteImagesText(const std::string& path)302    """303    if len(images) == 0:304        mean_observations = 0305    else:306        mean_observations = sum(307            (len(img.point3D_ids) for _, img in images.items())308        ) / len(images)309    HEADER = (310        "# Image list with two lines of data per image:\n"311        + "#   IMAGE_ID, QW, QX, QY, QZ, TX, TY, TZ, CAMERA_ID, NAME\n"312        + "#   POINTS2D[] as (X, Y, POINT3D_ID)\n"313        + "# Number of images: {}, mean observations per image: {}\n".format(314            len(images), mean_observations315        )316    )317 318    with open(path, "w") as fid:319        fid.write(HEADER)320        for _, img in images.items():321            image_header = [322                img.id,323                *img.qvec,324                *img.tvec,325                img.camera_id,326                img.name,327            ]328            first_line = " ".join(map(str, image_header))329            fid.write(first_line + "\n")330 331            points_strings = []332            for xy, point3D_id in zip(img.xys, img.point3D_ids):333                points_strings.append(" ".join(map(str, [*xy, point3D_id])))334            fid.write(" ".join(points_strings) + "\n")335 336 337def write_images_binary(images, path_to_model_file):338    """339    see: src/colmap/scene/reconstruction.cc340        void Reconstruction::ReadImagesBinary(const std::string& path)341        void Reconstruction::WriteImagesBinary(const std::string& path)342    """343    with open(path_to_model_file, "wb") as fid:344        write_next_bytes(fid, len(images), "Q")345        for _, img in images.items():346            write_next_bytes(fid, img.id, "i")347            write_next_bytes(fid, img.qvec.tolist(), "dddd")348            write_next_bytes(fid, img.tvec.tolist(), "ddd")349            write_next_bytes(fid, img.camera_id, "i")350            for char in img.name:351                write_next_bytes(fid, char.encode("utf-8"), "c")352            write_next_bytes(fid, b"\x00", "c")353            write_next_bytes(fid, len(img.point3D_ids), "Q")354            for xy, p3d_id in zip(img.xys, img.point3D_ids):355                write_next_bytes(fid, [*xy, p3d_id], "ddq")356 357 358def read_points3D_text(path):359    """360    see: src/colmap/scene/reconstruction.cc361        void Reconstruction::ReadPoints3DText(const std::string& path)362        void Reconstruction::WritePoints3DText(const std::string& path)363    """364    points3D = {}365    with open(path, "r") as fid:366        while True:367            line = fid.readline()368            if not line:369                break370            line = line.strip()371            if len(line) > 0 and line[0] != "#":372                elems = line.split()373                point3D_id = int(elems[0])374                xyz = np.array(tuple(map(float, elems[1:4])))375                rgb = np.array(tuple(map(int, elems[4:7])))376                error = float(elems[7])377                image_ids = np.array(tuple(map(int, elems[8::2])))378                point2D_idxs = np.array(tuple(map(int, elems[9::2])))379                points3D[point3D_id] = Point3D(380                    id=point3D_id,381                    xyz=xyz,382                    rgb=rgb,383                    error=error,384                    image_ids=image_ids,385                    point2D_idxs=point2D_idxs,386                )387    return points3D388 389 390def read_points3D_binary(path_to_model_file):391    """392    see: src/colmap/scene/reconstruction.cc393        void Reconstruction::ReadPoints3DBinary(const std::string& path)394        void Reconstruction::WritePoints3DBinary(const std::string& path)395    """396    points3D = {}397    with open(path_to_model_file, "rb") as fid:398        num_points = read_next_bytes(fid, 8, "Q")[0]399        for _ in range(num_points):400            binary_point_line_properties = read_next_bytes(401                fid, num_bytes=43, format_char_sequence="QdddBBBd"402            )403            point3D_id = binary_point_line_properties[0]404            xyz = np.array(binary_point_line_properties[1:4])405            rgb = np.array(binary_point_line_properties[4:7])406            error = np.array(binary_point_line_properties[7])407            track_length = read_next_bytes(408                fid, num_bytes=8, format_char_sequence="Q"409            )[0]410            track_elems = read_next_bytes(411                fid,412                num_bytes=8 * track_length,413                format_char_sequence="ii" * track_length,414            )415            image_ids = np.array(tuple(map(int, track_elems[0::2])))416            point2D_idxs = np.array(tuple(map(int, track_elems[1::2])))417            points3D[point3D_id] = Point3D(418                id=point3D_id,419                xyz=xyz,420                rgb=rgb,421                error=error,422                image_ids=image_ids,423                point2D_idxs=point2D_idxs,424            )425    return points3D426 427 428def write_points3D_text(points3D, path):429    """430    see: src/colmap/scene/reconstruction.cc431        void Reconstruction::ReadPoints3DText(const std::string& path)432        void Reconstruction::WritePoints3DText(const std::string& path)433    """434    if len(points3D) == 0:435        mean_track_length = 0436    else:437        mean_track_length = sum(438            (len(pt.image_ids) for _, pt in points3D.items())439        ) / len(points3D)440    HEADER = (441        "# 3D point list with one line of data per point:\n"442        + "#   POINT3D_ID, X, Y, Z, R, G, B, ERROR, TRACK[] as (IMAGE_ID, POINT2D_IDX)\n"443        + "# Number of points: {}, mean track length: {}\n".format(444            len(points3D), mean_track_length445        )446    )447 448    with open(path, "w") as fid:449        fid.write(HEADER)450        for _, pt in points3D.items():451            point_header = [pt.id, *pt.xyz, *pt.rgb, pt.error]452            fid.write(" ".join(map(str, point_header)) + " ")453            track_strings = []454            for image_id, point2D in zip(pt.image_ids, pt.point2D_idxs):455                track_strings.append(" ".join(map(str, [image_id, point2D])))456            fid.write(" ".join(track_strings) + "\n")457 458 459def write_points3D_binary(points3D, path_to_model_file):460    """461    see: src/colmap/scene/reconstruction.cc462        void Reconstruction::ReadPoints3DBinary(const std::string& path)463        void Reconstruction::WritePoints3DBinary(const std::string& path)464    """465    with open(path_to_model_file, "wb") as fid:466        write_next_bytes(fid, len(points3D), "Q")467        for _, pt in points3D.items():468            write_next_bytes(fid, pt.id, "Q")469            write_next_bytes(fid, pt.xyz.tolist(), "ddd")470            write_next_bytes(fid, pt.rgb.tolist(), "BBB")471            write_next_bytes(fid, pt.error, "d")472            track_length = pt.image_ids.shape[0]473            write_next_bytes(fid, track_length, "Q")474            for image_id, point2D_id in zip(pt.image_ids, pt.point2D_idxs):475                write_next_bytes(fid, [image_id, point2D_id], "ii")476 477 478def detect_model_format(path, ext):479    if (480        os.path.isfile(os.path.join(path, "cameras" + ext))481        and os.path.isfile(os.path.join(path, "images" + ext))482        and os.path.isfile(os.path.join(path, "points3D" + ext))483    ):484        print("Detected model format: '" + ext + "'")485        return True486 487    return False488 489 490def read_model(path, ext=""):491    # try to detect the extension automatically492    if ext == "":493        if detect_model_format(path, ".bin"):494            ext = ".bin"495        elif detect_model_format(path, ".txt"):496            ext = ".txt"497        else:498            print("Provide model format: '.bin' or '.txt'")499            return500 501    if ext == ".txt":502        cameras = read_cameras_text(os.path.join(path, "cameras" + ext))503        images = read_images_text(os.path.join(path, "images" + ext))504        points3D = read_points3D_text(os.path.join(path, "points3D") + ext)505    else:506        cameras = read_cameras_binary(os.path.join(path, "cameras" + ext))507        images = read_images_binary(os.path.join(path, "images" + ext))508        points3D = read_points3D_binary(os.path.join(path, "points3D") + ext)509    return cameras, images, points3D510 511 512def write_model(cameras, images, points3D, path, ext=".bin"):513    if ext == ".txt":514        write_cameras_text(cameras, os.path.join(path, "cameras" + ext))515        write_images_text(images, os.path.join(path, "images" + ext))516        write_points3D_text(points3D, os.path.join(path, "points3D") + ext)517    else:518        write_cameras_binary(cameras, os.path.join(path, "cameras" + ext))519        write_images_binary(images, os.path.join(path, "images" + ext))520        write_points3D_binary(points3D, os.path.join(path, "points3D") + ext)521    return cameras, images, points3D522 523 524def qvec2rotmat(qvec):525    return np.array(526        [527            [528                1 - 2 * qvec[2] ** 2 - 2 * qvec[3] ** 2,529                2 * qvec[1] * qvec[2] - 2 * qvec[0] * qvec[3],530                2 * qvec[3] * qvec[1] + 2 * qvec[0] * qvec[2],531            ],532            [533                2 * qvec[1] * qvec[2] + 2 * qvec[0] * qvec[3],534                1 - 2 * qvec[1] ** 2 - 2 * qvec[3] ** 2,535                2 * qvec[2] * qvec[3] - 2 * qvec[0] * qvec[1],536            ],537            [538                2 * qvec[3] * qvec[1] - 2 * qvec[0] * qvec[2],539                2 * qvec[2] * qvec[3] + 2 * qvec[0] * qvec[1],540                1 - 2 * qvec[1] ** 2 - 2 * qvec[2] ** 2,541            ],542        ]543    )544 545 546def rotmat2qvec(R):547    Rxx, Ryx, Rzx, Rxy, Ryy, Rzy, Rxz, Ryz, Rzz = R.flat548    K = (549        np.array(550            [551                [Rxx - Ryy - Rzz, 0, 0, 0],552                [Ryx + Rxy, Ryy - Rxx - Rzz, 0, 0],553                [Rzx + Rxz, Rzy + Ryz, Rzz - Rxx - Ryy, 0],554                [Ryz - Rzy, Rzx - Rxz, Rxy - Ryx, Rxx + Ryy + Rzz],555            ]556        )557        / 3.0558    )559    eigvals, eigvecs = np.linalg.eigh(K)560    qvec = eigvecs[[3, 0, 1, 2], np.argmax(eigvals)]561    if qvec[0] < 0:562        qvec *= -1563    return qvec564 565 566def main():567    parser = argparse.ArgumentParser(568        description="Read and write COLMAP binary and text models"569    )570    parser.add_argument("--input_model", help="path to input model folder")571    parser.add_argument(572        "--input_format",573        choices=[".bin", ".txt"],574        help="input model format",575        default="",576    )577    parser.add_argument("--output_model", help="path to output model folder")578    parser.add_argument(579        "--output_format",580        choices=[".bin", ".txt"],581        help="output model format",582        default=".txt",583    )584    args = parser.parse_args()585 586    cameras, images, points3D = read_model(587        path=args.input_model, ext=args.input_format588    )589 590    print("num_cameras:", len(cameras))591    print("num_images:", len(images))592    print("num_points3D:", len(points3D))593 594    if args.output_model is not None:595        write_model(596            cameras,597            images,598            points3D,599            path=args.output_model,600            ext=args.output_format,601        )602 603 604if __name__ == "__main__":605    main()606