diff --git a/build_deb.sh b/build_deb.sh index 931982f..c1961ed 100644 --- a/build_deb.sh +++ b/build_deb.sh @@ -9,8 +9,25 @@ fpm -s python --python-bin python3 --python-pip pip3 --python-package-name-prefi --after-install postinstall.sh \ --deb-generate-changes \ --deb-priority "optional" \ + --deb-systemd "complib.service" \ -d "python3-pip" \ - -v 0.0.2-8 -t deb setup.py + -d "nginx" \ + -d "libnginx-mod-rtmp" \ + -d "libsystemd-dev" \ + -d "python3-systemd" \ + -d "gstreamer1.0-tools" \ + -d "gstreamer1.0-plugins-bad" \ + -d "gstreamer1.0-plugins-base" \ + -d "gstreamer1.0-plugins-good" \ + -d "gstreamer1.0-omx-rpi" \ + -d "gstreamer1.0-omx-rpi-config" \ + -d "opencv-dev" \ + -d "opencv-libs" \ + -d "opencv-licenses" \ + -d "opencv-main" \ + -d "opencv-python" \ + -d "opencv-scripts" \ + -v 0.0.3-3 -t deb setup.py # --deb-changelog changelog \ # --deb-upstream-changelog changelog \ diff --git a/compLib/Vision.py b/compLib/Vision.py index 25f9113..f567e6a 100644 --- a/compLib/Vision.py +++ b/compLib/Vision.py @@ -6,6 +6,7 @@ from flask import Flask, Response RTMP_SERVER = os.getenv("RTMP_SERVER", "rtmp://localhost/live/stream") SERVE_VIDEO = os.getenv("SERVER_SRC", "/live") +BUILDING_DOCS = os.getenv("BUILDING_DOCS", "false") app = Flask(__name__) @@ -27,7 +28,12 @@ HTML = HTML.replace("{{ VIDEO_DST }}", SERVE_VIDEO) class __Streaming: """ - Private class for opencv stuff. + Class that handles rtmp streaming for opencv. + + DO NOT CREATE AN INSTANCE OF THIS CLASS YOURSELF! + + This is automatically done when importing this module. Use Vision.Streaming which is + an instance of this class! grab frames -> do your own processing -> publish frame -> view on http server """ @@ -40,7 +46,7 @@ class __Streaming: 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.__camera_stream = cv2.VideoCapture(0) self.__newest_frame = None self.__lock = threading.Lock() @@ -79,7 +85,7 @@ class __Streaming: buffer_frame = self.__newest_frame.copy() # encode frame for jpeg stream - (flag, encodedImage) = cv2.imencode(".jpg", buffer_frame) + (flag, encoded_image) = cv2.imencode(".jpg", buffer_frame) # if there was an error try again with the next frame if not flag: @@ -87,15 +93,17 @@ class __Streaming: # 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') + bytearray(encoded_image) + b'\r\n') -# instantiate private class __Streaming -Streaming = __Streaming() +Streaming = None +if BUILDING_DOCS == "false": + # instantiate private class __Streaming + Streaming = __Streaming() @app.route("/live") -def video_feed(): +def __video_feed(): """ Define route for serving jpeg stream. @@ -124,12 +132,13 @@ def __start_flask(): 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() +if BUILDING_DOCS == "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__': +if __name__ == '__main__' and BUILDING_DOCS == "false": while True: frame = Streaming.get_frame() criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 30, 0.001) @@ -140,5 +149,5 @@ if __name__ == '__main__': # find the chessboard corners ret, corners = cv2.findChessboardCorners(gray, (6, 9), None) - cv2.drawChessboardCorners(frame, (6, 9), corners, ret) + cv2.drawChessboardCorners(frame, (6, 9), corners, ret) Streaming.publish_frame(frame) diff --git a/compLib/VisionDaemon.py b/compLib/VisionDaemon.py new file mode 100644 index 0000000..c08aa1e --- /dev/null +++ b/compLib/VisionDaemon.py @@ -0,0 +1,20 @@ +import os + +import systemd.daemon +import LogstashLogging +from LogstashLogging import logstash_logger +import logging + +__run = """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://localhost/live/stream\"""" + + +if __name__ == '__main__': + try: + systemd.daemon.notify(systemd.daemon.Notification.READY) + except: + logstash_logger.warning("Warning, old systemd version detected") + systemd.daemon.notify('READY=1') + + logging.info("starting gstreamer background process") + os.system(__run) + logstash_logger.error("gstreamer stopped...") diff --git a/compLib/__init__.py b/compLib/__init__.py index 36693bb..29c6d95 100644 --- a/compLib/__init__.py +++ b/compLib/__init__.py @@ -1,4 +1,4 @@ -__version__ = "0.0.2" +__version__ = "0.0.3" import compLib.LogstashLogging import logging diff --git a/complib.service b/complib.service new file mode 100644 index 0000000..43e3f49 --- /dev/null +++ b/complib.service @@ -0,0 +1,11 @@ +[Unit] +Description=Monitoring service +[Service] +ExecStart=/usr/bin/python3 /usr/local/lib/python3.7/dist-packages/compLib/VisionDaemon.py +Environment="debug=False" +Restart=always +RestartSec=5 +Type=notify +[Install] +Alias=complib +WantedBy=default.target \ No newline at end of file diff --git a/docs/source/lib/Vision.rst b/docs/source/lib/Vision.rst new file mode 100644 index 0000000..8793ad4 --- /dev/null +++ b/docs/source/lib/Vision.rst @@ -0,0 +1,102 @@ +.. _lib_vision: + +Vision +===== + +This module provides an interface for grabbing an rtmp stream and using the images to do some processing in opencv. + +How do I use this module? + +1. Get frames from the raspberry pi camera +2. -- here comes your own processing -- +3. Publish the processed frames on an http server +4. You can view the http stream of your processed images in a web browser + +Opencv Stream +************* + +Because of the rtmp stream needing to buffer some frames and waiting for P-Frames, importing this module might take up +to 5 Seconds. + +.. autoclass:: compLib.Vision.__Streaming + :members: + +Examples +********* + +Using the Vision Module +--------------------- + +.. code-block:: python + + import cv2 + from compLib import Vision + + # get newest opencv frame from camera + frame = Vision.Streaming.get_frame() + + # do some processing with the frame..... + + # publish frame to streaming server + Vision.Streaming.publish_frame(frame) + +Connect the raspberry pi to your internet and view the stream at: "http://your_raspi_ip:9898/". This should display +your raspberry pi camera. Note: the stream will lag a little bit BUT the processing of the image will be done in +realtime. + +The output on the website should show whatever your raspberry pi cam records: + +.. image:: images/opencv_http_stream.png + :width: 680 + :alt: Processed frames from opencv + + +Chessboard Detection +------------------------------------------ + +In this example we process the captured stream of images and want to detect chessboards. Run this example and +point your raspberry pi camera to a chessboard and it should be detected. + +For testing you can point it at this image: + +.. image:: images/chessboard.jpg + :width: 680 + :alt: Chessboard for opencv processing + +.. code-block:: python + + import cv2 + from compLib import Vision + + # get newest opencv frame from camera + frame = Vision.Streaming.get_frame() + + criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 30, 0.001) + + # convert image to grayscale image + gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) + + # find the chessboard corners + ret, corners = cv2.findChessboardCorners(gray, (6, 9), None) + + # draw detected chessboard position onto the image + cv2.drawChessboardCorners(frame, (6, 9), corners, ret) + + # publish frame to streaming server + Vision.Streaming.publish_frame(frame) + +Connect the raspberry pi to your internet and view the stream at: "http://your_raspi_ip:9898/". + +The output image should look like this: + +.. image:: images/chessboard_detected.jpg + :width: 680 + :alt: Processed frames from opencv + +Here is a screenshot of the stream website while viewing the chessboard in this documentation. + +.. image:: images/opencv_processed.png + :width: 680 + :alt: Processed frames from opencv + + diff --git a/docs/source/lib/images/chessboard.jpg b/docs/source/lib/images/chessboard.jpg new file mode 100644 index 0000000..53ffa83 Binary files /dev/null and b/docs/source/lib/images/chessboard.jpg differ diff --git a/docs/source/lib/images/chessboard_detected.jpg b/docs/source/lib/images/chessboard_detected.jpg new file mode 100644 index 0000000..4f64fb6 Binary files /dev/null and b/docs/source/lib/images/chessboard_detected.jpg differ diff --git a/docs/source/lib/images/opencv_http_stream.png b/docs/source/lib/images/opencv_http_stream.png new file mode 100644 index 0000000..8fc6853 Binary files /dev/null and b/docs/source/lib/images/opencv_http_stream.png differ diff --git a/docs/source/lib/images/opencv_processed.png b/docs/source/lib/images/opencv_processed.png new file mode 100644 index 0000000..27b59c7 Binary files /dev/null and b/docs/source/lib/images/opencv_processed.png differ diff --git a/postinstall.sh b/postinstall.sh index 1bb1f0b..731ed4f 100644 --- a/postinstall.sh +++ b/postinstall.sh @@ -11,4 +11,28 @@ install_package() { install_package "smbus" install_package "requests" -install_package "python-logstash-async" \ No newline at end of file +install_package "python-logstash-async" + +echo "Setting up opencv4" +pkg-config --modversion opencv4 + +echo "Setting up nginx rtmp server" +sudo /etc/init.d/nginx start + +sudo tee /etc/nginx/nginx.conf > /dev/null <