-
-
Save n3wtron/4624820 to your computer and use it in GitHub Desktop.
#!/usr/bin/python | |
''' | |
Author: Igor Maculan - [email protected] | |
A Simple mjpg stream http server | |
''' | |
import cv2 | |
import Image | |
import threading | |
from BaseHTTPServer import BaseHTTPRequestHandler,HTTPServer | |
from SocketServer import ThreadingMixIn | |
import StringIO | |
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 = StringIO.StringIO() | |
jpg.save(tmpFile,'JPEG') | |
self.wfile.write("--jpgboundary") | |
self.send_header('Content-type','image/jpeg') | |
self.send_header('Content-length',str(tmpFile.len)) | |
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>') | |
self.wfile.write('<img src="http://127.0.0.1:8080/cam.mjpg"/>') | |
self.wfile.write('</body></html>') | |
return | |
class ThreadedHTTPServer(ThreadingMixIn, HTTPServer): | |
"""Handle requests in a separate thread.""" | |
def main(): | |
global capture | |
capture = cv2.VideoCapture(0) | |
capture.set(cv2.cv.CV_CAP_PROP_FRAME_WIDTH, 320); | |
capture.set(cv2.cv.CV_CAP_PROP_FRAME_HEIGHT, 240); | |
capture.set(cv2.cv.CV_CAP_PROP_SATURATION,0.2); | |
global img | |
try: | |
server = ThreadedHTTPServer(('localhost', 8080), CamHandler) | |
print "server started" | |
server.serve_forever() | |
except KeyboardInterrupt: | |
capture.release() | |
server.socket.close() | |
if __name__ == '__main__': | |
main() | |
Hi at all!
First of all I apologize for not responding to you, but the gist have no notifications.
To prevent this I've just created this repo https://github.com/n3wtron/simple_mjpeg_streamer_http_server , so please open issues, fork the project and open merge requests, so that we can improve the script together.
I'll create a branch for python3 version.
Thank you!
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)[1].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()
Great stuff. BTW, if you're viewing this on a pi (i.e. localhost) at the moment this appears not to work on raspberri pi chromium (broken pipe after a few renders). But it works on luakit browser
Not able to stream video and facing this error. I have made some changes in above code but still not working with Python 3.6.x
Please help me know whats going on wrong
(face) F:\FR\IGL\flask server>python simple_mjpeg_streamer_http_server.py
server started
127.0.0.1 - - [23/Apr/2019 00:20:34] "GET /cam.mjpg HTTP/1.1" 200 -
----------------------------------------
Exception happened during processing of request from ('127.0.0.1', 60377)
Traceback (most recent call last):
File "C:\Users\Ajinkya\Miniconda3\envs\face\lib\site-packages\PIL\ImageFile.py", line 485, in _save
fh = fp.fileno()
io.UnsupportedOperation: fileno
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "C:\Users\Ajinkya\Miniconda3\envs\face\lib\socketserver.py", line 651, in process_request_thread
self.finish_request(request, client_address)
File "C:\Users\Ajinkya\Miniconda3\envs\face\lib\socketserver.py", line 361, in finish_request
self.RequestHandlerClass(request, client_address, self)
File "C:\Users\Ajinkya\Miniconda3\envs\face\lib\socketserver.py", line 721, in __init__
self.handle()
File "C:\Users\Ajinkya\Miniconda3\envs\face\lib\http\server.py", line 418, in handle
self.handle_one_request()
File "C:\Users\Ajinkya\Miniconda3\envs\face\lib\http\server.py", line 406, in handle_one_request
method()
File "simple_mjpeg_streamer_http_server.py", line 24, in do_GET
jpg.save(tmpFile,'JPEG')
File "C:\Users\Ajinkya\Miniconda3\envs\face\lib\site-packages\PIL\Image.py", line 1969, in save
save_handler(self, fp, filename)
File "C:\Users\Ajinkya\Miniconda3\envs\face\lib\site-packages\PIL\JpegImagePlugin.py", line 761, in _save
ImageFile._save(im, fp, [("jpeg", (0, 0)+im.size, 0, rawmode)], bufsize)
File "C:\Users\Ajinkya\Miniconda3\envs\face\lib\site-packages\PIL\ImageFile.py", line 500, in _save
fp.write(d)
TypeError: string argument expected, got 'bytes'
This is the code
import cv2
from PIL import Image
import threading
from http.server import BaseHTTPRequestHandler,HTTPServer
from socketserver import ThreadingMixIn
from io import StringIO
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 = StringIO() #out_s = StringIO()
jpg.save(tmpFile,'JPEG')
self.wfile.write("--jpgboundary")
self.send_header('Content-type','image/jpeg')
self.send_header('Content-length',str(tmpFile.len))
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>')
self.wfile.write('<img src="http://127.0.0.1:8081/cam.mjpg"/>')
self.wfile.write('</body></html>')
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, 640);
capture.set(cv2.CAP_PROP_FRAME_HEIGHT, 480);
capture.set(cv2.CAP_PROP_SATURATION,0.2);
global img
try:
server = ThreadedHTTPServer(('localhost', 8081), CamHandler)
print("server started")
server.serve_forever()
except KeyboardInterrupt:
capture.release()
server.socket.close()
if __name__ == '__main__':
main()
This one works
import cv2
from http.server import BaseHTTPRequestHandler, HTTPServer
from socketserver import ThreadingMixIn
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
img_str = cv2.imencode('.jpg', img)[1].tostring()
self.send_header('Content-type', 'image/jpeg')
self.send_header('Content-length', len(img_str))
self.end_headers()
self.wfile.write(img_str)
self.wfile.write(b"\r\n--jpgboundary\r\n")
except KeyboardInterrupt:
self.wfile.write(b"\r\n--jpgboundary--\r\n")
break
except BrokenPipeError:
continue
return
if self.path.endswith('.html'):
self.send_response(200)
self.send_header('Content-type', 'text/html')
self.end_headers()
self.wfile.write(b'<html><head></head><body>')
self.wfile.write(b'<img src="http://127.0.0.1:8081/cam.mjpg"/>')
self.wfile.write(b'</body></html>')
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, 1280)
capture.set(cv2.CAP_PROP_FRAME_HEIGHT, 720)
global img
try:
server = ThreadedHTTPServer(('localhost', 8081), CamHandler)
print("server started at http://127.0.0.1:8081/cam.html")
server.serve_forever()
except KeyboardInterrupt:
capture.release()
server.socket.close()
if __name__ == '__main__':
main()
Nice man!! exactly I was looking for on web. Saved my time 👍 )
I am doing this for rtsp stream.
And this way RTSP gets embedded in a web page.
Thanks
Hi,
Please can you tell me how you did it with an RTSP IP camera onto a web page? I've been researching for weeks to get this done!!
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.
Hi saif-data, Do you have any idea how to remove those broken pipe error messages? Try/Exception not work..
import cv2
import numpy as np
import time
from PIL import Image
from io import BytesIO
import threading
from multiprocessing import Process
import config_ip_vid
try:
from http.server import BaseHTTPRequestHandler,HTTPServer
from socketserver import ThreadingMixIn
except ImportError:
from BaseHTTPServer import BaseHTTPRequestHandler,HTTPServer
from SocketServer import ThreadingMixIn
debug = False
gracefulexit=False
TFrame = None
videoserver = None
port = config_ip_vid.port_number
vid = config_ip_vid.vid_number
for i,j in zip(port,vid):
print("all file and ports******",i,j)
#print('port number -------',port)
#print('video number -------',vid)
#importing all the ports and video from config file
port_8081 = config_ip_vid.port_number1
vid1 = str(config_ip_vid.vid_number1)
print("@@@@@$$$$$$$",str(config_ip_vid.vid_number1))
port_8082 = config_ip_vid.port_number2
vid2 = config_ip_vid.vid_number2
port_8083 = config_ip_vid.port_number3
vid3 = config_ip_vid.vid_number3
port_8084 = config_ip_vid.port_number4
vid4 = config_ip_vid.vid_number4
port_8085 = config_ip_vid.port_number5
vid5 = config_ip_vid.vid_number5
class CamHandler(BaseHTTPRequestHandler):
def do_GET(self):
global gracefulexit
if self.path.endswith('.mjpg'):
self.send_response(200)
self.send_header('Content-Type','multipart/x-mixed-replace; boundary=--jpgboundary')
self.send_header('Cache-Control', 'no-store, no-cache, must-revalidate, pre-check=0, post-check=0, max-age=0')
self.end_headers()
dummy=np.zeros((100,100,3), np.uint8)
if debug:
print("vidserver: got new connection")
while True:
if gracefulexit:
break
try:
img=TFrame
if img is None:
if debug:
print("vidserver: sending dummy")
img = dummy
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(len(tmpFile.getvalue())))
self.end_headers()
jpg.save(self.wfile,'JPEG')
time.sleep(0.05)
except KeyboardInterrupt:
print("vidserver: got interrupt")
break
except Exception as e:
print("vidserver error:",e)
break
print("Exiting cam thread")
return
elif 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):
daemon_threads=True
pass
"""Handle requests in a separate thread."""
def startVideoServer(video_transmit_ip,video_transmit_port):
print("starting video server on %s" % video_transmit_ip)
global gracefulexit
try:
videoserver = ThreadedHTTPServer((video_transmit_ip, video_transmit_port), CamHandler)
# videoserver.anprObj=self
videoserver.serve_forever()
except KeyboardInterrupt:
pass
print("Exiting videoserver thread")
gracefulexit=True
videoserver.shutdown()
# cap = cv2.VideoCapture(vid)
# print("############$$$$$$$$$$$$$$$$$ Cap in process",cap)
# if (cap.isOpened() == False):
# print("Error opening video stream or file")
#
# while (cap.isOpened()):
# ret, TFrame = cap.read()
# print("Tframe---=======",TFrame)
# if ret != True: break
# cap.release()
# if videoserver is not None:
# print("Shutting down video server")
# videoserver.shutdown()
# videoserver.server_close()
# videoserver.socket.close()
# cv2.destroyAllWindows()
print("port 1%%%%%%",port[0])
print("port 2%%%%%%",port[1])
print("video 1%%%%%%",vid[0])
print("video 2%%%%%%",vid[1])
#for i,j in zip(port,vid):
video_transmit_ip = "localhost"
video_transmit_port = int(port_8081)#int(i)
video_transmit_port2 = int(port_8082)
video_transmit_port3 = int(port_8083)
video_transmit_port4 = int(port_8084)
video_transmit_port5 = int(port_8085)
#print("==========",i)
#print("==========",j)
vthread=threading.Thread(target=startVideoServer,args=(video_transmit_ip,video_transmit_port))
vthread.start()
vthread2=threading.Thread(target=startVideoServer,args=(video_transmit_ip,video_transmit_port2))
vthread2.start()
cap = cv2.VideoCapture(vid1)
cap2 = cv2.VideoCapture(vid2)
#print("############$$$$$$$$$$$$$$$$$ Cap in process",cap)
if (cap.isOpened() == False):
print("Error opening video stream or file")
if (cap2.isOpened() == False):
print("Error opening video stream or file")
while (cap.isOpened()|cap2.isOpened()):
ret, TFrame = cap.read()
ret1, img1 = cap2.read()
#print("Tframe---=======",TFrame)
if ret != True: break
cap.release()
cap2.release()
if videoserver is not None:
print("Shutting down video server")
videoserver.shutdown()
videoserver.server_close()
videoserver.socket.close()
cv2.destroyAllWindows()
**I want add multiple video input source to the web but it only displays one of the starting can any of you guys can help me through it i want to play multiple videos the above code is attached for the reference
**
Awesome code, really appreciate the project. To make this work in Edge change line 30 to:
self.wfile.write("--jpgboundary\r\n")
The CR,LF makes it work (for me in Edge version 42.17134.1.0). Chrome works either way. Also, for those who want to access from a different computer change line 44 to:
self.wfile.write('<img src="cam.mjpg"/>')
and change line 60 to:
server = ThreadedHTTPServer(('', 8080), CamHandler)
Then look up your IP address, in your browser enter address:
http://IP_ADDRESS:8080/cam.html
Many thanks n3wtron!