Skip to content

Commit 279d97d

Browse files
Add a camera synchronization node (#13)
* Adding Test Infra (#9) * adds test package * documentation on the node * pre-commit * ci failing * Initial MultiCamera node * Added Readme * Added yaml launch files and yaml config * Fix to pass pre-commit * Added lifecycle support for ROS Rolling - This is required since compressed image subs only work with lifecycle in rolling - Additional fixed package test errors * Added developing markdown and removed main from camera_sync component --------- Co-authored-by: Eddy Zhou <71026430+Edwardius@users.noreply.github.com>
1 parent b79face commit 279d97d

File tree

14 files changed

+1284
-1
lines changed

14 files changed

+1284
-1
lines changed

deep_msgs/CMakeLists.txt

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
# Copyright (c) 2025-present WATonomous. All rights reserved.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
cmake_minimum_required(VERSION 3.8)
15+
project(deep_msgs)
16+
17+
if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")
18+
add_compile_options(-Wall -Wextra -Wpedantic)
19+
endif()
20+
21+
# find dependencies
22+
find_package(ament_cmake REQUIRED)
23+
find_package(rclcpp REQUIRED)
24+
find_package(std_msgs REQUIRED)
25+
find_package(sensor_msgs REQUIRED)
26+
find_package(rosidl_default_generators REQUIRED)
27+
28+
rosidl_generate_interfaces(${PROJECT_NAME}
29+
"msg/MultiImage.msg"
30+
"msg/MultiImageRaw.msg"
31+
DEPENDENCIES std_msgs sensor_msgs
32+
)
33+
34+
35+
ament_package()

deep_msgs/msg/MultiImage.msg

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# MultiImage.msg
2+
# A message that carries multiple compressed images together
3+
4+
std_msgs/Header header
5+
sensor_msgs/CompressedImage[] images

deep_msgs/msg/MultiImageRaw.msg

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# MultiImageRaw.msg
2+
std_msgs/Header header
3+
sensor_msgs/Image[] images

deep_msgs/package.xml

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<?xml version="1.0"?>
2+
<package format="3">
3+
<name>deep_msgs</name>
4+
<version>0.0.0</version>
5+
<description>Package for deep ros specific msgs</description>
6+
<maintainer email="lucas.reljic@gmail.com">lucas</maintainer>
7+
<license>Apache 2.0</license>
8+
9+
<buildtool_depend>ament_cmake</buildtool_depend>
10+
<depend>std_msgs</depend>
11+
<depend>sensor_msgs</depend>
12+
<build_depend>rosidl_default_generators</build_depend>
13+
<exec_depend>rosidl_default_runtime</exec_depend>
14+
<member_of_group>rosidl_interface_packages</member_of_group>
15+
<export>
16+
<build_type>ament_cmake</build_type>
17+
</export>
18+
19+
</package>

deep_test/CMakeLists.txt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@
1111
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
14-
1514
cmake_minimum_required(VERSION 3.22)
1615
project(deep_test)
1716

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
# Copyright (c) 2025-present WATonomous. All rights reserved.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
cmake_minimum_required(VERSION 3.8)
15+
project(camera_sync)
16+
17+
if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")
18+
add_compile_options(-Wall -Wextra -Wpedantic)
19+
endif()
20+
21+
# find dependencies
22+
find_package(ament_cmake REQUIRED)
23+
find_package(rclcpp REQUIRED)
24+
find_package(rclcpp_lifecycle REQUIRED)
25+
find_package(rclcpp_components REQUIRED)
26+
find_package(lifecycle_msgs REQUIRED)
27+
find_package(sensor_msgs REQUIRED)
28+
find_package(cv_bridge REQUIRED)
29+
find_package(message_filters REQUIRED)
30+
find_package(image_transport REQUIRED)
31+
find_package(deep_msgs REQUIRED)
32+
33+
# Create include directory
34+
include_directories(include)
35+
36+
# Define lifecycle node support based on ROS distro
37+
get_filename_component(ROS_DISTRO_NAME "$ENV{ROS_DISTRO}" NAME)
38+
if(ROS_DISTRO_NAME STREQUAL "rolling") # Currently only Rolling supports lifecycle nodes with compressed image subs
39+
add_definitions(-DUSE_LIFECYCLE_NODE=1)
40+
else()
41+
add_definitions(-DUSE_LIFECYCLE_NODE=0)
42+
endif()
43+
44+
# Add the multi-camera sync library for component
45+
add_library(multi_camera_sync_component SHARED src/multi_camera_sync_node.cpp)
46+
target_link_libraries(multi_camera_sync_component
47+
rclcpp::rclcpp
48+
rclcpp_lifecycle::rclcpp_lifecycle
49+
rclcpp_components::component_manager
50+
lifecycle_msgs::lifecycle_msgs__rosidl_typesupport_cpp
51+
sensor_msgs::sensor_msgs__rosidl_typesupport_cpp
52+
cv_bridge::cv_bridge
53+
message_filters::message_filters
54+
image_transport::image_transport
55+
deep_msgs::deep_msgs__rosidl_typesupport_cpp
56+
)
57+
58+
# Register the component
59+
rclcpp_components_register_nodes(multi_camera_sync_component "camera_sync::MultiCameraSyncNode")
60+
set(node_plugins "${node_plugins}camera_sync::MultiCameraSyncNode;$<TARGET_FILE:multi_camera_sync_component>\n")
61+
62+
63+
64+
# Install executables and libraries
65+
install(TARGETS
66+
multi_camera_sync_component
67+
ARCHIVE DESTINATION lib
68+
LIBRARY DESTINATION lib
69+
RUNTIME DESTINATION bin
70+
)
71+
72+
73+
74+
# Install include directory
75+
install(DIRECTORY include/
76+
DESTINATION include
77+
)
78+
79+
# Install launch files if any
80+
install(DIRECTORY launch/
81+
DESTINATION share/${PROJECT_NAME}/launch
82+
FILES_MATCHING PATTERN "*.py" PATTERN "*.yaml" PATTERN "*.md"
83+
)
84+
85+
# Install config files
86+
install(DIRECTORY config/
87+
DESTINATION share/${PROJECT_NAME}/config
88+
FILES_MATCHING PATTERN "*.yaml"
89+
)
90+
91+
92+
ament_package()
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
# Camera Sync Development Notes
2+
3+
## Known Issues & Limitations
4+
5+
### 1. Lifecycle Node Incompatibility (ROS Humble, Jazzy and Earlier)
6+
**Problem**: Lifecycle nodes cannot subscribe to compressed images in ROS Humble and earlier distributions.
7+
- **Root Cause**: Missing lifecycle support in `image_transport` for compressed image subscriptions
8+
- **Solution**: There is a Macro used throughout the code which specifies if it is with the lifecycle node or not. This is crucial to keep in mind when developing as breaking changes can occur across ROS distros if it's not compatible with a Lifecycle Node or vice versa. It is best to test with both versions to ensure compatibility.
9+
10+
The Macro:
11+
12+
```cpp
13+
#if ROS_DISTRO_NAME STREQUAL "rolling"
14+
add_definitions(-DUSE_LIFECYCLE_NODE=1)
15+
#else
16+
add_definitions(-DUSE_LIFECYCLE_NODE=0) // Disable for Humble/earlier
17+
#endif
18+
```
19+
20+
### 2. Message Filters Synchronization Limitation
21+
**Problem**: `message_filters::Synchronizer` requires compile-time known number of topics.
22+
- **Limitation**: Cannot dynamically sync N cameras - must handle 2-6 cameras with separate synchronizers
23+
- **Implementation**: Switch statement creates different sync policies for each camera count
24+
25+
## Architecture Notes
26+
- **Component-only**: No standalone executable - use component loading only which is ideal to allow for IPC (zero-copy)
27+
- **Dual Mode**: Supports both raw and compressed image synchronization

deep_tools/camera_sync/README.md

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
# Multi-Camera Synchronization Node
2+
3+
A ROS2 node that uses message filters to time-synchronize N camera image messages, supporting both compressed and raw images.
4+
5+
## Features
6+
7+
- **Flexible camera count**: Supports 2-6 cameras simultaneously
8+
- **Image format support**: Works with both `sensor_msgs/Image` (raw) and `sensor_msgs/CompressedImage`
9+
- **Configurable synchronization**: Adjustable time tolerance and queue sizes
10+
- **Real-time monitoring**: Synchronization statistics and rate monitoring
11+
- **Easy integration**: Standard ROS2 node with parameter-based configuration
12+
13+
## Parameters
14+
15+
| Parameter | Type | Default | Description |
16+
|-----------|------|---------|-------------|
17+
| `camera_topics` | string[] | `[]` | List of camera image topics to synchronize |
18+
| `camera_names` | string[] | `[]` | Names for cameras (auto-generated if empty) |
19+
| `use_compressed` | bool | `false` | Use compressed images instead of raw RGB |
20+
| `sync_tolerance_ms` | double | `50.0` | Max time difference for sync (milliseconds) |
21+
| `queue_size` | int | `10` | Message filter queue size |
22+
| `publish_sync_info` | bool | `true` | Publish synchronization statistics |
23+
24+
## Usage
25+
26+
### Parameter usage:
27+
All configuration changes can be passed through CLI. Here are some example arguments:
28+
29+
```bash
30+
-p camera_topics:="['/camera1/image_raw', '/camera2/image_raw']" \
31+
-p camera_names:="['left_camera', 'right_camera']" \
32+
-p use_compressed:=true \
33+
-p sync_tolerance_ms:=30.0
34+
```
35+
36+
## How It Works
37+
38+
The node uses ROS2 message filters with approximate time synchronization policy to match images from multiple cameras based on their timestamps. Key components:
39+
40+
1. **Message Subscribers**: Creates subscribers for each camera topic
41+
2. **Synchronizer**: Uses `message_filters::sync_policies::ApproximateTime` to match messages
42+
3. **Callback & Publishing**: Creates MultiImage msgs with timestamp to batch the camera images together
43+
4. **Statistics**: Tracks synchronization rate and timing spread
44+
45+
### Synchronization Logic
46+
47+
- Messages are considered synchronized if their timestamps are within `sync_tolerance_ms`
48+
- The synchronizer maintains a queue of recent messages from each camera
49+
- When a valid sync set is found, all cameras' images are processed together
50+
- Unmatched messages are eventually dropped when they exceed the age penalty
51+
52+
## Monitoring
53+
54+
The node provides several ways to monitor synchronization performance:
55+
56+
1. **Console logs**: Periodic sync rate and timing statistics
57+
2. **ROS2 parameters**: Runtime inspection of configuration
58+
3. **Debug output**: Detailed timestamp information (when debug logging enabled)
59+
60+
Example log output:
61+
62+
```
63+
[INFO] [multi_camera_sync]: Sync #500: compressed images from 4 cameras, spread: 12.3 ms, rate: 29.8 Hz
64+
```
65+
66+
## Dependencies
67+
68+
- `rclcpp` - ROS2 C++ client library
69+
- `sensor_msgs` - Standard sensor message types
70+
- `message_filters` - Time synchronization utilities
71+
- `cv_bridge` - OpenCV integration (for future extensions)
72+
- `image_transport` - Efficient image transmission
73+
74+
## Limitations
75+
76+
- Maximum 6 cameras supported (can be extended by adding more sync policies)
77+
- Uses approximate time synchronization (not exact)
78+
- All cameras must use the same image message type (raw or compressed)
79+
- Requires reasonably synchronized system clocks across camera sources
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
# Copyright (c) 2025-present WATonomous. All rights reserved.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http:www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
16+
# Configuration file for multi-camera synchronization node
17+
#
18+
# This file contains the parameters for the multi_camera_sync_node.
19+
# You can modify these values to suit your camera setup.
20+
21+
---
22+
multi_camera_sync:
23+
ros__parameters:
24+
# Camera topics to synchronize
25+
# For raw images, use topics like: ["/camera1/image_raw", "/camera2/image_raw"]
26+
# For compressed images, use topics like: ["/camera1/image_raw/compressed", "/camera2/image_raw/compressed"]
27+
camera_topics: ["/front/image_compressed", "/back/image_compressed", "/port/image_compressed"]
28+
29+
# Optional names for the cameras (if not provided, will auto-generate camera_1, camera_2, etc.)
30+
camera_names: ["front", "back", "port"]
31+
32+
# Whether to use compressed images (sensor_msgs/CompressedImage) instead of raw (sensor_msgs/Image)
33+
# Set to true if your camera topics publish compressed images
34+
use_compressed: true
35+
36+
# Maximum time difference in milliseconds for message synchronization
37+
# Larger values allow more tolerance but may reduce temporal accuracy
38+
sync_tolerance_ms: 33.0
39+
40+
# Queue size for message filters
41+
# Larger values can handle more variable frame rates but use more memory
42+
queue_size: 10
43+
44+
# Whether to publish synchronization information and statistics
45+
publish_sync_info: true
46+
47+
# IPC-specific optimizations
48+
use_intra_process_comms: true
49+
50+
# QoS settings optimized for camera data with IPC
51+
qos_depth: 5
52+
qos_reliability: "reliable" # reliable or "best_effort" for higher throughput
53+
qos_durability: "volatile"
54+
qos_history: "keep_last"

0 commit comments

Comments
 (0)