import sys sys.path.append("..") import numpy as np from pycolmap import DualQuaternion, Image, SceneManager #------------------------------------------------------------------------------- image_to_idx = lambda im: int(im.name[:im.name.rfind(".")]) #------------------------------------------------------------------------------- def interpolate_linear(images, camera_id, file_format): if len(images) < 2: raise ValueError("Need at least two images for linear interpolation!") prev_image = images[0] prev_idx = image_to_idx(prev_image) prev_dq = DualQuaternion.FromQT(prev_image.q, prev_image.t) start = prev_idx new_images = [] for image in images[1:]: curr_idx = image_to_idx(image) curr_dq = DualQuaternion.FromQT(image.q, image.t) T = curr_idx - prev_idx Tinv = 1. / T # like quaternions, dq(x) = -dq(x), so we'll need to pick the one more # appropriate for interpolation by taking -dq if the dot product of the # two q-vectors is negative if prev_dq.q0.dot(curr_dq.q0) < 0: curr_dq = -curr_dq for i in xrange(1, T): t = i * Tinv dq = t * prev_dq + (1. - t) * curr_dq q, t = dq.ToQT() new_images.append( Image(file_format.format(prev_idx + i), args.camera_id, q, t)) prev_idx = curr_idx prev_dq = curr_dq return new_images #------------------------------------------------------------------------------- def interpolate_hermite(images, camera_id, file_format): if len(images) < 4: raise ValueError( "Need at least four images for Hermite spline interpolation!") new_images = [] # linear blending for the first frames T0 = image_to_idx(images[0]) dq0 = DualQuaternion.FromQT(images[0].q, images[0].t) T1 = image_to_idx(images[1]) dq1 = DualQuaternion.FromQT(images[1].q, images[1].t) if dq0.q0.dot(dq1.q0) < 0: dq1 = -dq1 dT = 1. / float(T1 - T0) for j in xrange(1, T1 - T0): t = j * dT dq = ((1. - t) * dq0 + t * dq1).normalize() new_images.append( Image(file_format.format(T0 + j), camera_id, *dq.ToQT())) T2 = image_to_idx(images[2]) dq2 = DualQuaternion.FromQT(images[2].q, images[2].t) if dq1.q0.dot(dq2.q0) < 0: dq2 = -dq2 # Hermite spline interpolation of dual quaternions # pdfs.semanticscholar.org/05b1/8ede7f46c29c2722fed3376d277a1d286c55.pdf for i in xrange(1, len(images) - 2): T3 = image_to_idx(images[i + 2]) dq3 = DualQuaternion.FromQT(images[i + 2].q, images[i + 2].t) if dq2.q0.dot(dq3.q0) < 0: dq3 = -dq3 prev_duration = T1 - T0 current_duration = T2 - T1 next_duration = T3 - T2 # approximate the derivatives at dq1 and dq2 using weighted central # differences dt1 = 1. / float(T2 - T0) dt2 = 1. / float(T3 - T1) m1 = (current_duration * dt1) * (dq2 - dq1) + \ (prev_duration * dt1) * (dq1 - dq0) m2 = (next_duration * dt2) * (dq3 - dq2) + \ (current_duration * dt2) * (dq2 - dq1) dT = 1. / float(current_duration) for j in xrange(1, current_duration): t = j * dT # 0 to 1 t2 = t * t # t squared t3 = t2 * t # t cubed # coefficients of the Hermite spline (a=>dq and b=>m) a1 = 2. * t3 - 3. * t2 + 1. b1 = t3 - 2. * t2 + t a2 = -2. * t3 + 3. * t2 b2 = t3 - t2 dq = (a1 * dq1 + b1 * m1 + a2 * dq2 + b2 * m2).normalize() new_images.append( Image(file_format.format(T1 + j), camera_id, *dq.ToQT())) T0, T1, T2 = T1, T2, T3 dq0, dq1, dq2 = dq1, dq2, dq3 # linear blending for the last frames dT = 1. / float(T2 - T1) for j in xrange(1, T2 - T1): t = j * dT # 0 to 1 dq = ((1. - t) * dq1 + t * dq2).normalize() new_images.append( Image(file_format.format(T1 + j), camera_id, *dq.ToQT())) return new_images #------------------------------------------------------------------------------- def main(args): scene_manager = SceneManager(args.input_folder) scene_manager.load() images = sorted(scene_manager.images.itervalues(), key=image_to_idx) if args.method.lower() == "linear": new_images = interpolate_linear(images, args.camera_id, args.format) else: new_images = interpolate_hermite(images, args.camera_id, args.format) map(scene_manager.add_image, new_images) scene_manager.save(args.output_folder) #------------------------------------------------------------------------------- if __name__ == "__main__": import argparse parser = argparse.ArgumentParser( description="Given a reconstruction with ordered images *with integer " "filenames* like '000100.png', fill in missing camera positions for " "intermediate frames.", formatter_class=argparse.ArgumentDefaultsHelpFormatter) parser.add_argument("input_folder") parser.add_argument("output_folder") parser.add_argument("--camera_id", type=int, default=1, help="camera id to use for the missing images") parser.add_argument("--format", type=str, default="{:06d}.png", help="filename format to use for added images") parser.add_argument( "--method", type=str.lower, choices=("linear", "hermite"), default="hermite", help="Pose imputation method") args = parser.parse_args() main(args)