11import  argparse 
22import  logging 
3- from  pathlib  import  Path 
43import  shutil 
54import  multiprocessing 
6- import  subprocess 
7- import  pprint 
5+ from   pathlib   import  Path 
6+ import  pycolmap 
87
9- from  .utils .read_write_model  import  read_cameras_binary 
108from  .utils .database  import  COLMAPDatabase 
119from  .triangulation  import  (
12-     import_features , import_matches , geometric_verification , run_command )
10+     import_features , import_matches , geometric_verification , OutputCapture )
1311
1412
1513def  create_empty_db (database_path ):
@@ -23,35 +21,13 @@ def create_empty_db(database_path):
2321    db .close ()
2422
2523
26- def  import_images (colmap_path , sfm_dir , image_dir , database_path ,
27-                   single_camera = False , verbose = False ):
24+ def  import_images (image_dir , database_path , camera_mode ):
2825    logging .info ('Importing images into the database...' )
2926    images  =  list (image_dir .iterdir ())
3027    if  len (images ) ==  0 :
3128        raise  IOError (f'No images found in { image_dir }  )
32- 
33-     # We need to create dummy features for COLMAP to import images with EXIF 
34-     dummy_dir  =  sfm_dir  /  'dummy_features' 
35-     dummy_dir .mkdir ()
36-     for  i  in  images :
37-         with  open (str (dummy_dir  /  (i .name  +  '.txt' )), 'w' ) as  f :
38-             f .write ('0 128' )
39- 
40-     cmd  =  [
41-         str (colmap_path ), 'feature_importer' ,
42-         '--database_path' , str (database_path ),
43-         '--image_path' , str (image_dir ),
44-         '--import_path' , str (dummy_dir ),
45-         '--ImageReader.single_camera' ,
46-         str (int (single_camera ))]
47-     run_command (cmd , verbose )
48- 
49-     db  =  COLMAPDatabase .connect (database_path )
50-     db .execute ("DELETE FROM keypoints;" )
51-     db .execute ("DELETE FROM descriptors;" )
52-     db .commit ()
53-     db .close ()
54-     shutil .rmtree (str (dummy_dir ))
29+     with  pycolmap .ostream ():
30+         pycolmap .import_images (database_path , image_dir , camera_mode )
5531
5632
5733def  get_image_ids (database_path ):
@@ -63,67 +39,41 @@ def get_image_ids(database_path):
6339    return  images 
6440
6541
66- def  run_reconstruction (colmap_path , sfm_dir , database_path , image_dir ,
67-                        min_num_matches = None , verbose = False ):
42+ def  run_reconstruction (sfm_dir , database_path , image_dir , verbose = False ):
6843    models_path  =  sfm_dir  /  'models' 
6944    models_path .mkdir (exist_ok = True , parents = True )
70-     cmd  =  [
71-         str (colmap_path ), 'mapper' ,
72-         '--database_path' , str (database_path ),
73-         '--image_path' , str (image_dir ),
74-         '--output_path' , str (models_path ),
75-         '--Mapper.num_threads' , str (min (multiprocessing .cpu_count (), 16 ))]
76-     if  min_num_matches :
77-         cmd  +=  ['--Mapper.min_num_matches' , str (min_num_matches )]
78-     logging .info ('Running the reconstruction with command:\n %s' , ' ' .join (cmd ))
79-     run_command (cmd , verbose )
80- 
81-     models  =  list (models_path .iterdir ())
82-     if  len (models ) ==  0 :
45+     logging .info ('Running 3D reconstruction...' )
46+     with  OutputCapture (verbose ):
47+         with  pycolmap .ostream ():
48+             reconstructions  =  pycolmap .incremental_mapping (
49+                 database_path , image_dir , models_path ,
50+                 num_threads = min (multiprocessing .cpu_count (), 16 ))
51+ 
52+     if  len (reconstructions ) ==  0 :
8353        logging .error ('Could not reconstruct any model!' )
8454        return  None 
85-     logging .info (f'Reconstructed { len (models )}  )
86- 
87-     largest_model  =  None 
88-     largest_model_num_images  =  0 
89-     for  model  in  models :
90-         num_images  =  len (read_cameras_binary (str (model  /  'cameras.bin' )))
91-         if  num_images  >  largest_model_num_images :
92-             largest_model  =  model 
93-             largest_model_num_images  =  num_images 
94-     assert  largest_model_num_images  >  0 
95-     logging .info (f'Largest model is #{ largest_model .name }  
96-                  f'with { largest_model_num_images }  )
97- 
98-     stats_raw  =  subprocess .check_output (
99-         [str (colmap_path ), 'model_analyzer' ,
100-          '--path' , str (largest_model )])
101-     stats_raw  =  stats_raw .decode ().split ("\n " )
102-     stats  =  dict ()
103-     for  stat  in  stats_raw :
104-         if  stat .startswith ("Registered images" ):
105-             stats ['num_reg_images' ] =  int (stat .split ()[- 1 ])
106-         elif  stat .startswith ("Points" ):
107-             stats ['num_sparse_points' ] =  int (stat .split ()[- 1 ])
108-         elif  stat .startswith ("Observations" ):
109-             stats ['num_observations' ] =  int (stat .split ()[- 1 ])
110-         elif  stat .startswith ("Mean track length" ):
111-             stats ['mean_track_length' ] =  float (stat .split ()[- 1 ])
112-         elif  stat .startswith ("Mean observations per image" ):
113-             stats ['num_observations_per_image' ] =  float (stat .split ()[- 1 ])
114-         elif  stat .startswith ("Mean reprojection error" ):
115-             stats ['mean_reproj_error' ] =  float (stat .split ()[- 1 ][:- 2 ])
55+     logging .info (f'Reconstructed { len (reconstructions )}  )
56+ 
57+     largest_index  =  None 
58+     largest_num_images  =  0 
59+     for  index , rec  in  reconstructions .items ():
60+         num_images  =  rec .num_reg_images ()
61+         if  num_images  >  largest_num_images :
62+             largest_index  =  index 
63+             largest_num_images  =  num_images 
64+     assert  largest_index  is  not None 
65+     logging .info (f'Largest model is #{ largest_index }  
66+                  f'with { largest_num_images }  )
11667
11768    for  filename  in  ['images.bin' , 'cameras.bin' , 'points3D.bin' ]:
118-         shutil .move (str ( largest_model   /   filename ),  str ( sfm_dir )) 
119- 
120-     return  stats 
69+         shutil .move (
70+              str ( models_path   /   str ( largest_index )  /   filename ),  str ( sfm_dir )) 
71+     return  reconstructions [ largest_index ] 
12172
12273
12374def  main (sfm_dir , image_dir , pairs , features , matches ,
124-          colmap_path = 'colmap' , single_camera = False ,
125-          skip_geometric_verification = False ,
126-          min_match_score = None , min_num_matches = None , verbose = False ):
75+          camera_mode = pycolmap .CameraMode .AUTO , verbose = False ,
76+          skip_geometric_verification = False , min_match_score = None ):
12777
12878    assert  features .exists (), features 
12979    assert  pairs .exists (), pairs 
@@ -133,19 +83,18 @@ def main(sfm_dir, image_dir, pairs, features, matches,
13383    database  =  sfm_dir  /  'database.db' 
13484
13585    create_empty_db (database )
136-     import_images (
137-         colmap_path , sfm_dir , image_dir , database , single_camera , verbose )
86+     import_images (image_dir , database , camera_mode )
13887    image_ids  =  get_image_ids (database )
13988    import_features (image_ids , database , features )
14089    import_matches (image_ids , database , pairs , matches ,
14190                   min_match_score , skip_geometric_verification )
14291    if  not  skip_geometric_verification :
143-         geometric_verification (colmap_path ,  database , pairs , verbose )
144-     stats  =  run_reconstruction (
145-          colmap_path ,  sfm_dir ,  database ,  image_dir ,  min_num_matches ,  verbose ) 
146-     if   stats   is   not   None : 
147-         stats [ 'num_input_images' ]  =  len (image_ids )
148-          logging . info ( 'Reconstruction statistics: \n %s' ,  pprint . pformat ( stats )) 
92+         geometric_verification (database , pairs , verbose )
93+     reconstruction  =  run_reconstruction (sfm_dir ,  database ,  image_dir ,  verbose ) 
94+     if   reconstruction   is   not   None : 
95+          logging . info ( f'Reconstruction statistics: \n { reconstruction . summary () } ' 
96+                       +   f' \n \t num_input_images  = { len (image_ids ) } ' 
97+     return   reconstruction 
14998
15099
151100if  __name__  ==  '__main__' :
@@ -157,12 +106,10 @@ def main(sfm_dir, image_dir, pairs, features, matches,
157106    parser .add_argument ('--features' , type = Path , required = True )
158107    parser .add_argument ('--matches' , type = Path , required = True )
159108
160-     parser .add_argument ('--colmap_path' , type = Path , default = 'colmap' )
161- 
162-     parser .add_argument ('--single_camera' , action = 'store_true' )
109+     parser .add_argument ('--camera_mode' , type = str , default = "AUTO" ,
110+                         choices = list (pycolmap .CameraMode .__members__ .keys ()))
163111    parser .add_argument ('--skip_geometric_verification' , action = 'store_true' )
164112    parser .add_argument ('--min_match_score' , type = float )
165-     parser .add_argument ('--min_num_matches' , type = int )
166113    parser .add_argument ('--verbose' , action = 'store_true' )
167114    args  =  parser .parse_args ()
168115
0 commit comments