Skip to content

Instantly share code, notes, and snippets.

@greg-randall
Created December 4, 2023 14:20
Show Gist options
  • Save greg-randall/7b41083ad12409d7b3df97d2a6f595a9 to your computer and use it in GitHub Desktop.
Save greg-randall/7b41083ad12409d7b3df97d2a6f595a9 to your computer and use it in GitHub Desktop.
Smooth timelapses by averaging frames. Full notes below.
#Averages a number of frames from your timelapse to create a smoother output.
#The number of frames you might want to average depends on the issues you're running into, but
#if you average too many it will make the world look like it's melting, and its slow.
#My timelapses tend to be multiday, and have long shooting intervals(5-20 minutes), so 6 frames
#averaged covers the day/night and night/day transition pretty well. 6-24 might be a reasonable range,
#but I would test ~10 seconds of frames at different averages, maybe try 6, 12, & 24.
#Place in a folder of JPGs that will sort correctly with a natsort,
#set the variable "average_number" and this runs multithreaded, so
#change "max_threads" to the number of threads you want.
#(I have some stats from my computer at the bottom, for me 4 threads
#was almost the fastest, and only consumes 4 threads. I have a reasonably
#nice computer as of 2023, but YMMV
import os
from natsort import natsorted
from PIL import Image
import numpy as np
import concurrent.futures
average_number=6
max_threads=4
# Get a list of all JPG files in the folder
jpg_files = [f for f in os.listdir('.') if f.endswith('.jpg')]
# Natsort the list
jpg_files = natsorted(jpg_files)
#print(jpg_files)
def combine_images(i):
jpgs_to_combine = jpg_files[i:i+average_number]
images = [Image.open(jpg) for jpg in jpgs_to_combine]
# Convert images to numpy arrays
np_images = [np.array(img) for img in images]
# Calculate the average
average_image = np.mean(np_images, axis=0)
# Convert back to PIL Image and save
Image.fromarray(np.uint8(average_image)).save(f'OUT_{average_number}_{i:05}.jpg')
print(f'Saving: OUT_{average_number}_{i:05}.jpg')
with concurrent.futures.ThreadPoolExecutor(max_workers=max_threads) as executor:
executor.map(combine_images, range(len(jpg_files) - average_number - 1))
"""
Was going to try all the way from 1 - 16 (cpu has 8 cores / 16 threads), but pretty clear 4 is the sweet spot for my machine.
Time: 170.86919450759888s Workers: 1
Time: 107.62791514396667s Workers: 2
Time: 92.45315217971802s Workers: 3
Time: 85.73750138282776s Workers: 4
Time: 82.29942393302917s Workers: 5
Time: 88.5809121131897s Workers: 6
Time: 86.28025555610657s Workers: 7
Time: 83.46813654899597s Workers: 8
Time: 87.20684361457825s Workers: 9
"""
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment