29
edits
(DIY final project) |
m (refine) |
||
| Line 77: | Line 77: | ||
To document the experiment, I used a fixed camera setup. | To document the experiment, I used a fixed camera setup. | ||
The camera was mounted above the Petri dish and | The camera was mounted above the Petri dish and under it a power source so that it could record for several hours without interruption. I configured the camera to take one photo every '''10 seconds'''. | ||
This interval was chosen because it created enough frames for a smooth time-lapse, while still keeping the number of images manageable. | This interval was chosen because it created enough frames for a smooth time-lapse, while still keeping the number of images manageable. | ||
During the recording session, the camera captured thousands of photographs of the slime molds as they slowly expanded | During the recording session, the camera captured thousands of photographs of the slime molds as they slowly expanded. | ||
== Creating the Video with Python == | == Creating the Video with Python == | ||
After the recording session, I transferred the images to my computer. | After the recording session, I transferred the images to my computer. | ||
The folder contained both image files and raw camera files, so I used only the JPG images for the final processing. I wrote a Python program to load the images in order, align them, remove problematic outsider frames when necessary, and combine the sequence into a video. | The folder contained both image files and raw camera files, so I used only the JPG images for the final processing. I wrote a Python program to load the images in order, align them, remove problematic outsider frames when necessary, and combine the sequence into a video.<syntaxhighlight lang="python" line="1"> | ||
import cv2 | |||
import os | |||
import glob | |||
import numpy as np | |||
# SETTINGS | |||
FPS = 30 | |||
OUTPUT = "timelapse_aligned.mp4" | |||
IMAGE_EXT = "*.JPG" | |||
# Read JPG files only | |||
files = sorted(glob.glob(IMAGE_EXT)) | |||
if not files: | |||
raise Exception("No JPG files found in this folder.") | |||
print(f"Found {len(files)} JPG images") | |||
# Use first image as reference | |||
ref = cv2.imread(files[0]) | |||
ref = cv2.resize(ref, (1920, 1080)) | |||
ref_gray = cv2.cvtColor(ref, cv2.COLOR_BGR2GRAY) | |||
h, w = ref.shape[:2] | |||
video = cv2.VideoWriter( | |||
OUTPUT, | |||
cv2.VideoWriter_fourcc(*"mp4v"), | |||
FPS, | |||
(w, h) | |||
) | |||
last_good = ref.copy() | |||
for i, file in enumerate(files): | |||
img = cv2.imread(file) | |||
if img is None: | |||
print("Skipped unreadable:", file) | |||
continue | |||
# Fix portrait/rotated outsiders automatically | |||
if img.shape[0] > img.shape[1]: | |||
img = cv2.rotate(img, cv2.ROTATE_90_CLOCKWISE) | |||
img = cv2.resize(img, (w, h)) | |||
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) | |||
# Align image to first image | |||
warp_matrix = np.eye(2, 3, dtype=np.float32) | |||
try: | |||
_, warp_matrix = cv2.findTransformECC( | |||
ref_gray, | |||
gray, | |||
warp_matrix, | |||
cv2.MOTION_EUCLIDEAN, | |||
criteria=(cv2.TERM_CRITERIA_EPS | cv2.TERM_CRITERIA_COUNT, 50, 1e-5) | |||
) | |||
aligned = cv2.warpAffine( | |||
img, | |||
warp_matrix, | |||
(w, h), | |||
flags=cv2.INTER_LINEAR + cv2.WARP_INVERSE_MAP | |||
) | |||
video.write(aligned) | |||
last_good = aligned | |||
except cv2.error: | |||
# If alignment fails, probably outsider / bad photo | |||
print("Skipped outsider or badly rotated image:", file) | |||
continue | |||
if i % 100 == 0: | |||
print(f"Processed {i}/{len(files)}") | |||
video.release() | |||
print("Done:", OUTPUT) | |||
</syntaxhighlight>The final time-lapse was created from the image sequence and exported as an MP4 file. After that, I edited the video further by adding an intro, title cards, music, and an ending message. | |||
== Artistic Concept == | == Artistic Concept == | ||
The final video was not meant to be only a biological documentation. I wanted it to feel like a short documentary or a small film. | The final video was not meant to be only a biological documentation. I wanted it to feel like a short documentary or a small film. | ||
| Line 112: | Line 187: | ||
The message is intentionally simple. I wanted the viewer to feel the connection to coexistence, difference, and anti-racist thinking without turning the video into a direct slogan. | The message is intentionally simple. I wanted the viewer to feel the connection to coexistence, difference, and anti-racist thinking without turning the video into a direct slogan. | ||
== Conclusion == | == Conclusion == | ||
This project combined biological experimentation, visual storytelling, and computational image processing. | This project combined biological experimentation, visual storytelling, and computational image processing. | ||
edits