Build a Plant Growth Time-Lapse Camera with a Raspberry Pi
Watching a seed push up and unfurl its first leaves in a few seconds of video never gets old, and it happens to be one of the easiest Raspberry Pi projects to start: no complicated sensors, no circuitry — just a camera, a steady capture schedule, and one ffmpeg command at the end to stitch it all together.
How it works
- The Pi Camera is mounted in a fixed position, pointed straight at the pot, and doesn’t move for the entire duration of the shoot.
- A Python script using the
picamera2library captures one photo, names it with a timestamp, and exits — it runs standalone each time it’s invoked. cron(or a systemd timer) calls that script on a fixed interval, say every 10 minutes, around the clock for weeks.- Once enough frames exist,
ffmpegstitches the whole chronologically-ordered image sequence into a single MP4 video.
The one thing that makes or breaks a smooth, flicker-free video: lock exposure and white balance for every single capture. If the camera is left to auto-adjust to whatever light is available at each moment of the day, the final video will flicker between bright and dark from frame to frame.
Parts list
- A Raspberry Pi (Pi 3, 4, or Zero 2 W all work)
- A Pi Camera Module (v2 or v3, connected via the CSI ribbon cable)
- A microSD card with enough headroom (32GB or more recommended, or add a USB drive for long shoots)
- A mount or clamp to hold the camera perfectly still between shots
- A stable 5V power supply, since the Pi needs to run continuously for weeks
Sample code
The capture script takes one photo with locked exposure settings to avoid flicker:
#!/usr/bin/env python3
import time
from datetime import datetime
from picamera2 import Picamera2
OUTPUT_DIR = "/home/pi/timelapse"
picam2 = Picamera2()
config = picam2.create_still_configuration(main={"size": (1920, 1080)})
picam2.configure(config)
# Lock exposure and white balance so every frame matches in brightness and color
picam2.set_controls({
"AeEnable": False,
"AwbEnable": False,
"ExposureTime": 20000, # microseconds, tune for the light where the plant sits
"AnalogueGain": 1.5,
"ColourGains": (1.4, 1.6), # (red, blue) - measure first to find stable values
})
picam2.start()
time.sleep(2) # let the sensor settle before capturing
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
filename = f"{OUTPUT_DIR}/frame_{timestamp}.jpg"
picam2.capture_file(filename)
picam2.close()
Add this line to crontab -e to capture every 10 minutes:
*/10 * * * * /usr/bin/python3 /home/pi/capture_frame.py >> /home/pi/timelapse.log 2>&1
After a few weeks, stitch all the frames into a video with ffmpeg:
ffmpeg -framerate 24 -pattern_type glob -i '/home/pi/timelapse/frame_*.jpg' \
-vf "scale=1920:-2" -c:v libx264 -pix_fmt yuv420p /home/pi/timelapse_output.mp4
-framerate 24 means 24 frames become 1 second of video — at a 10-minute capture interval, 1 second of video covers roughly 4 hours of real time.
Common pitfalls
- Leaving auto-exposure/auto-white-balance on is the single most common cause of flicker — always lock
AeEnableandAwbEnableas shown above. - Cron runs in a different environment than your terminal — it doesn’t inherit your full
PATH, so always use absolute paths (/usr/bin/python3, the full script path) in the crontab line. - The SD card fills up after a few weeks: each full-HD frame is roughly 1-2MB, and shooting every 10 minutes for a month can add up to several GB — consider a USB drive, or automatically delete frames once the video has been rendered.
- Filenames must sort in chronological order for
ffmpeg -pattern_type globto stitch them in the right sequence — theYYYYMMDD_HHMMSStimestamp format guarantees alphabetical order matches time order.
Where to go from here
Add a light sensor to automatically skip nighttime frames (saving storage that adds nothing to the video), overlay a date/time stamp on the video with ffmpeg’s drawtext filter, or automatically sync frames to cloud storage or a NAS each night so a failed SD card mid-shoot doesn’t cost you weeks of footage.
Comments