|Author: Igor Maculan - firstname.lastname@example.org|
|A Simple mjpg stream http server|
|from BaseHTTPServer import BaseHTTPRequestHandler,HTTPServer|
|from SocketServer import ThreadingMixIn|
|rc,img = capture.read()|
|if not rc:|
|jpg = Image.fromarray(imgRGB)|
|tmpFile = StringIO.StringIO()|
|class ThreadedHTTPServer(ThreadingMixIn, HTTPServer):|
|"""Handle requests in a separate thread."""|
|capture = cv2.VideoCapture(0)|
|server = ThreadedHTTPServer(('localhost', 8080), CamHandler)|
|print "server started"|
|if __name__ == '__main__':|
Igor, this is a terrific example! Wonderful work!!!!
When I run it on my Ubuntu 15.10 system, it works, but initially I get this error message:
127.0.0.1 - - [09/Jan/2016 16:54:47] "GET /cam.mjpg HTTP/1.1" 200 -
Exception happened during processing of request from ('127.0.0.1', 42188)
The program still serves the mjpeg stream, but this message comes on every re/connect. Do you have an idea why this happens?
Great work, Igor!
Wolfgang, the program uses pipes (http://www.python-course.eu/pipes.php) to communicate the output to the server. When you disconnect from the port, it breaks the existing pipe, and when you reconnect, it breaks the previous pipe and starts a new one.
If you were running the script locally and the opencv output was displayed in a window, you could use the 'Keyboard Interrupt' to break the loop without any errors. That doesn't work in the browser because you've already used a pipe to get there.
tl;dr : that error is nothing to worry about, you'll pretty much see it every time you disconnect or reconnect.
Hello Igor, I have adapted your code for a project of mine. Thanks for sharing this!
I noticed you did not include a license with this, and I want to be respectful of your ownership of this code. Am I free to use my modified version of this gist in my project under any license?
@Asymptote, in order to do this you need to adapt this into a threaded HTTP server. I have done so in my code. See the accepted answer from this thread: http://stackoverflow.com/questions/14088294/multithreaded-web-server-in-python
I wanted to see what we can do with this. It seems to work fine, because i don't get any error and the infinite loop is running, but when i want to access at "localhost:8080" with my browser, i receive an error : "Message corrupt". Any idea about it ?
For those who get the error "Cannot import Image", you should replace
Anyway, thank you for sharing this =)
The problem seems to be this line:
i fixed the error above by uninstalling pil and pillow (seem to cause conflicts if both are installed). Then I reinstalled only pillow.
Now I get a popup window "python.exe has stopped working.." when accessing the mjpg via browser
caused by this line:
Hello Igor, this looks very promising . I have a couple questions maybe someone could help with. Please forgive me I'm a bit of a noob at this.
I'm presently using SimpleCV, which includes an mjpeg streaming server that seems to work in a similar way to this code, however with that server I often have to re-load the page a few times to get the image feed to appear, and sometimes it disappears after it's been streaming for a while. I thought maybe it would be more reliable if I were to somehow get the stream to write to a location and then use Apache to actually serve it up. There is a post here about streaming OpenCV images via Apache. I discovered your code above when starting to work through that tutorial.
I don't understand how to write the code to get the components to talk to eachother. Would you mind giving a bit more detail and example of how to actually use the above code? I assume I would kick it off in a terminal and let it run, at which time I could access the stream via a browser, but I'm not sure how to actually save images from OpenCV/SimpleCV to the server.
And a followup, is there a reason you're not just using Apache? I'm sure there was a good reason, it just seems it's already installed with most Linux distros so I'm not sure why one would write another server.
Thanks so much!
I hope that I have been useful to you
I am having issue when multiple clients connect to the server. When first client connects, streaming works perfectly. As soon as I second client connects, get following dump many times and server crashes:
[mpjpeg @ 0x560aa30ffae0] Expected boundary '--' not found, instead found a line of 127 bytes
Any idea how to fix multi client access issue?
Hi, thanks very much for this code, exactly what I was after.
I had it working locally on the rpi but couldn't access it from either my phone or PC when trying to access it across the network. The Python output didn't show the 200 response message.
I adjusted lines 44 and 60 to point to the local IP of the rpi (which I've got acting as an access point if that makes any difference) and could get the 200 response while trying to access from both phone and PC although I never saw any output. This also stopped me viewing the stream locally from the pi. Reverted back and still no images locally or over the network.
I'll delete the code and start again and see if that fixes it but any ideas in the mean time why it might not be working. Ultimately I want to view the processed output after some openCV manipulation on my phone with the rpi being headless.
I got it working on macOS by doing the following:
How did you fix it?
127.0.0.1 - - [14/Aug/2017 11:29:15] "GET /cam.mjpg HTTP/1.1" 200 -
Exception happened during processing of request from ('127.0.0.1', 43004)
modified for Python 3
#!/usr/bin/python ''' Author: Igor Maculan - email@example.com A Simple mjpg stream http server ''' import cv2 from PIL import Image import threading from http.server import BaseHTTPRequestHandler,HTTPServer from socketserver import ThreadingMixIn from io import StringIO,BytesIO import time capture=None class CamHandler(BaseHTTPRequestHandler): def do_GET(self): if self.path.endswith('.mjpg'): self.send_response(200) self.send_header('Content-type','multipart/x-mixed-replace; boundary=--jpgboundary') self.end_headers() while True: try: rc,img = capture.read() if not rc: continue imgRGB=cv2.cvtColor(img,cv2.COLOR_BGR2RGB) jpg = Image.fromarray(imgRGB) tmpFile = BytesIO() jpg.save(tmpFile,'JPEG') self.wfile.write("--jpgboundary".encode()) self.send_header('Content-type','image/jpeg') self.send_header('Content-length',str(tmpFile.getbuffer().nbytes)) self.end_headers() jpg.save(self.wfile,'JPEG') time.sleep(0.05) except KeyboardInterrupt: break return if self.path.endswith('.html'): self.send_response(200) self.send_header('Content-type','text/html') self.end_headers() self.wfile.write('<html><head></head><body>'.encode()) self.wfile.write('<img src="http://127.0.0.1:8087/cam.mjpg"/>'.encode()) self.wfile.write('</body></html>'.encode()) return class ThreadedHTTPServer(ThreadingMixIn, HTTPServer): """Handle requests in a separate thread.""" def main(): global capture capture = cv2.VideoCapture(0) capture.set(cv2.CAP_PROP_FRAME_WIDTH, 320); capture.set(cv2.CAP_PROP_FRAME_HEIGHT, 240); capture.set(cv2.CAP_PROP_SATURATION,0.2); global img try: server = ThreadedHTTPServer(('localhost', 8087), CamHandler) print( "server started") server.serve_forever() except KeyboardInterrupt: capture.release() server.socket.close() if __name__ == '__main__': main()
I modified a little bit the code to get the video_capture_path by command line (it works with file/webcam/rtsp as input). Also, since it's multithreaded, I create a lock to avoid race condition on camera's read() method. I've dropped the dependency to PIL since OpenCV can encode to JPEG and the dependency to StringIO, BytesIO, due to use numpy "tobytes()" method. One limitation with the following code is that multiple threads cannot read the same frame, so using a video file as input, more viewers implies in "fast" video because between consecutive reads in one thread, some frames may have already been skipped (read) by other threads.
Awesome code, really appreciate the project. To make this work in Edge change line 30 to:
Hi at all!
I'll create a branch for python3 version.
I wanted a stripped down example for teaching Python and openCV to kids. The above program was a great start and I got help from several other sources on the web. I did strip down the program some more and it's now unsuitable as a fork. I may be able to pass only my corrections along in your github but for now this is what I have that runs on a Raspberry Pi, Python 3, openCV 3.
Note that another CRLF is added to the boundary and note the boundary at the end of a MIME part has to have two dashes in front of the boundary specified in the Content-Type so I removed the extraneous dashes in Content-Type. Now the message is much closer to the standard and should work on most or all browsers.
For the drawing example, I included an openCV drawing from another web posting.
Camera commands are for an old Logitech camera and I figured them out (plus several other properties not in this example) using program v4l2-ctl to dump the camera properties and openCV get property help figure out what openCV was doing to them (mostly normalizing what the camera needs to values between 0 and 1).
# Stream an edited video camera image to HTTP JPEG format (MJPEG) # Capture a USB camera image using openCV # Change the image using openCV # Serve the image as HTTP in MIME multipart JPG format and each image replaces the previous import cv2 import threading from http.server import BaseHTTPRequestHandler,HTTPServer from socketserver import ThreadingMixIn import time capture=None class CamHandler(BaseHTTPRequestHandler): def do_GET(self): self.send_response(200) self.send_header('Content-type','multipart/x-mixed-replace; boundary=jpgboundary') self.end_headers() while True: try: # capture image from camera rc,img = capture.read() if not rc: continue # play with image to be displayed - example "scribbling" with line, rectangle with a circle in it, half an ellipse, and top part of some text # note that in Python many but not all the draw functins will also return the image # line(img...) is basic - img is changed; can also use img = line(img...), and another copy can be made with img2 = line(img...) (I think) cv2.line(img,(0,0),(511,511),(255,0,0),5) cv2.rectangle(img,(384,0),(510,128),(0,255,0),3) cv2.circle(img,(447,63), 63, (0,0,255), -1) cv2.ellipse(img,(256,256),(100,50),0,0,180,255,-1) cv2.putText(img,'OpenCV',(10,500), cv2.FONT_HERSHEY_SIMPLEX, 4,(255,255,255),2,cv2.LINE_AA) # done playing with image img_str = cv2.imencode('.jpg', img).tostring() # change image to jpeg format self.send_header('Content-type','image/jpeg') self.end_headers() self.wfile.write(img_str) self.wfile.write(b"\r\n--jpgboundary\r\n") # end of this part except KeyboardInterrupt: # end of the message - not sure how we ever get here, though print("KeyboardInterrpt in server loop - breaking the loop (server now hung?)") self.wfile.write(b"\r\n--jpgboundary--\r\n") break return class ThreadedHTTPServer(ThreadingMixIn, HTTPServer): """Handle requests in a separate thread.""" def main(): global capture capture = cv2.VideoCapture(0) # connect openCV to camera 0 # set desired camera properties capture.set(cv2.CAP_PROP_AUTO_EXPOSURE, 0.25) # 0.25 is turn OFF auto exposure (Logitech); 0.75 is ON time.sleep(.5) # wait for auto exposure change to be set capture.set(cv2.CAP_PROP_EXPOSURE, .01) # fairly dark - low exposure # a few other properties that can be set - not a complete list # capture.set(cv2.CAP_PROP_BRIGHTNESS, .4); #1 is bright 0 or-1 is dark .4 is fairly dark default Brightness 0.5019607843137255 # capture.set(cv2.CAP_PROP_CONTRAST, 1); # capture.set(cv2.CAP_PROP_FRAME_WIDTH, 320); # capture.set(cv2.CAP_PROP_FRAME_HEIGHT, 240); # capture.set(cv2.CAP_PROP_SATURATION,0.2); try: server = ThreadedHTTPServer(('localhost', 1181), CamHandler) print("server starting") server.serve_forever() except KeyboardInterrupt: # ctrl-c comes here but need another to end all. Probably should have terminated thread here, too. print("KeyboardInterrpt in server - ending server") capture.release() server.socket.close() if __name__ == '__main__': main()