-
Notifications
You must be signed in to change notification settings - Fork 0
/
camera.py
129 lines (106 loc) · 4.16 KB
/
camera.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
'''Camera.py
This module contains all the functionality for the live video-feed.
It's based on an article on the the website of Miguel Grinberg:
https://blog.miguelgrinberg.com/post/video-streaming-with-flask
'''
import io
import threading
from time import sleep, time
from picamera import PiCamera
from _thread import get_ident
class CameraEvent(object):
'''
An Event-like class that signals all active clients
when a new frame is available.
'''
def __init__(self):
self.events = {}
def wait(self):
'''Invoked from each client's thread to wait for the next frame.'''
ident = get_ident()
if ident not in self.events:
# this is a new client
# add an entry for it in the self.events dict
# each entry has two elements, a threading.Event() and a timestamp
self.events[ident] = [threading.Event(), time()]
return self.events[ident][0].wait()
def set(self):
'''Invoked by the camera thread when a new frame is available.'''
now = time()
remove = None
for ident, event in self.events.items():
if not event[0].isSet():
# if this client's event is not set, then set it
# also update the last set timestamp to now
event[0].set()
event[1] = now
else:
# if the client's event is already set, it means the client
# did not process a previous frame
# if the event stays set for more than 5 seconds, then assume
# the client is gone and remove it
if now - event[1] > 5:
remove = ident
if remove:
del self.events[remove]
def clear(self):
'''Invoked from each client's thread after a frame was processed.'''
self.events[get_ident()][0].clear()
class BaseCamera(object):
thread = None # background thread that reads frames from camera
frame = None # current frame is stored here by background thread
last_access = 0 # time of last client access to the camera
event = CameraEvent()
def __init__(self):
'''Start the background camera thread if it isn't running yet.'''
if BaseCamera.thread is None:
BaseCamera.last_access = time()
# start background frame thread
BaseCamera.thread = threading.Thread(target=self._thread)
BaseCamera.thread.start()
# wait until frames are available
while self.get_frame() is None:
sleep(0)
def get_frame(self):
'''Return the current camera frame.'''
BaseCamera.last_access = time()
# wait for a signal from the camera thread
BaseCamera.event.wait()
BaseCamera.event.clear()
return BaseCamera.frame
@staticmethod
def frames():
'''Generator that returns frames from the camera.'''
raise RuntimeError('Must be implemented by subclasses.')
@classmethod
def _thread(cls):
'''Camera background thread.'''
frames_iterator = cls.frames()
for frame in frames_iterator:
BaseCamera.frame = frame
BaseCamera.event.set() # send signal to clients
sleep(0)
# if there hasn't been any clients asking for frames in
# the last 10 seconds then stop the thread
if time() - BaseCamera.last_access > 10:
frames_iterator.close()
break
BaseCamera.thread = None
class Camera(BaseCamera):
@staticmethod
def frames():
with PiCamera() as camera:
# Set framerate and resolution
# camera.framerate = 30
# camera.resolution = (640, 480)
# let camera warm up
sleep(2)
stream = io.BytesIO()
for _ in camera.capture_continuous(stream, 'jpeg',
use_video_port=True):
# return current frame
stream.seek(0)
yield stream.read()
# reset stream for next frame
stream.seek(0)
stream.truncate()