refactor: reorganize multi-camera examples

This commit is contained in:
slz
2026-09-07 11:27:57 +08:00
parent d38f0db90d
commit 7c4954791c
9 changed files with 315 additions and 290 deletions
+3 -2
View File
@@ -11,9 +11,10 @@ For command-line maintenance and diagnostic utilities, see the official
| Source | Purpose | Experience Level | Official Guide | | Source | Purpose | Experience Level | Official Guide |
| :---: | --- | :---: | --- | | :---: | --- | :---: | --- |
| [Net camera](./net_camera) | Launch supported Orbbec network cameras. | ⭐️ | [Network camera guide](https://orbbec.github.io/OrbbecSDK_ROS2/en/source/camera_devices/5_advanced_guide/configuration/net_camera.html) | | [Multi-camera network](./multi_net_camera) | Launch multiple supported Orbbec network cameras. | ⭐️ | [Network camera guide](https://orbbec.github.io/OrbbecSDK_ROS2/en/source/camera_devices/5_advanced_guide/configuration/net_camera.html) |
| [GigE Action Command](./gige_action_command) | Configure and trigger a Gemini 335Le camera group over GVCP. | ⭐️⭐️ | [README](./gige_action_command/README.md) | | [GigE Action Command](./gige_action_command) | Configure and trigger a Gemini 335Le camera group over GVCP. | ⭐️⭐️ | [README](./gige_action_command/README.md) |
| [GMSL camera](./gmsl_camera) | Launch single, multiple, or synchronized GMSL cameras. | ⭐️ | [GMSL camera guide](https://orbbec.github.io/OrbbecSDK_ROS2/en/source/camera_devices/5_advanced_guide/multi_camera/gmsl_camera.html) | | [GMSL camera](./gmsl_multi_camera_sync) | Synchronize two GMSL-connected Gemini 330 Series cameras. | ⭐️⭐️ | [README](./gmsl_multi_camera_sync/README.md) |
| [Shared component container](./multi_camera_shared_container) | Load multiple Gemini 330 Series cameras into one component container. | ⭐️⭐️ | [README](./multi_camera_shared_container/README.md) |
| [AE/AWB lock test](./ae_awb_lock) | Verify AE/AWB convergence, frame-metadata capture, and manual lock-in through ROS 2. | ⭐️⭐️ | [README](./ae_awb_lock/README.md) | | [AE/AWB lock test](./ae_awb_lock) | Verify AE/AWB convergence, frame-metadata capture, and manual lock-in through ROS 2. | ⭐️⭐️ | [README](./ae_awb_lock/README.md) |
| [Benchmark](./benchmark) | Benchmark the performance of different camera configurations. | ⭐️⭐️ | [Performance benchmark tools](https://orbbec.github.io/OrbbecSDK_ROS2/en/source/camera_devices/6_benchmark/benchmark_tools.html) | | [Benchmark](./benchmark) | Benchmark the performance of different camera configurations. | ⭐️⭐️ | [Performance benchmark tools](https://orbbec.github.io/OrbbecSDK_ROS2/en/source/camera_devices/6_benchmark/benchmark_tools.html) |
| [Multi-camera synchronization verification](./multi_camera_synced_verification_tool) | Verify synchronization accuracy across multiple cameras. | ⭐️⭐️⭐️ | [Synchronization verification guide](https://orbbec.github.io/OrbbecSDK_ROS2/en/source/camera_devices/5_advanced_guide/multi_camera/multi_camera_synced_verification_tool.html) | | [Multi-camera synchronization verification](./multi_camera_synced_verification_tool) | Verify synchronization accuracy across multiple cameras. | ⭐️⭐️⭐️ | [Synchronization verification guide](https://orbbec.github.io/OrbbecSDK_ROS2/en/source/camera_devices/5_advanced_guide/multi_camera/multi_camera_synced_verification_tool.html) |
@@ -1,122 +0,0 @@
import os
from ament_index_python.packages import get_package_share_directory
from launch import LaunchDescription
from launch_ros.actions import Node, LoadComposableNodes
from launch.actions import IncludeLaunchDescription, GroupAction, TimerAction
from launch.launch_description_sources import PythonLaunchDescriptionSource
from launch.actions import DeclareLaunchArgument
from launch.conditions import UnlessCondition, IfCondition
from launch.substitutions import LaunchConfiguration
from launch.substitutions import TextSubstitution
from launch_ros.descriptions import ComposableNode
GMSL_CAMERA_LAUNCH_DIRS = {
"gemini_330_gmsl.launch.py": ("examples", "gmsl_camera"), # Gemini 335Lg / 338Lg
"gemini_301_series.launch.py": ("launch",), # Gemini 305G / 309G
"gemini345_lg.launch.py": ("launch",), # Gemini 345Lg
}
def get_camera_launch_file(package_dir, launch_file_name):
return os.path.join(
package_dir, *GMSL_CAMERA_LAUNCH_DIRS[launch_file_name], launch_file_name
)
def load_yaml(file_path):
with open(file_path, "r") as f:
return yaml.safe_load(f)
def merge_params(default_params, yaml_params):
for key, value in yaml_params.items():
if key in default_params:
default_params[key] = value
return default_params
def convert_value(value):
if isinstance(value, str):
try:
return int(value)
except ValueError:
pass
try:
return float(value)
except ValueError:
pass
if value.lower() == "true":
return True
elif value.lower() == "false":
return False
return value
def generate_launch_description():
# Include launch files
package_dir = get_package_share_directory("orbbec_camera")
config_file_dir = os.path.join(package_dir, "config")
secondary_config_file_path = os.path.join(config_file_dir, "camera_secondary_params.yaml")
attach_to_shared_component_container_arg = LaunchConfiguration(
"attach_to_shared_component_container", default=False
)
component_container_name_arg = LaunchConfiguration(
"component_container_name", default="shared_orbbec_container"
)
shared_orbbec_container = Node(
name=component_container_name_arg,
package="rclcpp_components",
executable="component_container_mt",
output="log",
condition=UnlessCondition(attach_to_shared_component_container_arg),
)
attach_to_shared_component_container_arg = TextSubstitution(text="true")
launch1_include = IncludeLaunchDescription(
# Supported launch files: gemini_330_gmsl.launch.py, gemini_301_series.launch.py,
# and gemini345_lg.launch.py.
PythonLaunchDescriptionSource(
get_camera_launch_file(package_dir, "gemini_330_gmsl.launch.py")
),
launch_arguments={
"camera_name": "camera_01",
"usb_port": "gmsl2-1",
"device_num": "2",
"sync_mode": "standalone",
"config_file_path": secondary_config_file_path,
"attach_to_shared_component_container": attach_to_shared_component_container_arg,
"component_container_name": component_container_name_arg,
}.items(),
)
launch2_include = IncludeLaunchDescription(
# Supported launch files: gemini_330_gmsl.launch.py, gemini_301_series.launch.py,
# and gemini345_lg.launch.py.
PythonLaunchDescriptionSource(
get_camera_launch_file(package_dir, "gemini_330_gmsl.launch.py")
),
launch_arguments={
"camera_name": "camera_02",
"usb_port": "gmsl2-3",
"device_num": "2",
"sync_mode": "standalone",
"config_file_path": secondary_config_file_path,
"attach_to_shared_component_container": attach_to_shared_component_container_arg,
"component_container_name": component_container_name_arg,
}.items(),
)
# Launch description
ld = LaunchDescription(
[
shared_orbbec_container,
TimerAction(period=0.0, actions=[GroupAction([launch2_include])]),
TimerAction(period=2.0, actions=[GroupAction([launch1_include])]),
]
)
return ld
@@ -1,124 +0,0 @@
import os
from ament_index_python.packages import get_package_share_directory
from launch import LaunchDescription
from launch_ros.actions import Node, LoadComposableNodes
from launch.actions import IncludeLaunchDescription, GroupAction, TimerAction
from launch.launch_description_sources import PythonLaunchDescriptionSource
from launch.actions import DeclareLaunchArgument
from launch.conditions import UnlessCondition, IfCondition
from launch.substitutions import LaunchConfiguration
from launch.substitutions import TextSubstitution
from launch_ros.descriptions import ComposableNode
GMSL_CAMERA_LAUNCH_DIRS = {
"gemini_330_gmsl.launch.py": ("examples", "gmsl_camera"), # Gemini 335Lg / 338Lg
"gemini_301_series.launch.py": ("launch",), # Gemini 305G / 309G
"gemini345_lg.launch.py": ("launch",), # Gemini 345Lg
}
def get_camera_launch_file(package_dir, launch_file_name):
return os.path.join(
package_dir, *GMSL_CAMERA_LAUNCH_DIRS[launch_file_name], launch_file_name
)
def load_yaml(file_path):
with open(file_path, "r") as f:
return yaml.safe_load(f)
def merge_params(default_params, yaml_params):
for key, value in yaml_params.items():
if key in default_params:
default_params[key] = value
return default_params
def convert_value(value):
if isinstance(value, str):
try:
return int(value)
except ValueError:
pass
try:
return float(value)
except ValueError:
pass
if value.lower() == "true":
return True
elif value.lower() == "false":
return False
return value
def generate_launch_description():
# Include launch files
package_dir = get_package_share_directory("orbbec_camera")
config_file_dir = os.path.join(package_dir, "config")
secondary_config_file_path = os.path.join(config_file_dir, "camera_secondary_params.yaml")
attach_to_shared_component_container_arg = LaunchConfiguration(
"attach_to_shared_component_container", default=False
)
component_container_name_arg = LaunchConfiguration(
"component_container_name", default="shared_orbbec_container"
)
shared_orbbec_container = Node(
name=component_container_name_arg,
package="rclcpp_components",
executable="component_container_mt",
output="log",
condition=UnlessCondition(attach_to_shared_component_container_arg),
)
attach_to_shared_component_container_arg = TextSubstitution(text="true")
launch1_include = IncludeLaunchDescription(
# Supported launch files: gemini_330_gmsl.launch.py, gemini_301_series.launch.py,
# and gemini345_lg.launch.py.
PythonLaunchDescriptionSource(
get_camera_launch_file(package_dir, "gemini_330_gmsl.launch.py")
),
launch_arguments={
"camera_name": "camera_01",
"usb_port": "gmsl2-1",
"device_num": "2",
"sync_mode": "secondary_synced",
"gmsl_trigger_fps": "3000",
"enable_gmsl_trigger": "true",
"config_file_path": secondary_config_file_path,
"attach_to_shared_component_container": attach_to_shared_component_container_arg,
"component_container_name": component_container_name_arg,
}.items(),
)
launch2_include = IncludeLaunchDescription(
# Supported launch files: gemini_330_gmsl.launch.py, gemini_301_series.launch.py,
# and gemini345_lg.launch.py.
PythonLaunchDescriptionSource(
get_camera_launch_file(package_dir, "gemini_330_gmsl.launch.py")
),
launch_arguments={
"camera_name": "camera_02",
"usb_port": "gmsl2-3",
"device_num": "2",
"sync_mode": "secondary_synced",
"config_file_path": secondary_config_file_path,
"attach_to_shared_component_container": attach_to_shared_component_container_arg,
"component_container_name": component_container_name_arg,
}.items(),
)
# Launch description
ld = LaunchDescription(
[
shared_orbbec_container,
TimerAction(period=0.0, actions=[GroupAction([launch2_include])]),
TimerAction(period=2.0, actions=[GroupAction([launch1_include])]),
]
)
return ld
@@ -0,0 +1,22 @@
# GMSL Multi-Camera Synchronization
This example starts two GMSL-connected Gemini 330 Series cameras in hardware synchronization
mode. It uses the package's standard `gemini_330_series.launch.py` file.
Before starting the cameras, grant access to the camera synchronization device:
```bash
sudo chmod 777 /dev/camsync
```
Before starting the example, update the `usb_port` values in
`multi_gmsl_camera_synced.launch.py` if your system reports different GMSL links. The default
values are `gmsl2-1` and `gmsl2-3`.
```bash
ros2 launch orbbec_camera multi_gmsl_camera_synced.launch.py
```
Both cameras use `secondary_synced` mode. `camera_01` enables the host-side GMSL trigger at
30 fps (`gmsl_trigger_fps=3000`), while `camera_02` receives the same trigger. The receiving
camera is started first, followed by the trigger-generating camera after two seconds.
@@ -0,0 +1,52 @@
import os
from ament_index_python.packages import get_package_share_directory
from launch import LaunchDescription
from launch.actions import IncludeLaunchDescription, GroupAction, TimerAction
from launch.launch_description_sources import PythonLaunchDescriptionSource
def generate_launch_description():
# Include launch files
package_dir = get_package_share_directory("orbbec_camera")
launch_file_dir = os.path.join(package_dir, "launch")
config_file_dir = os.path.join(package_dir, "config")
secondary_config_file_path = os.path.join(config_file_dir, "camera_secondary_params.yaml")
launch1_include = IncludeLaunchDescription(
PythonLaunchDescriptionSource(
os.path.join(launch_file_dir, "gemini_330_series.launch.py")
),
launch_arguments={
"camera_name": "camera_01",
"usb_port": "gmsl2-1",
"device_num": "2",
"sync_mode": "secondary_synced",
"gmsl_trigger_fps": "3000",
"enable_gmsl_trigger": "true",
"config_file_path": secondary_config_file_path,
}.items(),
)
launch2_include = IncludeLaunchDescription(
PythonLaunchDescriptionSource(
os.path.join(launch_file_dir, "gemini_330_series.launch.py")
),
launch_arguments={
"camera_name": "camera_02",
"usb_port": "gmsl2-3",
"device_num": "2",
"sync_mode": "secondary_synced",
"config_file_path": secondary_config_file_path,
}.items(),
)
# Launch description
ld = LaunchDescription(
[
TimerAction(period=0.0, actions=[GroupAction([launch2_include])]),
TimerAction(period=2.0, actions=[GroupAction([launch1_include])]),
# The camera that enables the GMSL trigger should be launched at last
]
)
return ld
@@ -0,0 +1,28 @@
# Attach Cameras to a Shared Component Container
This example shows how to load two Gemini 330 Series camera components into one externally
created ROS 2 component container.
Before starting the example, update the `usb_port` values in
`multi_camera_shared_container.launch.py` to match your two cameras. The default values are
`2-1` and `2-2`.
```bash
ros2 launch orbbec_camera multi_camera_shared_container.launch.py
```
The example performs three steps:
1. Starts one multithreaded component container named `shared_orbbec_container`.
2. Includes the example-specific `gemini_330_series_shared_container.launch.py` once for each
camera.
3. Passes `attach_to_shared_component_container=true` and the same
`component_container_name` to both includes, so neither include creates its own container.
`use_intra_process_comms=true` enables intra-process communication for the loaded camera
components. Keep every `camera_name` unique, and make sure the shared container is running
before a camera component is loaded.
The package's main `launch/gemini_330_series.launch.py` is not modified by this example. The
example-specific launcher mirrors its Gemini 330 Series parameters and adds only the component
container selection controls needed for this demonstration.
@@ -1,12 +1,24 @@
import os import os
import yaml import yaml
from launch import LaunchDescription from launch import LaunchDescription
from launch.actions import DeclareLaunchArgument, OpaqueFunction, GroupAction from launch.actions import DeclareLaunchArgument, OpaqueFunction
from launch.substitutions import LaunchConfiguration
from launch_ros.actions import PushRosNamespace, ComposableNodeContainer, Node
from launch_ros.descriptions import ComposableNode
from launch.conditions import UnlessCondition from launch.conditions import UnlessCondition
from launch_ros.actions import LoadComposableNodes from launch.substitutions import LaunchConfiguration
from launch_ros.actions import LoadComposableNodes, Node
from launch_ros.descriptions import ComposableNode
OPTIONAL_BOOLEAN_PARAMS = {
'enable_hardware_noise_removal_filter',
'enable_noise_removal_filter',
}
LAUNCH_ONLY_PARAMS = {
'attach_to_shared_component_container',
'component_container_name',
'use_intra_process_comms',
}
def load_yaml(file_path): def load_yaml(file_path):
with open(file_path, 'r') as f: with open(file_path, 'r') as f:
@@ -38,25 +50,58 @@ def convert_value(value):
def load_parameters(context, args): def load_parameters(context, args):
default_params = {arg.name: LaunchConfiguration(arg.name).perform(context) for arg in args} default_params = {
arg.name: LaunchConfiguration(arg.name).perform(context)
for arg in args
if arg.name not in LAUNCH_ONLY_PARAMS
}
config_file_path = LaunchConfiguration('config_file_path').perform(context) config_file_path = LaunchConfiguration('config_file_path').perform(context)
if config_file_path: if config_file_path:
yaml_params = load_yaml(config_file_path) yaml_params = load_yaml(config_file_path)
default_params = merge_params(default_params, yaml_params) default_params = merge_params(default_params, yaml_params)
skip_convert = {'config_file_path', 'usb_port', 'serial_number', 'device_preset_version'} skip_convert = {'config_file_path', 'usb_port', 'serial_number', 'bag_record_filename', 'bag_filename',
return { 'enhanced_depth_model_path', 'depth_colorizer_mode', 'device_preset_version'}
key: (value if key in skip_convert else convert_value(value))
for key, value in default_params.items() result = {}
} for key, value in default_params.items():
# An empty optional boolean means "auto". Do not pass it to the ROS node,
# because a declared bool parameter cannot represent an unset value.
if key in OPTIONAL_BOOLEAN_PARAMS and value == '':
continue
if key in skip_convert:
result[key] = value
elif 'enable_pub_plugins' in key:
if isinstance(value, str):
if value.startswith('[') and value.endswith(']'):
try:
result[key] = yaml.safe_load(value)
except:
result[key] = [value]
else:
result[key] = [value]
elif isinstance(value, list):
result[key] = value
else:
result[key] = [str(value)]
else:
result[key] = convert_value(value)
return result
def generate_launch_description(): def generate_launch_description():
args = [ args = [
DeclareLaunchArgument('device_type', default_value='camera'),
DeclareLaunchArgument('camera_name', default_value='camera'), DeclareLaunchArgument('camera_name', default_value='camera'),
DeclareLaunchArgument('depth_registration', default_value='true'), DeclareLaunchArgument('depth_registration', default_value='false'),
DeclareLaunchArgument('serial_number', default_value=''), DeclareLaunchArgument('serial_number', default_value=''),
DeclareLaunchArgument('usb_port', default_value=''), DeclareLaunchArgument('usb_port', default_value=''),
DeclareLaunchArgument('device_num', default_value='1'), DeclareLaunchArgument('device_num', default_value='1'),
# Bag recording: record the live stream to an Orbbec .bag file
DeclareLaunchArgument('bag_record_filename', default_value=''),
# Bag playback: load a previously recorded .bag file as a virtual device
DeclareLaunchArgument('bag_filename', default_value=''),
DeclareLaunchArgument('bag_loop', default_value='false'),
DeclareLaunchArgument('upgrade_firmware', default_value=''), DeclareLaunchArgument('upgrade_firmware', default_value=''),
DeclareLaunchArgument('preset_firmware_path', default_value=''), DeclareLaunchArgument('preset_firmware_path', default_value=''),
DeclareLaunchArgument('load_config_json_file_path', default_value=''), DeclareLaunchArgument('load_config_json_file_path', default_value=''),
@@ -68,12 +113,15 @@ def generate_launch_description():
DeclareLaunchArgument('enable_colored_point_cloud', default_value='false'), DeclareLaunchArgument('enable_colored_point_cloud', default_value='false'),
DeclareLaunchArgument('cloud_frame_id', default_value=''), DeclareLaunchArgument('cloud_frame_id', default_value=''),
DeclareLaunchArgument('connection_delay', default_value='10'), DeclareLaunchArgument('connection_delay', default_value='10'),
DeclareLaunchArgument('color_frame_queue_max_frames', default_value='10'),
DeclareLaunchArgument('color_width', default_value='0'), DeclareLaunchArgument('color_width', default_value='0'),
DeclareLaunchArgument('color_height', default_value='0'), DeclareLaunchArgument('color_height', default_value='0'),
DeclareLaunchArgument('color_fps', default_value='0'), DeclareLaunchArgument('color_fps', default_value='0'),
DeclareLaunchArgument('color_format', default_value='ANY'), DeclareLaunchArgument('color_format', default_value='ANY'),
DeclareLaunchArgument('enable_color', default_value='true'), DeclareLaunchArgument('enable_color', default_value='true'),
DeclareLaunchArgument('color_qos', default_value='default'), DeclareLaunchArgument('color_qos', default_value='default'),
DeclareLaunchArgument('color_qos_history', default_value='default'),
DeclareLaunchArgument('color_qos_depth', default_value='-1'),
DeclareLaunchArgument('color_camera_info_qos', default_value='default'), DeclareLaunchArgument('color_camera_info_qos', default_value='default'),
DeclareLaunchArgument('enable_color_auto_exposure_priority', default_value='false'), DeclareLaunchArgument('enable_color_auto_exposure_priority', default_value='false'),
DeclareLaunchArgument('color_rotation', default_value='-1'),#color rotation degree : 0, 90, 180, 270 DeclareLaunchArgument('color_rotation', default_value='-1'),#color rotation degree : 0, 90, 180, 270
@@ -97,15 +145,21 @@ def generate_launch_description():
DeclareLaunchArgument('color_contrast', default_value='-1'), DeclareLaunchArgument('color_contrast', default_value='-1'),
DeclareLaunchArgument('color_hue', default_value='-1'), DeclareLaunchArgument('color_hue', default_value='-1'),
DeclareLaunchArgument('color_backlight_compensation', default_value='-1'),#range: 0 - 6, default: 3 DeclareLaunchArgument('color_backlight_compensation', default_value='-1'),#range: 0 - 6, default: 3
DeclareLaunchArgument('color_anti_flicker', default_value='false'),
DeclareLaunchArgument('color_powerline_freq', default_value=''),#disable ,50hz ,60hz ,auto DeclareLaunchArgument('color_powerline_freq', default_value=''),#disable ,50hz ,60hz ,auto
DeclareLaunchArgument('enable_color_decimation_filter', default_value='false'), DeclareLaunchArgument('enable_color_decimation_filter', default_value='false'),
DeclareLaunchArgument('color_decimation_filter_scale', default_value='-1'), DeclareLaunchArgument('color_decimation_filter_scale', default_value='-1'),
DeclareLaunchArgument('color_denoising_level', default_value='-1'),#0: Auto; 1-8: higher values indicate stronger denoising.
#Note: The color_denoising_level configuration is supported only when AE is enabled, and requires new firmware support.
DeclareLaunchArgument('depth_width', default_value='0'), DeclareLaunchArgument('depth_width', default_value='0'),
DeclareLaunchArgument('depth_height', default_value='0'), DeclareLaunchArgument('depth_height', default_value='0'),
DeclareLaunchArgument('depth_fps', default_value='0'), DeclareLaunchArgument('depth_fps', default_value='0'),
DeclareLaunchArgument('depth_format', default_value='ANY'), DeclareLaunchArgument('depth_format', default_value='ANY'),
DeclareLaunchArgument('enable_depth', default_value='true'), DeclareLaunchArgument('enable_depth', default_value='true'),
DeclareLaunchArgument('depth_qos', default_value='default'), DeclareLaunchArgument('depth_qos', default_value='default'),
DeclareLaunchArgument('depth_qos_history', default_value='default'),
DeclareLaunchArgument('depth_qos_depth', default_value='-1'),
DeclareLaunchArgument('depth_camera_info_qos', default_value='default'), DeclareLaunchArgument('depth_camera_info_qos', default_value='default'),
DeclareLaunchArgument('enable_depth_auto_exposure_priority', default_value='false'), DeclareLaunchArgument('enable_depth_auto_exposure_priority', default_value='false'),
DeclareLaunchArgument('depth_precision', default_value=''), DeclareLaunchArgument('depth_precision', default_value=''),
@@ -123,6 +177,8 @@ def generate_launch_description():
DeclareLaunchArgument('left_ir_format', default_value='ANY'), DeclareLaunchArgument('left_ir_format', default_value='ANY'),
DeclareLaunchArgument('enable_left_ir', default_value='false'), DeclareLaunchArgument('enable_left_ir', default_value='false'),
DeclareLaunchArgument('left_ir_qos', default_value='default'), DeclareLaunchArgument('left_ir_qos', default_value='default'),
DeclareLaunchArgument('left_ir_qos_history', default_value='default'),
DeclareLaunchArgument('left_ir_qos_depth', default_value='-1'),
DeclareLaunchArgument('left_ir_camera_info_qos', default_value='default'), DeclareLaunchArgument('left_ir_camera_info_qos', default_value='default'),
DeclareLaunchArgument('left_ir_rotation', default_value='-1'),#left_ir rotation degree : 0, 90, 180, 270 DeclareLaunchArgument('left_ir_rotation', default_value='-1'),#left_ir rotation degree : 0, 90, 180, 270
DeclareLaunchArgument('left_ir_flip', default_value='false'), DeclareLaunchArgument('left_ir_flip', default_value='false'),
@@ -135,6 +191,8 @@ def generate_launch_description():
DeclareLaunchArgument('right_ir_format', default_value='ANY'), DeclareLaunchArgument('right_ir_format', default_value='ANY'),
DeclareLaunchArgument('enable_right_ir', default_value='false'), DeclareLaunchArgument('enable_right_ir', default_value='false'),
DeclareLaunchArgument('right_ir_qos', default_value='default'), DeclareLaunchArgument('right_ir_qos', default_value='default'),
DeclareLaunchArgument('right_ir_qos_history', default_value='default'),
DeclareLaunchArgument('right_ir_qos_depth', default_value='-1'),
DeclareLaunchArgument('right_ir_camera_info_qos', default_value='default'), DeclareLaunchArgument('right_ir_camera_info_qos', default_value='default'),
DeclareLaunchArgument('right_ir_rotation', default_value='-1'),#right_ir rotation degree : 0, 90, 180, 270 DeclareLaunchArgument('right_ir_rotation', default_value='-1'),#right_ir rotation degree : 0, 90, 180, 270
DeclareLaunchArgument('right_ir_flip', default_value='false'), DeclareLaunchArgument('right_ir_flip', default_value='false'),
@@ -161,20 +219,25 @@ def generate_launch_description():
DeclareLaunchArgument('tf_publish_rate', default_value='0.0'), DeclareLaunchArgument('tf_publish_rate', default_value='0.0'),
DeclareLaunchArgument('ir_info_url', default_value=''), DeclareLaunchArgument('ir_info_url', default_value=''),
DeclareLaunchArgument('color_info_url', default_value=''), DeclareLaunchArgument('color_info_url', default_value=''),
# Network device settings: default enumerate_net_device is set to true, which will automatically enumerate network devices # Network device settings: default enumerate_net_device is set to true, which will automatically enumerate network devices
# If you do not want to automatically enumerate network devices, # If you do not want to automatically enumerate network devices,
# you can set enumerate_net_device to false, net_device_ip to the device's IP address, and net_device_port to the default value of 8090 # you can set enumerate_net_device to false, net_device_ip to the device's IP address, and net_device_port to the default value of 8090
DeclareLaunchArgument('enumerate_net_device', default_value='false'), DeclareLaunchArgument('enumerate_net_device', default_value='true'),
DeclareLaunchArgument('net_device_ip', default_value=''), DeclareLaunchArgument('net_device_ip', default_value=''),
DeclareLaunchArgument('net_device_port', default_value='0'), DeclareLaunchArgument('net_device_port', default_value='0'),
DeclareLaunchArgument('device_access_mode', default_value='Default'), # Default, EA or CA . only for 335le
DeclareLaunchArgument('exposure_range_mode', default_value='default'),#default, ultimate or regular DeclareLaunchArgument('exposure_range_mode', default_value='default'),#default, ultimate or regular
DeclareLaunchArgument('log_level', default_value='info'), DeclareLaunchArgument('log_level', default_value='info'),
DeclareLaunchArgument('log_file_name', default_value=''), DeclareLaunchArgument('log_file_name', default_value=''),
DeclareLaunchArgument('enable_publish_extrinsic', default_value='false'), DeclareLaunchArgument('enable_publish_extrinsic', default_value='false'),
DeclareLaunchArgument('enable_d2c_viewer', default_value='false'), DeclareLaunchArgument('enable_d2c_viewer', default_value='false'),
DeclareLaunchArgument('depth_colorizer_mode', default_value='none'),
DeclareLaunchArgument('disparity_to_depth_mode', default_value='HW'), DeclareLaunchArgument('disparity_to_depth_mode', default_value='HW'),
DeclareLaunchArgument('enable_ldp', default_value='true'), DeclareLaunchArgument('enable_ldp', default_value='false'),
DeclareLaunchArgument('ldp_power_level', default_value='-1'), DeclareLaunchArgument('ldp_power_level', default_value='-1'),
DeclareLaunchArgument('enable_lrm_obstacle_distance_publish', default_value='false'),
DeclareLaunchArgument('lrm_obstacle_distance_publish_rate', default_value='10.0'),
DeclareLaunchArgument('sync_mode', default_value='standalone'), DeclareLaunchArgument('sync_mode', default_value='standalone'),
DeclareLaunchArgument('depth_delay_us', default_value='0'), DeclareLaunchArgument('depth_delay_us', default_value='0'),
DeclareLaunchArgument('color_delay_us', default_value='0'), DeclareLaunchArgument('color_delay_us', default_value='0'),
@@ -182,6 +245,7 @@ def generate_launch_description():
DeclareLaunchArgument('trigger_out_delay_us', default_value='0'), DeclareLaunchArgument('trigger_out_delay_us', default_value='0'),
DeclareLaunchArgument('trigger_out_enabled', default_value='true'), DeclareLaunchArgument('trigger_out_enabled', default_value='true'),
DeclareLaunchArgument('enable_fps_boost', default_value='false'), DeclareLaunchArgument('enable_fps_boost', default_value='false'),
DeclareLaunchArgument('software_trigger_enabled', default_value='true'),
DeclareLaunchArgument('frames_per_trigger', default_value='1'), DeclareLaunchArgument('frames_per_trigger', default_value='1'),
DeclareLaunchArgument('software_trigger_period', default_value='33'), # ms DeclareLaunchArgument('software_trigger_period', default_value='33'), # ms
DeclareLaunchArgument('enable_ptp_config', default_value='false'),#Only for Gemini 335Le DeclareLaunchArgument('enable_ptp_config', default_value='false'),#Only for Gemini 335Le
@@ -192,13 +256,20 @@ def generate_launch_description():
DeclareLaunchArgument('enable_hdr_merge', default_value='false'), DeclareLaunchArgument('enable_hdr_merge', default_value='false'),
DeclareLaunchArgument('enable_sequence_id_filter', default_value='false'), DeclareLaunchArgument('enable_sequence_id_filter', default_value='false'),
DeclareLaunchArgument('enable_threshold_filter', default_value='false'), DeclareLaunchArgument('enable_threshold_filter', default_value='false'),
DeclareLaunchArgument('enable_hardware_noise_removal_filter', default_value='false'), # Empty means that the node will not change the device's current setting.
DeclareLaunchArgument('enable_noise_removal_filter', default_value='true'), DeclareLaunchArgument('enable_hardware_noise_removal_filter', default_value=''),
DeclareLaunchArgument('enable_noise_removal_filter', default_value=''),
DeclareLaunchArgument('enable_disp_outliers_filter', default_value='false'),
DeclareLaunchArgument('enable_spatial_filter', default_value='false'), DeclareLaunchArgument('enable_spatial_filter', default_value='false'),
DeclareLaunchArgument('enable_temporal_filter', default_value='false'), DeclareLaunchArgument('enable_temporal_filter', default_value='false'),
DeclareLaunchArgument('enable_disparity_to_depth', default_value='true'), DeclareLaunchArgument('enable_disparity_to_depth', default_value='true'),
DeclareLaunchArgument('enable_hole_filling_filter', default_value='false'), DeclareLaunchArgument('enable_hole_filling_filter', default_value='false'),
DeclareLaunchArgument('enable_spatial_fast_filter', default_value='false'),
DeclareLaunchArgument('enable_spatial_moderate_filter', default_value='false'),
DeclareLaunchArgument('enable_false_positive_filter', default_value='false'), DeclareLaunchArgument('enable_false_positive_filter', default_value='false'),
DeclareLaunchArgument('enable_enhanced_depth', default_value='false'),
DeclareLaunchArgument('enhanced_depth_model_path', default_value=''),
DeclareLaunchArgument('enhanced_depth_confidence_threshold', default_value='51'),
DeclareLaunchArgument('decimation_filter_scale', default_value='-1'), DeclareLaunchArgument('decimation_filter_scale', default_value='-1'),
DeclareLaunchArgument('sequence_id_filter_id', default_value='-1'), DeclareLaunchArgument('sequence_id_filter_id', default_value='-1'),
DeclareLaunchArgument('threshold_filter_max', default_value='-1'), DeclareLaunchArgument('threshold_filter_max', default_value='-1'),
@@ -217,18 +288,26 @@ def generate_launch_description():
DeclareLaunchArgument('hdr_merge_gain_1', default_value='-1'), DeclareLaunchArgument('hdr_merge_gain_1', default_value='-1'),
DeclareLaunchArgument('hdr_merge_exposure_2', default_value='-1'), DeclareLaunchArgument('hdr_merge_exposure_2', default_value='-1'),
DeclareLaunchArgument('hdr_merge_gain_2', default_value='-1'), DeclareLaunchArgument('hdr_merge_gain_2', default_value='-1'),
DeclareLaunchArgument('spatial_fast_filter_radius', default_value='-1'),
DeclareLaunchArgument('spatial_moderate_filter_diff_threshold', default_value='-1'),
DeclareLaunchArgument('spatial_moderate_filter_magnitude', default_value='-1'),
DeclareLaunchArgument('spatial_moderate_filter_radius', default_value='-1'),
DeclareLaunchArgument('align_mode', default_value='SW'), DeclareLaunchArgument('align_mode', default_value='SW'),
DeclareLaunchArgument('align_target_stream', default_value='COLOR'),# COLOR or DEPTH DeclareLaunchArgument('align_target_stream', default_value='COLOR'),# COLOR or DEPTH
DeclareLaunchArgument('diagnostic_period', default_value='1.0'), DeclareLaunchArgument('diagnostic_period', default_value='1.0'), # seconds
DeclareLaunchArgument('enable_laser', default_value='true'), DeclareLaunchArgument('enable_laser', default_value='true'),
DeclareLaunchArgument('depth_precision', default_value=''), DeclareLaunchArgument('depth_precision', default_value=''),
DeclareLaunchArgument('device_preset', default_value='Default'), DeclareLaunchArgument('device_preset', default_value='Default'),
DeclareLaunchArgument('device_preset_version', default_value=''), DeclareLaunchArgument('device_preset_version', default_value=''),
DeclareLaunchArgument('color_preset', default_value='Default'),# color preset name reported by the device
DeclareLaunchArgument('retry_on_usb3_detection_failure', default_value='false'), DeclareLaunchArgument('retry_on_usb3_detection_failure', default_value='false'),
DeclareLaunchArgument('laser_energy_level', default_value='-1'), DeclareLaunchArgument('laser_energy_level', default_value='-1'),
DeclareLaunchArgument('enable_sync_host_time', default_value='false'), DeclareLaunchArgument('enable_sync_host_time', default_value='false'),
DeclareLaunchArgument('time_sync_period', default_value='6.0'), # seconds DeclareLaunchArgument('time_sync_period', default_value='6.0'), # seconds
DeclareLaunchArgument('time_domain', default_value='global'),# global, device, system DeclareLaunchArgument('time_domain', default_value='global'),# global, device, system
DeclareLaunchArgument('timestamp_clock_type', default_value=''),# realtime or monotonic, default is realtime.
DeclareLaunchArgument('enable_frame_drop_log', default_value='false'),
DeclareLaunchArgument('frame_timestamp_csv_file', default_value=''),
DeclareLaunchArgument('enable_color_undistortion', default_value='false'), DeclareLaunchArgument('enable_color_undistortion', default_value='false'),
DeclareLaunchArgument('enable_depth_undistortion', default_value='false'), DeclareLaunchArgument('enable_depth_undistortion', default_value='false'),
DeclareLaunchArgument('enable_left_ir_undistortion', default_value='false'), DeclareLaunchArgument('enable_left_ir_undistortion', default_value='false'),
@@ -271,9 +350,28 @@ def generate_launch_description():
DeclareLaunchArgument('laser_index0_depth_gain', default_value='16'), DeclareLaunchArgument('laser_index0_depth_gain', default_value='16'),
DeclareLaunchArgument('laser_index0_ir_brightness', default_value='60'), DeclareLaunchArgument('laser_index0_ir_brightness', default_value='60'),
DeclareLaunchArgument('laser_index0_ir_ae_max_exposure', default_value='30000'), DeclareLaunchArgument('laser_index0_ir_ae_max_exposure', default_value='30000'),
DeclareLaunchArgument('show_fps_enable', default_value='false'),
#color image transport plugins
DeclareLaunchArgument('color.image_raw.enable_pub_plugins',default_value='["image_transport/compressed", "image_transport/raw", "image_transport/theora"]'),
#depth image transport plugins
DeclareLaunchArgument('depth.image_raw.enable_pub_plugins',default_value='["image_transport/compressedDepth", "image_transport/raw"]'),
#infra1
DeclareLaunchArgument('left_ir.image_raw.enable_pub_plugins',default_value='["image_transport/compressed", "image_transport/raw", "image_transport/theora"]'),
#infra2
DeclareLaunchArgument('right_ir.image_raw.enable_pub_plugins',default_value='["image_transport/compressed", "image_transport/raw", "image_transport/theora"]'),
# Force IP parameters
DeclareLaunchArgument("force_ip_enable", default_value="false"), # Whether to enable Force IP function
DeclareLaunchArgument("force_ip_mac", default_value=""), # If multiple cameras are connected, specify target MAC (e.g. "54:14:FD:06:07:DA")
DeclareLaunchArgument("force_ip_address", default_value="192.168.1.10"), # Static IP address to assign
DeclareLaunchArgument("force_ip_subnet_mask", default_value="255.255.255.0"), # Subnet mask used for static IP
DeclareLaunchArgument("force_ip_gateway", default_value="192.168.1.1"), # Gateway address used for static IP
DeclareLaunchArgument('intra_camera_sync_reference', default_value=""),#Start, Middle or End
DeclareLaunchArgument('attach_to_shared_component_container', default_value='false'),
DeclareLaunchArgument('component_container_name', default_value='orbbec_container'),
DeclareLaunchArgument('use_intra_process_comms', default_value='false'), DeclareLaunchArgument('use_intra_process_comms', default_value='false'),
DeclareLaunchArgument('attach_component_container_enable', default_value='false'),
DeclareLaunchArgument('attach_component_container_name', default_value='orbbec_container'),
] ]
@@ -295,32 +393,37 @@ def generate_launch_description():
) )
] ]
else: else:
attach_to_shared_component_container_arg = LaunchConfiguration('attach_to_shared_component_container', default=False) attach_to_shared_container = LaunchConfiguration(
component_container_name_arg = LaunchConfiguration('component_container_name', default='orbbec_container') "attach_to_shared_component_container"
orbbec_container = Node(
name=component_container_name_arg,
package='rclcpp_components',
executable='component_container_mt',
output='screen',
condition=UnlessCondition(attach_to_shared_component_container_arg)
) )
return [ container_name = LaunchConfiguration("component_container_name")
orbbec_container, camera_container = Node(
LoadComposableNodes( package="rclcpp_components",
target_container=component_container_name_arg, executable="component_container_mt",
composable_node_descriptions=[ name=container_name,
output="screen",
condition=UnlessCondition(attach_to_shared_container),
)
camera_component = LoadComposableNodes(
target_container=container_name,
composable_node_descriptions=[
ComposableNode( ComposableNode(
namespace=LaunchConfiguration("camera_name"), namespace=LaunchConfiguration("camera_name"),
name=LaunchConfiguration("camera_name"), package="orbbec_camera",
package='orbbec_camera', plugin="orbbec_camera::OBCameraNodeDriver",
plugin='orbbec_camera::OBCameraNodeDriver', name=LaunchConfiguration("camera_name"),
parameters=params, parameters=params,
extra_arguments=[{'use_intra_process_comms': LaunchConfiguration("use_intra_process_comms")}], extra_arguments=[
{
"use_intra_process_comms": LaunchConfiguration(
"use_intra_process_comms"
)
}
],
) )
] ],
) )
] return [camera_container, camera_component]
return LaunchDescription( return LaunchDescription(
args + [ args + [
@@ -0,0 +1,65 @@
import os
from ament_index_python.packages import get_package_share_directory
from launch import LaunchDescription
from launch.actions import IncludeLaunchDescription, GroupAction, TimerAction
from launch.launch_description_sources import PythonLaunchDescriptionSource
from launch_ros.actions import Node
def generate_launch_description():
# Include launch files
package_dir = get_package_share_directory("orbbec_camera")
launch_file_dir = os.path.join(
package_dir,
"examples",
"multi_camera_shared_container",
)
component_container_name = "shared_orbbec_container"
shared_orbbec_container = Node(
name=component_container_name,
package="rclcpp_components",
executable="component_container_mt",
output="screen",
)
launch1_include = IncludeLaunchDescription(
PythonLaunchDescriptionSource(
os.path.join(launch_file_dir, "gemini_330_series_shared_container.launch.py")
),
launch_arguments={
"camera_name": "camera_01",
"usb_port": "2-1",
"device_num": "2",
"attach_to_shared_component_container": "true",
"component_container_name": component_container_name,
"use_intra_process_comms": "true",
}.items(),
)
launch2_include = IncludeLaunchDescription(
PythonLaunchDescriptionSource(
os.path.join(launch_file_dir, "gemini_330_series_shared_container.launch.py")
),
launch_arguments={
"camera_name": "camera_02",
"usb_port": "2-2",
"device_num": "2",
"attach_to_shared_component_container": "true",
"component_container_name": component_container_name,
"use_intra_process_comms": "true",
}.items(),
)
# If you need more cameras, just add more launch_include here, and change the usb_port and device_num
# Launch description
ld = LaunchDescription(
[
shared_orbbec_container,
TimerAction(period=0.0, actions=[GroupAction([launch1_include])]),
TimerAction(period=2.0, actions=[GroupAction([launch2_include])]),
]
)
return ld