def extract(properties, *args, **kargs): dirpath = bpy.path.abspath(properties.dirpath) if not dirpath: raise AttributeError( 'COLMAP Workspace Directory must be provided.\nCameras, images and points3D files must exist.' ) # find the requisite files in either format requisites = ['cameras', 'images', 'points3D'] extensions = ['.bin', '.txt', None] for ext in extensions: if not ext: raise AttributeError( 'COLMAP Workspace Directory must contain:\ncameras, images and points3D files.\nThese files must be in either .BIN or .TXT format.' ) elif len([ f for f in os.listdir(dirpath) for c in requisites if f == f'{c}{ext}' ]) == 3: # found the correct set of files with this extension break # check for a project.ini as it may contain an alternate image path setting try: # https://stackoverflow.com/a/25493615 with open(os.path.join(dirpath, 'project.ini'), 'r') as f: config_string = f'[DEFAULT]\n{f.read()}' config = ConfigParser() config.read_string(config_string) image_path = config['DEFAULT']['image_path'] except: image_path = os.path.join(dirpath, '..', 'images') cameras = {} trackers = {} data = {'trackers': trackers, 'cameras': cameras} # https://colmap.github.io/format.html try: ccameras, images, points3D = read_model(dirpath, ext=ext) except Exception as ex: raise AttributeError( f'Error when reading COLMAP workspace directory:\n{str(ex)}') model = list(ccameras.values())[0] resolution = (model.width, model.height) data.setdefault('resolution', resolution) def shift(co): # COLMAP uses the convention that the upper left image corner has coordinate (0, 0) # and the center of the upper left most pixel has coordinate (0.5, 0.5). # Translate the point to the center of the image. return (co[0] - resolution[0] / 2.0 + 0.5, co[1] - resolution[1] / 2.0 + 0.5) for idx, i in images.items(): camera = ccameras[i.camera_id] f, cx, cy = parse_camera_param_list(camera) filename = i.name.strip() if not os.path.isabs(filename) or not os.path.isfile(filename): filename = os.path.join(image_path, filename) # The coordinates of the projection/camera center are given by -R^t * T, # where R^t is the inverse/transpose of the 3x3 rotation matrix composed # from the quaternion and T is the translation vector. The local camera # coordinate system of an image is defined in a way that the X axis points # to the right, the Y axis to the bottom, and the Z axis to the front as # seen from the image. R = Quaternion(i.qvec).to_matrix() R.transpose() # c = -R^T t T = Vector(i.tvec) c = -1 * R @ T # t = -R * c R.transpose() R.rotate(Euler((pi, 0, 0))) t = -1 * R @ c cameras.setdefault( idx, { 'filename': filename, 'f': f, 'k': (0, 0, 0), 't': tuple(t), 'principal': (cx, cy), 'R': tuple(map(tuple, tuple(R))), 'trackers': { i.point3D_ids[tidx]: shift(i.xys[tidx]) for tidx in range(len(i.xys)) if i.point3D_ids[tidx] >= 0 }, }) for idx, p in points3D.items(): trackers.setdefault(idx, { 'co': tuple(p.xyz), 'rgb': tuple(p.rgb), 'error': p.error, }) return data
def extract(properties, *args, **kargs): dirpath = bpy.path.abspath(properties.dirpath) # find the requisite files in either format requisites = ['cameras', 'images', 'points3D'] extensions = ['.bin', '.txt'] files = [ f for f in os.listdir(dirpath) for c in requisites if f.startswith(c) ] ext = set(extensions).intersection( [os.path.splitext(r)[1].lower() for r in files]) if len(files) != 3 or len(ext) != 1: raise Exception( 'COLMAP sparse reconstruction must contain a cameras, images and points3D file in .BIN or .TXT format' ) # check for a project.ini as it may contain an alternate image path setting try: # https://stackoverflow.com/a/25493615 with open(os.path.join(dirpath, 'project.ini'), 'r') as f: config_string = f'[DEFAULT]\n{f.read()}' config = ConfigParser() config.read_string(config_string) image_path = config['DEFAULT']['image_path'] except: image_path = dirpath cameras = {} trackers = {} data = {'trackers': trackers, 'cameras': cameras} # https://colmap.github.io/format.html ccameras, images, points3D = read_model(dirpath, ext=ext.pop()) model = list(ccameras.values())[0] resolution = (model.width, model.height) data.setdefault('resolution', resolution) for idx, i in images.items(): camera = ccameras[i.camera_id] f, cx, cy = parse_camera_param_list(camera) filename = i.name.strip() if not os.path.isabs(filename) or not os.path.isfile(filename): filename = os.path.join(image_path, filename) # The coordinates of the projection/camera center are given by -R^t * T, # where R^t is the inverse/transpose of the 3x3 rotation matrix composed # from the quaternion and T is the translation vector. The local camera # coordinate system of an image is defined in a way that the X axis points # to the right, the Y axis to the bottom, and the Z axis to the front as # seen from the image. R = Quaternion(i.qvec).to_matrix() R.transpose() # c = -R^T t T = Vector(i.tvec) c = -1 * R @ T # t = -R * c R.transpose() R.rotate(Euler((pi, 0, 0))) t = -1 * R @ c cameras.setdefault( idx, { 'filename': filename, 'f': f, 'k': (0, 0, 0), 't': tuple(t), 'principal': (cx, cy), 'R': tuple(map(tuple, tuple(R))), 'trackers': {}, }) for idx, p in points3D.items(): trackers.setdefault(idx, {'co': tuple(p.xyz), 'rgb': tuple(p.rgb)}) # COLMAP uses the convention that the upper left image corner has coordinate (0, 0) # and the center of the upper left most pixel has coordinate (0.5, 0.5). for image_idx, image_id in enumerate(p.image_ids): # COLMAP db format allows more dimensions, but sparse file format may not reperesent it # to be safe, truncated coords to 2 dimensions co = list(images[image_id].xys[p.point2D_idxs[image_idx]])[:2] co[0] = co[0] - resolution[0] / 2.0 + 0.5 co[1] = co[1] - resolution[1] / 2.0 + 0.5 cameras[p.image_ids[0]]['trackers'].setdefault(idx, co) return data