Interpolate keyframes with cubic spline. Returns list of (camera, frame_idx).
(self, steps_per_segment: int = 60)
| 212 | self.keyframes.clear() |
| 213 | |
| 214 | def interpolate(self, steps_per_segment: int = 60) -> List[Tuple[OrbitCamera, int]]: |
| 215 | """Interpolate keyframes with cubic spline. Returns list of (camera, frame_idx).""" |
| 216 | if len(self.keyframes) < 2: |
| 217 | if self.keyframes: |
| 218 | cam = OrbitCamera( |
| 219 | self.keyframes[0].eye, |
| 220 | self.keyframes[0].look_at, |
| 221 | self.keyframes[0].up, |
| 222 | self.keyframes[0].fov_deg, |
| 223 | ) |
| 224 | return [(cam, self.keyframes[0].frame_idx)] |
| 225 | return [] |
| 226 | |
| 227 | from scipy.interpolate import CubicSpline |
| 228 | |
| 229 | n = len(self.keyframes) |
| 230 | t_knots = np.linspace(0, 1, n) |
| 231 | t_interp = np.linspace(0, 1, (n - 1) * steps_per_segment + 1) |
| 232 | |
| 233 | # Stack all fields |
| 234 | eyes = np.array([kf.eye for kf in self.keyframes]) |
| 235 | look_ats = np.array([kf.look_at for kf in self.keyframes]) |
| 236 | ups = np.array([kf.up for kf in self.keyframes]) |
| 237 | fovs = np.array([kf.fov_deg for kf in self.keyframes]) |
| 238 | frames = np.array([kf.frame_idx for kf in self.keyframes], dtype=np.float64) |
| 239 | |
| 240 | cs_eye = CubicSpline(t_knots, eyes) |
| 241 | cs_look = CubicSpline(t_knots, look_ats) |
| 242 | cs_up = CubicSpline(t_knots, ups) |
| 243 | cs_fov = CubicSpline(t_knots, fovs) |
| 244 | cs_frame = CubicSpline(t_knots, frames) |
| 245 | |
| 246 | result = [] |
| 247 | for t in t_interp: |
| 248 | e = cs_eye(t).astype(np.float32) |
| 249 | la = cs_look(t).astype(np.float32) |
| 250 | u = _normalize(cs_up(t).astype(np.float32)) |
| 251 | f = float(cs_fov(t)) |
| 252 | fi = int(np.clip(np.round(cs_frame(t)), 0, max(frames))) |
| 253 | cam = OrbitCamera(e, la, u, f) |
| 254 | result.append((cam, fi)) |
| 255 | return result |
| 256 | |
| 257 | def save(self, path: str): |
| 258 | data = [kf.to_dict() for kf in self.keyframes] |
no test coverage detected