├── LICENSE ├── launch └── rtsp_camera.launch ├── .gitignore ├── package.xml ├── src └── rtsp_driver_node ├── README.md └── CMakeLists.txt /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Andrea F Daniele 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /launch/rtsp_camera.launch: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | env/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .coverage 42 | .coverage.* 43 | .cache 44 | nosetests.xml 45 | coverage.xml 46 | *.cover 47 | .hypothesis/ 48 | 49 | # Translations 50 | *.mo 51 | *.pot 52 | 53 | # Django stuff: 54 | *.log 55 | local_settings.py 56 | 57 | # Flask stuff: 58 | instance/ 59 | .webassets-cache 60 | 61 | # Scrapy stuff: 62 | .scrapy 63 | 64 | # Sphinx documentation 65 | docs/_build/ 66 | 67 | # PyBuilder 68 | target/ 69 | 70 | # Jupyter Notebook 71 | .ipynb_checkpoints 72 | 73 | # pyenv 74 | .python-version 75 | 76 | # celery beat schedule file 77 | celerybeat-schedule 78 | 79 | # SageMath parsed files 80 | *.sage.py 81 | 82 | # dotenv 83 | .env 84 | 85 | # virtualenv 86 | .venv 87 | venv/ 88 | ENV/ 89 | 90 | # Spyder project settings 91 | .spyderproject 92 | .spyproject 93 | 94 | # Rope project settings 95 | .ropeproject 96 | 97 | # mkdocs documentation 98 | /site 99 | 100 | # mypy 101 | .mypy_cache/ 102 | -------------------------------------------------------------------------------- /package.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | rtsp_ros_driver 4 | 0.0.0 5 | The rtsp_ros_driver package 6 | 7 | 8 | 9 | 10 | Andrea F. Daniele 11 | 12 | 13 | 14 | 15 | 16 | MIT 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | catkin 52 | cv_bridge 53 | rospy 54 | sensor_msgs 55 | cv_bridge 56 | rospy 57 | sensor_msgs 58 | cv_bridge 59 | rospy 60 | sensor_msgs 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | -------------------------------------------------------------------------------- /src/rtsp_driver_node: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python2 2 | 3 | import os 4 | import cv2 5 | import sys 6 | import time 7 | import rospy 8 | from cv_bridge import CvBridge 9 | from sensor_msgs.msg import Image, CameraInfo 10 | from camera_info_manager import * 11 | from threading import Thread 12 | from time import sleep 13 | 14 | 15 | verbose = False 16 | camera_frame = '' 17 | camera_info_manager = None 18 | camera_info_publisher = None 19 | is_shutdown = False 20 | async_info_publisher = False 21 | async_info_publisher_freq = 30 22 | 23 | def camera_info_publisher_fcn(_): 24 | global camera_frame, camera_info_manager, camera_info_publisher, async_info_publisher_freq, is_shutdown 25 | wait_time = 1.0 / async_info_publisher_freq 26 | while True: 27 | if is_shutdown or rospy.is_shutdown(): 28 | return 29 | # publish camera calibration 30 | camera_info_msg = camera_info_manager.getCameraInfo() 31 | camera_info_msg.header.frame_id = camera_frame 32 | camera_info_msg.header.stamp = rospy.Time.now() 33 | camera_info_publisher.publish( camera_info_msg ) 34 | # wait for 1 second 35 | sleep(wait_time) 36 | 37 | 38 | if __name__ == '__main__': 39 | # initialize ROS node 40 | print("Initializing ROS node... ", end=' ') 41 | rospy.init_node('rtsp_camera_driver_node') 42 | print("Done!") 43 | 44 | # get ROS parameters 45 | resource = rospy.get_param('~rtsp_resource') 46 | camera_name = rospy.get_param('~camera_name') 47 | camera_frame = rospy.get_param('~camera_frame') 48 | image_raw_topic = rospy.get_param('~image_raw_topic') 49 | camera_info_topic = rospy.get_param('~camera_info_topic') 50 | 51 | # check the environment variable ROS_HOME 52 | if( 'ROS_HOME' not in os.environ ): 53 | rospy.logerr("The environment variable ROS_HOME is not set. Please set it before you run this node. It should be pointing to your `catkin_ws` directory.") 54 | exit(0) 55 | 56 | # open RTSP stream 57 | cap = cv2.VideoCapture(resource) 58 | if not cap.isOpened(): 59 | rospy.logerr("Error opening resource `%s`. Please check." % resource) 60 | exit(0) 61 | rospy.loginfo("Resource successfully opened") 62 | 63 | # create publishers 64 | image_pub = rospy.Publisher(image_raw_topic, Image, queue_size=1) 65 | camera_info_publisher = rospy.Publisher(camera_info_topic, CameraInfo, queue_size=1) 66 | 67 | # initialize ROS_CV_Bridge 68 | ros_cv_bridge = CvBridge() 69 | 70 | # initialize Camera Info Manager 71 | camera_info_manager = CameraInfoManager(cname=camera_name, namespace=camera_name) 72 | camera_info_manager.loadCameraInfo() 73 | if not camera_info_manager.isCalibrated(): 74 | rospy.logwarn("No calibration found for the current camera") 75 | 76 | # if async_info_publisher, create and launch async publisher node 77 | if async_info_publisher: 78 | camera_info_pub = Thread(target=camera_info_publisher_fcn, args=(camera_info_manager, )) 79 | camera_info_pub.start() 80 | 81 | # initialize variables 82 | print("Correctly opened resource, starting to publish feed.") 83 | rval, cv_image = cap.read() 84 | last_t = time.time() 85 | last_print_t = time.time() 86 | t_buffer = [] 87 | 88 | # process frames 89 | while rval: 90 | # get new frame 91 | rval, cv_image = cap.read() 92 | # handle Ctrl-C 93 | key = cv2.waitKey(20) 94 | if rospy.is_shutdown() or key == 27 or key == 1048603: 95 | break 96 | # convert CV image to ROS message 97 | image_msg = ros_cv_bridge.cv2_to_imgmsg(cv_image, encoding="bgr8") 98 | image_msg.header.frame_id = camera_frame 99 | image_msg.header.stamp = rospy.Time.now() 100 | image_pub.publish( image_msg ) 101 | # publish camera calibration in case of sync publisher 102 | if not async_info_publisher: 103 | camera_info_msg = camera_info_manager.getCameraInfo() 104 | camera_info_msg.header.frame_id = camera_frame 105 | camera_info_msg.header.stamp = image_msg.header.stamp 106 | camera_info_publisher.publish( camera_info_msg ) 107 | # compute frequency 108 | cur_t = time.time() 109 | t_buffer.append( cur_t - last_t ) 110 | # print frequency (if verbose) 111 | if cur_t - last_print_t > 1: 112 | wait_avg_sec = float(sum(t_buffer))/float(len(t_buffer)) 113 | hz = 1. / wait_avg_sec 114 | if verbose: rospy.loginfo('Streaming @ %.1f Hz' % hz) 115 | last_print_t = cur_t 116 | last_t = cur_t 117 | 118 | # stop thread 119 | if async_info_publisher: 120 | is_shutdown = True 121 | camera_info_pub.join() 122 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # rtsp_ros_driver 2 | 3 | This ROS package contains a driver node that reads frames from an RTSP video stream (e.g., IP Camera) and publishes them out as [sensor_msgs/Image](http://docs.ros.org/api/sensor_msgs/html/msg/Image.html) ROS messages. 4 | 5 | 6 | # Configuration 7 | 8 | `rtsp_ros_driver` implements the ROS [camera_info_manager](http://wiki.ros.org/camera_info_manager_py) interface. 9 | This means that the driver node can seamlessly handle camera calibration files. `rtsp_ros_driver` stores camera 10 | calibration files in the directory `$ROS_HOME/camera_info/`. Export the environment variable `$ROS_HOME` to point to 11 | your `catkin_ws` directory. 12 | 13 | ``` 14 | export ROS_HOME= 15 | ``` 16 | 17 | 18 | # Tutorial 19 | 20 | You can launch the `rtsp_ros_driver` main node by running: 21 | 22 | ``` 23 | roslaunch rtsp_ros_driver rtsp_camera.launch 24 | ``` 25 | 26 | 27 | # Launch files 28 | 29 | ## rtsp_camera.launch 30 | Launches the main node of the package (i.e., `rtsp_driver_node`). Unlike the `rtsp_driver_node` node, this launch file allows us to specify hostname, username, password, and other parameters about the camera separately. These parameters will be combined into a standard URL format and sent to the `rtsp_driver_node` node. 31 | 32 | ### Subscribed topics 33 | None. 34 | 35 | ### Published topics 36 | Identical to the `rtsp_driver_node` node (see below). 37 | 38 | ### Arguments 39 | 40 | #### Mandatory arguments 41 | 42 | _hostname_ 43 |    44 | (`string`) 45 |
46 |          47 | Hostname or IP of the RTSP camera. 48 |
49 | 50 | _username_ 51 |    52 | (`string`) 53 |
54 |          55 | Username for the RTSP camera. 56 |
57 | 58 | _password_ 59 |    60 | (`string`) 61 |
62 |          63 | Password for the RTSP camera. 64 |
65 | 66 | _stream_ 67 |    68 | (`string`) 69 |
70 |          71 | Name of the video stream published by the RTSP camera. 72 |
73 | 74 | 75 | #### Optional arguments 76 | 77 | _camera\_name_ 78 |    79 | (`string`, default: _rtsp\_camera_) 80 |
81 |          82 | Namespace of the camera. 83 |
84 | 85 | _camera\_frame_ 86 |    87 | (`string`, default: _rtsp\_camera\_link_) 88 |
89 |          90 | Name of the camera reference frame. 91 |
92 | 93 | _image\_raw\_topic_ 94 |    95 | (`string`, default: _~image\_raw_) 96 |
97 |          98 | Name of the output image topic. 99 |
100 | 101 | _camera\_info\_topic_ 102 |    103 | (`string`, default: _~camera\_info_) 104 |
105 |          106 | Name of the output camera info topic. 107 |
108 | 109 | _port_ 110 |    111 | (`int`, default: _554_) 112 |
113 |          114 | Port of the RTSP camera. 115 |
116 | 117 | 118 | 119 | # Nodes 120 | 121 | ## rtsp_driver_node 122 | Main node of the package, it is responsible for reading frames from the given RTSP video stream source and publishing them out as [sensor_msgs/Image](http://docs.ros.org/api/sensor_msgs/html/msg/Image.html) ROS messages. 123 | 124 | ### Subscribed topics 125 | None. 126 | 127 | ### Published topics 128 | 129 | _//_ 130 |    131 | ([sensor_msgs/Image](http://docs.ros.org/api/sensor_msgs/html/msg/Image.html)) 132 |
133 |          134 | Raw image stream from the camera. 135 |
136 | 137 | _//_ 138 |    139 | ([sensor_msgs/CameraInfo](http://docs.ros.org/api/sensor_msgs/html/msg/CameraInfo.html)) 140 |
141 |          142 | Camera metadata. 143 |
144 | 145 | ### Parameters 146 | 147 | #### Mandatory parameters 148 | 149 | ~_rtsp\_resource_ 150 |    151 | (`string`) 152 |
153 |          154 | RTSP URL to the camera string. 155 |
156 | 157 | 158 | #### Optional parameters 159 | 160 | ~_camera\_name_ 161 |    162 | (`string`, default: _rtsp\_camera_) 163 |
164 |          165 | Namespace of the camera. 166 |
167 | 168 | ~_camera\_frame_ 169 |    170 | (`string`, default: _rtsp\_camera\_link_) 171 |
172 |          173 | Name of the camera reference frame. 174 |
175 | 176 | ~_image\_raw\_topic_ 177 |    178 | (`string`, default: _~image\_raw_) 179 |
180 |          181 | Name of the output image topic. 182 |
183 | 184 | ~_camera\_info\_topic_ 185 |    186 | (`string`, default: _~camera\_info_) 187 |
188 |          189 | Name of the output camera info topic. 190 |
191 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 2.8.3) 2 | project(rtsp_ros_driver) 3 | 4 | ## Compile as C++11, supported in ROS Kinetic and newer 5 | # add_compile_options(-std=c++11) 6 | 7 | ## Find catkin macros and libraries 8 | ## if COMPONENTS list like find_package(catkin REQUIRED COMPONENTS xyz) 9 | ## is used, also find other catkin packages 10 | find_package(catkin REQUIRED COMPONENTS 11 | cv_bridge 12 | rospy 13 | sensor_msgs 14 | ) 15 | 16 | ## System dependencies are found with CMake's conventions 17 | # find_package(Boost REQUIRED COMPONENTS system) 18 | 19 | 20 | ## Uncomment this if the package has a setup.py. This macro ensures 21 | ## modules and global scripts declared therein get installed 22 | ## See http://ros.org/doc/api/catkin/html/user_guide/setup_dot_py.html 23 | # catkin_python_setup() 24 | 25 | ################################################ 26 | ## Declare ROS messages, services and actions ## 27 | ################################################ 28 | 29 | ## To declare and build messages, services or actions from within this 30 | ## package, follow these steps: 31 | ## * Let MSG_DEP_SET be the set of packages whose message types you use in 32 | ## your messages/services/actions (e.g. std_msgs, actionlib_msgs, ...). 33 | ## * In the file package.xml: 34 | ## * add a build_depend tag for "message_generation" 35 | ## * add a build_depend and a run_depend tag for each package in MSG_DEP_SET 36 | ## * If MSG_DEP_SET isn't empty the following dependency has been pulled in 37 | ## but can be declared for certainty nonetheless: 38 | ## * add a run_depend tag for "message_runtime" 39 | ## * In this file (CMakeLists.txt): 40 | ## * add "message_generation" and every package in MSG_DEP_SET to 41 | ## find_package(catkin REQUIRED COMPONENTS ...) 42 | ## * add "message_runtime" and every package in MSG_DEP_SET to 43 | ## catkin_package(CATKIN_DEPENDS ...) 44 | ## * uncomment the add_*_files sections below as needed 45 | ## and list every .msg/.srv/.action file to be processed 46 | ## * uncomment the generate_messages entry below 47 | ## * add every package in MSG_DEP_SET to generate_messages(DEPENDENCIES ...) 48 | 49 | ## Generate messages in the 'msg' folder 50 | # add_message_files( 51 | # FILES 52 | # Message1.msg 53 | # Message2.msg 54 | # ) 55 | 56 | ## Generate services in the 'srv' folder 57 | # add_service_files( 58 | # FILES 59 | # Service1.srv 60 | # Service2.srv 61 | # ) 62 | 63 | ## Generate actions in the 'action' folder 64 | # add_action_files( 65 | # FILES 66 | # Action1.action 67 | # Action2.action 68 | # ) 69 | 70 | ## Generate added messages and services with any dependencies listed here 71 | # generate_messages( 72 | # DEPENDENCIES 73 | # sensor_msgs 74 | # ) 75 | 76 | ################################################ 77 | ## Declare ROS dynamic reconfigure parameters ## 78 | ################################################ 79 | 80 | ## To declare and build dynamic reconfigure parameters within this 81 | ## package, follow these steps: 82 | ## * In the file package.xml: 83 | ## * add a build_depend and a run_depend tag for "dynamic_reconfigure" 84 | ## * In this file (CMakeLists.txt): 85 | ## * add "dynamic_reconfigure" to 86 | ## find_package(catkin REQUIRED COMPONENTS ...) 87 | ## * uncomment the "generate_dynamic_reconfigure_options" section below 88 | ## and list every .cfg file to be processed 89 | 90 | ## Generate dynamic reconfigure parameters in the 'cfg' folder 91 | # generate_dynamic_reconfigure_options( 92 | # cfg/DynReconf1.cfg 93 | # cfg/DynReconf2.cfg 94 | # ) 95 | 96 | ################################### 97 | ## catkin specific configuration ## 98 | ################################### 99 | ## The catkin_package macro generates cmake config files for your package 100 | ## Declare things to be passed to dependent projects 101 | ## INCLUDE_DIRS: uncomment this if your package contains header files 102 | ## LIBRARIES: libraries you create in this project that dependent projects also need 103 | ## CATKIN_DEPENDS: catkin_packages dependent projects also need 104 | ## DEPENDS: system dependencies of this project that dependent projects also need 105 | catkin_package( 106 | # INCLUDE_DIRS include 107 | # LIBRARIES rtsp_ros_driver 108 | # CATKIN_DEPENDS cv_bridge rospy sensor_msgs 109 | # DEPENDS system_lib 110 | ) 111 | 112 | ########### 113 | ## Build ## 114 | ########### 115 | 116 | ## Specify additional locations of header files 117 | ## Your package locations should be listed before other locations 118 | include_directories( 119 | # include 120 | ${catkin_INCLUDE_DIRS} 121 | ) 122 | 123 | ## Declare a C++ library 124 | # add_library(${PROJECT_NAME} 125 | # src/${PROJECT_NAME}/rtsp_ros_driver.cpp 126 | # ) 127 | 128 | ## Add cmake target dependencies of the library 129 | ## as an example, code may need to be generated before libraries 130 | ## either from message generation or dynamic reconfigure 131 | # add_dependencies(${PROJECT_NAME} ${${PROJECT_NAME}_EXPORTED_TARGETS} ${catkin_EXPORTED_TARGETS}) 132 | 133 | ## Declare a C++ executable 134 | ## With catkin_make all packages are built within a single CMake context 135 | ## The recommended prefix ensures that target names across packages don't collide 136 | # add_executable(${PROJECT_NAME}_node src/rtsp_ros_driver_node.cpp) 137 | 138 | ## Rename C++ executable without prefix 139 | ## The above recommended prefix causes long target names, the following renames the 140 | ## target back to the shorter version for ease of user use 141 | ## e.g. "rosrun someones_pkg node" instead of "rosrun someones_pkg someones_pkg_node" 142 | # set_target_properties(${PROJECT_NAME}_node PROPERTIES OUTPUT_NAME node PREFIX "") 143 | 144 | ## Add cmake target dependencies of the executable 145 | ## same as for the library above 146 | # add_dependencies(${PROJECT_NAME}_node ${${PROJECT_NAME}_EXPORTED_TARGETS} ${catkin_EXPORTED_TARGETS}) 147 | 148 | ## Specify libraries to link a library or executable target against 149 | # target_link_libraries(${PROJECT_NAME}_node 150 | # ${catkin_LIBRARIES} 151 | # ) 152 | 153 | ############# 154 | ## Install ## 155 | ############# 156 | 157 | # all install targets should use catkin DESTINATION variables 158 | # See http://ros.org/doc/api/catkin/html/adv_user_guide/variables.html 159 | 160 | ## Mark executable scripts (Python etc.) for installation 161 | ## in contrast to setup.py, you can choose the destination 162 | # install(PROGRAMS 163 | # scripts/my_python_script 164 | # DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION} 165 | # ) 166 | 167 | ## Mark executables and/or libraries for installation 168 | # install(TARGETS ${PROJECT_NAME} ${PROJECT_NAME}_node 169 | # ARCHIVE DESTINATION ${CATKIN_PACKAGE_LIB_DESTINATION} 170 | # LIBRARY DESTINATION ${CATKIN_PACKAGE_LIB_DESTINATION} 171 | # RUNTIME DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION} 172 | # ) 173 | 174 | ## Mark cpp header files for installation 175 | # install(DIRECTORY include/${PROJECT_NAME}/ 176 | # DESTINATION ${CATKIN_PACKAGE_INCLUDE_DESTINATION} 177 | # FILES_MATCHING PATTERN "*.h" 178 | # PATTERN ".svn" EXCLUDE 179 | # ) 180 | 181 | ## Mark other files for installation (e.g. launch and bag files, etc.) 182 | # install(FILES 183 | # # myfile1 184 | # # myfile2 185 | # DESTINATION ${CATKIN_PACKAGE_SHARE_DESTINATION} 186 | # ) 187 | 188 | ############# 189 | ## Testing ## 190 | ############# 191 | 192 | ## Add gtest based cpp test target and link libraries 193 | # catkin_add_gtest(${PROJECT_NAME}-test test/test_rtsp_ros_driver.cpp) 194 | # if(TARGET ${PROJECT_NAME}-test) 195 | # target_link_libraries(${PROJECT_NAME}-test ${PROJECT_NAME}) 196 | # endif() 197 | 198 | ## Add folders to be run by python nosetests 199 | # catkin_add_nosetests(test) 200 | --------------------------------------------------------------------------------