Time Lapse PhotographyΒΆ
Some things happen too slowly for us to perceive, such as the legend of the flow of people, sunrise and sunset, and the blooming of flower buds. Time-lapse photography allows you to see these exciting things clearly.
You will use two windows at the same time in this project:
One is Terminal, where you will enter wasd
to control the camera orientation, enter q
to record, then enter e
to stop, and enter g
to exit shooting. If the program has not been terminated after exiting the shooting, please press ctrl+c
.
Another browser interface, after the program runs, you will need to enter http://<Your Raspberry Pi IP>:9000/mjpg
in the PC browser (such as chrome) to view the screen.
Run the Code
cd /home/pi/pan-tilt-hat/examples
sudo python3 time_lapse_photography
View the Image
After the code runs, the terminal will display the following prompt:
No desktop !
* Serving Flask app "vilib.vilib" (lazy loading)
* Environment: production
WARNING: Do not use the development server in a production environment.
Use a production WSGI server instead.
* Debug mode: off
* Running on http://0.0.0.0:9000/ (Press CTRL+C to quit)
Then you can enter http://<your IP>:9000/mjpg
in the browser to view the video screen. such as: http://192.168.18.113:9000/mjpg
Code
#!/usr/bin/env python3
'''
Time-lapse photography based on the Raspistill command
'''
from time import time, sleep, strftime, localtime
from vilib import Vilib
import os
import sys
sys.path.append('./')
from servo import Servo
# import readchar
import cv2
import threading
import tty
import termios
def readchar():
fd = sys.stdin.fileno()
old_settings = termios.tcgetattr(fd)
try:
tty.setraw(sys.stdin.fileno())
ch = sys.stdin.read(1)
finally:
termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
return ch
manual = '''
Press keys on keyboard to record value!
W: up
A: left
S: right
D: down
Q: start Time-lapse photography
E: stop
G: Quit
'''
# endregion
# region servos init
pan = Servo(pin=13, min_angle=-90, max_angle=90) # pan_servo_pin (BCM)
tilt = Servo(pin=12, min_angle=-90, max_angle=30) # be careful to limit the angle of the steering gear
panAngle = 0
tiltAngle = 0
pan.set_angle(panAngle)
tilt.set_angle(tiltAngle)
#endregion init
# # check dir
def check_dir(dir):
if not os.path.exists(dir):
try:
os.makedirs(dir)
except Exception as e:
print(e)
# region servo control
def limit(x,min,max):
if x > max:
return max
elif x < min:
return min
else:
return x
def servo_control(key):
global panAngle,tiltAngle
if key == 'w':
tiltAngle -= 1
tiltAngle = limit(tiltAngle, -90, 30)
tilt.set_angle(tiltAngle)
if key == 's':
tiltAngle += 1
tiltAngle = limit(tiltAngle, -90, 30)
tilt.set_angle(tiltAngle)
if key == 'a':
panAngle += 1
panAngle = limit(panAngle, -90, 90)
pan.set_angle(panAngle)
if key == 'd':
panAngle -= 1
panAngle = limit(panAngle, -90, 90)
pan.set_angle(panAngle)
# endregion servo control
# Video synthesis
def video_synthesis(name:str,output:str,path:str,fps=30,format='.jpg',datetime=False):
print('\nprocessing video, please wait ....')
# video parameter
fourcc = cv2.VideoWriter_fourcc(*'XVID')
out = cv2.VideoWriter(path+'/'+name+'.avi', fourcc, fps, (640,480))
width = 640
height = 480
# traverse
for root, dirs, files in os.walk(output):
print('%s pictures need to be processed ...'%len(files))
files = sorted(files)
for file in files:
# print('Format:',os.path.splitext(file)[1])
if os.path.splitext(file)[1] == format:
# imread
frame = cv2.imread(output+'/'+file)
# add datetime watermark
if datetime == True:
# print('name:',os.path.splitext(file)[1])
time = os.path.splitext(file)[0].split('-')
year = time[0]
month = time[1]
day = time[2]
hour = time[3]
minute = time[4]
second = time[5]
frame = cv2.putText(frame,
'%s.%s.%s %s:%s:%s'%(year,month,day,hour,minute,second),
(width - 180, height - 25),
cv2.FONT_HERSHEY_SIMPLEX, 0.5,
(255, 255, 255),
1,
cv2.LINE_AA) # anti-aliasing
# write video
out.write(frame)
# release the VideoWriter object
out.release()
# remove photos cache
os.system('sudo rm -r %s'%output)
print('\nDone.The video save as %s/%s'%(path,name))
# keyboard scan thread
key = None
breakout_flag=False
def keyboard_scan():
global key
while True:
key = None
key = readchar().lower()
sleep(0.01)
if breakout_flag==True:
break
# continuous_shooting
def continuous_shooting(path, interval_s=3, duration_s=3600):
print('\nStart time-lapse photography, press the "e" key to stop')
start_time = time()
node_time = start_time
while True:
if time()-node_time > interval_s:
node_time = time()
Vilib.take_photo(photo_name=strftime("%Y-%m-%d-%H-%M-%S", localtime()),path=path)
if key == 'e' or time()-start_time > duration_s:
break
sleep(0.01) # second
# main
def main():
global key
Vilib.camera_start(vflip=True,hflip=True)
Vilib.display(local=True,web=True)
sleep(2)
print(manual)
sleep(0.2)
t = threading.Thread(target=keyboard_scan)
t.setDaemon(True)
t.start()
path = "/home/pi/Videos/vilib/time_lapse"
check_dir(path)
while True:
servo_control(key)
# time-lapse photography
if key == 'q':
# check path
output = path+'/'+strftime("%Y-%m-%d-%H-%M-%S", localtime())
check_dir(output)
# take a picture every 3 seconds for 3600 seconds
continuous_shooting(output, interval_s=3, duration_s=3600)
# video_synthesis
name=strftime("%Y-%m-%d-%H-%M-%S", localtime())
video_synthesis(name=name,
output=output,
path=path,
fps=30,
format='.jpg',
datetime=True)
# esc
if key == 'g':
Vilib.camera_close()
global breakout_flag
breakout_flag=True
sleep(0.1)
print('The program ends, please press CTRL+C to exit.')
break
sleep(0.01)
if __name__ == "__main__":
main()
How it works?
Similar to Continuous Shooting, this example also needs to be split for analysis. It includes the following parts:
Servo control
Key input
Path management
Shooting
Video synthesis
Servo Control: It is exactly the same as Continuous Shooting, no need to repeat it.
Key input: Its implementation is consistent with Continuous Shooting (ie
readchar()
), but it is called by a separate thread. We extract the relevant code separately, as follows:''' Time-lapse photography based on the Raspistill command ''' from time import sleep, import sys import tty import termios import threading # region read keyboard def readchar(): pass # keyboard scan thread key = None breakout_flag=False def keyboard_scan(): global key while True: key = None key = readchar().lower() sleep(0.01) if breakout_flag==True: break # main def main(): t = threading.Thread(target=keyboard_scan) t.setDaemon(True) t.start() while True: # esc if key == 'g': global breakout_flag breakout_flag=True sleep(0.1) print('The program ends, please press CTRL+C to exit.') break sleep(0.01) if __name__ == "__main__": main()
Simply put, the
t = threading.Thread(target=keyboard_scan)
line of the main function generates a thread and calls thekeyboard_scan()
function. This function callsreadchar()
in a loop until thebreakout_flag
ends after being modified.For details on the use of threads, please refer to Threading - Python Docs.
Route Management: Used to ensure that the file read and write path during shooting is correct. It includes the following:
import os # # check dir def check_dir(dir): if not os.path.exists(dir): try: os.makedirs(dir) except Exception as e: print(e) # main def main(): path = "/home/pi/Videos/vilib/time_lapse" check_dir(path) while True: if key == 'q': #check path output = path+'/'+strftime("%Y-%m-%d-%H-%M-%S", localtime()) check_dir(output) # take_photo # video_synthesis if __name__ == "__main__": main()
The target directory for our output videos is
path
. And generating video requires a large number of temporary still photos, which are stored inoutput
. The function ofcheck_dir()
is to check whether the target folder exists, and create it if it does not exist.An
os
library is imported here, which allows python to use related functions of the operating system. Such as reading and writing files, creating files and directories, and manipulate paths. For details, please see OS - Python Docs.Shooting: Similar to Continuous Shooting, the difference is that instead of writing a specific number of photos, you manually press
e
to stop. This is achieved because the keyboard input is separated from the main program and runs on the thread separately.from time import time, sleep, strftime, localtime from vilib import Vilib # continuous_shooting def continuous_shooting(path, interval_s=3, duration_s=3600): print('\nStart time-lapse photography, press the "e" key to stop') start_time = time() node_time = start_time while True: if time()-node_time > interval_s: node_time = time() Vilib.take_photo(photo_name=strftime("%Y-%m-%d-%H-%M-%S", localtime()),path=path) if key == 'e' or time()-start_time > duration_s: break sleep(0.01) # second # main def main(): Vilib.camera_start(vflip=True,hflip=True) Vilib.display(local=True,web=True) while True: # time-lapse photography if key == 'q': #check path # take a picture every 3 seconds for 3600 seconds continuous_shooting(output, interval_s=3, duration_s=3600) # video_synthesis # esc if key == 'g': Vilib.camera_close() break sleep(0.01) if __name__ == "__main__": main()
Video synthesis: It uses the photos stored in the
output
path as frames, and generates a video output to thepath
.from time import time, sleep, strftime, localtime from vilib import Vilib import cv2 import os # Video synthesis def video_synthesis(name:str,output:str,path:str,fps=30,format='.jpg',datetime=False): print('\nprocessing video, please wait ....') # video parameter fourcc = cv2.VideoWriter_fourcc(*'XVID') out = cv2.VideoWriter(path+'/'+name+'.avi', fourcc, fps, (640,480)) width = 640 height = 480 # traverse for root, dirs, files in os.walk(output): print('%s pictures need to be processed ...'%len(files)) files = sorted(files) for file in files: # print('Format:',os.path.splitext(file)[1]) if os.path.splitext(file)[1] == format: # imread frame = cv2.imread(output+'/'+file) # add datetime watermark if datetime == True: # print('name:',os.path.splitext(file)[1]) time = os.path.splitext(file)[0].split('-') year = time[0] month = time[1] day = time[2] hour = time[3] minute = time[4] second = time[5] frame = cv2.putText(frame, '%s.%s.%s %s:%s:%s'%(year,month,day,hour,minute,second), (width - 180, height - 25), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 255, 255), 1, cv2.LINE_AA) # anti-aliasing # write video out.write(frame) # release the VideoWriter object out.release() # remove photos cache os.system('sudo rm -r %s'%output) print('\nDone.The video save as %s/%s'%(path,name)) # main def main() while True: if key == 'q': #check path # take_photo # video_synthesis name=strftime("%Y-%m-%d-%H-%M-%S", localtime()) video_synthesis(name=name, output=output, path=path, fps=30, format='.jpg', datetime=True) if __name__ == "__main__": main()
Here, the video writer object is initialized first. The code show as below:
fourcc = cv2.VideoWriter_fourcc(*'XVID') out = cv2.VideoWriter(output+'/'+name, fourcc, fps, (640,480))
This module is derived from OpenCV, please refer to VideoWriter-OpenCV Docs for details.
Then, loop through each frame to form a video:
for root, dirs, files in os.walk(input): print('%s pictures be processed'%len(files)) files = sorted(files) for file in files: if os.path.splitext(file)[1] == format: # imread frame = cv2.imread(input+'/'+file) # add datetime watermark if datetime == True: time = os.path.splitext(file)[0].split('-') year = time[0] month = time[1] day = time[2] hour = time[3] minute = time[4] second = time[5] frame = cv2.putText(frame, '%s.%s.%s %s:%s:%s'%(year,month,day,hour,minute,second), (width - 180, height - 25), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 255, 255),1,cv2.LINE_AA) # anti-aliasing # write video out.write(frame)
After the video is processed, release the VideoWriter.
# release the VideoWriter object out.release()
Finally delete the
input
folder. Of course, if you have enough space, comment out this line of code to keep the original picture.# remove photos cache os.system('sudo rm -r %s'%output) print('\nDone.The video save as %s/%s'%(path,name))