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)