So far all examples were implying that we receive images on a regular computer
(with a monitor) so we can use functions like cv2.imshow()
to immediately see
a result (the received stream).
However, this is not always an option. Consider the following scenario: multiple cameras send images to a headless server (computer that does not have a monitor and could be located somewhere in datacenter).
We want to be able to connect somehow to this server remotely (ideally using web browser) and check our video streams.
So we want to connect/disconnect to our video streams occasionally and this should not affect processing of those streams (motion or object detection, recording etc).
Lets see how this can be implemented.
There will be nothing new on this side:
1 import socket
2 import time
3 from imutils.video import VideoStream
4 import imagezmq
5
6 sender = imagezmq.ImageSender(connect_to='tcp://192.168.0.100:5555')
7
8 rpi_name = socket.gethostname() # send unique RPi hostname with each image
9 picam = VideoStream(usePiCamera=True).start()
10 time.sleep(2.0) # allow camera sensor to warm up
11 while True: # send images as stream until Ctrl-C
12 image = picam.read()
13 sender.send_image(rpi_name, image)
The script creates an image sender in a default REQ/REP mode that connects to the server with IP address 192.168.0.100, port 5555 and starts an infinite loop of reading images from the PI camera and sending them to the server.
This code is pretty similar to test_1_receive_images.py:
1 # run this program on the Mac to display image streams from multiple RPis
2 import cv2
3 import imagezmq
4
5 def processImage(image):
6 # Do something useful here, for example, run motion detection and record
7 # a stream to a file if detected.
8 pass
9
10 # Create a hub for receiving images from cameras
11 image_hub = imagezmq.ImageHub()
12
13 # Create a PUB server to send images for monitoring purposes in a non-blocking mode
14 stream_monitor = imagezmq.ImageSender(connect_to = 'tcp://*:5566', REQ_REP = False)
15
16 # Start main loop
17 while True:
18 rpi_name, image = image_hub.recv_image()
19 image_hub.send_reply(b'OK')
20 processImage(image)
21 stream_monitor.send_image(rpi_name, image)
Additional things in this script are lines 14 and 21.
Line 14 - here we create an ImageSender object in PUB/SUB mode. This object will be used to publish images after they are processed for monitoring (this is done in Line 21). You can read more about PUB/SUB here: REQ/REP versus PUB/SUB Messaging Patterns.
This code handles HTTP requests and can serve video stream from the headless server to your browser.
We use a simple Python library that can handle incoming HTTP connections to create a very simple HTTP server. Whenever there is an incoming request from the browser we start pulling images from the queue, encode them into a JPEG format and return to the browser as part of multipart data.
As a result, we will receive a stream of frames that will assemble into a live video in a browser.
1 import cv2
2 import imagezmq
3 from werkzeug.wrappers import Request, Response
4 from werkzeug.serving import run_simple
5
6 def sendImagesToWeb():
7 # When we have incoming request, create a receiver and subscribe to a publisher
8 receiver = imagezmq.ImageHub(open_port='tcp://localhost:5566', REQ_REP = False)
9 while True:
10 # Pull an image from the queue
11 camName, frame = receiver.recv_image()
12 # Using OpenCV library create a JPEG image from the frame we have received
13 jpg = cv2.imencode('.jpg', frame)[1]
14 # Convert this JPEG image into a binary string that we can send to the browser via HTTP
15 yeild b'--frame\r\nContent-Type:image/jpeg\r\n\r\n'+jpg.tostring()+b'\r\n'
16
17 # Add `application` method to Request class and define this method here
18 @Request.application
19 def application(request):
20 # What we do is we `sendImagesToWeb` as Iterator (generator) and create a Response object
21 # based on its output.
22 return Response(sendImagesToWeb(), mimetype='multipart/x-mixed-replace; boundary=frame')
23
24 if __name__ == '__main__':
25 # This code starts simple HTTP server that listens on interface with IP 192.168.0.114, port 4000
26 run_simple('192.168.0.114', 4000, application)