Created
December 4, 2023 14:20
-
-
Save greg-randall/7b41083ad12409d7b3df97d2a6f595a9 to your computer and use it in GitHub Desktop.
Smooth timelapses by averaging frames. Full notes below.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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