3434import collections
3535import numpy as np
3636import struct
37+ import argparse
3738
3839
3940CameraModel = collections .namedtuple (
4546Point3D = collections .namedtuple (
4647 "Point3D" , ["id" , "xyz" , "rgb" , "error" , "image_ids" , "point2D_idxs" ])
4748
49+
4850class Image (BaseImage ):
4951 def qvec2rotmat (self ):
5052 return qvec2rotmat (self .qvec )
@@ -63,8 +65,10 @@ def qvec2rotmat(self):
6365 CameraModel (model_id = 9 , model_name = "RADIAL_FISHEYE" , num_params = 5 ),
6466 CameraModel (model_id = 10 , model_name = "THIN_PRISM_FISHEYE" , num_params = 12 )
6567}
66- CAMERA_MODEL_IDS = dict ([(camera_model .model_id , camera_model ) \
68+ CAMERA_MODEL_IDS = dict ([(camera_model .model_id , camera_model )
6769 for camera_model in CAMERA_MODELS ])
70+ CAMERA_MODEL_NAMES = dict ([(camera_model .model_name , camera_model )
71+ for camera_model in CAMERA_MODELS ])
6872
6973
7074def read_next_bytes (fid , num_bytes , format_char_sequence , endian_character = "<" ):
@@ -79,6 +83,22 @@ def read_next_bytes(fid, num_bytes, format_char_sequence, endian_character="<"):
7983 return struct .unpack (endian_character + format_char_sequence , data )
8084
8185
86+ def write_next_bytes (fid , data , format_char_sequence , endian_character = "<" ):
87+ """pack and write to a binary file.
88+ :param fid:
89+ :param data: data to send, if multiple elements are sent at the same time,
90+ they should be encapsuled either in a list or a tuple
91+ :param format_char_sequence: List of {c, e, f, d, h, H, i, I, l, L, q, Q}.
92+ should be the same length as the data list or tuple
93+ :param endian_character: Any of {@, =, <, >, !}
94+ """
95+ if isinstance (data , (list , tuple )):
96+ bytes = struct .pack (endian_character + format_char_sequence , * data )
97+ else :
98+ bytes = struct .pack (endian_character + format_char_sequence , data )
99+ fid .write (bytes )
100+
101+
82102def read_cameras_text (path ):
83103 """
84104 see: src/base/reconstruction.cc
@@ -134,6 +154,43 @@ def read_cameras_binary(path_to_model_file):
134154 return cameras
135155
136156
157+ def write_cameras_text (cameras , path ):
158+ """
159+ see: src/base/reconstruction.cc
160+ void Reconstruction::WriteCamerasText(const std::string& path)
161+ void Reconstruction::ReadCamerasText(const std::string& path)
162+ """
163+ HEADER = '# Camera list with one line of data per camera:\n '
164+ '# CAMERA_ID, MODEL, WIDTH, HEIGHT, PARAMS[]\n '
165+ '# Number of cameras: {}\n ' .format (len (cameras ))
166+ with open (path , "w" ) as fid :
167+ fid .write (HEADER )
168+ for _ , cam in cameras .items ():
169+ to_write = [cam .id , cam .model , cam .width , cam .height , * cam .params ]
170+ line = " " .join ([str (elem ) for elem in to_write ])
171+ fid .write (line + "\n " )
172+
173+
174+ def write_cameras_binary (cameras , path_to_model_file ):
175+ """
176+ see: src/base/reconstruction.cc
177+ void Reconstruction::WriteCamerasBinary(const std::string& path)
178+ void Reconstruction::ReadCamerasBinary(const std::string& path)
179+ """
180+ with open (path_to_model_file , "wb" ) as fid :
181+ write_next_bytes (fid , len (cameras ), "Q" )
182+ for _ , cam in cameras .items ():
183+ model_id = CAMERA_MODEL_NAMES [cam .model ].model_id
184+ camera_properties = [cam .id ,
185+ model_id ,
186+ cam .width ,
187+ cam .height ]
188+ write_next_bytes (fid , camera_properties , "iiQQ" )
189+ for p in cam .params :
190+ write_next_bytes (fid , float (p ), "d" )
191+ return cameras
192+
193+
137194def read_images_text (path ):
138195 """
139196 see: src/base/reconstruction.cc
@@ -200,6 +257,55 @@ def read_images_binary(path_to_model_file):
200257 return images
201258
202259
260+ def write_images_text (images , path ):
261+ """
262+ see: src/base/reconstruction.cc
263+ void Reconstruction::ReadImagesText(const std::string& path)
264+ void Reconstruction::WriteImagesText(const std::string& path)
265+ """
266+ if len (images ) == 0 :
267+ mean_observations = 0
268+ else :
269+ mean_observations = sum ((len (img .point3D_ids ) for _ , img in images .items ()))/ len (images )
270+ HEADER = '# Image list with two lines of data per image:\n '
271+ '# IMAGE_ID, QW, QX, QY, QZ, TX, TY, TZ, CAMERA_ID, NAME\n '
272+ '# POINTS2D[] as (X, Y, POINT3D_ID)\n '
273+ '# Number of images: {}, mean observations per image: {}\n ' .format (len (images ), mean_observations )
274+
275+ with open (path , "w" ) as fid :
276+ fid .write (HEADER )
277+ for _ , img in images .items ():
278+ image_header = [img .id , * img .qvec , * img .tvec , img .camera_id , img .name ]
279+ first_line = " " .join (map (str , image_header ))
280+ fid .write (first_line + "\n " )
281+
282+ points_strings = []
283+ for xy , point3D_id in zip (img .xys , img .point3D_ids ):
284+ points_strings .append (" " .join (map (str , [* xy , point3D_id ])))
285+ fid .write (" " .join (points_strings ) + "\n " )
286+
287+
288+ def write_images_binary (images , path_to_model_file ):
289+ """
290+ see: src/base/reconstruction.cc
291+ void Reconstruction::ReadImagesBinary(const std::string& path)
292+ void Reconstruction::WriteImagesBinary(const std::string& path)
293+ """
294+ with open (path_to_model_file , "wb" ) as fid :
295+ write_next_bytes (fid , len (images ), "Q" )
296+ for _ , img in images .items ():
297+ write_next_bytes (fid , img .id , "i" )
298+ write_next_bytes (fid , img .qvec .tolist (), "dddd" )
299+ write_next_bytes (fid , img .tvec .tolist (), "ddd" )
300+ write_next_bytes (fid , img .camera_id , "i" )
301+ for char in img .name :
302+ write_next_bytes (fid , char .encode ("utf-8" ), "c" )
303+ write_next_bytes (fid , b"\x00 " , "c" )
304+ write_next_bytes (fid , len (img .point3D_ids ), "Q" )
305+ for xy , p3d_id in zip (img .xys , img .point3D_ids ):
306+ write_next_bytes (fid , [* xy , p3d_id ], "ddq" )
307+
308+
203309def read_points3D_text (path ):
204310 """
205311 see: src/base/reconstruction.cc
@@ -257,6 +363,50 @@ def read_points3d_binary(path_to_model_file):
257363 return points3D
258364
259365
366+ def write_points3D_text (points3D , path ):
367+ """
368+ see: src/base/reconstruction.cc
369+ void Reconstruction::ReadPoints3DText(const std::string& path)
370+ void Reconstruction::WritePoints3DText(const std::string& path)
371+ """
372+ if len (points3D ) == 0 :
373+ mean_track_length = 0
374+ else :
375+ mean_track_length = sum ((len (pt .image_ids ) for _ , pt in points3D .items ()))/ len (points3D )
376+ HEADER = '# 3D point list with one line of data per point:\n '
377+ '# POINT3D_ID, X, Y, Z, R, G, B, ERROR, TRACK[] as (IMAGE_ID, POINT2D_IDX)\n '
378+ '# Number of points: {}, mean track length: {}\n ' .format (len (points3D ), mean_track_length )
379+
380+ with open (path , "w" ) as fid :
381+ fid .write (HEADER )
382+ for _ , pt in points3D .items ():
383+ point_header = [pt .id , * pt .xyz , * pt .rgb , pt .error ]
384+ fid .write (" " .join (map (str , point_header )) + " " )
385+ track_strings = []
386+ for image_id , point2D in zip (pt .image_ids , pt .point2D_idxs ):
387+ track_strings .append (" " .join (map (str , [image_id , point2D ])))
388+ fid .write (" " .join (track_strings ) + "\n " )
389+
390+
391+ def write_points3d_binary (points3D , path_to_model_file ):
392+ """
393+ see: src/base/reconstruction.cc
394+ void Reconstruction::ReadPoints3DBinary(const std::string& path)
395+ void Reconstruction::WritePoints3DBinary(const std::string& path)
396+ """
397+ with open (path_to_model_file , "wb" ) as fid :
398+ write_next_bytes (fid , len (points3D ), "Q" )
399+ for _ , pt in points3D .items ():
400+ write_next_bytes (fid , pt .id , "Q" )
401+ write_next_bytes (fid , pt .xyz .tolist (), "ddd" )
402+ write_next_bytes (fid , pt .rgb .tolist (), "BBB" )
403+ write_next_bytes (fid , pt .error , "d" )
404+ track_length = pt .image_ids .shape [0 ]
405+ write_next_bytes (fid , track_length , "Q" )
406+ for image_id , point2D_id in zip (pt .image_ids , pt .point2D_idxs ):
407+ write_next_bytes (fid , [image_id , point2D_id ], "ii" )
408+
409+
260410def read_model (path , ext ):
261411 if ext == ".txt" :
262412 cameras = read_cameras_text (os .path .join (path , "cameras" + ext ))
@@ -269,6 +419,18 @@ def read_model(path, ext):
269419 return cameras , images , points3D
270420
271421
422+ def write_model (cameras , images , points3D , path , ext ):
423+ if ext == ".txt" :
424+ write_cameras_text (cameras , os .path .join (path , "cameras" + ext ))
425+ write_images_text (images , os .path .join (path , "images" + ext ))
426+ write_points3D_text (points3D , os .path .join (path , "points3D" ) + ext )
427+ else :
428+ write_cameras_binary (cameras , os .path .join (path , "cameras" + ext ))
429+ write_images_binary (images , os .path .join (path , "images" + ext ))
430+ write_points3d_binary (points3D , os .path .join (path , "points3D" ) + ext )
431+ return cameras , images , points3D
432+
433+
272434def qvec2rotmat (qvec ):
273435 return np .array ([
274436 [1 - 2 * qvec [2 ]** 2 - 2 * qvec [3 ]** 2 ,
@@ -297,16 +459,25 @@ def rotmat2qvec(R):
297459
298460
299461def main ():
300- if len (sys .argv ) != 3 :
301- print ("Usage: python read_model.py path/to/model/folder [.txt,.bin]" )
302- return
303-
304- cameras , images , points3D = read_model (path = sys .argv [1 ], ext = sys .argv [2 ])
462+ parser = argparse .ArgumentParser (description = 'Read and write COLMAP binary and text models' )
463+ parser .add_argument ('input_model' , help = 'path to input model folder' )
464+ parser .add_argument ('input_format' , choices = ['.bin' , '.txt' ],
465+ help = 'input model format' )
466+ parser .add_argument ('--output_model' , metavar = 'PATH' ,
467+ help = 'path to output model folder' )
468+ parser .add_argument ('--output_format' , choices = ['.bin' , '.txt' ],
469+ help = 'outut model format' , default = '.txt' )
470+ args = parser .parse_args ()
471+
472+ cameras , images , points3D = read_model (path = args .input_model , ext = args .input_format )
305473
306474 print ("num_cameras:" , len (cameras ))
307475 print ("num_images:" , len (images ))
308476 print ("num_points3D:" , len (points3D ))
309477
478+ if args .output_model is not None :
479+ write_model (cameras , images , points3D , path = args .output_model , ext = args .output_format )
480+
310481
311482if __name__ == "__main__" :
312483 main ()
0 commit comments