├── .env-example ├── pyproject.toml ├── stream.bash ├── open_rtsp.py ├── requirements.txt ├── setup.py ├── README.md ├── .gitignore ├── stream-raw.py └── stream.py /.env-example: -------------------------------------------------------------------------------- 1 | ROBOFLOW_API_KEY=your-roboflow-private-api-key 2 | PROJECT_NAME=your-project-name 3 | PROJECT_VERSION=1 4 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = [ 3 | "setuptools>=57", 4 | "wheel" 5 | ] 6 | build-backend = "setuptools.build_meta" -------------------------------------------------------------------------------- /stream.bash: -------------------------------------------------------------------------------- 1 | python3 stream.py \ 2 | --device_id 0 \ 3 | --fps 30 \ 4 | --image_width 640 \ 5 | --image_height 480 \ 6 | --stream_uri /video_stream 7 | -------------------------------------------------------------------------------- /open_rtsp.py: -------------------------------------------------------------------------------- 1 | import cv2 2 | 3 | cap = cv2.VideoCapture("rtsp://172.27.23.235:8554/video_stream") 4 | while True: 5 | 6 | ret, frame = cap.read() 7 | if ret: 8 | cv2.imshow("RTSP View", frame) 9 | cv2.waitKey(1) 10 | else: 11 | print("unable to open camera") 12 | break 13 | cap.release() 14 | cv2.destroyAllWindows() 15 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | certifi==2021.5.30 2 | chardet==4.0.0 3 | cycler==0.10.0 4 | idna==2.10 5 | kiwisolver>=1.3.1 6 | matplotlib 7 | numpy>=1.18.5 8 | opencv-python>=4.1.2 9 | Pillow>=7.1.2 10 | pyparsing==2.4.7 11 | python-dateutil 12 | python-dotenv 13 | requests 14 | six 15 | urllib3>=1.26.6 16 | wget 17 | tqdm>=4.41.0 18 | PyYAML>=5.3.1 19 | wget 20 | requests_toolbelt 21 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import setuptools 2 | from setuptools import find_packages 3 | import re 4 | 5 | with open("./roboflow/__init__.py", "r") as f: 6 | content = f.read() 7 | # from https://www.py4u.net/discuss/139845 8 | version = re.search(r'__version__\s*=\s*[\'"]([^\'"]*)[\'"]', content).group(1) 9 | 10 | 11 | with open("README.md", "r") as fh: 12 | long_description = fh.read() 13 | 14 | setuptools.setup( 15 | name="roboflow", 16 | version=version, 17 | author="Roboflow", 18 | author_email="jacob@roboflow.com", 19 | description="python client for the Roboflow application", 20 | long_description=long_description, 21 | long_description_content_type="text/markdown", 22 | url="https://github.com/roboflow-ai/roboflow-python", 23 | install_requires=[ 24 | "certifi==2021.5.30", 25 | "chardet==4.0.0", 26 | "cycler==0.10.0", 27 | "glob2", 28 | "idna==2.10", 29 | "kiwisolver>=1.3.1", 30 | "matplotlib", 31 | "numpy>=1.18.5", 32 | "opencv-python-headless>=4.5.1.48", 33 | "Pillow>=7.1.2", 34 | "pyparsing==2.4.7", 35 | "python-dateutil", 36 | "python-dotenv", 37 | "requests", 38 | "requests_toolbelt", 39 | "six", 40 | "urllib3==1.26.6", 41 | "tqdm>=4.41.0", 42 | "PyYAML>=5.3.1", 43 | "wget", 44 | ], 45 | packages=find_packages(exclude=("tests",)), 46 | extras_require={ 47 | "dev": ["flake8", "black==22.3.0", "isort", "responses", "twine", "wheel"], 48 | }, 49 | classifiers=[ 50 | "Programming Language :: Python :: 3", 51 | "License :: OSI Approved :: MIT License", 52 | "Operating System :: OS Independent", 53 | ], 54 | python_requires=">=3.6", 55 | ) 56 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Roboflow RTSP Streaming Application 2 | 3 | --- 4 | ![roboflow logo](https://i.imgur.com/lXCoVt5.png) 5 | 6 | [Website](https://docs.roboflow.com/python) • [Docs](https://docs.roboflow.com/python) • [Blog](https://blog.roboflow.com) 7 | • [Twitter](https://twitter.com/roboflow) • [Linkedin](https://www.linkedin.com/company/roboflow-ai) 8 | • [Universe](https://universe.roboflow.com) 9 | 10 | **Roboflow** makes managing, preprocessing, augmenting, and versioning datasets for computer vision seamless. This is 11 | the official Roboflow python package that interfaces with the [Roboflow API](https://docs.roboflow.com). Key features of 12 | Roboflow: 13 | 14 | ## Installating Roboflow 15 | 16 | To install this package, please use `Python 3.6` or higher. We provide three different ways to install the Roboflow 17 | package to use within your own projects. 18 | 19 | Install from PyPi (Recommended): 20 | 21 | ```bash 22 | pip install roboflow 23 | ``` 24 | 25 | Install Roboflow from Source: 26 | 27 | ```bash 28 | git clone https://github.com/roboflow-ai/roboflow-python.git 29 | cd roboflow-python 30 | python3 -m venv env 31 | source env/bin/activate 32 | pip3 install -r requirements.txt 33 | ``` 34 | 35 | # RTSP streaming using GStreamer 36 | 37 | https://user-images.githubusercontent.com/113200203/210094301-7906bf10-205f-4980-85b2-111752dbdbdf.mp4 38 | 39 | Python implementation to stream camera feed from OpenCV videoCapture via RTSP server using GStreamer 1.0. 40 | 41 | ## Installating Gstreamer and RTSP Application 42 | 43 | This implementation has been developed and tested on Ubuntu 16.04 and 18.04. So the installation steps are specific to debian based linux distros. 44 | 45 | ### Step-1 Install GStreamer-1.0 and related plugins 46 | 47 | sudo apt-get install libgstreamer1.0-0 gstreamer1.0-plugins-base gstreamer1.0-plugins-good gstreamer1.0-plugins-bad gstreamer1.0-plugins-ugly gstreamer1.0-libav gstreamer1.0-doc gstreamer1.0-tools gstreamer1.0-x gstreamer1.0-alsa gstreamer1.0-gl gstreamer1.0-gtk3 gstreamer1.0-qt5 gstreamer1.0-pulseaudio 48 | 49 | ### Step-2 Install RTSP server 50 | 51 | sudo apt-get install libglib2.0-dev libgstrtspserver-1.0-dev gstreamer1.0-rtsp 52 | 53 | ### Step-3 Install OpenCV2 54 | 55 | pip install opencv-python 56 | 57 | ### Requirement 58 | - Python 3.6+ 59 | - Linux Based OS (Ubuntu 20.04 - Tested) 60 | 61 | ### Usage 62 | > Run stream.py with required arguments to start the rtsp server 63 | 64 | ##### Sample 65 | 66 | python stream.py --device_id "rtsp://wowzaec2demo.streamlock.net/vod/mp4:BigBuckBunny_115k.mp4" --fps 24 --image_width 240 --image_height 160 --port 8554 67 | 68 | ### Visualization 69 | 70 | You can view the video feed on `rtsp://server-ip-address:8554/stream_uri` 71 | 72 | e.g: `rtsp://192.168.1.12:8554/video_stream` 73 | 74 | You can either use any video player which supports rtsp streaming like VLC player or you can use the `open-rtsp.py` script to view the video feed. 75 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # env specific 2 | config.json 3 | .env 4 | # Byte-compiled / optimized / DLL files 5 | __pycache__/ 6 | *.py[cod] 7 | *$py.class 8 | .idea 9 | # C extensions 10 | *.so 11 | 12 | # Distribution / packaging 13 | .Python 14 | build/ 15 | develop-eggs/ 16 | dist/ 17 | downloads/ 18 | eggs/ 19 | .eggs/ 20 | lib/ 21 | lib64/ 22 | parts/ 23 | sdist/ 24 | var/ 25 | wheels/ 26 | share/python-wheels/ 27 | *.egg-info/ 28 | .installed.cfg 29 | *.egg 30 | MANIFEST 31 | 32 | # PyInstaller 33 | # Usually these files are written by a python script from a template 34 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 35 | *.manifest 36 | *.spec 37 | 38 | # Installer logs 39 | pip-log.txt 40 | pip-delete-this-directory.txt 41 | 42 | # Unit test / coverage reports 43 | htmlcov/ 44 | .tox/ 45 | .nox/ 46 | .coverage 47 | .coverage.* 48 | .cache 49 | nosetests.xml 50 | coverage.xml 51 | *.cover 52 | *.py,cover 53 | .hypothesis/ 54 | .pytest_cache/ 55 | cover/ 56 | 57 | # Translations 58 | *.mo 59 | *.pot 60 | 61 | # Django stuff: 62 | *.log 63 | local_settings.py 64 | db.sqlite3 65 | db.sqlite3-journal 66 | 67 | # Flask stuff: 68 | instance/ 69 | .webassets-cache 70 | 71 | # Scrapy stuff: 72 | .scrapy 73 | 74 | # Sphinx documentation 75 | docs/_build/ 76 | 77 | # PyBuilder 78 | .pybuilder/ 79 | target/ 80 | 81 | # Jupyter Notebook 82 | .ipynb_checkpoints 83 | 84 | # IPython 85 | profile_default/ 86 | ipython_config.py 87 | 88 | # pyenv 89 | # For a library or package, you might want to ignore these files since the code is 90 | # intended to run in multiple environments; otherwise, check them in: 91 | # .python-version 92 | 93 | # pipenv 94 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 95 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 96 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 97 | # install all needed dependencies. 98 | #Pipfile.lock 99 | 100 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 101 | __pypackages__/ 102 | 103 | # Celery stuff 104 | celerybeat-schedule 105 | celerybeat.pid 106 | 107 | # SageMath parsed files 108 | *.sage.py 109 | 110 | # Environments 111 | .env 112 | .venv 113 | env/ 114 | venv/ 115 | ENV/ 116 | env.bak/ 117 | venv.bak/ 118 | 119 | # Spyder project settings 120 | .spyderproject 121 | .spyproject 122 | 123 | # Rope project settings 124 | .ropeproject 125 | 126 | # mkdocs documentation 127 | /site 128 | 129 | # mypy 130 | .mypy_cache/ 131 | .dmypy.json 132 | dmypy.json 133 | 134 | # Pyre type checker 135 | .pyre/ 136 | 137 | # pytype static type analyzer 138 | .pytype/ 139 | 140 | # Cython debug symbols 141 | cython_debug/ 142 | 143 | #config stuff 144 | tests/config.json 145 | 146 | #dataset download stuff 147 | 148 | test/ 149 | train/ 150 | valid/ 151 | data.yaml 152 | README.roboflow.txt 153 | *.zip 154 | .DS_Store 155 | -------------------------------------------------------------------------------- /stream-raw.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | """ 4 | Created on Mon Jan 20 02:07:13 2019 5 | 6 | @author: prabhakar 7 | """ 8 | # import necessary argumnets 9 | import gi 10 | import cv2 11 | import argparse 12 | 13 | # import required library like Gstreamer and GstreamerRtspServer 14 | gi.require_version('Gst', '1.0') 15 | gi.require_version('GstRtspServer', '1.0') 16 | from gi.repository import Gst, GstRtspServer, GObject, GLib 17 | from roboflow import Roboflow 18 | 19 | # Sensor Factory class which inherits the GstRtspServer base class and add 20 | # properties to it. 21 | class SensorFactory(GstRtspServer.RTSPMediaFactory): 22 | def __init__(self, **properties): 23 | super(SensorFactory, self).__init__(**properties) 24 | self.cap = cv2.VideoCapture("rtsp://wowzaec2demo.streamlock.net/vod/mp4:BigBuckBunny_115k.mp4") 25 | self.number_frames = 0 26 | self.fps = opt.fps 27 | self.duration = 1 / self.fps * Gst.SECOND # duration of a frame in nanoseconds 28 | self.launch_string = 'appsrc name=source is-live=true block=true format=GST_FORMAT_TIME ' \ 29 | 'caps=video/x-raw,format=BGR,width={},height={},framerate={}/1 ' \ 30 | '! videoconvert ! video/x-raw,format=I420 ' \ 31 | '! x264enc speed-preset=ultrafast tune=zerolatency ' \ 32 | '! rtph264pay config-interval=1 name=pay0 pt=96' \ 33 | .format(opt.image_width, opt.image_height, self.fps) 34 | 35 | # method to capture the video feed from the camera and push it to the 36 | # streaming buffer. 37 | def on_need_data(self, src, length): 38 | if self.cap.isOpened(): 39 | ret, frame = self.cap.read() 40 | if ret: 41 | # It is better to change the resolution of the camera 42 | # instead of changing the image shape as it affects the image quality. 43 | frame = cv2.resize(frame, (opt.image_width, opt.image_height), \ 44 | interpolation = cv2.INTER_LINEAR) 45 | 46 | data = frame.tostring() 47 | buf = Gst.Buffer.new_allocate(None, len(data), None) 48 | buf.fill(0, data) 49 | buf.duration = self.duration 50 | timestamp = self.number_frames * self.duration 51 | buf.pts = buf.dts = int(timestamp) 52 | buf.offset = timestamp 53 | self.number_frames += 1 54 | retval = src.emit('push-buffer', buf) 55 | print('pushed buffer, frame {}, duration {} ns, durations {} s'.format(self.number_frames, 56 | self.duration, 57 | self.duration / Gst.SECOND)) 58 | 59 | if retval != Gst.FlowReturn.OK: 60 | print(retval) 61 | 62 | # attach the launch string to the override method 63 | def do_create_element(self, url): 64 | return Gst.parse_launch(self.launch_string) 65 | 66 | # attaching the source element to the rtsp media 67 | def do_configure(self, rtsp_media): 68 | self.number_frames = 0 69 | appsrc = rtsp_media.get_element().get_child_by_name('source') 70 | appsrc.connect('need-data', self.on_need_data) 71 | 72 | # Rtsp server implementation where we attach the factory sensor with the stream uri 73 | class GstServer(GstRtspServer.RTSPServer): 74 | def __init__(self, **properties): 75 | super(GstServer, self).__init__(**properties) 76 | self.factory = SensorFactory() 77 | self.factory.set_shared(True) 78 | self.set_service(str(opt.port)) 79 | self.get_mount_points().add_factory(opt.stream_uri, self.factory) 80 | self.attach(None) 81 | 82 | # getting the required information from the user 83 | parser = argparse.ArgumentParser() 84 | parser.add_argument("--device_id", required=True, help="device id for the \ 85 | video device or video file location") 86 | parser.add_argument("--fps", required=True, help="fps of the camera", type = int) 87 | parser.add_argument("--image_width", required=True, help="video frame width", type = int) 88 | parser.add_argument("--image_height", required=True, help="video frame height", type = int) 89 | parser.add_argument("--port", default=8554, help="port to stream video", type = int) 90 | parser.add_argument("--stream_uri", default = "/video_stream", help="rtsp video stream uri") 91 | opt = parser.parse_args() 92 | 93 | try: 94 | opt.device_id = int(opt.device_id) 95 | except ValueError: 96 | pass 97 | 98 | # initializing the threads and running the stream on loop. 99 | GObject.threads_init() 100 | Gst.init(None) 101 | server = GstServer() 102 | loop = GLib.MainLoop() 103 | loop.run() -------------------------------------------------------------------------------- /stream.py: -------------------------------------------------------------------------------- 1 | # import necessary argumnets 2 | import gi 3 | import cv2 4 | import argparse 5 | 6 | # import required library like Gstreamer and GstreamerRtspServer 7 | gi.require_version('Gst', '1.0') 8 | gi.require_version('GstRtspServer', '1.0') 9 | from gi.repository import Gst, GstRtspServer, GObject, GLib 10 | from roboflow import Roboflow 11 | 12 | # Roboflow Authentication 13 | # obtaining your API key: https://docs.roboflow.com/rest-api#obtaining-your-api-key 14 | rf = Roboflow(api_key="API") 15 | workspace = rf.workspace() 16 | 17 | # # Gstreamer variables 18 | # device_id = "rtsp://wowzaec2demo.streamlock.net/vod/mp4:BigBuckBunny_115k.mp4" 19 | # fps = 24 20 | # image_width = 240 21 | # image_height = 160 22 | # port = 8554 23 | stream_uri = "/video_stream" 24 | 25 | # Access output RTSP via VLC or other application 26 | # Example RTSP output: rtsp://172.27.23.235:8554/video_stream 27 | 28 | # getting the required information from the user # UNCOMMENT FOR ARGPARSER 29 | parser = argparse.ArgumentParser() 30 | parser.add_argument("--device_id", default="rtsp://wowzaec2demo.streamlock.net/vod/mp4:BigBuckBunny_115k.mp4", help="device id for the \ 31 | video device or video file location") 32 | parser.add_argument("--fps", default=24, help="fps of the camera", type = int) 33 | parser.add_argument("--image_width", default=240, help="video frame width", type = int) 34 | parser.add_argument("--image_height", default=160, help="video frame height", type = int) 35 | parser.add_argument("--port", default=8554, help="port to stream video", type = int) 36 | # parser.add_argument("--stream_uri", default = "/video_stream", help="rtsp video stream uri") 37 | opt = parser.parse_args() 38 | 39 | try: 40 | device_id = int(opt.device_id) 41 | except ValueError: 42 | pass 43 | 44 | 45 | # Sensor Factory class which inherits the GstRtspServer base class and add 46 | # properties to it. 47 | class SensorFactory(GstRtspServer.RTSPMediaFactory): 48 | def __init__(self, **properties): 49 | super(SensorFactory, self).__init__(**properties) 50 | self.cap = cv2.VideoCapture("rtsp://wowzaec2demo.streamlock.net/vod/mp4:BigBuckBunny_115k.mp4") 51 | self.number_frames = 0 52 | self.fps = opt.fps 53 | self.duration = 1 / self.fps * Gst.SECOND # duration of a frame in nanoseconds 54 | self.launch_string = 'appsrc name=source is-live=true block=true format=GST_FORMAT_TIME ' \ 55 | 'caps=video/x-raw,format=BGR,width={},height={},framerate={}/1 ' \ 56 | '! videoconvert ! video/x-raw,format=I420 ' \ 57 | '! x264enc speed-preset=ultrafast tune=zerolatency ' \ 58 | '! rtph264pay config-interval=1 name=pay0 pt=96' \ 59 | .format(opt.image_width, opt.image_height, self.fps) 60 | 61 | # method to capture the video feed from the camera and push it to the 62 | # streaming buffer. 63 | def on_need_data(self, src, length): 64 | if self.cap.isOpened(): 65 | ret, frame = self.cap.read() 66 | if ret: 67 | # It is better to change the resolution of the camera 68 | # instead of changing the image shape as it affects the image quality. 69 | frame = cv2.resize(frame, (opt.image_width, opt.image_height), \ 70 | interpolation = cv2.INTER_LINEAR) 71 | 72 | data = frame.tostring() 73 | buf = Gst.Buffer.new_allocate(None, len(data), None) 74 | buf.fill(0, data) 75 | buf.duration = self.duration 76 | timestamp = self.number_frames * self.duration 77 | buf.pts = buf.dts = int(timestamp) 78 | buf.offset = timestamp 79 | self.number_frames += 1 80 | frame_counter = self.number_frames 81 | retval = src.emit('push-buffer', buf) 82 | print('pushed buffer, frame {}, duration {} ns, durations {} s'.format(self.number_frames, 83 | self.duration, 84 | self.duration / Gst.SECOND)) 85 | 86 | if retval != Gst.FlowReturn.OK: 87 | print(retval) 88 | 89 | if frame_counter % 60 == 0: 90 | 91 | raw_data_location = frame 92 | raw_data_extension = ".jpg" 93 | 94 | # replace * with your model version number for inference 95 | inference_endpoint = ["obs-3", 16] 96 | upload_destination = "obs-3" 97 | 98 | conditionals = { 99 | "required_objects_count" : 0, 100 | "required_class_count": 0, 101 | "target_classes": [], 102 | "minimum_size_requirement" : float('-inf'), 103 | "maximum_size_requirement" : float('inf'), 104 | "confidence_interval" : [10,90], 105 | } 106 | 107 | workspace.active_learning(raw_data_location=raw_data_location, 108 | raw_data_extension=raw_data_extension, 109 | inference_endpoint=inference_endpoint, 110 | upload_destination=upload_destination, 111 | conditionals=conditionals,use_localhost=False) 112 | 113 | # attach the launch string to the override method 114 | def do_create_element(self, url): 115 | return Gst.parse_launch(self.launch_string) 116 | 117 | # attaching the source element to the rtsp media 118 | def do_configure(self, rtsp_media): 119 | self.number_frames = 0 120 | appsrc = rtsp_media.get_element().get_child_by_name('source') 121 | appsrc.connect('need-data', self.on_need_data) 122 | 123 | # Rtsp server implementation where we attach the factory sensor with the stream uri 124 | class GstServer(GstRtspServer.RTSPServer): 125 | def __init__(self, **properties): 126 | super(GstServer, self).__init__(**properties) 127 | self.factory = SensorFactory() 128 | self.factory.set_shared(True) 129 | self.set_service(str(opt.port)) 130 | self.get_mount_points().add_factory(stream_uri, self.factory) 131 | self.attach(None) 132 | 133 | # initializing the threads and running the stream on loop. 134 | GObject.threads_init() 135 | Gst.init(None) 136 | server = GstServer() 137 | loop = GLib.MainLoop() 138 | loop.run() --------------------------------------------------------------------------------