diff --git a/README.md b/README.md index 5a9ed13..f7db811 100644 --- a/README.md +++ b/README.md @@ -20,3 +20,9 @@ pip install sphinx-rtd-theme [Inline documentation example](https://pythonhosted.org/an_example_pypi_project/sphinx.html#full-code-example) [reStructured Text](https://pythonhosted.org/an_example_pypi_project/sphinx.html#restructured-text-rest-resources) + +# Stream Video + +``` +sudo raspivid -t 0 -b 10000000 -w 1920 -h 1080 -fps 30 -n -o - | gst-launch-1.0 fdsrc ! video/x-h264,width=1280,height=720,framerate=30/1,noise-reduction=1,profile=high,stream-format=byte-stream ! h264parse ! queue ! flvmux streamable=true ! rtmpsink location=\"rtmp://10.1.1.68/live/stream\"``` +``` diff --git a/compLib/Vision.py b/compLib/Vision.py new file mode 100644 index 0000000..25f9113 --- /dev/null +++ b/compLib/Vision.py @@ -0,0 +1,144 @@ +import os +import threading + +import cv2 +from flask import Flask, Response + +RTMP_SERVER = os.getenv("RTMP_SERVER", "rtmp://localhost/live/stream") +SERVE_VIDEO = os.getenv("SERVER_SRC", "/live") + +app = Flask(__name__) + +HTML = """ + + + Opencv Output + + +

Opencv Output

+ + + +""" + +# it would be better to use jinja2 here, but I don't want to blow up the package dependencies... +HTML = HTML.replace("{{ VIDEO_DST }}", SERVE_VIDEO) + + +class __Streaming: + """ + Private class for opencv stuff. + + grab frames -> do your own processing -> publish frame -> view on http server + """ + + def __init__(self): + """ + Create instance of __Streaming class + + This is done implicitly when importing the vision module and will only fail if you would + create an object of this class. (There can (SHOULD!) only be one VideCapture) + """ + self.__camera_stream = cv2.VideoCapture(RTMP_SERVER) + #self.__camera_stream = cv2.VideoCapture(0) + self.__newest_frame = None + self.__lock = threading.Lock() + + def get_frame(self): + """ + Grab the newest frame from the rtmp stream. + + :return: An opencv frame + """ + ret, img16 = self.__camera_stream.read() + return img16 + + def publish_frame(self, image): + """ + Publish an opencv frame to the http webserver. + + :param image: Opencv frame that will be published + :return: None + """ + with self.__lock: + self.__newest_frame = image.copy() + + def _newest_frame_generator(self): + """ + Private generator which is called directly from flask server. + + :return: Yields image/jpeg encoded frames published from publish_frame function. + """ + while True: + # use a buffer frame to copy the newest frame with lock and then freeing it immediately + buffer_frame = None + with self.__lock: + if self.__newest_frame is None: + continue + + buffer_frame = self.__newest_frame.copy() + + # encode frame for jpeg stream + (flag, encodedImage) = cv2.imencode(".jpg", buffer_frame) + + # if there was an error try again with the next frame + if not flag: + continue + + # else yield encoded frame with mimetype image/jpeg + yield (b'--frame\r\n' b'Content-Type: image/jpeg\r\n\r\n' + + bytearray(encodedImage) + b'\r\n') + + +# instantiate private class __Streaming +Streaming = __Streaming() + + +@app.route("/live") +def video_feed(): + """ + Define route for serving jpeg stream. + + :return: Return the response generated along with the specific media. + """ + return Response(Streaming._newest_frame_generator(), + mimetype="multipart/x-mixed-replace; boundary=frame") + + +@app.route("/") +def __index(): + """ + Define route for serving a static http site to view the stream. + + :return: Static html page + """ + return HTML + + +def __start_flask(): + """ + Function for running flask server in a thread. + + :return: + """ + app.run(host="0.0.0.0", port=9898, debug=True, threaded=True, use_reloader=False) + + +# start flask service in the background +__webserver_thread = threading.Thread(target=__start_flask) +__webserver_thread.start() + +# for debugging and testing start processing frames and detecting a 6 by 9 calibration chessboard +if __name__ == '__main__': + while True: + frame = Streaming.get_frame() + criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 30, 0.001) + + # processing + gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) + + # find the chessboard corners + ret, corners = cv2.findChessboardCorners(gray, (6, 9), None) + + cv2.drawChessboardCorners(frame, (6, 9), corners, ret) + Streaming.publish_frame(frame)