Author: Juan de los Rios(March 2023 - July 2023)
Fork from https://github.com/2b-t
This repository contains a Docker and all the documentation required to launch an Intel Realsense camera with the Robot Operating System ROS 2.
In order to make it work with Nvidia Jetson Orin/Nano, librealsense has to be built from source (refer to Dockerfile).
On the robot everything is installed and a server can be run to start on boot up under the name of peripheral.service
There are two different approaches for creating a Docker for a Realsense camera, one uses existing Debian packages while the other performs a full compilation from source. It is then important to mount /dev
as a volume so that the Docker can access the hardware.
In the docker-compose.yml
this is done with the options:
volumes:
- /dev:/dev
device_cgroup_rules:
- 'c 81:* rmw'
- 'c 189:* rmw'
Allow the container to display contents on your host machine by typing
$ xhost +local:root
Then build the Docker container with
$ docker-compose -f docker-compose-gui.yml build
or directly with the devcontainer
in Visual Studio Code. For Nvidia graphic cards the file docker-compose-gui-nvidia.yml
in combination with the nvidia-container-runtime
has to be used instead.
After it is done building connect the Realsense, start the container
$ docker compose -f docker-compose-gui.yml up
In order to make it run with three cameras:
- Find out the serial number of the new camera
- Change multi_camera_launch.py to following launch file with new serial number in line 63 and rebuild:
# Copyright 2023 Intel Corporation. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# DESCRIPTION #
# ----------- #
# Use this launch file to launch 2 devices.
# The Parameters available for definition in the command line for each camera are described in rs_launch.configurable_parameters
# For each device, the parameter name was changed to include an index.
# For example: to set camera_name for device1 set parameter camera_name1.
# command line example:
# ros2 launch realsense2_camera rs_multi_camera_launch.py camera_name1:=D400 device_type2:=l5. device_type1:=d4..
"""Launch realsense2_camera node."""
import copy
from launch import LaunchDescription
import launch_ros.actions
from launch.actions import IncludeLaunchDescription, DeclareLaunchArgument
from launch.substitutions import LaunchConfiguration, ThisLaunchFileDir
from launch.launch_description_sources import PythonLaunchDescriptionSource
import sys
import pathlib
sys.path.append(str(pathlib.Path(__file__).parent.absolute()))
import rs_launch
local_parameters = [{'name': 'camera_name1', 'default': 'camera_back', 'description': 'camera unique name'},
{'name': 'camera_name2', 'default': 'camera2', 'description': 'camera unique name'},
]
def set_configurable_parameters(local_params):
return dict([(param['original_name'], LaunchConfiguration(param['name'])) for param in local_params])
def duplicate_params(general_params, posix):
local_params = copy.deepcopy(general_params)
for param in local_params:
param['original_name'] = param['name']
param['name'] += posix
return local_params
def generate_launch_description():
params1 = duplicate_params(rs_launch.configurable_parameters, '1')
params2 = duplicate_params(rs_launch.configurable_parameters, '2')
arg_declare = DeclareLaunchArgument(
'camera'
)
return LaunchDescription(
rs_launch.declare_configurable_parameters(local_parameters) +
rs_launch.declare_configurable_parameters(params1) +
rs_launch.declare_configurable_parameters(params2) +
[
IncludeLaunchDescription(
PythonLaunchDescriptionSource([ThisLaunchFileDir(), '/rs_launch.py']),
launch_arguments={'camera_name': 'camera_shoulder_right', "serial_no":"_<new_serial_number>"}.items(),
),
IncludeLaunchDescription(
PythonLaunchDescriptionSource([ThisLaunchFileDir(), '/rs_launch.py']),
launch_arguments={'camera_name': 'camera_shoulder_left', "serial_no":"_829212071824"}.items(),
),
IncludeLaunchDescription(
PythonLaunchDescriptionSource([ThisLaunchFileDir(), '/rs_launch.py']),
launch_arguments={'camera_name': 'camera_back', "serial_no":"_829212071844", "pointcloud.enable":"false"}.items(),
),
launch_ros.actions.Node(
package = "depth_handler",
executable = "depth_subscriber"
),
launch_ros.actions.Node(
package = "depth_handler",
executable = "pcd_subscriber",
name = 'pointcloud_shoulder_left',
parameters = [{'camera_side': 'left'}]
),
launch_ros.actions.Node(
package = "depth_handler",
executable = "pcd_subscriber",
name = 'pointcloud_shoulder_right',
parameters = [{'camera_side': 'right'}]
),
])
The Intel Realsense driver has several serious flaws/bugs. Probably the main one is that it is closely connected to the kernel version of the Linux operating system. If the Dockerfile above do not work then you are likely unlucky and it is an incompatible version of the kernel of your host system and you will either have to downgrade your kernel or switch to another Ubuntu version that is officially supported. Furthermore from time to time the software will give you cryptic error messages. For some restarting the corresponding software component might help, for others you will find a fix googling and with others you will have to learn to live.
The Realsense is pretty picky about USB 3.x cables. If your camera is detected via rs-enumerate-devices
, you can see it realsense-viewer
but can't output its video stream, then it might be that your cable lacks the bandwidth. Either you can try to turn down the resolution of the camera in the realsense-viewer
or switch cable (preferably to one that is already known to work).
# Copyright 2023 Intel Corporation. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Launch realsense2_camera node."""
import os
import yaml
from launch import LaunchDescription
import launch_ros.actions
from launch.actions import DeclareLaunchArgument, OpaqueFunction
from launch.substitutions import LaunchConfiguration
configurable_parameters = [{'name': 'camera_name', 'default': 'camera', 'description': 'camera unique name'},
{'name': 'serial_no', 'default': "''", 'description': 'choose device by serial number'},
{'name': 'usb_port_id', 'default': "''", 'description': 'choose device by usb port id'},
{'name': 'device_type', 'default': "''", 'description': 'choose device by type'},
{'name': 'config_file', 'default': "''", 'description': 'yaml config file'},
{'name': 'unite_imu_method', 'default': "0", 'description': '[0-None, 1-copy, 2-linear_interpolation]'},
{'name': 'json_file_path', 'default': "''", 'description': 'allows advanced configuration'},
{'name': 'log_level', 'default': 'info', 'description': 'debug log level [DEBUG|INFO|WARN|ERROR|FATAL]'},
{'name': 'output', 'default': 'screen', 'description': 'pipe node output [screen|log]'},
{'name': 'depth_module.profile', 'default': '424,240,15', 'description': 'depth module profile'},
{'name': 'enable_depth', 'default': 'true', 'description': 'enable depth stream'},
{'name': 'rgb_camera.profile', 'default': '424,240,6', 'description': 'color image width'},
{'name': 'rgb_camera.enable_auto_exposure', 'default': 'true', 'description': 'enable/disable auto exposure for color image'},
{'name': 'enable_color', 'default': 'true', 'description': 'enable color stream'},
{'name': 'enable_infra1', 'default': 'false', 'description': 'enable infra1 stream'},
{'name': 'enable_infra2', 'default': 'false', 'description': 'enable infra2 stream'},
{'name': 'infra_rgb', 'default': 'false', 'description': 'enable infra2 stream'},
{'name': 'enable_fisheye1', 'default': 'true', 'description': 'enable fisheye1 stream'},
{'name': 'enable_fisheye2', 'default': 'true', 'description': 'enable fisheye2 stream'},
{'name': 'enable_confidence', 'default': 'true', 'description': 'enable depth stream'},
{'name': 'gyro_fps', 'default': '0', 'description': "''"},
{'name': 'accel_fps', 'default': '0', 'description': "''"},
{'name': 'enable_gyro', 'default': 'false', 'description': "''"},
{'name': 'enable_accel', 'default': 'false', 'description': "''"},
{'name': 'enable_pose', 'default': 'true', 'description': "''"},
{'name': 'pose_fps', 'default': '200', 'description': "''"},
{'name': 'pointcloud.enable', 'default': 'true', 'description': ''},
{'name': 'pointcloud.stream_filter', 'default': 'RS2_STREAM_COLOR', 'description': 'texture stream for pointcloud'},
{'name': 'pointcloud.stream_index_filter','default': '0', 'description': 'texture stream index for pointcloud'},
{'name': 'enable_sync', 'default': 'true', 'description': "''"},
{'name': 'align_depth.enable', 'default': 'true', 'description': "''"},
{'name': 'colorizer.enable', 'default': 'false', 'description': "''"},
{'name': 'clip_distance', 'default': '-2.', 'description': "''"},
{'name': 'linear_accel_cov', 'default': '0.01', 'description': "''"},
{'name': 'initial_reset', 'default': 'false', 'description': "''"},
{'name': 'allow_no_texture_points', 'default': 'true', 'description': "''"},
{'name': 'pointcloud.ordered_pc', 'default': 'false', 'description': ''},
{'name': 'publish_tf', 'default': 'true', 'description': '[bool] enable/disable publishing static & dynamic TF'},
{'name': 'tf_publish_rate', 'default': '0.0', 'description': '[double] rate in Hz for publishing dynamic TF'},
{'name': 'diagnostics_period', 'default': '0.0', 'description': 'Rate of publishing diagnostics. 0=Disabled'},
{'name': 'decimation_filter.enable', 'default': 'true', 'description': 'Rate of publishing static_tf'},
{'name': 'rosbag_filename', 'default': "''", 'description': 'A realsense bagfile to run from as a device'},
{'name': 'depth_module.exposure.1', 'default': '7500', 'description': 'Depth module first exposure value. Used for hdr_merge filter'},
{'name': 'depth_module.gain.1', 'default': '16', 'description': 'Depth module first gain value. Used for hdr_merge filter'},
{'name': 'depth_module.exposure.2', 'default': '1', 'description': 'Depth module second exposure value. Used for hdr_merge filter'},
{'name': 'depth_module.gain.2', 'default': '16', 'description': 'Depth module second gain value. Used for hdr_merge filter'},
{'name': 'depth_module.exposure', 'default': '8500', 'description': 'Depth module manual exposure value'},
{'name': 'depth_module.hdr_enabled', 'default': 'false', 'description': 'Depth module hdr enablement flag. Used for hdr_merge filter'},
{'name': 'depth_module.enable_auto_exposure', 'default': 'true', 'description': 'enable/disable auto exposure for depth image'},
{'name': 'hdr_merge.enable', 'default': 'false', 'description': 'hdr_merge filter enablement flag'},
{'name': 'wait_for_device_timeout', 'default': '-1.', 'description': 'Timeout for waiting for device to connect (Seconds)'},
{'name': 'reconnect_timeout', 'default': '6.', 'description': 'Timeout(seconds) between consequtive reconnection attempts'},
]
def declare_configurable_parameters(parameters):
return [DeclareLaunchArgument(param['name'], default_value=param['default'], description=param['description']) for param in parameters]
def set_configurable_parameters(parameters):
return dict([(param['name'], LaunchConfiguration(param['name'])) for param in parameters])
def yaml_to_dict(path_to_yaml):
with open(path_to_yaml, "r") as f:
return yaml.load(f, Loader=yaml.SafeLoader)
def launch_setup(context, *args, **kwargs):
_config_file = LaunchConfiguration("config_file").perform(context)
params_from_file = {} if _config_file == "''" else yaml_to_dict(_config_file)
log_level = 'info'
# Realsense
if (os.getenv('ROS_DISTRO') == "dashing") or (os.getenv('ROS_DISTRO') == "eloquent"):
return [
launch_ros.actions.Node(
package='realsense2_camera',
node_namespace=LaunchConfiguration("camera_name"),
node_name=LaunchConfiguration("camera_name"),
node_executable='realsense2_camera_node',
prefix=['stdbuf -o L'],
parameters=[set_configurable_parameters(configurable_parameters)
, params_from_file
],
output='screen',
arguments=['--ros-args', '--log-level', LaunchConfiguration('log_level')],
)
]
else:
return [
launch_ros.actions.Node(
package='realsense2_camera',
namespace=LaunchConfiguration("camera_name"),
name=LaunchConfiguration("camera_name"),
executable='realsense2_camera_node',
parameters=[set_configurable_parameters(configurable_parameters)
, params_from_file
],
output='screen',
arguments=['--ros-args', '--log-level', LaunchConfiguration('log_level')],
emulate_tty=True,
)
]
def generate_launch_description():
return LaunchDescription(declare_configurable_parameters(configurable_parameters) + [
OpaqueFunction(function=launch_setup)
])