Closed kizniche closed 5 years ago
The BaseCamera
has a bunch of class variables (here). These are going to be shared across all subclasses. This prevents having more than one active camera at a time. To support multiple cameras, you should refactor that so that these variable which keep the state of the background camera task are separate for each implementation.
Thank you for the quick response. That makes sense and is what I had a hunch about but didn't get a chance to test. I'll work on it this weekend and report back with what I come up with to help anyone else who may come across this issue. Thanks, again.
I got this working, thanks to your tip. Here is what I changed in order to separate the camera instances. This may or may not be best practice, but it was the first thing that came to mind. The following is what I changed:
HTML:
{% for each_camera in cameras %}
<img src="/video_feed/opencv/{{each_camera.opencv_device}}">
{% endif %}
<img src="/video_feed/picamera/0">
Flask route:
@app.route('/video_feed/<camera_type>/<device>')
def video_feed(camera_type, device):
"""Video streaming route. Put this in the src attribute of an img tag."""
camera_stream = import_module('camera_' + camera_type).Camera
if camera_type == 'opencv':
camera_stream.set_video_source(int(device))
return Response(gen(camera_stream(camera_type=camera_type, device=int(device))),
mimetype='multipart/x-mixed-replace; boundary=frame')
base_camera.py:
class BaseCamera(object):
thread = {} # background thread that reads frames from camera
frame = {} # current frame is stored here by background thread
last_access = {} # time of last client access to the camera
event = {}
def __init__(self, camera_type=None, device=None):
"""Start the background camera thread if it isn't running yet."""
self.unique_name = "{cam}_{dev}".format(cam=camera_type, dev=device)
BaseCamera.event[self.unique_name] = CameraEvent()
if self.unique_name not in BaseCamera.thread:
BaseCamera.thread[self.unique_name] = None
if BaseCamera.thread[self.unique_name] is None:
BaseCamera.last_access[self.unique_name] = time.time()
# start background frame thread
BaseCamera.thread[self.unique_name] = threading.Thread(target=self._thread,
args=(self.unique_name,))
BaseCamera.thread[self.unique_name].start()
# wait until frames are available
while self.get_frame() is None:
time.sleep(0)
def get_frame(self):
"""Return the current camera frame."""
BaseCamera.last_access[self.unique_name] = time.time()
# wait for a signal from the camera thread
BaseCamera.event[self.unique_name].wait()
BaseCamera.event[self.unique_name].clear()
return BaseCamera.frame[self.unique_name]
@staticmethod
def frames():
""""Generator that returns frames from the camera."""
raise RuntimeError('Must be implemented by subclasses')
@classmethod
def _thread(cls, unique_name):
"""Camera background thread."""
print('Starting camera thread')
frames_iterator = cls.frames()
for frame in frames_iterator:
BaseCamera.frame[unique_name] = frame
BaseCamera.event[unique_name].set() # send signal to clients
time.sleep(0)
# if there hasn't been any clients asking for frames in
# the last 5 seconds then stop the thread
if time.time() - BaseCamera.last_access[unique_name] > 5:
frames_iterator.close()
print('Stopping camera thread due to inactivity')
break
BaseCamera.thread[unique_name] = None
Many thanks, again, for the work you put into this code. I've integrated it into my project, Mycodo. I'll put a proper attribution to you and your repo before I make the next release.
Cheers!
Oh, that's a clever solution, great!
This issue will be automatically closed due to being inactive for more than six months. Seeing that I haven't responded to your last comment, it is quite possible that I have dropped the ball on this issue and I apologize about that. If that is the case, do not take the closing of the issue personally as it is an automated process doing it, just reopen it and I'll get back to you.
Hello @kizniche, Thank you for the above code, I am using the same concept to stream the live feeds from cameras into the flask application. I am stuck on stoping the thread which is already started, I have added multiple video streams dynamically but I want to stop this thread using a different controller.
Please can you help me out with this?
Thanks in advance.
@rajeshcis The following is how the camera threads are stopped in Mycodo, either purposefully or from inactivity: https://github.com/kizniche/Mycodo/blob/49a7fcd9f04aba4f09b5e792c9069a4e528b7690/mycodo/mycodo_flask/camera/base_camera.py#L113-L124
Hello @kizniche,
As I am a newbie in Flask, I didn't quite get the part in the HTML where you put the each_camera.opencv_device
to get multiple openCV cameras into one video stream page. Where would I include a list of openCV in so that the HTML knows the endpoints route of my openCV cameras?
Thank you.
Hey @kizniche ,
I saw your post above regarding what you changed to allow 2 cameras to stream at once. Is what you posted the only changes you made? I'm looking to stream 2 USB cameras with OpenCV at the same time. Wasn't sure if those were the only changes needed to get it up and running.
hi i am new here , i want to integrate video streaming in my mircoservices architecture using docker. i am able to stream video on one page, but when i change the page and come back i got errors , there is no image no more whilst the camera is still activated . i would like to know how to handle this issue.
Thanks for publishing this code. It works great and I'm able to stream both a Pi camera or a USB camera. However, I'm having difficulty getting more than one to stream at the same time. I'm running the Flask app in apache2 using the WSGI module.
I've tried calling the same route with different parameters to access the different devices, and this works, but not both at the same time. The first call starts a stream, then the next only displays the first cam's images, so I have two of the same stream on the same page.
I then tried making two different routes, but that produced the same result as the first method.
Could you help me understand what I'm missing in trying to get this to work? Thanks.