|
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 |
|
|
|
|
|
|
|
|
|
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 = [] |
|
|
|
|
|
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 |
|
|
|
|
|
|
|
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 |
|
|
|
|
|
|
|
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 |
|
t2 = t * t |
|
t3 = t2 * t |
|
|
|
|
|
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 |
|
|
|
|
|
dT = 1. / float(T2 - T1) |
|
for j in xrange(1, T2 - T1): |
|
t = j * dT |
|
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) |
|
|