├── .gitignore
├── LICENSE
├── README.md
├── app.py
├── docs
├── 692db.png
├── dinosaur.png
├── elu.png
├── lego.png
├── scene0707.png
├── sph.png
├── teaser.png
└── xyz.png
├── inputs
├── colmap
│ └── scene0707
│ │ ├── images.txt
│ │ └── images
│ │ ├── 0.jpg
│ │ ├── 192.jpg
│ │ └── 64.jpg
├── nerf
│ └── lego
│ │ ├── images
│ │ ├── r_0.png
│ │ ├── r_19.png
│ │ ├── r_20.png
│ │ ├── r_21.png
│ │ └── r_23.png
│ │ └── transforms.json
└── quick
│ ├── cam_c2w
│ ├── images
│ │ ├── 001.png
│ │ ├── 011.png
│ │ ├── 065.png
│ │ └── 077.png
│ ├── poses.json
│ └── poses
│ │ ├── 001.npy
│ │ ├── 011.npy
│ │ ├── 065.npy
│ │ └── 077.npy
│ ├── cam_elu
│ ├── poses.json
│ └── poses
│ │ ├── 0.txt
│ │ ├── 1.txt
│ │ ├── 2.txt
│ │ ├── 3.txt
│ │ ├── 4.txt
│ │ └── 5.txt
│ ├── cam_sph
│ ├── poses.json
│ └── poses
│ │ ├── 0.txt
│ │ ├── 1.txt
│ │ ├── 2.txt
│ │ └── 3.txt
│ ├── cam_w2c
│ ├── images
│ │ ├── 001.png
│ │ ├── 004.png
│ │ ├── 008.png
│ │ └── 010.png
│ ├── poses.json
│ └── poses
│ │ ├── 001.npy
│ │ ├── 004.npy
│ │ ├── 008.npy
│ │ └── 010.npy
│ └── cam_xyz
│ ├── poses.json
│ └── poses
│ ├── 0.txt
│ ├── 1.txt
│ ├── 2.txt
│ └── 3.txt
├── requirements.txt
└── src
├── loader.py
├── utils.py
└── visualizer.py
/.gitignore:
--------------------------------------------------------------------------------
1 | __pycache__
2 | .DS_Store
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "[]"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright [yyyy] [name of copyright owner]
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # CameraViewer: A lightweight tool for camera pose visualization
2 | Visualize camera poses in seconds!
3 | A python tool that helps plot cameras for 3D computer vision research.
4 |
5 |
6 |
7 | ## Installation
8 | Create an environment with Python >= 3.9 (Recommend to use [Anaconda](https://www.anaconda.com/download/) or [Miniconda](https://docs.conda.io/en/latest/miniconda.html))
9 | ```
10 | conda create -n viewer python=3.9
11 | conda activate viewer
12 | pip install -r requirements.txt
13 | ```
14 |
15 | ## Run Demo
16 |
17 | #### 1. Camera poses by positions in [spherical coordinates](https://en.wikipedia.org/wiki/Spherical_coordinate_system) (polar, azimuth, radius).
18 | Cameras will look at the origin point, and their up directions will be +Z.
19 |
20 | To read poses from a single JSON file ([details](#prepare_json)), run the command:
21 | ```
22 | python app.py --root inputs/quick/cam_sph/
23 | ```
24 |
25 | To read poses from a folder of pose files ([details](#prepare_folder)), run the command with ```--type sph``` to parse the files:
26 | ```
27 | python app.py --root inputs/quick/cam_sph/ --type sph
28 | ```
29 |
30 | The plotted figure will be opened in your browser:
31 |
32 |
33 |
34 |
35 |
36 |
37 | #### 2. Camera poses by positions in [cartesian coordinates](https://en.wikipedia.org/wiki/Cartesian_coordinate_system) (x, y, z).
38 | Cameras will look at the origin point, and their up directions will be +Z.
39 |
40 | To read poses from a single JSON file ([details](#prepare_json)), run the command:
41 | ```
42 | python app.py --root inputs/quick/cam_xyz/
43 | ```
44 |
45 | To read poses from a folder of pose files ([details](#prepare_folder)), run the command with ```--type xyz``` to parse the files:
46 | ```
47 | python app.py --root inputs/quick/cam_xyz/ --type xyz
48 | ```
49 |
50 |
51 |
52 | #### 3. Camera poses by eye (camera) positions, look-at positions, and up vectors.
53 |
54 | To read poses from a single JSON file ([details](#prepare_json)), run the command:
55 | ```
56 | python app.py --root inputs/quick/cam_elu/
57 | ```
58 |
59 | To read poses from a folder of pose files ([details](#prepare_folder)), run the command with ```--type elu``` to parse the files:
60 | ```
61 | python app.py --root inputs/quick/cam_elu/ --type elu
62 | ```
63 |
64 |
65 |
66 | #### 4. Camera poses by camera-to-world matrix.
67 |
68 | To read poses from a single JSON file ([details](#prepare_json)), run the command:
69 | ```
70 | python app.py --root inputs/quick/cam_c2w/
71 | ```
72 |
73 | To read poses from a folder of pose files ([details](#prepare_folder)), run the command with ```--type c2w``` to parse the files:
74 | ```
75 | python app.py --root inputs/quick/cam_c2w/ --type c2w
76 | ```
77 |
78 |
79 |
80 | #### 5. Camera poses by world-to-camera matrix.
81 |
82 | To read poses from a single JSON file ([details](#prepare_json)), run the command:
83 | ```
84 | python app.py --root inputs/quick/cam_w2c/ --image_size 128
85 | ```
86 |
87 | To read poses from a folder of pose files ([details](#prepare_folder)), run the command with ```--type w2c``` to parse the files:
88 | ```
89 | python app.py --root inputs/quick/cam_w2c/ --type w2c --image_size 128
90 | ```
91 |
92 |
93 | #### 6. Camera poses in NeRF format.
94 | Poses are read from ```transforms.json``` under ```--root``` folder.
95 | ```
96 | python app.py --root inputs/nerf/lego/ --format nerf --scene_size 6
97 | ```
98 |
99 |
100 | #### 7. Camera poses in COLMAP format.
101 | Poses are read from ```images.txt``` under ```--root``` folder.
102 | ```
103 | python app.py --root inputs/colmap/scene0707/ --format colmap --scene_size 7
104 | ```
105 |
106 |
107 | ## Prepare Cameras in Quick Format
108 |
109 | There are two options to prepare camera poses: JSON or folder
110 |
111 |
112 | ### 1. Prepare JSON
113 |
114 | Step 1: Create a folder containing ```poses.json``` and ```images/``` (optional). For example:
115 | ```
116 | mkdir -p inputs/obj/
117 | touch inputs/obj/poses.json
118 | mkdir -p inputs/obj/images/ #optional
119 | ```
120 |
121 | Step 2: Prepare the ```poses.json``` file with the following structure:
122 | ```
123 | {
124 | "type": "sph",
125 | "frames": [
126 | {
127 | "image_name": "0.png",
128 | "pose": [75, 0, 4]
129 | },
130 | {
131 | "image_name": "1.png",
132 | "pose": [75, 90, 4]
133 | },
134 | {
135 | "image_name": "2.png",
136 | "pose": [75, 180, 4]
137 | }
138 | ]
139 | }
140 | ```
141 | The value of ```"type"``` specifies the type of camera poses, which can be:
142 | 1. ```sph```: A row vector of spherical coordinates: polar (degree), azimuth (degree), radius. [Demo sph](#demo_sph).
143 | 2. ```xyz```: A row vector of cartesian coordinates: x, y, z. [Demo xyz](#demo_xyz).
144 | 3. ```elu```: A matrix including eye position (1st row), look-at position (2nd row), and up vector (3rd row). [Demo elu](#demo_elu).
145 | 4. ```c2w```: A camera-to-world matrix. [Demo cam2world](#demo_c2w).
146 | 5. ```w2c```: A world-to-camera matrix. [Demo world2cam](#demo_w2c).
147 |
148 | Step 3 (Optional): Put the corresponding images under ```images/```. For example:
149 | ```
150 | inputs
151 | ├── obj
152 | ├── images
153 | ├── 0.png
154 | ├── 1.png
155 | ├── 2.png
156 | ```
157 |
158 | The image files can be in PNG (.png) or JPEG (.jpg / .jpeg) format.
159 |
160 |
161 | ### 2. Prepare folder
162 |
163 | Step 1: Create a folder containing ```poses/``` and ```images/``` (optional). For example:
164 | ```
165 | mkdir -p inputs/obj/
166 | mkdir -p inputs/obj/poses/
167 | mkdir -p inputs/obj/images/ #optional
168 | ```
169 |
170 | Step 2: Put camera pose files under ```poses/```. For example:
171 | ```
172 | inputs
173 | ├── obj
174 | ├── poses
175 | ├── 0.txt
176 | ├── 1.txt
177 | ├── 2.txt
178 | ```
179 | The camera poses can be in the mentioned 5 types: sph, xyz, elu, c2w, w2c. The pose files can be in plain text (.txt) or Numpy (.npy).
180 |
181 |
182 | Step 3 (Optional): Put the corresponding images under ```images/``` with the same names to the pose files. For example:
183 | ```
184 | inputs
185 | ├── obj
186 | ├── images
187 | ├── 0.png
188 | ├── 1.png
189 | ├── 2.png
190 | ```
191 |
192 | The image files can be in PNG (.png) or JPEG (.jpg / .jpeg) format.
193 |
194 | ## Known Issues
195 | - Running the commands in PyCharm might not open the figures in your browser. Please use terminal if this issue occurred.
196 | ## Acknowledgement
197 |
198 | Part of the code is modified from:
199 | - [Zero-1-to-3](https://github.com/cvlab-columbia/zero123)
200 | - [Instant-NGP](https://github.com/NVlabs/instant-ngp)
201 |
202 | Thanks to the maintainers of these projects!
--------------------------------------------------------------------------------
/app.py:
--------------------------------------------------------------------------------
1 | import os, sys
2 | import argparse
3 | import numpy as np
4 |
5 | from src.visualizer import CameraVisualizer
6 | from src.loader import load_quick, load_nerf, load_colmap
7 | from src.utils import load_image, rescale_cameras, recenter_cameras
8 |
9 | parser = argparse.ArgumentParser()
10 | parser.add_argument('--root', type=str)
11 | parser.add_argument('--format', default='quick', choices=['quick', 'nerf', 'colmap'])
12 | parser.add_argument('--type', default=None, choices=[None, 'sph', 'xyz', 'elu', 'c2w', 'w2c'])
13 | parser.add_argument('--no_images', action='store_true')
14 | parser.add_argument('--mesh_path', type=str, default=None)
15 | parser.add_argument('--image_size', type=int, default=256)
16 | parser.add_argument('--scene_size', type=int, default=5)
17 | parser.add_argument('--y_up', action='store_true')
18 | parser.add_argument('--recenter', action='store_true')
19 | parser.add_argument('--rescale', type=float, default=None)
20 |
21 | args = parser.parse_args()
22 |
23 | root_path = args.root
24 |
25 | poses = []
26 | legends = []
27 | colors = []
28 | images = None
29 |
30 | if args.format == 'quick':
31 | poses, legends, colors, image_paths = load_quick(root_path, args.type)
32 |
33 | elif args.format == 'nerf':
34 | poses, legends, colors, image_paths = load_nerf(root_path)
35 |
36 | elif args.format == 'colmap':
37 | poses, legends, colors, image_paths = load_colmap(root_path)
38 |
39 | if args.recenter:
40 | poses = recenter_cameras(poses)
41 |
42 | if args.rescale is not None:
43 | poses = rescale_cameras(poses, args.rescale)
44 |
45 | if args.y_up:
46 | for i in range(0, len(poses)):
47 | poses[i] = poses[i][[0, 2, 1, 3]]
48 | poses[i][1, :] *= -1
49 |
50 | if not args.no_images:
51 | images = []
52 | for fpath in image_paths:
53 | if fpath is None:
54 | images.append(None)
55 | continue
56 |
57 | if not os.path.exists(fpath):
58 | images.append(None)
59 | print(f'Image not found at {fpath}')
60 | continue
61 |
62 | images.append(load_image(fpath, sz=args.image_size))
63 |
64 | viz = CameraVisualizer(poses, legends, colors, images=images)
65 | fig = viz.update_figure(args.scene_size, base_radius=1, zoom_scale=1, show_grid=True, show_ticklabels=True, show_background=True, y_up=args.y_up)
66 |
67 | fig.show()
68 |
--------------------------------------------------------------------------------
/docs/692db.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xt4d/CameraViewer/d951c403e0a156f77c96e334f824d07f5f10633c/docs/692db.png
--------------------------------------------------------------------------------
/docs/dinosaur.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xt4d/CameraViewer/d951c403e0a156f77c96e334f824d07f5f10633c/docs/dinosaur.png
--------------------------------------------------------------------------------
/docs/elu.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xt4d/CameraViewer/d951c403e0a156f77c96e334f824d07f5f10633c/docs/elu.png
--------------------------------------------------------------------------------
/docs/lego.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xt4d/CameraViewer/d951c403e0a156f77c96e334f824d07f5f10633c/docs/lego.png
--------------------------------------------------------------------------------
/docs/scene0707.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xt4d/CameraViewer/d951c403e0a156f77c96e334f824d07f5f10633c/docs/scene0707.png
--------------------------------------------------------------------------------
/docs/sph.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xt4d/CameraViewer/d951c403e0a156f77c96e334f824d07f5f10633c/docs/sph.png
--------------------------------------------------------------------------------
/docs/teaser.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xt4d/CameraViewer/d951c403e0a156f77c96e334f824d07f5f10633c/docs/teaser.png
--------------------------------------------------------------------------------
/docs/xyz.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xt4d/CameraViewer/d951c403e0a156f77c96e334f824d07f5f10633c/docs/xyz.png
--------------------------------------------------------------------------------
/inputs/colmap/scene0707/images.txt:
--------------------------------------------------------------------------------
1 | # Image list with two lines of data per image:
2 | # IMAGE_ID, QW, QX, QY, QZ, TX, TY, TZ, CAMERA_ID, NAME
3 | # POINTS2D[] as (X, Y, POINT3D_ID)
4 | 0 0.96189108396292755 0.039409358542869311 0.24606776867738878 0.11253043263933116 -2.1236667882274385 -2.4074735104736198 5.9633090289628292 0 0.jpg
5 | placeholder
6 | 64 0.9974813822968478 0.0066881297756471014 0.057842570805899662 0.040501825808703103 -1.1100342290448786 -0.74444845526585324 2.2274061359123856 0 64.jpg
7 | placeholder
8 | 192 0.83593989892900133 -0.019022056137035705 -0.53056310342994784 -0.13908788602057728 3.0060690020464436 -0.16022586999382854 0.65428841970310569 0 192.jpg
9 | placeholder
--------------------------------------------------------------------------------
/inputs/colmap/scene0707/images/0.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xt4d/CameraViewer/d951c403e0a156f77c96e334f824d07f5f10633c/inputs/colmap/scene0707/images/0.jpg
--------------------------------------------------------------------------------
/inputs/colmap/scene0707/images/192.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xt4d/CameraViewer/d951c403e0a156f77c96e334f824d07f5f10633c/inputs/colmap/scene0707/images/192.jpg
--------------------------------------------------------------------------------
/inputs/colmap/scene0707/images/64.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xt4d/CameraViewer/d951c403e0a156f77c96e334f824d07f5f10633c/inputs/colmap/scene0707/images/64.jpg
--------------------------------------------------------------------------------
/inputs/nerf/lego/images/r_0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xt4d/CameraViewer/d951c403e0a156f77c96e334f824d07f5f10633c/inputs/nerf/lego/images/r_0.png
--------------------------------------------------------------------------------
/inputs/nerf/lego/images/r_19.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xt4d/CameraViewer/d951c403e0a156f77c96e334f824d07f5f10633c/inputs/nerf/lego/images/r_19.png
--------------------------------------------------------------------------------
/inputs/nerf/lego/images/r_20.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xt4d/CameraViewer/d951c403e0a156f77c96e334f824d07f5f10633c/inputs/nerf/lego/images/r_20.png
--------------------------------------------------------------------------------
/inputs/nerf/lego/images/r_21.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xt4d/CameraViewer/d951c403e0a156f77c96e334f824d07f5f10633c/inputs/nerf/lego/images/r_21.png
--------------------------------------------------------------------------------
/inputs/nerf/lego/images/r_23.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xt4d/CameraViewer/d951c403e0a156f77c96e334f824d07f5f10633c/inputs/nerf/lego/images/r_23.png
--------------------------------------------------------------------------------
/inputs/nerf/lego/transforms.json:
--------------------------------------------------------------------------------
1 | {
2 | "camera_angle_x": 0.6911112070083618,
3 | "frames": [
4 | {
5 | "file_path": "./images/r_0.png",
6 | "rotation": 0.012566370614359171,
7 | "transform_matrix": [
8 | [
9 | -0.963964581489563,
10 | -0.2611401677131653,
11 | 0.0507759265601635,
12 | 0.2046843022108078
13 | ],
14 | [
15 | 0.26603081822395325,
16 | -0.9462433457374573,
17 | 0.18398693203926086,
18 | 0.7416750192642212
19 | ],
20 | [
21 | 7.450580596923828e-09,
22 | 0.1908649355173111,
23 | 0.9816163182258606,
24 | 3.957021951675415
25 | ],
26 | [
27 | 0.0,
28 | 0.0,
29 | 0.0,
30 | 1.0
31 | ]
32 | ]
33 | },
34 | {
35 | "file_path": "./images/r_19.png",
36 | "rotation": 0.012566370614359171,
37 | "transform_matrix": [
38 | [
39 | 0.13194742798805237,
40 | 0.33291926980018616,
41 | -0.9336779117584229,
42 | -3.7637763023376465
43 | ],
44 | [
45 | -0.9912567138671875,
46 | 0.04431530460715294,
47 | -0.12428304553031921,
48 | -0.5010010004043579
49 | ],
50 | [
51 | 0.0,
52 | 0.9419134259223938,
53 | 0.33585572242736816,
54 | 1.3538779020309448
55 | ],
56 | [
57 | 0.0,
58 | 0.0,
59 | 0.0,
60 | 1.0
61 | ]
62 | ]
63 | },
64 | {
65 | "file_path": "./images/r_20.png",
66 | "rotation": 0.012566370614359171,
67 | "transform_matrix": [
68 | [
69 | -0.915581226348877,
70 | 0.09515299648046494,
71 | -0.390713095664978,
72 | -1.5750149488449097
73 | ],
74 | [
75 | -0.4021328091621399,
76 | -0.21664556860923767,
77 | 0.8895806670188904,
78 | 3.58601450920105
79 | ],
80 | [
81 | 0.0,
82 | 0.9716021418571472,
83 | 0.23662075400352478,
84 | 0.9538488388061523
85 | ],
86 | [
87 | 0.0,
88 | 0.0,
89 | 0.0,
90 | 1.0
91 | ]
92 | ]
93 | },
94 | {
95 | "file_path": "./images/r_21.png",
96 | "rotation": 0.012566370614359171,
97 | "transform_matrix": [
98 | [
99 | 0.995094895362854,
100 | 0.040533654391765594,
101 | -0.09023939073085785,
102 | -0.3637666404247284
103 | ],
104 | [
105 | -0.09892485290765762,
106 | 0.4077320694923401,
107 | -0.9077270030975342,
108 | -3.6591649055480957
109 | ],
110 | [
111 | 0.0,
112 | 0.912201464176178,
113 | 0.4097418785095215,
114 | 1.6517224311828613
115 | ],
116 | [
117 | 0.0,
118 | 0.0,
119 | 0.0,
120 | 1.0
121 | ]
122 | ]
123 | },
124 | {
125 | "file_path": "./images/r_23.png",
126 | "rotation": 0.012566370614359171,
127 | "transform_matrix": [
128 | [
129 | -0.24700850248336792,
130 | -0.2722141444683075,
131 | 0.9299926161766052,
132 | 3.74891996383667
133 | ],
134 | [
135 | 0.9690133333206177,
136 | -0.06938935071229935,
137 | 0.23706182837486267,
138 | 0.9556267261505127
139 | ],
140 | [
141 | 0.0,
142 | 0.9597314596176147,
143 | 0.2809188961982727,
144 | 1.132420301437378
145 | ],
146 | [
147 | 0.0,
148 | 0.0,
149 | 0.0,
150 | 1.0
151 | ]
152 | ]
153 | }
154 | ]
155 | }
156 |
--------------------------------------------------------------------------------
/inputs/quick/cam_c2w/images/001.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xt4d/CameraViewer/d951c403e0a156f77c96e334f824d07f5f10633c/inputs/quick/cam_c2w/images/001.png
--------------------------------------------------------------------------------
/inputs/quick/cam_c2w/images/011.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xt4d/CameraViewer/d951c403e0a156f77c96e334f824d07f5f10633c/inputs/quick/cam_c2w/images/011.png
--------------------------------------------------------------------------------
/inputs/quick/cam_c2w/images/065.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xt4d/CameraViewer/d951c403e0a156f77c96e334f824d07f5f10633c/inputs/quick/cam_c2w/images/065.png
--------------------------------------------------------------------------------
/inputs/quick/cam_c2w/images/077.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xt4d/CameraViewer/d951c403e0a156f77c96e334f824d07f5f10633c/inputs/quick/cam_c2w/images/077.png
--------------------------------------------------------------------------------
/inputs/quick/cam_c2w/poses.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": "c2w",
3 | "frames": [
4 | {
5 | "image_name": "001.png",
6 | "pose": [
7 | [
8 | 0.9105374217033386,
9 | -0.09619740396738052,
10 | 0.4020790457725525,
11 | 1.6208324432373047
12 | ],
13 | [
14 | 0.41342654824256897,
15 | 0.21186675131320953,
16 | -0.885545551776886,
17 | -3.5697484016418457
18 | ],
19 | [
20 | 0.0,
21 | 0.972552478313446,
22 | 0.2326831966638565,
23 | 0.9379759430885315
24 | ],
25 | [
26 | 0.0,
27 | 0.0,
28 | 0.0,
29 | 1.0
30 | ]
31 | ]
32 | },
33 | {
34 | "image_name": "011.png",
35 | "pose": [
36 | [
37 | 0.36621522903442383,
38 | 0.21183644235134125,
39 | -0.9060969352722168,
40 | -3.6525936126708984
41 | ],
42 | [
43 | -0.9305301904678345,
44 | 0.08336939662694931,
45 | -0.3565994203090668,
46 | -1.4374982118606567
47 | ],
48 | [
49 | 0.0,
50 | 0.9737426042556763,
51 | 0.22765137255191803,
52 | 0.9176920652389526
53 | ],
54 | [
55 | 0.0,
56 | 0.0,
57 | 0.0,
58 | 1.0
59 | ]
60 | ]
61 | },
62 | {
63 | "image_name": "077.png",
64 | "pose": [
65 | [
66 | -0.9482272863388062,
67 | 0.09684965759515762,
68 | -0.3024653494358063,
69 | -1.219276785850525
70 | ],
71 | [
72 | -0.3175927400588989,
73 | -0.28916114568710327,
74 | 0.9030618071556091,
75 | 3.6403586864471436
76 | ],
77 | [
78 | 0.0,
79 | 0.9523684978485107,
80 | 0.30494922399520874,
81 | 1.2292896509170532
82 | ],
83 | [
84 | 0.0,
85 | 0.0,
86 | 0.0,
87 | 1.0
88 | ]
89 | ]
90 | },
91 | {
92 | "image_name": "065.png",
93 | "pose": [
94 | [
95 | -0.35092002153396606,
96 | -0.2937421202659607,
97 | 0.8891403675079346,
98 | 3.5842397212982178
99 | ],
100 | [
101 | 0.9364054203033447,
102 | -0.11008051037788391,
103 | 0.33320730924606323,
104 | 1.343201756477356
105 | ],
106 | [
107 | 0.0,
108 | 0.9495250582695007,
109 | 0.3136911392211914,
110 | 1.2645295858383179
111 | ],
112 | [
113 | 0.0,
114 | 0.0,
115 | 0.0,
116 | 1.0
117 | ]
118 | ]
119 | }
120 | ]
121 | }
--------------------------------------------------------------------------------
/inputs/quick/cam_c2w/poses/001.npy:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xt4d/CameraViewer/d951c403e0a156f77c96e334f824d07f5f10633c/inputs/quick/cam_c2w/poses/001.npy
--------------------------------------------------------------------------------
/inputs/quick/cam_c2w/poses/011.npy:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xt4d/CameraViewer/d951c403e0a156f77c96e334f824d07f5f10633c/inputs/quick/cam_c2w/poses/011.npy
--------------------------------------------------------------------------------
/inputs/quick/cam_c2w/poses/065.npy:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xt4d/CameraViewer/d951c403e0a156f77c96e334f824d07f5f10633c/inputs/quick/cam_c2w/poses/065.npy
--------------------------------------------------------------------------------
/inputs/quick/cam_c2w/poses/077.npy:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xt4d/CameraViewer/d951c403e0a156f77c96e334f824d07f5f10633c/inputs/quick/cam_c2w/poses/077.npy
--------------------------------------------------------------------------------
/inputs/quick/cam_elu/poses.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": "elu",
3 | "frames": [
4 | {
5 | "image_name": null,
6 | "pose": [[3.5, 0, 1], [0, 0, 0], [0, 0, 1]]
7 | },
8 | {
9 | "pose": [[0, 3.5, 1], [0, 0, 0], [0, 0, 1]]
10 | },
11 | {
12 | "pose": [[-3.5, 0, 1], [0, 0, 0], [0, 0, 1]]
13 | },
14 | {
15 | "pose": [[0, -3.5, 1], [0, 0, 0], [0, 0, 1]]
16 | },
17 | {
18 | "pose": [[0, 0, 3.64], [0, 0, 0], [0, 0, 1]]
19 | },
20 | {
21 | "pose": [[0, 0, -3.64], [0, 0, 1], [0, 0, -1]]
22 | }
23 | ]
24 | }
--------------------------------------------------------------------------------
/inputs/quick/cam_elu/poses/0.txt:
--------------------------------------------------------------------------------
1 | 3.5 0 1
2 | 0 0 0
3 | 0 0 1
--------------------------------------------------------------------------------
/inputs/quick/cam_elu/poses/1.txt:
--------------------------------------------------------------------------------
1 | 0 3.5 1
2 | 0 0 0
3 | 0 0 1
--------------------------------------------------------------------------------
/inputs/quick/cam_elu/poses/2.txt:
--------------------------------------------------------------------------------
1 | -3.5 0 1
2 | 0 0 0
3 | 0 0 1
--------------------------------------------------------------------------------
/inputs/quick/cam_elu/poses/3.txt:
--------------------------------------------------------------------------------
1 | 0 -3.5 1
2 | 0 0 0
3 | 0 0 1
--------------------------------------------------------------------------------
/inputs/quick/cam_elu/poses/4.txt:
--------------------------------------------------------------------------------
1 | 0 0 3.64
2 | 0 0 0
3 | 0 0 1
--------------------------------------------------------------------------------
/inputs/quick/cam_elu/poses/5.txt:
--------------------------------------------------------------------------------
1 | 0 0 -3.64
2 | 0 0 1
3 | 0 0 -1
--------------------------------------------------------------------------------
/inputs/quick/cam_sph/poses.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": "sph",
3 | "frames": [
4 | {
5 | "image_name": null,
6 | "pose": [75, 0, 4]
7 | },
8 | {
9 | "pose": [75, 90, 4]
10 | },
11 | {
12 | "pose": [75, 180, 4]
13 | },
14 | {
15 | "pose": [75, 270, 4]
16 | }
17 | ]
18 | }
--------------------------------------------------------------------------------
/inputs/quick/cam_sph/poses/0.txt:
--------------------------------------------------------------------------------
1 | 75 0 4
--------------------------------------------------------------------------------
/inputs/quick/cam_sph/poses/1.txt:
--------------------------------------------------------------------------------
1 | 75 90 4
--------------------------------------------------------------------------------
/inputs/quick/cam_sph/poses/2.txt:
--------------------------------------------------------------------------------
1 | 75 180 4
--------------------------------------------------------------------------------
/inputs/quick/cam_sph/poses/3.txt:
--------------------------------------------------------------------------------
1 | 75 270 4
--------------------------------------------------------------------------------
/inputs/quick/cam_w2c/images/001.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xt4d/CameraViewer/d951c403e0a156f77c96e334f824d07f5f10633c/inputs/quick/cam_w2c/images/001.png
--------------------------------------------------------------------------------
/inputs/quick/cam_w2c/images/004.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xt4d/CameraViewer/d951c403e0a156f77c96e334f824d07f5f10633c/inputs/quick/cam_w2c/images/004.png
--------------------------------------------------------------------------------
/inputs/quick/cam_w2c/images/008.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xt4d/CameraViewer/d951c403e0a156f77c96e334f824d07f5f10633c/inputs/quick/cam_w2c/images/008.png
--------------------------------------------------------------------------------
/inputs/quick/cam_w2c/images/010.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xt4d/CameraViewer/d951c403e0a156f77c96e334f824d07f5f10633c/inputs/quick/cam_w2c/images/010.png
--------------------------------------------------------------------------------
/inputs/quick/cam_w2c/poses.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": "w2c",
3 | "frames": [
4 | {
5 | "image_name": "008.png",
6 | "pose": [
7 | [
8 | 0.732423722743988,
9 | 0.6808491349220276,
10 | 2.1576926911848204e-08,
11 | 2.5004760928482028e-08
12 | ],
13 | [
14 | 0.44146132469177246,
15 | -0.47490230202674866,
16 | 0.7613013982772827,
17 | -5.960464477539063e-08
18 | ],
19 | [
20 | 0.5183313488960266,
21 | -0.5575951933860779,
22 | -0.6483982801437378,
23 | -1.7872748374938965
24 | ]
25 | ]
26 | },
27 | {
28 | "image_name": "001.png",
29 | "pose": [
30 | [
31 | 0.2826493978500366,
32 | 0.9592232704162598,
33 | 1.1397052190886825e-08,
34 | -4.187575797232057e-08
35 | ],
36 | [
37 | -0.6753110289573669,
38 | 0.19899044930934906,
39 | 0.710181474685669,
40 | -1.341104507446289e-07
41 | ],
42 | [
43 | 0.6812226176261902,
44 | -0.20073238015174866,
45 | 0.7040186524391174,
46 | -1.504714846611023
47 | ]
48 | ]
49 | },
50 | {
51 | "image_name": "010.png",
52 | "pose": [
53 | [
54 | -0.8365102410316467,
55 | -0.5479512214660645,
56 | 2.562700984753974e-08,
57 | 1.353646723600832e-07
58 | ],
59 | [
60 | -0.22922249138355255,
61 | 0.349934458732605,
62 | 0.9082967042922974,
63 | -1.043081283569336e-07
64 | ],
65 | [
66 | -0.49770230054855347,
67 | 0.7597995400428772,
68 | -0.4183264672756195,
69 | -1.506967306137085
70 | ]
71 | ]
72 | },
73 | {
74 | "image_name": "004.png",
75 | "pose": [
76 | [
77 | -0.899004340171814,
78 | 0.4379400610923767,
79 | -3.6114071733095443e-09,
80 | 1.2242657021488412e-07
81 | ],
82 | [
83 | -0.19711413979530334,
84 | -0.4046363830566406,
85 | 0.8929812908172607,
86 | 2.2351741790771484e-07
87 | ],
88 | [
89 | 0.39107227325439453,
90 | 0.8027939200401306,
91 | 0.4500938355922699,
92 | -1.9792877435684204
93 | ]
94 | ]
95 | }
96 | ]
97 | }
--------------------------------------------------------------------------------
/inputs/quick/cam_w2c/poses/001.npy:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xt4d/CameraViewer/d951c403e0a156f77c96e334f824d07f5f10633c/inputs/quick/cam_w2c/poses/001.npy
--------------------------------------------------------------------------------
/inputs/quick/cam_w2c/poses/004.npy:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xt4d/CameraViewer/d951c403e0a156f77c96e334f824d07f5f10633c/inputs/quick/cam_w2c/poses/004.npy
--------------------------------------------------------------------------------
/inputs/quick/cam_w2c/poses/008.npy:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xt4d/CameraViewer/d951c403e0a156f77c96e334f824d07f5f10633c/inputs/quick/cam_w2c/poses/008.npy
--------------------------------------------------------------------------------
/inputs/quick/cam_w2c/poses/010.npy:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xt4d/CameraViewer/d951c403e0a156f77c96e334f824d07f5f10633c/inputs/quick/cam_w2c/poses/010.npy
--------------------------------------------------------------------------------
/inputs/quick/cam_xyz/poses.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": "xyz",
3 | "frames": [
4 | {
5 | "image_name": null,
6 | "pose": [4, 0, 1]
7 | },
8 | {
9 | "pose": [0, 4, 1]
10 | },
11 | {
12 | "pose": [-4, 0, 1]
13 | },
14 | {
15 | "pose": [0, -4, 1]
16 | }
17 | ]
18 | }
--------------------------------------------------------------------------------
/inputs/quick/cam_xyz/poses/0.txt:
--------------------------------------------------------------------------------
1 | 4 0 1
--------------------------------------------------------------------------------
/inputs/quick/cam_xyz/poses/1.txt:
--------------------------------------------------------------------------------
1 | 0 4 1
--------------------------------------------------------------------------------
/inputs/quick/cam_xyz/poses/2.txt:
--------------------------------------------------------------------------------
1 | -4 0 1
--------------------------------------------------------------------------------
/inputs/quick/cam_xyz/poses/3.txt:
--------------------------------------------------------------------------------
1 | 0 -4 1
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | numpy==1.24.3
2 | Pillow==9.5.0
3 | plotly==5.14.1
--------------------------------------------------------------------------------
/src/loader.py:
--------------------------------------------------------------------------------
1 | import os
2 | import numpy as np
3 | import json
4 |
5 | from .utils import elu_to_c2w, spherical_to_cartesian, load_image, qvec_to_rotmat, rotmat
6 |
7 |
8 | def load_quick(root_path, type):
9 |
10 | poses = []
11 | legends = []
12 | colors = []
13 | image_paths = []
14 |
15 | if type is None:
16 | pose_path = os.path.join(root_path, 'poses.json')
17 | print(f'Load poses from {pose_path}')
18 | with open(pose_path, 'r') as fin:
19 | jdata = json.load(fin)
20 | type = jdata['type']
21 | frame_list = jdata['frames']
22 | else:
23 | pose_root = os.path.join(root_path, 'poses')
24 | print(f'Load poses from {pose_root}')
25 | frame_list = os.listdir(pose_root)
26 |
27 | image_root = os.path.join(root_path, 'images')
28 | print(f'Load images from {image_root}')
29 |
30 | for idx, frame in enumerate(frame_list):
31 |
32 | fid = idx
33 |
34 | if isinstance(frame, str):
35 |
36 | fname = frame
37 | vals = fname.split('.')
38 | fid, ext = vals[0], vals[-1]
39 |
40 | fpath = os.path.join(pose_root, fname)
41 |
42 | if ext == 'npy':
43 | mat = np.load(fpath)
44 | elif ext == 'txt':
45 | mat = np.loadtxt(fpath)
46 |
47 | img_paths = [ os.path.join(image_root, f'{fid}.{ext}') for ext in ['png', 'jpg', 'jpeg']]
48 | img_paths = [ fpath for fpath in img_paths if os.path.exists(fpath) ]
49 |
50 | img_path = img_paths[0] if len(img_paths) > 0 else None
51 |
52 | elif isinstance(frame, dict):
53 |
54 | if 'image_name' in frame and frame['image_name']:
55 | fname = frame['image_name']
56 | img_path = os.path.join(image_root, fname)
57 | else:
58 | img_path = None
59 |
60 | mat = np.array(frame['pose'])
61 |
62 | if type == 'c2w':
63 | c2w = mat
64 | if c2w.shape[0] == 3:
65 | c2w = np.concatenate([c2w, np.zeros((1, 4))], axis=0)
66 | c2w[-1, -1] = 1
67 |
68 | if type == 'w2c':
69 | w2c = mat
70 | if w2c.shape[0] == 3:
71 | w2c = np.concatenate([w2c, np.zeros((1, 4))], axis=0)
72 | w2c[-1, -1] = 1
73 | c2w = np.linalg.inv(w2c)
74 |
75 | elif type == 'elu':
76 | eye = mat[0, :]
77 | lookat = mat[1, :]
78 | up = mat[2, :]
79 | c2w = elu_to_c2w(eye, lookat, up)
80 |
81 | elif type == 'sph' or type == 'xyz':
82 |
83 | assert (mat.size == 3)
84 |
85 | if type == 'sph':
86 | eye = spherical_to_cartesian((np.deg2rad(mat[0]), np.deg2rad(mat[1]), mat[2]))
87 | else:
88 | eye = mat
89 |
90 | lookat = np.zeros(3)
91 | up = np.array([0, 0, 1])
92 | c2w = elu_to_c2w(eye, lookat, up)
93 |
94 | poses.append(c2w)
95 | legends.append( os.path.basename(img_path) if img_path else str(fid) )
96 | colors.append('blue')
97 | image_paths.append(img_path)
98 |
99 | return poses, legends, colors, image_paths
100 |
101 |
102 | def load_nerf(root_path):
103 |
104 | poses = []
105 | legends = []
106 | colors = []
107 | image_paths = []
108 |
109 | pose_path = os.path.join(root_path, 'transforms.json')
110 | print(f'Load poses from {pose_path}')
111 |
112 | with open(pose_path, 'r') as fin:
113 | jdata = json.load(fin)
114 |
115 | for fi, frm in enumerate(jdata['frames']):
116 |
117 | c2w = np.array(frm['transform_matrix'])
118 | poses.append(c2w)
119 | colors.append('blue')
120 |
121 | if 'file_path' in frm:
122 | fpath = frm['file_path']
123 | fname = os.path.basename(fpath)
124 |
125 | legends.append(fname)
126 | image_paths.append(os.path.join(root_path, fpath))
127 | else:
128 | legends.append(str(fi))
129 | image_paths.append(None)
130 |
131 | return poses, legends, colors, image_paths
132 |
133 |
134 | def load_colmap(root_path):
135 |
136 | poses = []
137 | legends = []
138 | colors = []
139 | image_paths = []
140 |
141 | pose_path = os.path.join(root_path, 'images.txt')
142 | print(f'Load poses from {pose_path}')
143 |
144 | fin = open(pose_path, 'r')
145 |
146 | up = np.zeros(3)
147 |
148 | i = 0
149 | for line in fin:
150 | line = line.strip()
151 | if line[0] == "#":
152 | continue
153 | i = i + 1
154 | if i % 2 == 0:
155 | continue
156 | elems = line.split(' ')
157 |
158 | fname = '_'.join(elems[9:])
159 | legends.append(fname)
160 |
161 | fpath = os.path.join(root_path, 'images', fname)
162 | image_paths.append(fpath)
163 |
164 | qvec = np.array(tuple(map(float, elems[1:5])))
165 | tvec = np.array(tuple(map(float, elems[5:8])))
166 | rot = qvec_to_rotmat(-qvec)
167 | tvec = tvec.reshape(3)
168 |
169 | w2c = np.eye(4)
170 | w2c[:3, :3] = rot
171 | w2c[:3, -1] = tvec
172 | c2w = np.linalg.inv(w2c)
173 |
174 | c2w[0:3,2] *= -1 # flip the y and z axis
175 | c2w[0:3,1] *= -1
176 | c2w = c2w[[1,0,2,3],:]
177 | c2w[2,:] *= -1 # flip whole world upside down
178 |
179 | up += c2w[0:3,1]
180 |
181 | poses.append(c2w)
182 | colors.append('blue')
183 |
184 | fin.close()
185 |
186 | up = up / np.linalg.norm(up)
187 | up_rot = rotmat(up,[0,0,1]) # rotate up vector to [0,0,1]
188 | up_rot = np.pad(up_rot,[0,1])
189 | up_rot[-1, -1] = 1
190 |
191 | for i in range(0, len(poses)):
192 | poses[i] = np.matmul(up_rot, poses[i])
193 |
194 | return poses, legends, colors, image_paths
195 |
--------------------------------------------------------------------------------
/src/utils.py:
--------------------------------------------------------------------------------
1 | import os
2 | import numpy as np
3 | from PIL import Image
4 |
5 |
6 | def load_image(fpath, sz=256):
7 | img = Image.open(fpath)
8 | img = img.resize((sz, sz))
9 | return np.asarray(img)[:, :, :3]
10 |
11 |
12 | def spherical_to_cartesian(sph):
13 |
14 | theta, azimuth, radius = sph
15 |
16 | return np.array([
17 | radius * np.sin(theta) * np.cos(azimuth),
18 | radius * np.sin(theta) * np.sin(azimuth),
19 | radius * np.cos(theta),
20 | ])
21 |
22 |
23 | def cartesian_to_spherical(xyz):
24 |
25 | xy = xyz[0]**2 + xyz[1]**2
26 | radius = np.sqrt(xy + xyz[2]**2)
27 | theta = np.arctan2(np.sqrt(xy), xyz[2])
28 | azimuth = np.arctan2(xyz[1], xyz[0])
29 |
30 | return np.array([theta, azimuth, radius])
31 |
32 |
33 | def elu_to_c2w(eye, lookat, up):
34 |
35 | if isinstance(eye, list):
36 | eye = np.array(eye)
37 | if isinstance(lookat, list):
38 | lookat = np.array(lookat)
39 | if isinstance(up, list):
40 | up = np.array(up)
41 |
42 | l = eye - lookat
43 | if np.linalg.norm(l) < 1e-8:
44 | l[-1] = 1
45 | l = l / np.linalg.norm(l)
46 |
47 | s = np.cross(l, up)
48 | if np.linalg.norm(s) < 1e-8:
49 | s[0] = 1
50 | s = s / np.linalg.norm(s)
51 | uu = np.cross(s, l)
52 |
53 | rot = np.eye(3)
54 | rot[0, :] = -s
55 | rot[1, :] = uu
56 | rot[2, :] = l
57 |
58 | c2w = np.eye(4)
59 | c2w[:3, :3] = rot.T
60 | c2w[:3, 3] = eye
61 |
62 | return c2w
63 |
64 |
65 | def c2w_to_elu(c2w):
66 |
67 | w2c = np.linalg.inv(c2w)
68 | eye = c2w[:3, 3]
69 | lookat_dir = -w2c[2, :3]
70 | lookat = eye + lookat_dir
71 | up = w2c[1, :3]
72 |
73 | return eye, lookat, up
74 |
75 |
76 | def qvec_to_rotmat(qvec):
77 | return np.array([
78 | [
79 | 1 - 2 * qvec[2]**2 - 2 * qvec[3]**2,
80 | 2 * qvec[1] * qvec[2] - 2 * qvec[0] * qvec[3],
81 | 2 * qvec[3] * qvec[1] + 2 * qvec[0] * qvec[2]
82 | ], [
83 | 2 * qvec[1] * qvec[2] + 2 * qvec[0] * qvec[3],
84 | 1 - 2 * qvec[1]**2 - 2 * qvec[3]**2,
85 | 2 * qvec[2] * qvec[3] - 2 * qvec[0] * qvec[1]
86 | ], [
87 | 2 * qvec[3] * qvec[1] - 2 * qvec[0] * qvec[2],
88 | 2 * qvec[2] * qvec[3] + 2 * qvec[0] * qvec[1],
89 | 1 - 2 * qvec[1]**2 - 2 * qvec[2]**2
90 | ]
91 | ])
92 |
93 |
94 | def rotmat(a, b):
95 | a, b = a / np.linalg.norm(a), b / np.linalg.norm(b)
96 | v = np.cross(a, b)
97 | c = np.dot(a, b)
98 | # handle exception for the opposite direction input
99 | if c < -1 + 1e-10:
100 | return rotmat(a + np.random.uniform(-1e-2, 1e-2, 3), b)
101 | s = np.linalg.norm(v)
102 | kmat = np.array([[0, -v[2], v[1]], [v[2], 0, -v[0]], [-v[1], v[0], 0]])
103 | return np.eye(3) + kmat + kmat.dot(kmat) * ((1 - c) / (s ** 2 + 1e-10))
104 |
105 |
106 | def recenter_cameras(c2ws):
107 |
108 | is_list = False
109 | if isinstance(c2ws, list):
110 | is_list = True
111 | c2ws = np.stack(c2ws)
112 |
113 | center = c2ws[..., :3, -1].mean(axis=0)
114 | c2ws[..., :3, -1] = c2ws[..., :3, -1] - center
115 |
116 | if is_list:
117 | c2ws = [ c2w for c2w in c2ws ]
118 |
119 | return c2ws
120 |
121 |
122 | def rescale_cameras(c2ws, scale):
123 |
124 | is_list = False
125 | if isinstance(c2ws, list):
126 | is_list = True
127 | c2ws = np.stack(c2ws)
128 |
129 | c2ws[..., :3, -1] *= scale
130 |
131 | if is_list:
132 | c2ws = [ c2w for c2w in c2ws ]
133 |
134 | return c2ws
135 |
--------------------------------------------------------------------------------
/src/visualizer.py:
--------------------------------------------------------------------------------
1 | import os
2 |
3 | from PIL import Image
4 | import plotly.graph_objects as go
5 | import numpy as np
6 |
7 |
8 | def calc_cam_cone_pts_3d(c2w, fov_deg, zoom = 1.0):
9 |
10 | fov_rad = np.deg2rad(fov_deg)
11 |
12 | cam_x = c2w[0, -1]
13 | cam_y = c2w[1, -1]
14 | cam_z = c2w[2, -1]
15 |
16 | corn1 = [np.tan(fov_rad / 2.0), np.tan(fov_rad / 2.0), -1.0]
17 | corn2 = [-np.tan(fov_rad / 2.0), np.tan(fov_rad / 2.0), -1.0]
18 | corn3 = [-np.tan(fov_rad / 2.0), -np.tan(fov_rad / 2.0), -1.0]
19 | corn4 = [np.tan(fov_rad / 2.0), -np.tan(fov_rad / 2.0), -1.0]
20 | corn5 = [0, np.tan(fov_rad / 2.0), -1.0]
21 |
22 | corn1 = np.dot(c2w[:3, :3], corn1)
23 | corn2 = np.dot(c2w[:3, :3], corn2)
24 | corn3 = np.dot(c2w[:3, :3], corn3)
25 | corn4 = np.dot(c2w[:3, :3], corn4)
26 | corn5 = np.dot(c2w[:3, :3], corn5)
27 |
28 | # Now attach as offset to actual 3D camera position:
29 | corn1 = np.array(corn1) / np.linalg.norm(corn1, ord=2) * zoom
30 | corn_x1 = cam_x + corn1[0]
31 | corn_y1 = cam_y + corn1[1]
32 | corn_z1 = cam_z + corn1[2]
33 | corn2 = np.array(corn2) / np.linalg.norm(corn2, ord=2) * zoom
34 | corn_x2 = cam_x + corn2[0]
35 | corn_y2 = cam_y + corn2[1]
36 | corn_z2 = cam_z + corn2[2]
37 | corn3 = np.array(corn3) / np.linalg.norm(corn3, ord=2) * zoom
38 | corn_x3 = cam_x + corn3[0]
39 | corn_y3 = cam_y + corn3[1]
40 | corn_z3 = cam_z + corn3[2]
41 | corn4 = np.array(corn4) / np.linalg.norm(corn4, ord=2) * zoom
42 | corn_x4 = cam_x + corn4[0]
43 | corn_y4 = cam_y + corn4[1]
44 | corn_z4 = cam_z + corn4[2]
45 | corn5 = np.array(corn5) / np.linalg.norm(corn5, ord=2) * zoom
46 | corn_x5 = cam_x + corn5[0]
47 | corn_y5 = cam_y + corn5[1]
48 | corn_z5 = cam_z + corn5[2]
49 |
50 | xs = [cam_x, corn_x1, corn_x2, corn_x3, corn_x4, corn_x5]
51 | ys = [cam_y, corn_y1, corn_y2, corn_y3, corn_y4, corn_y5]
52 | zs = [cam_z, corn_z1, corn_z2, corn_z3, corn_z4, corn_z5]
53 |
54 | return np.array([xs, ys, zs]).T
55 |
56 |
57 | class CameraVisualizer:
58 |
59 | def __init__(self, poses, legends, colors, images=None, mesh_path=None, camera_x=1.0):
60 | self._fig = None
61 |
62 | self._camera_x = camera_x
63 |
64 | self._poses = poses
65 | self._legends = legends
66 | self._colors = colors
67 |
68 | self._raw_images = None
69 | self._bit_images = None
70 | self._image_colorscale = None
71 |
72 | if images is not None:
73 | self._raw_images = images
74 | self._bit_images = []
75 | self._image_colorscale = []
76 | for img in images:
77 | if img is None:
78 | self._bit_images.append(None)
79 | self._image_colorscale.append(None)
80 | continue
81 |
82 | bit_img, colorscale = self.encode_image(img)
83 | self._bit_images.append(bit_img)
84 | self._image_colorscale.append(colorscale)
85 |
86 | self._mesh = None
87 | if mesh_path is not None and os.path.exists(mesh_path):
88 | import trimesh
89 | self._mesh = trimesh.load(mesh_path, force='mesh')
90 |
91 |
92 | def encode_image(self, raw_image):
93 | '''
94 | :param raw_image (H, W, 3) array of uint8 in [0, 255].
95 | '''
96 | # https://stackoverflow.com/questions/60685749/python-plotly-how-to-add-an-image-to-a-3d-scatter-plot
97 |
98 | dum_img = Image.fromarray(np.ones((3, 3, 3), dtype='uint8')).convert('P', palette='WEB')
99 | idx_to_color = np.array(dum_img.getpalette()).reshape((-1, 3))
100 |
101 | bit_image = Image.fromarray(raw_image).convert('P', palette='WEB', dither=None)
102 | # bit_image = Image.fromarray(raw_image.clip(0, 254)).convert(
103 | # 'P', palette='WEB', dither=None)
104 | colorscale = [
105 | [i / 255.0, 'rgb({}, {}, {})'.format(*rgb)] for i, rgb in enumerate(idx_to_color)]
106 |
107 | return bit_image, colorscale
108 |
109 |
110 | def update_figure(
111 | self, scene_bounds,
112 | base_radius=0.0, zoom_scale=1.0, fov_deg=50.,
113 | mesh_z_shift=0.0, mesh_scale=1.0,
114 | show_background=False, show_grid=False, show_ticklabels=False, y_up=False
115 | ):
116 |
117 | fig = go.Figure()
118 |
119 | if self._mesh is not None:
120 | fig.add_trace(
121 | go.Mesh3d(
122 | x=self._mesh.vertices[:, 0] * mesh_scale,
123 | y=self._mesh.vertices[:, 2] * -mesh_scale,
124 | z=(self._mesh.vertices[:, 1] + mesh_z_shift) * mesh_scale,
125 | i=self._mesh.faces[:, 0],
126 | j=self._mesh.faces[:, 1],
127 | k=self._mesh.faces[:, 2],
128 | color=None,
129 | facecolor=None,
130 | opacity=0.8,
131 | lighting={'ambient': 1},
132 | )
133 | )
134 |
135 | for i in range(len(self._poses)):
136 |
137 | pose = self._poses[i]
138 | clr = self._colors[i]
139 | legend = self._legends[i]
140 |
141 | edges = [(0, 1), (0, 2), (0, 3), (0, 4), (1, 2), (2, 3), (3, 4), (4, 1), (0, 5)]
142 |
143 | cone = calc_cam_cone_pts_3d(pose, fov_deg)
144 | radius = np.linalg.norm(pose[:3, -1])
145 |
146 | if self._bit_images and self._bit_images[i]:
147 |
148 | raw_image = self._raw_images[i]
149 | bit_image = self._bit_images[i]
150 | colorscale = self._image_colorscale[i]
151 |
152 | (H, W, C) = raw_image.shape
153 |
154 | z = np.zeros((H, W)) + base_radius
155 | (x, y) = np.meshgrid(np.linspace(-1.0 * self._camera_x, 1.0 * self._camera_x, W), np.linspace(1.0, -1.0, H) * H / W)
156 |
157 | xyz = np.concatenate([x[..., None], y[..., None], z[..., None]], axis=-1)
158 |
159 | rot_xyz = np.matmul(xyz, pose[:3, :3].T) + pose[:3, -1]
160 |
161 | x, y, z = rot_xyz[:, :, 0], rot_xyz[:, :, 1], rot_xyz[:, :, 2]
162 |
163 | fig.add_trace(go.Surface(
164 | x=x, y=y, z=z,
165 | surfacecolor=bit_image,
166 | cmin=0,
167 | cmax=255,
168 | colorscale=colorscale,
169 | showscale=False,
170 | lighting_diffuse=1.0,
171 | lighting_ambient=1.0,
172 | lighting_fresnel=1.0,
173 | lighting_roughness=1.0,
174 | lighting_specular=0.3))
175 |
176 | for (i, edge) in enumerate(edges):
177 | (x1, x2) = (cone[edge[0], 0], cone[edge[1], 0])
178 | (y1, y2) = (cone[edge[0], 1], cone[edge[1], 1])
179 | (z1, z2) = (cone[edge[0], 2], cone[edge[1], 2])
180 | fig.add_trace(go.Scatter3d(
181 | x=[x1, x2], y=[y1, y2], z=[z1, z2], mode='lines',
182 | line=dict(color=clr, width=3),
183 | name=legend, showlegend=(i == 0)))
184 |
185 | # Add label.
186 | if cone[0, 2] < 0:
187 | fig.add_trace(go.Scatter3d(
188 | x=[cone[0, 0]], y=[cone[0, 1]], z=[cone[0, 2] - 0.05], showlegend=False,
189 | mode='text', text=legend, textposition='bottom center'))
190 | else:
191 | fig.add_trace(go.Scatter3d(
192 | x=[cone[0, 0]], y=[cone[0, 1]], z=[cone[0, 2] + 0.05], showlegend=False,
193 | mode='text', text=legend, textposition='top center'))
194 |
195 | # look at the center of scene
196 | fig.update_layout(
197 | height=720,
198 | autosize=True,
199 | hovermode=False,
200 | margin=go.layout.Margin(l=0, r=0, b=0, t=0),
201 | showlegend=True,
202 | legend=dict(
203 | yanchor='bottom',
204 | y=0.01,
205 | xanchor='right',
206 | x=0.99,
207 | ),
208 | scene=dict(
209 | aspectmode='manual',
210 | aspectratio=dict(x=1, y=1, z=1),
211 | camera=dict(
212 | eye=dict(x=1.5, y=1.5, z=1.0),
213 | center=dict(x=0.0, y=0.0, z=0.0),
214 | up=dict(x=0.0, y=0.0, z=1.0)),
215 | xaxis_title='X',
216 | yaxis_title='Z' if y_up else 'Y',
217 | zaxis_title='Y' if y_up else 'Z',
218 | xaxis=dict(
219 | range=[-scene_bounds, scene_bounds],
220 | showticklabels=show_ticklabels,
221 | showgrid=show_grid,
222 | zeroline=False,
223 | showbackground=show_background,
224 | showspikes=False,
225 | showline=False,
226 | ticks=''),
227 | yaxis=dict(
228 | range=[-scene_bounds, scene_bounds],
229 | showticklabels=show_ticklabels,
230 | showgrid=show_grid,
231 | zeroline=False,
232 | showbackground=show_background,
233 | showspikes=False,
234 | showline=False,
235 | ticks=''),
236 | zaxis=dict(
237 | range=[-scene_bounds, scene_bounds],
238 | showticklabels=show_ticklabels,
239 | showgrid=show_grid,
240 | zeroline=False,
241 | showbackground=show_background,
242 | showspikes=False,
243 | showline=False,
244 | ticks='')
245 | )
246 | )
247 |
248 | self._fig = fig
249 | return fig
250 |
--------------------------------------------------------------------------------