Add multi_camera_synced_verification_tool example
@@ -15,7 +15,7 @@ interleave_skip_index: 1
|
||||
time_domain: "global" # global, device, system
|
||||
enable_sync_host_time: true
|
||||
|
||||
trigger_out_enabled: true
|
||||
trigger_out_enabled: false
|
||||
frames_per_trigger: 0
|
||||
software_trigger_period: 0
|
||||
|
||||
@@ -31,7 +31,7 @@ color_exposure: 30 # 3ms
|
||||
color_gain: 16 # -1 default
|
||||
|
||||
# depth params
|
||||
enable_depth: true
|
||||
enable_depth: false
|
||||
depth_width: 0
|
||||
depth_height: 0
|
||||
depth_fps: 0
|
||||
@@ -39,7 +39,7 @@ depth_format: "Y16"
|
||||
|
||||
|
||||
#left ir params
|
||||
enable_left_ir: false
|
||||
enable_left_ir: true
|
||||
left_ir_width: 0
|
||||
left_ir_height: 0
|
||||
left_ir_fps: 0
|
||||
|
||||
@@ -1,17 +1,13 @@
|
||||
{
|
||||
"save_rgbir_params": {
|
||||
"time_domain": "device",
|
||||
"time_domain": "global",
|
||||
"usb_ports": [
|
||||
"2-1",
|
||||
"2-4",
|
||||
"2-7",
|
||||
"2-3"
|
||||
],
|
||||
"camera_name": [
|
||||
"G330_0",
|
||||
"G330_1",
|
||||
"G330_2",
|
||||
"G330_3"
|
||||
"camera_01",
|
||||
"camera_02"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
# multi_camera_synced_verification_tool
|
||||
|
||||
> This article will introduce how to verify the synchronization accuracy of multi-camera synchronization.
|
||||
>
|
||||
> First, please see how to use [multi_camera_synced](../multi_camera_synced/README.MD).
|
||||
|
||||
## Directory Structure
|
||||
|
||||
```plaintext
|
||||
.
|
||||
├── multicamera_sync/
|
||||
│ ├── output/
|
||||
│ ├──20250218102900/
|
||||
│ ├──DevicesInfo.txt
|
||||
│ ├──StreamProfileInfo.txt
|
||||
│ ├── Python/
|
||||
│ ├──Config.ini
|
||||
├── gemini_330_series_synced_verify.launch.py
|
||||
├── multi_camera_synced_verify.launch.py
|
||||
```
|
||||
|
||||
### multicamera_sync
|
||||
|
||||
#### output
|
||||
|
||||
> The ouput folder is the folder where the camera pictures are output
|
||||
|
||||
In the output example provided
|
||||
|
||||
* `20250218102900` : Represents the camera image information collected at 10:29:00 on February 18, 2025.
|
||||
* `TotalModeFrames` : Camera image information storage directory.
|
||||
* `results-2025-02-25_163648` : The result after analyzing and matching the image information of TotalModeFrames.
|
||||
* `DevicesInfo.txt` : Camera equipment basic information (need to be modified).
|
||||
* `StreamProfileInfo.txt` : Camera video stream information (no need to modify).
|
||||
|
||||
#### Python
|
||||
|
||||
Config.ini : Configuration file for Python analysis script (need to be modified).
|
||||
|
||||
## Preparation for operation
|
||||
|
||||
### save_rgbir node
|
||||
|
||||
Edit multi_camera_synced_verify.launch.py and fill in the activated camera device,we can find that save_rgbir is started at the end.
|
||||
|
||||
```python
|
||||
# Launch description
|
||||
ld = LaunchDescription(
|
||||
[
|
||||
shared_orbbec_container,
|
||||
TimerAction(period=0.0, actions=[GroupAction([launch2_include])]),
|
||||
TimerAction(period=2.0, actions=[GroupAction([launch1_include])]),
|
||||
# The primary camera should be launched at last
|
||||
TimerAction(period=6.0, actions=[GroupAction([save_rgbir])]),
|
||||
# save_rgbir is synced verification tool
|
||||
]
|
||||
)
|
||||
|
||||
return ld
|
||||
```
|
||||
|
||||
save_rgbir is a tool for saving images. The configuration file of this tool is in [multi_save_rgbir_params.json](../../config/tools/multisavergbir/multi_save_rgbir_params.json).
|
||||
|
||||
```json
|
||||
{
|
||||
"save_rgbir_params": {
|
||||
"time_domain": "global",
|
||||
"usb_ports": [
|
||||
"2-1",
|
||||
"2-3"
|
||||
],
|
||||
"camera_name": [
|
||||
"camera_01",
|
||||
"camera_02"
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
* `time_domain` : Timestamp Type
|
||||
* `usb_ports` : "primary", "secondary 1", "secondary 2", "secondary 3", fill in as many usb_ports as there are cameras
|
||||
* `camera_name` : The name of the camera setting, for example: camera_01
|
||||
|
||||
### DevicesInfo.txt
|
||||
|
||||
Edit DevicesInfo.txt. Only the `primarySerialNumber`, `index` and `serialNumber` parameters need to be changed. Other parameters do not need to be changed.Refer to the example of [20250218102900](./multicamera_sync/output/20250218102900).
|
||||
|
||||
* `primarySerialNumber` : The SN serial number of the primary camera
|
||||
* `index` : Camera index
|
||||
* `serialNumber` : The SN serial number of the camera
|
||||
|
||||
### Config.ini
|
||||
|
||||
Edit Config.ini.Modify `frameRate` and `tspRangeThreshold`.
|
||||
|
||||
## Run this example
|
||||
|
||||
### Run launch and save camera pictures
|
||||
|
||||
* Frst terminal
|
||||
|
||||
```bash
|
||||
ros2 launch orbbec_camera multi_camera_synced_verify.launch.py
|
||||
```
|
||||
|
||||
* Second terminal
|
||||
|
||||
```bash
|
||||
ros2 service call /save_rgbir/start_capture orbbec_camera_msgs/srv/SetInt32 '{data: 100}'
|
||||
```
|
||||
|
||||
When the terminal displays "over", the image is saved.A new multicamera_sync folder will be generated under the workspace.
|
||||
|
||||
### Analyzing camera image data
|
||||
|
||||
You need to copy the modified [Pyhotn folder](./Python) to the new multi_camera_synced subdirectory, and copy the modified [DevicesInfo.txt ](./multicamera_sync/output/20250218102900/DevicesInfo.txt)and [StreamProfileInfo.txt](./multicamera_sync/output/20250218102900/StreamProfileInfo.txt) to the same level directory as the TotalModeFrames folder.
|
||||
|
||||
* Finally everything is ready, run the python script
|
||||
|
||||
```bash
|
||||
cd multicamera_sync/Python
|
||||
python3 SyncFramesMain.py
|
||||
```
|
||||
|
||||
After the operation is successful, you can view the synchronization effect in the r`esults folder`
|
||||
@@ -0,0 +1,265 @@
|
||||
import os
|
||||
import yaml
|
||||
from launch import LaunchDescription
|
||||
from launch.actions import DeclareLaunchArgument, OpaqueFunction, GroupAction
|
||||
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_ros.actions import LoadComposableNodes
|
||||
|
||||
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 load_parameters(context, args):
|
||||
default_params = {arg.name: LaunchConfiguration(arg.name).perform(context) for arg in args}
|
||||
config_file_path = LaunchConfiguration('config_file_path').perform(context)
|
||||
if config_file_path:
|
||||
yaml_params = load_yaml(config_file_path)
|
||||
default_params = merge_params(default_params, yaml_params)
|
||||
skip_convert = {'config_file_path', 'usb_port', 'serial_number'}
|
||||
return {
|
||||
key: (value if key in skip_convert else convert_value(value))
|
||||
for key, value in default_params.items()
|
||||
}
|
||||
|
||||
|
||||
def generate_launch_description():
|
||||
args = [
|
||||
DeclareLaunchArgument('camera_name', default_value='camera'),
|
||||
DeclareLaunchArgument('depth_registration', default_value='true'),
|
||||
DeclareLaunchArgument('serial_number', default_value=''),
|
||||
DeclareLaunchArgument('usb_port', default_value=''),
|
||||
DeclareLaunchArgument('device_num', default_value='1'),
|
||||
DeclareLaunchArgument('uvc_backend', default_value='libuvc'),#libuvc or v4l2
|
||||
DeclareLaunchArgument('point_cloud_qos', default_value='default'),
|
||||
DeclareLaunchArgument('enable_point_cloud', default_value='true'),
|
||||
DeclareLaunchArgument('enable_colored_point_cloud', default_value='false'),
|
||||
DeclareLaunchArgument('cloud_frame_id', default_value=''),
|
||||
DeclareLaunchArgument('connection_delay', default_value='10'),
|
||||
DeclareLaunchArgument('color_width', default_value='0'),
|
||||
DeclareLaunchArgument('color_height', default_value='0'),
|
||||
DeclareLaunchArgument('color_fps', default_value='0'),
|
||||
DeclareLaunchArgument('color_format', default_value='ANY'),
|
||||
DeclareLaunchArgument('enable_color', default_value='true'),
|
||||
DeclareLaunchArgument('color_qos', default_value='default'),
|
||||
DeclareLaunchArgument('color_camera_info_qos', default_value='default'),
|
||||
DeclareLaunchArgument('enable_color_auto_exposure', default_value='true'),
|
||||
DeclareLaunchArgument('color_exposure', default_value='-1'),
|
||||
DeclareLaunchArgument('color_gain', default_value='-1'),
|
||||
DeclareLaunchArgument('enable_color_auto_white_balance', default_value='true'),
|
||||
DeclareLaunchArgument('color_white_balance', default_value='-1'),
|
||||
DeclareLaunchArgument('color_ae_max_exposure', default_value='-1'),
|
||||
DeclareLaunchArgument('color_brightness', default_value='-1'),
|
||||
DeclareLaunchArgument('depth_width', default_value='0'),
|
||||
DeclareLaunchArgument('depth_height', default_value='0'),
|
||||
DeclareLaunchArgument('depth_fps', default_value='0'),
|
||||
DeclareLaunchArgument('depth_format', default_value='ANY'),
|
||||
DeclareLaunchArgument('enable_depth', default_value='true'),
|
||||
DeclareLaunchArgument('depth_qos', default_value='default'),
|
||||
DeclareLaunchArgument('depth_camera_info_qos', default_value='default'),
|
||||
DeclareLaunchArgument('left_ir_width', default_value='0'),
|
||||
DeclareLaunchArgument('left_ir_height', default_value='0'),
|
||||
DeclareLaunchArgument('left_ir_fps', default_value='0'),
|
||||
DeclareLaunchArgument('left_ir_format', default_value='ANY'),
|
||||
DeclareLaunchArgument('enable_left_ir', default_value='false'),
|
||||
DeclareLaunchArgument('left_ir_qos', default_value='default'),
|
||||
DeclareLaunchArgument('left_ir_camera_info_qos', default_value='default'),
|
||||
DeclareLaunchArgument('right_ir_width', default_value='0'),
|
||||
DeclareLaunchArgument('right_ir_height', default_value='0'),
|
||||
DeclareLaunchArgument('right_ir_fps', default_value='0'),
|
||||
DeclareLaunchArgument('right_ir_format', default_value='ANY'),
|
||||
DeclareLaunchArgument('enable_right_ir', default_value='false'),
|
||||
DeclareLaunchArgument('right_ir_qos', default_value='default'),
|
||||
DeclareLaunchArgument('right_ir_camera_info_qos', default_value='default'),
|
||||
DeclareLaunchArgument('enable_ir_auto_exposure', default_value='true'),
|
||||
DeclareLaunchArgument('ir_exposure', default_value='-1'),
|
||||
DeclareLaunchArgument('ir_gain', default_value='-1'),
|
||||
DeclareLaunchArgument('ir_ae_max_exposure', default_value='-1'),
|
||||
DeclareLaunchArgument('ir_brightness', default_value='-1'),
|
||||
DeclareLaunchArgument('enable_sync_output_accel_gyro', default_value='false'),
|
||||
DeclareLaunchArgument('enable_accel', default_value='false'),
|
||||
DeclareLaunchArgument('accel_rate', default_value='200hz'),
|
||||
DeclareLaunchArgument('accel_range', default_value='4g'),
|
||||
DeclareLaunchArgument('enable_gyro', default_value='false'),
|
||||
DeclareLaunchArgument('gyro_rate', default_value='200hz'),
|
||||
DeclareLaunchArgument('gyro_range', default_value='1000dps'),
|
||||
DeclareLaunchArgument('liner_accel_cov', default_value='0.01'),
|
||||
DeclareLaunchArgument('angular_vel_cov', default_value='0.01'),
|
||||
DeclareLaunchArgument('publish_tf', default_value='true'),
|
||||
DeclareLaunchArgument('tf_publish_rate', default_value='0.0'),
|
||||
DeclareLaunchArgument('ir_info_url', default_value=''),
|
||||
DeclareLaunchArgument('color_info_url', default_value=''),
|
||||
DeclareLaunchArgument('log_level', default_value='none'),
|
||||
DeclareLaunchArgument('enable_publish_extrinsic', default_value='false'),
|
||||
DeclareLaunchArgument('enable_d2c_viewer', default_value='false'),
|
||||
DeclareLaunchArgument('enable_hardware_d2d', default_value='true'),
|
||||
DeclareLaunchArgument('enable_ldp', default_value='true'),
|
||||
DeclareLaunchArgument('enable_soft_filter', default_value='true'),
|
||||
DeclareLaunchArgument('soft_filter_max_diff', default_value='-1'),
|
||||
DeclareLaunchArgument('soft_filter_speckle_size', default_value='-1'),
|
||||
DeclareLaunchArgument('sync_mode', default_value='standalone'),
|
||||
DeclareLaunchArgument('depth_delay_us', default_value='0'),
|
||||
DeclareLaunchArgument('color_delay_us', default_value='0'),
|
||||
DeclareLaunchArgument('trigger2image_delay_us', default_value='0'),
|
||||
DeclareLaunchArgument('trigger_out_delay_us', default_value='0'),
|
||||
DeclareLaunchArgument('trigger_out_enabled', default_value='true'),
|
||||
DeclareLaunchArgument('frames_per_trigger', default_value='2'),
|
||||
DeclareLaunchArgument('software_trigger_period', default_value='33'), # ms
|
||||
DeclareLaunchArgument('enable_frame_sync', default_value='true'),
|
||||
DeclareLaunchArgument('ordered_pc', default_value='false'),
|
||||
DeclareLaunchArgument('enable_depth_scale', default_value='true'),
|
||||
DeclareLaunchArgument('enable_decimation_filter', default_value='false'),
|
||||
DeclareLaunchArgument('enable_hdr_merge', default_value='false'),
|
||||
DeclareLaunchArgument('enable_sequence_id_filter', default_value='false'),
|
||||
DeclareLaunchArgument('enable_threshold_filter', default_value='false'),
|
||||
DeclareLaunchArgument('enable_hardware_noise_removal_filter', default_value='true'),
|
||||
DeclareLaunchArgument('enable_noise_removal_filter', default_value='true'),
|
||||
DeclareLaunchArgument('enable_spatial_filter', default_value='false'),
|
||||
DeclareLaunchArgument('enable_temporal_filter', default_value='false'),
|
||||
DeclareLaunchArgument('enable_hole_filling_filter', default_value='false'),
|
||||
DeclareLaunchArgument('decimation_filter_scale', default_value='-1'),
|
||||
DeclareLaunchArgument('sequence_id_filter_id', default_value='-1'),
|
||||
DeclareLaunchArgument('threshold_filter_max', default_value='-1'),
|
||||
DeclareLaunchArgument('threshold_filter_min', default_value='-1'),
|
||||
DeclareLaunchArgument('noise_removal_filter_min_diff', default_value='256'),
|
||||
DeclareLaunchArgument('noise_removal_filter_max_size', default_value='80'),
|
||||
DeclareLaunchArgument('spatial_filter_alpha', default_value='-1.0'),
|
||||
DeclareLaunchArgument('spatial_filter_diff_threshold', default_value='-1'),
|
||||
DeclareLaunchArgument('spatial_filter_magnitude', default_value='-1'),
|
||||
DeclareLaunchArgument('spatial_filter_radius', default_value='-1'),
|
||||
DeclareLaunchArgument('temporal_filter_diff_threshold', default_value='-1.0'),
|
||||
DeclareLaunchArgument('temporal_filter_weight', default_value='-1.0'),
|
||||
DeclareLaunchArgument('hole_filling_filter_mode', default_value=''),
|
||||
DeclareLaunchArgument('hdr_merge_exposure_1', default_value='-1'),
|
||||
DeclareLaunchArgument('hdr_merge_gain_1', default_value='-1'),
|
||||
DeclareLaunchArgument('hdr_merge_exposure_2', default_value='-1'),
|
||||
DeclareLaunchArgument('hdr_merge_gain_2', default_value='-1'),
|
||||
DeclareLaunchArgument('align_mode', default_value='SW'),
|
||||
DeclareLaunchArgument('diagnostic_period', default_value='1.0'),
|
||||
DeclareLaunchArgument('enable_laser', default_value='true'),
|
||||
DeclareLaunchArgument('depth_precision', default_value=''),
|
||||
DeclareLaunchArgument('device_preset', default_value='Default'),
|
||||
DeclareLaunchArgument('retry_on_usb3_detection_failure', default_value='false'),
|
||||
DeclareLaunchArgument('laser_energy_level', default_value='-1'),
|
||||
DeclareLaunchArgument('enable_sync_host_time', default_value='true'),
|
||||
DeclareLaunchArgument('time_domain', default_value='global'),# global, device, system
|
||||
DeclareLaunchArgument('enable_color_undistortion', default_value='false'),
|
||||
DeclareLaunchArgument('config_file_path', default_value=''),
|
||||
DeclareLaunchArgument('enable_heartbeat', default_value='false'),
|
||||
DeclareLaunchArgument('gmsl_trigger_fps', default_value='3000'),
|
||||
DeclareLaunchArgument('enable_gmsl_trigger', default_value='false'),
|
||||
DeclareLaunchArgument('disparity_range_mode', default_value='-1'),
|
||||
DeclareLaunchArgument('disparity_search_offset', default_value='-1'),
|
||||
DeclareLaunchArgument('disparity_offset_config', default_value='false'),
|
||||
DeclareLaunchArgument('offset_index0', default_value='-1'),
|
||||
DeclareLaunchArgument('offset_index1', default_value='-1'),
|
||||
DeclareLaunchArgument('frame_aggregate_mode', default_value='ANY'), # full_frame、color_frame、ANY or disable
|
||||
DeclareLaunchArgument('interleave_ae_mode', default_value='laser'), # 'hdr' or 'laser'
|
||||
DeclareLaunchArgument('interleave_frame_enable', default_value='false'),
|
||||
DeclareLaunchArgument('interleave_skip_enable', default_value='false'),
|
||||
DeclareLaunchArgument('interleave_skip_index', default_value='1'), # 0:skip pattern ir 1: skip flood ir
|
||||
|
||||
DeclareLaunchArgument('hdr_index1_laser_control', default_value='1'),#interleave_hdr_param
|
||||
DeclareLaunchArgument('hdr_index1_depth_exposure', default_value='1'),
|
||||
DeclareLaunchArgument('hdr_index1_depth_gain', default_value='16'),
|
||||
DeclareLaunchArgument('hdr_index1_ir_brightness', default_value='20'),
|
||||
DeclareLaunchArgument('hdr_index1_ir_ae_max_exposure', default_value='2000'),
|
||||
DeclareLaunchArgument('hdr_index0_laser_control', default_value='1'),
|
||||
DeclareLaunchArgument('hdr_index0_depth_exposure', default_value='7500'),
|
||||
DeclareLaunchArgument('hdr_index0_depth_gain', default_value='16'),
|
||||
DeclareLaunchArgument('hdr_index0_ir_brightness', default_value='60'),
|
||||
DeclareLaunchArgument('hdr_index0_ir_ae_max_exposure', default_value='10000'),
|
||||
|
||||
DeclareLaunchArgument('laser_index1_laser_control', default_value='0'),#interleave_laser_param
|
||||
DeclareLaunchArgument('laser_index1_depth_exposure', default_value='3000'),
|
||||
DeclareLaunchArgument('laser_index1_depth_gain', default_value='16'),
|
||||
DeclareLaunchArgument('laser_index1_ir_brightness', default_value='60'),
|
||||
DeclareLaunchArgument('laser_index1_ir_ae_max_exposure', default_value='17000'),
|
||||
DeclareLaunchArgument('laser_index0_laser_control', default_value='1'),
|
||||
DeclareLaunchArgument('laser_index0_depth_exposure', default_value='3000'),
|
||||
DeclareLaunchArgument('laser_index0_depth_gain', default_value='16'),
|
||||
DeclareLaunchArgument('laser_index0_ir_brightness', default_value='60'),
|
||||
DeclareLaunchArgument('laser_index0_ir_ae_max_exposure', default_value='30000'),
|
||||
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'),
|
||||
|
||||
]
|
||||
|
||||
def get_params(context, args):
|
||||
return [load_parameters(context, args)]
|
||||
|
||||
def create_node_action(context, args):
|
||||
params = get_params(context, args)
|
||||
ros_distro = os.environ.get("ROS_DISTRO", "humble")
|
||||
if ros_distro == "foxy":
|
||||
return [
|
||||
Node(
|
||||
package="orbbec_camera",
|
||||
executable="orbbec_camera_node",
|
||||
name="ob_camera_node",
|
||||
namespace=LaunchConfiguration("camera_name"),
|
||||
parameters=params,
|
||||
output="screen",
|
||||
)
|
||||
]
|
||||
else:
|
||||
attach_to_shared_component_container_arg = LaunchConfiguration('attach_to_shared_component_container', default=False)
|
||||
component_container_name_arg = LaunchConfiguration('component_container_name', default='orbbec_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 [
|
||||
orbbec_container,
|
||||
LoadComposableNodes(
|
||||
target_container=component_container_name_arg,
|
||||
composable_node_descriptions=[
|
||||
ComposableNode(
|
||||
namespace=LaunchConfiguration("camera_name"),
|
||||
name=LaunchConfiguration("camera_name"),
|
||||
package='orbbec_camera',
|
||||
plugin='orbbec_camera::OBCameraNodeDriver',
|
||||
parameters=params,
|
||||
extra_arguments=[{'use_intra_process_comms': LaunchConfiguration("use_intra_process_comms")}],
|
||||
)
|
||||
]
|
||||
)
|
||||
]
|
||||
|
||||
return LaunchDescription(
|
||||
args + [
|
||||
OpaqueFunction(function=lambda context: create_node_action(context, args))
|
||||
]
|
||||
)
|
||||
@@ -0,0 +1,125 @@
|
||||
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
|
||||
|
||||
|
||||
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")
|
||||
launch_file_dir = os.path.join(
|
||||
package_dir, "examples/multi_camera_synced_verification_tool"
|
||||
)
|
||||
config_file_dir = os.path.join(package_dir, "config")
|
||||
config_file_path = os.path.join(config_file_dir, "camera_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="screen",
|
||||
condition=UnlessCondition(attach_to_shared_component_container_arg),
|
||||
)
|
||||
|
||||
save_rgbir = LoadComposableNodes(
|
||||
target_container=component_container_name_arg,
|
||||
composable_node_descriptions=[
|
||||
ComposableNode(
|
||||
namespace="save_rgbir",
|
||||
name="save_rgbir",
|
||||
package="orbbec_camera",
|
||||
plugin="orbbec_camera::tools::MultiCameraSubscriber",
|
||||
)
|
||||
],
|
||||
)
|
||||
|
||||
attach_to_shared_component_container_arg = TextSubstitution(text="true")
|
||||
|
||||
launch1_include = IncludeLaunchDescription(
|
||||
PythonLaunchDescriptionSource(
|
||||
os.path.join(launch_file_dir, "gemini_330_series_synced_verify.launch.py")
|
||||
),
|
||||
launch_arguments={
|
||||
"camera_name": "camera_01",
|
||||
"usb_port": "2-1",
|
||||
"device_num": "2",
|
||||
"sync_mode": "primary",
|
||||
"config_file_path": config_file_path,
|
||||
"trigger_out_enabled": "false",
|
||||
"attach_to_shared_component_container": attach_to_shared_component_container_arg,
|
||||
"component_container_name": component_container_name_arg,
|
||||
}.items(),
|
||||
)
|
||||
|
||||
launch2_include = IncludeLaunchDescription(
|
||||
PythonLaunchDescriptionSource(
|
||||
os.path.join(launch_file_dir, "gemini_330_series_synced_verify.launch.py")
|
||||
),
|
||||
launch_arguments={
|
||||
"camera_name": "camera_02",
|
||||
"usb_port": "2-3",
|
||||
"device_num": "2",
|
||||
"sync_mode": "secondary_synced",
|
||||
"config_file_path": config_file_path,
|
||||
"trigger_out_enabled": "false",
|
||||
"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])]),
|
||||
# The primary camera should be launched at last
|
||||
TimerAction(period=6.0, actions=[GroupAction([save_rgbir])]),
|
||||
# save_rgbir is synced verification tool
|
||||
]
|
||||
)
|
||||
|
||||
return ld
|
||||
@@ -0,0 +1,6 @@
|
||||
## config_frameMatch.py
|
||||
|
||||
Definition of 3 folders:
|
||||
matchFrames: data with card control within half frame.
|
||||
notMatchFrames: data with card control not within half frame, not matched.
|
||||
abnormal: data within the group that exceeds tspRangeThreshold setting
|
||||
@@ -0,0 +1,5 @@
|
||||
[Parameter]
|
||||
# Unit: FPS, please fill in according to the actual frame rate of the dataset, otherwise it will lead to inaccurate matching
|
||||
frameRate=30
|
||||
# Unit: ms. When the timestamp range of a group of data frames after matching is greater than or equal to tspRangeThreshold, the file name will be marked
|
||||
tspRangeThreshold=4
|
||||
@@ -0,0 +1,97 @@
|
||||
import os
|
||||
import subprocess
|
||||
import json
|
||||
import platform
|
||||
|
||||
def list_subdirectories(path):
|
||||
# 遍历指定目录
|
||||
for f in os.listdir(path):
|
||||
# 判断是否是子目录
|
||||
if os.path.isdir(os.path.join(path, f)):
|
||||
yield f
|
||||
|
||||
def read_pid_from_device_json(frames_dir):
|
||||
file_path = frames_dir + "/DevicesInfo.txt"
|
||||
if not os.path.exists(file_path):
|
||||
print(file_path + " not exists. Please check it")
|
||||
return ""
|
||||
|
||||
with open(file_path, 'r') as f:
|
||||
data = json.load(f)
|
||||
|
||||
if not 'devicePid' in data:
|
||||
print("Not found 'devicePid' in " + file_path)
|
||||
return ""
|
||||
|
||||
return data['devicePid']
|
||||
|
||||
def execute_sync_width_pid(frames_dir, pid):
|
||||
sync_script_file=""
|
||||
if pid.lower() == "0x0675":
|
||||
sync_script_file="script/config_frameMatch_Gemini2VL.py"
|
||||
elif pid.lower() == "0x0670" or pid.lower() == "0x0701":
|
||||
sync_script_file="script/config_frameMatch-systemTimestamp.py"
|
||||
elif pid.lower() == "0x0660":
|
||||
sync_script_file="script/config_frameMatch-Astra2_new.py"
|
||||
elif pid.lower() == "0x0803" or pid.lower() == "0x0807" or pid.lower() == "0x0801" or pid.lower() == "0x0805" or pid.lower() == "0x080b" or pid.lower() == "0x080e":
|
||||
sync_script_file="script/config_frameMatch_g330.py"
|
||||
else:
|
||||
sync_script_file="script/config_frameMatch.py"
|
||||
|
||||
if len(sync_script_file) > 0:
|
||||
print(f"execute synchronize frames. script_file={sync_script_file}, frames_dir={frames_dir}")
|
||||
if platform.system() == "Windows":
|
||||
output = subprocess.check_output(["python", sync_script_file, os.path.abspath(f"{frames_dir}"), os.getcwd()])
|
||||
print(output.decode("gbk"))
|
||||
else:
|
||||
output = subprocess.check_output(["python3", sync_script_file, os.path.abspath(f"{frames_dir}"), os.getcwd()])
|
||||
print(output.decode("utf-8"))
|
||||
else:
|
||||
print("Invalid pid=" + pid + ", not match sync frame script")
|
||||
|
||||
def sync_frames(frames_dir):
|
||||
pid = read_pid_from_device_json(frames_dir)
|
||||
if len(pid) <= 0:
|
||||
print("Get pid failed.")
|
||||
return
|
||||
|
||||
execute_sync_width_pid(frames_dir, pid)
|
||||
|
||||
def main():
|
||||
# 指定目录路径
|
||||
frames_output_path = os.path.abspath("../output")
|
||||
|
||||
# 获取所有子目录
|
||||
subdirectories = list(list_subdirectories(frames_output_path))
|
||||
|
||||
if not subdirectories:
|
||||
print("No subdirectories found in the specified directory.")
|
||||
|
||||
# 打印子目录列表并让用户选择
|
||||
for i, subdir in enumerate(subdirectories):
|
||||
print(f"{i + 1}. {subdir}")
|
||||
|
||||
# 获取用户输入
|
||||
choice = input("Please select a subdirectory by number (or 'q' to quit): ")
|
||||
|
||||
if choice.lower() == 'q':
|
||||
return
|
||||
|
||||
# 验证用户输入
|
||||
is_index_valid = False
|
||||
try:
|
||||
index = int(choice) - 1
|
||||
if 0 <= index < len(subdirectories):
|
||||
is_index_valid = True
|
||||
else:
|
||||
print(f"Invalid choice. index={index}. Please enter a number between 1 and the total number of subdirectories.")
|
||||
except ValueError:
|
||||
print("Invalid choice(ValueException). Please enter a number between 1 and the total number of subdirectories")
|
||||
if is_index_valid:
|
||||
selected_subdir = subdirectories[index]
|
||||
print(f"You selected the subdirectory: {selected_subdir}")
|
||||
sync_frames(f"{frames_output_path }/{selected_subdir}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,385 @@
|
||||
import os.path
|
||||
import argparse
|
||||
import sys
|
||||
import json
|
||||
import shutil
|
||||
import configparser
|
||||
|
||||
from pathlib import Path
|
||||
from datetime import datetime
|
||||
|
||||
## Config.ini中配置
|
||||
# 单位:FPS,请依据数据集实际帧率填写,否则会导致匹配不准确
|
||||
frameRate = -1
|
||||
# 单位:ms,当匹配后某组数据帧的时间戳极差大于等于tspRangeThreshold,文件名会增加标注
|
||||
tspRangeThreshold = -1
|
||||
|
||||
## Python脚本自动解析, 不要修改
|
||||
primaryId = '-1' #主机ID
|
||||
mainPythonWorkPath = "" # SyncFramesMain.py的工作目录
|
||||
sourceFramesPath = "" # 请勿修改,固定值,TotalMode目录路径
|
||||
sourceRootPath = "" # 存放TotalMode的根目录
|
||||
resultPath = "" # 保存sync分析结果
|
||||
streamProfileDict = dict() # 保存Python结果
|
||||
|
||||
class PictureInfo(object):
|
||||
class Struct(object):
|
||||
def __init__(self, deviceId, sensorType, syncTimeStamp, picturePath, deviceIdFull, fileExt):
|
||||
self.deviceId = deviceId
|
||||
self.sensorType = sensorType
|
||||
self.syncTimeStamp = syncTimeStamp
|
||||
self.picturePath = picturePath
|
||||
self.deviceIdFull = deviceIdFull
|
||||
self.fileExt = fileExt
|
||||
|
||||
def make_struct(self, deviceId, sensorType, syncTimeStamp, picturePath, deviceIdFull, fileExt):
|
||||
return self.Struct(deviceId, sensorType, syncTimeStamp, picturePath, deviceIdFull, fileExt)
|
||||
|
||||
class StreamProfileInfo:
|
||||
def __init__(self, sensorType, width, height, format, fps):
|
||||
self.sensorType = sensorType
|
||||
self.width = width
|
||||
self.height = height
|
||||
self.format = format
|
||||
self.fps = fps
|
||||
|
||||
def initPrimaryId():
|
||||
global primaryId
|
||||
|
||||
deviceInfoPath = f"{sourceRootPath}/DevicesInfo.txt"
|
||||
if not os.path.exists(deviceInfoPath):
|
||||
print(f"Initialize primaryId failed. {deviceInfoPath} not exists")
|
||||
return False
|
||||
|
||||
with open(deviceInfoPath, 'r') as f:
|
||||
data = json.load(f)
|
||||
|
||||
if not 'devices' in data:
|
||||
print(f"Initialize primaryId failed. {deviceInfoPath} not contain item 'devices'")
|
||||
return False
|
||||
|
||||
for item in data['devices']:
|
||||
if 'isPrimaryDevice' in item and 'index' in item and item['isPrimaryDevice']:
|
||||
primaryId = str(item['index'])
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def initSyncConfigParams():
|
||||
global frameRate, tspRangeThreshold
|
||||
|
||||
configPath = f"{mainPythonWorkPath}/Config.ini"
|
||||
if not os.path.exists(configPath):
|
||||
print(f"Initliaze synchronized config parameter failed. {configPath} not exists.")
|
||||
return False
|
||||
|
||||
# 创建ConfigParser对象
|
||||
config = configparser.ConfigParser()
|
||||
|
||||
# 读取ini文件
|
||||
config.read(configPath, 'utf-8')
|
||||
|
||||
if not config.has_option('Parameter', 'frameRate'):
|
||||
print(f"Initliaze synchronized config parameter failed. Not define 'frameRate' in ${configPath}")
|
||||
return False
|
||||
|
||||
if not config.has_option('Parameter', 'tspRangeThreshold'):
|
||||
print(f"Initliaze synchronized config parameter failed. Not define 'tspRangeThreshold' in ${configPath}")
|
||||
return False
|
||||
|
||||
frameRate = config.getfloat('Parameter', 'frameRate')
|
||||
if frameRate <= 0 or frameRate >= 1000:
|
||||
print(f"Initliaze synchronized config parameter failed. Invalid frameRate={frameRate}")
|
||||
return False
|
||||
|
||||
tspRangeThreshold = config.getfloat('Parameter', 'tspRangeThreshold')
|
||||
if tspRangeThreshold <= 0:
|
||||
print(f"Initliaze synchronized config parameter failed. Invalid tspRangeThreshold={tspRangeThreshold}")
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def getDeviceCount():
|
||||
deviceInfoPath = f"{sourceRootPath}/DevicesInfo.txt"
|
||||
if not os.path.exists(deviceInfoPath):
|
||||
print(f"Get device count failed. {deviceInfoPath} not exists")
|
||||
return 0
|
||||
|
||||
with open(deviceInfoPath, 'r') as f:
|
||||
data = json.load(f)
|
||||
|
||||
if not 'devices' in data:
|
||||
print(f"Get device count failed. {deviceInfoPath} not contain item 'devices'")
|
||||
return 0
|
||||
|
||||
return len(data['devices'])
|
||||
|
||||
def initProfileInfoDict():
|
||||
global streamProfileDict
|
||||
|
||||
streamProfileDict = dict()
|
||||
profilePath = f"{sourceRootPath}/StreamProfileInfo.txt"
|
||||
if not os.path.exists(profilePath):
|
||||
print(f"Initialize profile information dictionary failed. {profilePath} not exists")
|
||||
return False
|
||||
|
||||
with open(profilePath, 'r') as f:
|
||||
data = json.load(f)
|
||||
|
||||
if not 'streamProfiles' in data:
|
||||
print(f"Initialize profile information dictionary failed. {profilePath} not contain item 'streamProfiles'")
|
||||
return False
|
||||
|
||||
for item in data['streamProfiles']:
|
||||
if 'sensorType' in item and 'width' in item and 'height' in item and 'format' in item and 'fps' in item:
|
||||
profileInfo = StreamProfileInfo(item['sensorType'], item['width'], item['height'], item['format'], item['fps'])
|
||||
streamProfileDict.setdefault(item['sensorType'], profileInfo)
|
||||
else:
|
||||
print(f"Initialize profile information dictionary. Error invalid StreamProfile, {item}")
|
||||
|
||||
return len(streamProfileDict.items()) > 0
|
||||
|
||||
def initResultDir():
|
||||
global resultPath
|
||||
timeText = datetime.now().strftime("%Y-%m-%d_%H%M%S")
|
||||
resultPath = f"{sourceRootPath}/results-{timeText}"
|
||||
if not os.path.exists(resultPath):
|
||||
os.makedirs(resultPath)
|
||||
|
||||
def isFrameFile(fileName):
|
||||
fileExt = os.path.splitext(fileName)
|
||||
if len(fileExt) < 2:
|
||||
return False
|
||||
|
||||
frameExts = {".jpeg", ".jpg", ".png", ".raw", "bmp"}
|
||||
return fileExt[1] in frameExts
|
||||
|
||||
def initPictureInfoDictionary(rootFilePath, pictureInfoDict):
|
||||
frameFileCount = 0
|
||||
for dirpath, dirnames, filenames in os.walk(rootFilePath):
|
||||
# print(f"dirpath={dirpath}, dirpath.basename=" + os.path.basename(dirpath))
|
||||
for fileName in filenames:
|
||||
filePath = os.path.join(dirpath, fileName)
|
||||
if not isFrameFile(fileName):
|
||||
continue
|
||||
|
||||
frameFileCount += 1
|
||||
fileNameNoExt = os.path.splitext(fileName)[0]
|
||||
pictureNameList = fileNameNoExt.split('_')
|
||||
deviceId = pictureNameList[2][5:]
|
||||
sensorType = pictureNameList[0].replace('#', '_')
|
||||
syncTimeStamp = int(pictureNameList[3][1:])
|
||||
|
||||
# 创建结构体
|
||||
pictureInfo = PictureInfo()
|
||||
info = pictureInfo.make_struct(deviceId, sensorType, syncTimeStamp, filePath, os.path.basename(dirpath), os.path.splitext(fileName)[1])
|
||||
|
||||
# 格式化字典一个key对应一个数组
|
||||
pictureInfoDict.setdefault(deviceId, []).append(info)
|
||||
|
||||
if 0 == frameFileCount:
|
||||
print("initialize pictureInfoDict failed. Not found frame file")
|
||||
|
||||
def matchFrame(pictureInfoDict):
|
||||
global primaryId, frameRate
|
||||
global streamProfileDict
|
||||
|
||||
# 获取比较图像数组,取第一个设备的Color数组来进行比较
|
||||
compareList = []
|
||||
for key in pictureInfoDict:
|
||||
listTmp = list(pictureInfoDict[key])
|
||||
for info in listTmp:
|
||||
if info.deviceId == primaryId and info.sensorType == 'color':
|
||||
compareList.append(info)
|
||||
|
||||
sorted_dict = dict(sorted(pictureInfoDict.items(), key=lambda x: x[1], reverse=False))
|
||||
pictureInfoDict = sorted_dict
|
||||
|
||||
# 根据开流情况动态分析,减少循环次数
|
||||
hasColorProfile = 'OB_SENSOR_COLOR' in streamProfileDict
|
||||
hasDepthProfile = 'OB_SENSOR_DEPTH' in streamProfileDict
|
||||
hasIRProfile = 'OB_SENSOR_IR' in streamProfileDict
|
||||
|
||||
# 匹配相邻帧
|
||||
resultDict = {}
|
||||
consumedList = []
|
||||
dictIndex = 0
|
||||
frameTspGap = int(1000.0/frameRate+0.5)
|
||||
for comparePic in compareList:
|
||||
resultDict.setdefault(dictIndex, []).append(comparePic)
|
||||
for key in pictureInfoDict:
|
||||
listTmp = list(pictureInfoDict[key])
|
||||
# 对比彩色
|
||||
if hasColorProfile:
|
||||
for info in listTmp:
|
||||
if info.deviceId == primaryId and info.sensorType == comparePic.sensorType:
|
||||
continue
|
||||
|
||||
if info in consumedList:
|
||||
continue
|
||||
|
||||
if info.sensorType == 'color' and info.syncTimeStamp >= comparePic.syncTimeStamp and info.syncTimeStamp - comparePic.syncTimeStamp <= frameTspGap:
|
||||
resultDict.setdefault(dictIndex, []).append(info)
|
||||
consumedList.append(info)
|
||||
break
|
||||
|
||||
# 对Depth
|
||||
if hasDepthProfile:
|
||||
for info in listTmp:
|
||||
if info.deviceId == primaryId and info.sensorType == comparePic.sensorType:
|
||||
continue
|
||||
|
||||
if info in consumedList:
|
||||
continue
|
||||
|
||||
if info.sensorType == 'depth' and info.syncTimeStamp >= comparePic.syncTimeStamp and info.syncTimeStamp - comparePic.syncTimeStamp <= frameTspGap:
|
||||
resultDict.setdefault(dictIndex, []).append(info)
|
||||
consumedList.append(info)
|
||||
break
|
||||
|
||||
# IR
|
||||
if hasIRProfile:
|
||||
for info in listTmp:
|
||||
if info.deviceId == primaryId and info.sensorType == comparePic.sensorType:
|
||||
continue
|
||||
|
||||
if info in consumedList:
|
||||
continue
|
||||
|
||||
if info.sensorType == 'ir' and info.syncTimeStamp >= comparePic.syncTimeStamp and info.syncTimeStamp - comparePic.syncTimeStamp <= frameTspGap:
|
||||
resultDict.setdefault(dictIndex, []).append(info)
|
||||
consumedList.append(info)
|
||||
break
|
||||
|
||||
# 计算偏差
|
||||
for info in resultDict[dictIndex]:
|
||||
info.tspDiff = info.syncTimeStamp - comparePic.syncTimeStamp
|
||||
|
||||
dictIndex += 1
|
||||
|
||||
return resultDict
|
||||
|
||||
|
||||
def safe_make_dir(path):
|
||||
if not os.path.exists(path):
|
||||
os.makedirs(path)
|
||||
|
||||
|
||||
def handleSyncFrames():
|
||||
global resultPath
|
||||
global sourceFramesPath
|
||||
global streamProfileDict
|
||||
|
||||
fp = open(f"{resultPath}/frameMatchLog.txt", "w")
|
||||
|
||||
if not initProfileInfoDict():
|
||||
print("Initialize stream profile dictionary failed. exit")
|
||||
return
|
||||
print(f"Stream profiles: {streamProfileDict}")
|
||||
streamProfileCount = len(streamProfileDict.items())
|
||||
|
||||
pictureInfoDict = {}
|
||||
|
||||
initPictureInfoDictionary(Path(sourceFramesPath), pictureInfoDict)
|
||||
if len(pictureInfoDict) <= 0:
|
||||
print("init pictureInfoDict failed. pictureInfoDict.len = 0")
|
||||
return
|
||||
|
||||
# # Dump pictureInfoDict
|
||||
# for key in pictureInfoDict:
|
||||
# print("===================key:%s" % key)
|
||||
# listTmp = list(pictureInfoDict[key])
|
||||
# for info in listTmp:
|
||||
# print("=====================value : deviceId:%s\tsensorType:%s\tsyncTimeStamp:%s\tpicturePath:%s" % (info.deviceId,info.sensorType,info.syncTimeStamp,info.picturePath))
|
||||
|
||||
deviceCount = getDeviceCount()
|
||||
print("=================device count:%d" % deviceCount)
|
||||
|
||||
resultDict = {}
|
||||
resultDict = matchFrame(pictureInfoDict)
|
||||
|
||||
for key in resultDict:
|
||||
fp.write("===================result key:%s\n" % key)
|
||||
print("===================result key:%s" % key)
|
||||
listTmp = list(resultDict[key])
|
||||
for info in listTmp:
|
||||
fp.write("=====================result value : deviceId:%s\tsensorType:%s\tsyncTimeStamp:%s\tpicturePath:%s\n" % (
|
||||
info.deviceId, info.sensorType, info.syncTimeStamp, info.picturePath))
|
||||
print("=====================result value : deviceId:%s\tsensorType:%s\tsyncTimeStamp:%s\tpicturePath:%s" % (
|
||||
info.deviceId, info.sensorType, info.syncTimeStamp, info.picturePath))
|
||||
|
||||
matchPath = f"{resultPath}/matchFrames/"
|
||||
safe_make_dir(matchPath)
|
||||
notMatchPath = f"{resultPath}/notMatchFrames/"
|
||||
abnormalPath = f"{resultPath}/abnormal/"
|
||||
|
||||
for key in resultDict:
|
||||
listTmp = list(resultDict[key])
|
||||
if (len(listTmp) == (deviceCount * streamProfileCount)):
|
||||
haveAbnormalData = False
|
||||
for info in listTmp:
|
||||
if info.tspDiff >= tspRangeThreshold:
|
||||
path = matchPath + str(key) + "_" + info.sensorType + "_" + info.deviceIdFull + "_" + str(
|
||||
info.syncTimeStamp) + "_[" + str(info.tspDiff) + "]" + "_xxxxxx" + info.fileExt
|
||||
shutil.copy(info.picturePath, path)
|
||||
haveAbnormalData = True
|
||||
else:
|
||||
path = matchPath + str(key) + "_" + info.sensorType + "_" + info.deviceIdFull + "_" + str(
|
||||
info.syncTimeStamp) + "_[" + str(info.tspDiff) + "]" + info.fileExt
|
||||
shutil.copy(info.picturePath, path)
|
||||
|
||||
if haveAbnormalData:
|
||||
safe_make_dir(abnormalPath)
|
||||
for info in listTmp:
|
||||
if info.tspDiff >= tspRangeThreshold:
|
||||
path = abnormalPath + str(key) + "_" + info.sensorType + "_" + info.deviceIdFull + "_" + str(
|
||||
info.syncTimeStamp) + "_[" + str(info.tspDiff) + "]" + "_xxxxxx" + info.fileExt
|
||||
shutil.copy(info.picturePath, path)
|
||||
else:
|
||||
path = abnormalPath + str(key) + "_" + info.sensorType + "_" + info.deviceIdFull + "_" + str(
|
||||
info.syncTimeStamp) + "_[" + str(info.tspDiff) + "]" + info.fileExt
|
||||
shutil.copy(info.picturePath, path)
|
||||
else:
|
||||
safe_make_dir(notMatchPath)
|
||||
for info in listTmp:
|
||||
newFilePath = notMatchPath + str(key) + "_" + info.sensorType + "_" + info.deviceIdFull + "_" + str(
|
||||
info.syncTimeStamp) + "_[" + str(info.tspDiff) + "]" + info.fileExt
|
||||
shutil.copy(info.picturePath, newFilePath)
|
||||
|
||||
def main():
|
||||
if not initPrimaryId():
|
||||
print("Initialize primaryId failed. exit.")
|
||||
return
|
||||
|
||||
if not initSyncConfigParams():
|
||||
print("Initialized Sync config parameter failed. exit.")
|
||||
return
|
||||
|
||||
initResultDir()
|
||||
|
||||
print(f"PrimaryId={primaryId}, frameRate={frameRate}, tspRangeThreshold={tspRangeThreshold}")
|
||||
handleSyncFrames()
|
||||
|
||||
return
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
parser = argparse.ArgumentParser(description="Synchronize frames support for 'Orbbec Astra2'")
|
||||
parser.add_argument('frames_dir', type=str, help="Frames directory")
|
||||
parser.add_argument('main_script_root_dir', type=str, help="SyncFramesMain.py working directory")
|
||||
args = parser.parse_args()
|
||||
|
||||
sourceRootPath = args.frames_dir
|
||||
sourceFramesPath = f"{args.frames_dir}/TotalModeFrames"
|
||||
mainPythonWorkPath = args.main_script_root_dir
|
||||
print(f"Start of Astra2 synchronize frame. sourceRootPath={sourceRootPath}")
|
||||
|
||||
main()
|
||||
|
||||
print("Finish of Astra2 synchronize frame.")
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,408 @@
|
||||
import os.path
|
||||
import argparse
|
||||
import sys
|
||||
import json
|
||||
import shutil
|
||||
import configparser
|
||||
|
||||
from pathlib import Path
|
||||
from datetime import datetime
|
||||
|
||||
## Config.ini中配置
|
||||
# 单位:FPS,请依据数据集实际帧率填写,否则会导致匹配不准确
|
||||
frameRate = -1
|
||||
# 单位:ms,当匹配后某组数据帧的时间戳极差大于等于tspRangeThreshold,文件名会增加标注
|
||||
tspRangeThreshold = -1
|
||||
|
||||
## Python脚本自动解析, 不要修改
|
||||
primaryId = '-1' #主机ID
|
||||
mainPythonWorkPath = "" # SyncFramesMain.py的工作目录
|
||||
sourceFramesPath = "" # 请勿修改,固定值,TotalMode目录路径
|
||||
sourceRootPath = "" # 存放TotalMode的根目录
|
||||
resultPath = "" # 保存sync分析结果
|
||||
streamProfileDict = dict() # 保存Python结果
|
||||
|
||||
class PictureInfo(object):
|
||||
class Struct(object):
|
||||
def __init__(self, deviceId, sensorType, syncTimeStamp, picturePath, deviceIdFull, fileExt):
|
||||
self.deviceId = deviceId
|
||||
self.sensorType = sensorType
|
||||
self.syncTimeStamp = syncTimeStamp
|
||||
self.picturePath = picturePath
|
||||
self.deviceIdFull = deviceIdFull
|
||||
self.fileExt = fileExt
|
||||
|
||||
def make_struct(self, deviceId, sensorType, syncTimeStamp, picturePath, deviceIdFull, fileExt):
|
||||
return self.Struct(deviceId, sensorType, syncTimeStamp, picturePath, deviceIdFull, fileExt)
|
||||
|
||||
class StreamProfileInfo:
|
||||
def __init__(self, sensorType, width, height, format, fps):
|
||||
self.sensorType = sensorType
|
||||
self.width = width
|
||||
self.height = height
|
||||
self.format = format
|
||||
self.fps = fps
|
||||
|
||||
def initPrimaryId():
|
||||
global primaryId
|
||||
|
||||
deviceInfoPath = f"{sourceRootPath}/DevicesInfo.txt"
|
||||
if not os.path.exists(deviceInfoPath):
|
||||
print(f"Initialize primaryId failed. {deviceInfoPath} not exists")
|
||||
return False
|
||||
|
||||
with open(deviceInfoPath, 'r') as f:
|
||||
data = json.load(f)
|
||||
|
||||
if not 'devices' in data:
|
||||
print(f"Initialize primaryId failed. {deviceInfoPath} not contain item 'devices'")
|
||||
return False
|
||||
|
||||
for item in data['devices']:
|
||||
if 'isPrimaryDevice' in item and 'index' in item and item['isPrimaryDevice']:
|
||||
primaryId = str(item['index'])
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def initSyncConfigParams():
|
||||
global frameRate, tspRangeThreshold
|
||||
|
||||
configPath = f"{mainPythonWorkPath}/Config.ini"
|
||||
if not os.path.exists(configPath):
|
||||
print(f"Initliaze synchronized config parameter failed. {configPath} not exists.")
|
||||
return False
|
||||
|
||||
# 创建ConfigParser对象
|
||||
config = configparser.ConfigParser()
|
||||
|
||||
# 读取ini文件
|
||||
config.read(configPath, 'utf-8')
|
||||
|
||||
if not config.has_option('Parameter', 'frameRate'):
|
||||
print(f"Initliaze synchronized config parameter failed. Not define 'frameRate' in ${configPath}")
|
||||
return False
|
||||
|
||||
if not config.has_option('Parameter', 'tspRangeThreshold'):
|
||||
print(f"Initliaze synchronized config parameter failed. Not define 'tspRangeThreshold' in ${configPath}")
|
||||
return False
|
||||
|
||||
frameRate = config.getfloat('Parameter', 'frameRate')
|
||||
if frameRate <= 0 or frameRate >= 1000:
|
||||
print(f"Initliaze synchronized config parameter failed. Invalid frameRate={frameRate}")
|
||||
return False
|
||||
|
||||
tspRangeThreshold = config.getfloat('Parameter', 'tspRangeThreshold')
|
||||
if tspRangeThreshold <= 0:
|
||||
print(f"Initliaze synchronized config parameter failed. Invalid tspRangeThreshold={tspRangeThreshold}")
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def getDeviceCount():
|
||||
deviceInfoPath = f"{sourceRootPath}/DevicesInfo.txt"
|
||||
if not os.path.exists(deviceInfoPath):
|
||||
print(f"Get device count failed. {deviceInfoPath} not exists")
|
||||
return 0
|
||||
|
||||
with open(deviceInfoPath, 'r') as f:
|
||||
data = json.load(f)
|
||||
|
||||
if not 'devices' in data:
|
||||
print(f"Get device count failed. {deviceInfoPath} not contain item 'devices'")
|
||||
return 0
|
||||
|
||||
return len(data['devices'])
|
||||
|
||||
def initProfileInfoDict():
|
||||
global streamProfileDict
|
||||
|
||||
streamProfileDict = dict()
|
||||
profilePath = f"{sourceRootPath}/StreamProfileInfo.txt"
|
||||
if not os.path.exists(profilePath):
|
||||
print(f"Initialize profile information dictionary failed. {profilePath} not exists")
|
||||
return False
|
||||
|
||||
with open(profilePath, 'r') as f:
|
||||
data = json.load(f)
|
||||
|
||||
if not 'streamProfiles' in data:
|
||||
print(f"Initialize profile information dictionary failed. {profilePath} not contain item 'streamProfiles'")
|
||||
return False
|
||||
|
||||
for item in data['streamProfiles']:
|
||||
if 'sensorType' in item and 'width' in item and 'height' in item and 'format' in item and 'fps' in item:
|
||||
profileInfo = StreamProfileInfo(item['sensorType'], item['width'], item['height'], item['format'], item['fps'])
|
||||
streamProfileDict.setdefault(item['sensorType'], profileInfo)
|
||||
else:
|
||||
print(f"Initialize profile information dictionary. Error invalid StreamProfile, {item}")
|
||||
|
||||
return len(streamProfileDict.items()) > 0
|
||||
|
||||
def initResultDir():
|
||||
global resultPath
|
||||
timeText = datetime.now().strftime("%Y-%m-%d_%H%M%S")
|
||||
resultPath = f"{sourceRootPath}/results-{timeText}"
|
||||
if not os.path.exists(resultPath):
|
||||
os.makedirs(resultPath)
|
||||
|
||||
def isFrameFile(fileName):
|
||||
fileExt = os.path.splitext(fileName)
|
||||
if len(fileExt) < 2:
|
||||
return False
|
||||
|
||||
frameExts = {".jpeg", ".jpg", ".png", ".raw", "bmp"}
|
||||
return fileExt[1] in frameExts
|
||||
|
||||
def initPictureInfoDictionary(rootFilePath, pictureInfoDict):
|
||||
frameFileCount = 0
|
||||
for dirpath, dirnames, filenames in os.walk(rootFilePath):
|
||||
# print(f"dirpath={dirpath}, dirpath.basename=" + os.path.basename(dirpath))
|
||||
for fileName in filenames:
|
||||
filePath = os.path.join(dirpath, fileName)
|
||||
if not isFrameFile(fileName):
|
||||
continue
|
||||
|
||||
frameFileCount += 1
|
||||
fileNameNoExt = os.path.splitext(fileName)[0]
|
||||
pictureNameList = fileNameNoExt.split('_')
|
||||
deviceId = pictureNameList[2][5:]
|
||||
sensorType = pictureNameList[0].replace('#', '_')
|
||||
syncTimeStamp = int(pictureNameList[5][1:])
|
||||
|
||||
# 创建结构体
|
||||
pictureInfo = PictureInfo()
|
||||
info = pictureInfo.make_struct(deviceId, sensorType, syncTimeStamp, filePath, os.path.basename(dirpath), os.path.splitext(fileName)[1])
|
||||
|
||||
# 格式化字典一个key对应一个数组
|
||||
pictureInfoDict.setdefault(deviceId, []).append(info)
|
||||
|
||||
if 0 == frameFileCount:
|
||||
print("initialize pictureInfoDict failed. Not found frame file")
|
||||
|
||||
|
||||
def matchFrame(pictureInfoDict):
|
||||
global primaryId, frameRate
|
||||
global streamProfileDict
|
||||
|
||||
# 获取比较图像数组,取第一个设备的Color数组来进行比较
|
||||
compareList = []
|
||||
for key in pictureInfoDict:
|
||||
listTmp = list(pictureInfoDict[key])
|
||||
for info in listTmp:
|
||||
if info.deviceId == primaryId and info.sensorType == 'color':
|
||||
compareList.append(info)
|
||||
|
||||
# 根据开流情况动态分析,减少循环次数
|
||||
hasColorProfile = 'OB_SENSOR_COLOR' in streamProfileDict
|
||||
hasDepthProfile = 'OB_SENSOR_DEPTH' in streamProfileDict
|
||||
hasIRProfile = 'OB_SENSOR_IR' in streamProfileDict
|
||||
hasIRLeftProfile = 'OB_SENSOR_IR_LEFT' in streamProfileDict
|
||||
hasIRRightProfile = 'OB_SENSOR_IR_RIGHT' in streamProfileDict
|
||||
|
||||
# 匹配相邻帧
|
||||
resultDict = {}
|
||||
consumedList = []
|
||||
dictIndex = 0
|
||||
frameTspHalfGap = int(1000.0/frameRate/2.0+0.5)
|
||||
for comparePic in compareList:
|
||||
resultDict.setdefault(dictIndex, []).append(comparePic)
|
||||
for key in pictureInfoDict:
|
||||
listTmp = list(pictureInfoDict[key])
|
||||
# 对比彩色
|
||||
if hasColorProfile:
|
||||
for info in listTmp:
|
||||
if info.deviceId == primaryId and info.sensorType == comparePic.sensorType:
|
||||
continue
|
||||
|
||||
if info in consumedList:
|
||||
continue
|
||||
|
||||
if info.sensorType == 'color' and abs(info.syncTimeStamp - comparePic.syncTimeStamp) < frameTspHalfGap:
|
||||
resultDict.setdefault(dictIndex, []).append(info)
|
||||
consumedList.append(info)
|
||||
break
|
||||
|
||||
# 对Depth
|
||||
if hasDepthProfile:
|
||||
for info in listTmp:
|
||||
if info.deviceId == primaryId and info.sensorType == comparePic.sensorType:
|
||||
continue
|
||||
|
||||
if info in consumedList:
|
||||
continue
|
||||
|
||||
if info.sensorType == 'depth' and abs(info.syncTimeStamp - comparePic.syncTimeStamp) < frameTspHalfGap:
|
||||
resultDict.setdefault(dictIndex, []).append(info)
|
||||
consumedList.append(info)
|
||||
break
|
||||
|
||||
# IR
|
||||
if hasIRProfile:
|
||||
for info in listTmp:
|
||||
if info.deviceId == primaryId and info.sensorType == comparePic.sensorType:
|
||||
continue
|
||||
|
||||
if info in consumedList:
|
||||
continue
|
||||
|
||||
if info.sensorType == 'ir' and abs(info.syncTimeStamp - comparePic.syncTimeStamp) < frameTspHalfGap:
|
||||
resultDict.setdefault(dictIndex, []).append(info)
|
||||
consumedList.append(info)
|
||||
break
|
||||
|
||||
# 左IR
|
||||
if hasIRLeftProfile:
|
||||
for info in listTmp:
|
||||
if info.deviceId == primaryId and info.sensorType == comparePic.sensorType:
|
||||
continue
|
||||
|
||||
if info in consumedList:
|
||||
continue
|
||||
|
||||
if info.sensorType == 'ir_left' and abs(info.syncTimeStamp - comparePic.syncTimeStamp) < frameTspHalfGap:
|
||||
resultDict.setdefault(dictIndex, []).append(info)
|
||||
consumedList.append(info)
|
||||
break
|
||||
|
||||
# 右IR
|
||||
if hasIRRightProfile:
|
||||
for info in listTmp:
|
||||
if info.deviceId == primaryId and info.sensorType == comparePic.sensorType:
|
||||
continue
|
||||
|
||||
if info in consumedList:
|
||||
continue
|
||||
|
||||
if info.sensorType == 'ir_right' and abs(info.syncTimeStamp - comparePic.syncTimeStamp) < frameTspHalfGap:
|
||||
resultDict.setdefault(dictIndex, []).append(info)
|
||||
consumedList.append(info)
|
||||
break
|
||||
|
||||
# 计算偏差
|
||||
minTsp = min(resultDict[dictIndex], key=lambda x: x.syncTimeStamp)
|
||||
for info in resultDict[dictIndex]:
|
||||
info.tspDiff = info.syncTimeStamp - minTsp.syncTimeStamp
|
||||
|
||||
dictIndex += 1
|
||||
|
||||
return resultDict
|
||||
|
||||
|
||||
def safe_make_dir(path):
|
||||
if not os.path.exists(path):
|
||||
os.makedirs(path)
|
||||
|
||||
|
||||
def handleSyncFrames():
|
||||
global resultPath
|
||||
global sourceFramesPath
|
||||
global streamProfileDict
|
||||
|
||||
fp = open(f"{resultPath}/frameMatchLog.txt", "w")
|
||||
|
||||
if not initProfileInfoDict():
|
||||
print("Initialize stream profile dictionary failed. exit")
|
||||
return
|
||||
print(f"Stream profiles: {streamProfileDict}")
|
||||
streamProfileCount = len(streamProfileDict.items())
|
||||
|
||||
pictureInfoDict = {}
|
||||
|
||||
initPictureInfoDictionary(Path(sourceFramesPath), pictureInfoDict)
|
||||
if len(pictureInfoDict) <= 0:
|
||||
print("init pictureInfoDict failed. pictureInfoDict.len = 0")
|
||||
return
|
||||
|
||||
# # Dump pictureInfoDict
|
||||
# for key in pictureInfoDict:
|
||||
# print("===================key:%s" % key)
|
||||
# listTmp = list(pictureInfoDict[key])
|
||||
# for info in listTmp:
|
||||
# print("=====================value : deviceId:%s\tsensorType:%s\tsyncTimeStamp:%s\tpicturePath:%s" % (info.deviceId,info.sensorType,info.syncTimeStamp,info.picturePath))
|
||||
|
||||
deviceCount = getDeviceCount()
|
||||
print("=================device count:%d" % deviceCount)
|
||||
|
||||
resultDict = {}
|
||||
resultDict = matchFrame(pictureInfoDict)
|
||||
|
||||
for key in resultDict:
|
||||
fp.write("===================result key:%s\n" % key)
|
||||
print("===================result key:%s" % key)
|
||||
listTmp = list(resultDict[key])
|
||||
for info in listTmp:
|
||||
fp.write("=====================result value : deviceId:%s\tsensorType:%s\tsyncTimeStamp:%s\tpicturePath:%s\n" % (
|
||||
info.deviceId, info.sensorType, info.syncTimeStamp, info.picturePath))
|
||||
print("=====================result value : deviceId:%s\tsensorType:%s\tsyncTimeStamp:%s\tpicturePath:%s" % (
|
||||
info.deviceId, info.sensorType, info.syncTimeStamp, info.picturePath))
|
||||
|
||||
matchPath = f"{resultPath}/matchFrames/"
|
||||
safe_make_dir(matchPath)
|
||||
notMatchPath = f"{resultPath}/notMatchFrames/"
|
||||
abnormalPath = f"{resultPath}/abnormal/"
|
||||
|
||||
for key in resultDict:
|
||||
listTmp = list(resultDict[key])
|
||||
if (len(listTmp) == (deviceCount * streamProfileCount)):
|
||||
haveAbnormalData = False
|
||||
for info in listTmp:
|
||||
if info.tspDiff >= tspRangeThreshold:
|
||||
path = matchPath + str(key) + "_" + info.sensorType + "_" + info.deviceIdFull + "_" + str(
|
||||
info.syncTimeStamp) + "_[" + str(info.tspDiff) + "]" + "_xxxxxx" + info.fileExt
|
||||
shutil.copy(info.picturePath, path)
|
||||
haveAbnormalData = True
|
||||
else:
|
||||
path = matchPath + str(key) + "_" + info.sensorType + "_" + info.deviceIdFull + "_" + str(
|
||||
info.syncTimeStamp) + "_[" + str(info.tspDiff) + "]" + info.fileExt
|
||||
shutil.copy(info.picturePath, path)
|
||||
|
||||
if haveAbnormalData:
|
||||
safe_make_dir(abnormalPath)
|
||||
for info in listTmp:
|
||||
if info.tspDiff >= tspRangeThreshold:
|
||||
path = abnormalPath + str(key) + "_" + info.sensorType + "_" + info.deviceIdFull + "_" + str(
|
||||
info.syncTimeStamp) + "_[" + str(info.tspDiff) + "]" + "_xxxxxx" + info.fileExt
|
||||
shutil.copy(info.picturePath, path)
|
||||
else:
|
||||
path = abnormalPath + str(key) + "_" + info.sensorType + "_" + info.deviceIdFull + "_" + str(
|
||||
info.syncTimeStamp) + "_[" + str(info.tspDiff) + "]" + info.fileExt
|
||||
shutil.copy(info.picturePath, path)
|
||||
else:
|
||||
safe_make_dir(notMatchPath)
|
||||
for info in listTmp:
|
||||
newFilePath = notMatchPath + str(key) + "_" + info.sensorType + "_" + info.deviceIdFull + "_" + str(
|
||||
info.syncTimeStamp) + "_[" + str(info.tspDiff) + "]" + info.fileExt
|
||||
shutil.copy(info.picturePath, newFilePath)
|
||||
|
||||
def main():
|
||||
if not initPrimaryId():
|
||||
print("Initialize primaryId failed. exit.")
|
||||
return
|
||||
|
||||
if not initSyncConfigParams():
|
||||
print("Initialized Sync config parameter failed. exit.")
|
||||
return
|
||||
|
||||
initResultDir()
|
||||
|
||||
print(f"PrimaryId={primaryId}, frameRate={frameRate}, tspRangeThreshold={tspRangeThreshold}")
|
||||
handleSyncFrames()
|
||||
|
||||
return
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
parser = argparse.ArgumentParser(description="Synchronize frames support for 'Orbbec Gemini 2'")
|
||||
parser.add_argument('frames_dir', type=str, help="Frames directory")
|
||||
parser.add_argument('main_script_root_dir', type=str, help="SyncFramesMain.py working directory")
|
||||
args = parser.parse_args()
|
||||
|
||||
sourceRootPath = args.frames_dir
|
||||
sourceFramesPath = f"{args.frames_dir}/TotalModeFrames"
|
||||
mainPythonWorkPath = args.main_script_root_dir
|
||||
print(f"Start of Gemini2 synchronize frame. sourceRootPath={sourceRootPath}")
|
||||
|
||||
main()
|
||||
|
||||
print("Finish of Gemini2 synchronize frame.")
|
||||
@@ -0,0 +1,430 @@
|
||||
import os.path
|
||||
import argparse
|
||||
import sys
|
||||
import json
|
||||
import shutil
|
||||
import configparser
|
||||
|
||||
from pathlib import Path
|
||||
from datetime import datetime
|
||||
|
||||
import os
|
||||
import re
|
||||
|
||||
## Config.ini中配置
|
||||
# 单位:FPS,请依据数据集实际帧率填写,否则会导致匹配不准确
|
||||
frameRate = -1
|
||||
# 单位:ms,当匹配后某组数据帧的时间戳极差大于等于tspRangeThreshold,文件名会增加标注
|
||||
tspRangeThreshold = -1
|
||||
|
||||
## Python脚本自动解析, 不要修改
|
||||
primaryId = '-1' #主机ID
|
||||
mainPythonWorkPath = "" # SyncFramesMain.py的工作目录
|
||||
sourceFramesPath = "" # 请勿修改,固定值,TotalMode目录路径
|
||||
sourceRootPath = "" # 存放TotalMode的根目录
|
||||
resultPath = "" # 保存sync分析结果
|
||||
streamProfileDict = dict() # 保存Python结果
|
||||
|
||||
class PictureInfo(object):
|
||||
class Struct(object):
|
||||
def __init__(self, deviceId, sensorType, syncTimeStamp, picturePath, deviceIdFull, fileExt):
|
||||
self.deviceId = deviceId
|
||||
self.sensorType = sensorType
|
||||
self.syncTimeStamp = syncTimeStamp
|
||||
self.picturePath = picturePath
|
||||
self.deviceIdFull = deviceIdFull
|
||||
self.fileExt = fileExt
|
||||
|
||||
def make_struct(self, deviceId, sensorType, syncTimeStamp, picturePath, deviceIdFull, fileExt):
|
||||
return self.Struct(deviceId, sensorType, syncTimeStamp, picturePath, deviceIdFull, fileExt)
|
||||
|
||||
class StreamProfileInfo:
|
||||
def __init__(self, sensorType, width, height, format, fps):
|
||||
self.sensorType = sensorType
|
||||
self.width = width
|
||||
self.height = height
|
||||
self.format = format
|
||||
self.fps = fps
|
||||
|
||||
def initPrimaryId():
|
||||
global primaryId
|
||||
|
||||
deviceInfoPath = f"{sourceRootPath}/DevicesInfo.txt"
|
||||
if not os.path.exists(deviceInfoPath):
|
||||
print(f"Initialize primaryId failed. {deviceInfoPath} not exists")
|
||||
return False
|
||||
|
||||
with open(deviceInfoPath, 'r') as f:
|
||||
data = json.load(f)
|
||||
|
||||
if not 'devices' in data:
|
||||
print(f"Initialize primaryId failed. {deviceInfoPath} not contain item 'devices'")
|
||||
return False
|
||||
|
||||
for item in data['devices']:
|
||||
if 'isPrimaryDevice' in item and 'index' in item and item['isPrimaryDevice']:
|
||||
primaryId = str(item['index'])
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def initSyncConfigParams():
|
||||
global frameRate, tspRangeThreshold
|
||||
|
||||
configPath = f"{mainPythonWorkPath}/Config.ini"
|
||||
if not os.path.exists(configPath):
|
||||
print(f"Initliaze synchronized config parameter failed. {configPath} not exists.")
|
||||
return False
|
||||
|
||||
# 创建ConfigParser对象
|
||||
config = configparser.ConfigParser()
|
||||
|
||||
# 读取ini文件
|
||||
config.read(configPath, 'utf-8')
|
||||
|
||||
if not config.has_option('Parameter', 'frameRate'):
|
||||
print(f"Initliaze synchronized config parameter failed. Not define 'frameRate' in ${configPath}")
|
||||
return False
|
||||
|
||||
if not config.has_option('Parameter', 'tspRangeThreshold'):
|
||||
print(f"Initliaze synchronized config parameter failed. Not define 'tspRangeThreshold' in ${configPath}")
|
||||
return False
|
||||
|
||||
frameRate = config.getfloat('Parameter', 'frameRate')
|
||||
if frameRate <= 0 or frameRate >= 1000:
|
||||
print(f"Initliaze synchronized config parameter failed. Invalid frameRate={frameRate}")
|
||||
return False
|
||||
|
||||
tspRangeThreshold = config.getfloat('Parameter', 'tspRangeThreshold')
|
||||
if tspRangeThreshold <= 0:
|
||||
print(f"Initliaze synchronized config parameter failed. Invalid tspRangeThreshold={tspRangeThreshold}")
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def getDeviceCount():
|
||||
deviceInfoPath = f"{sourceRootPath}/DevicesInfo.txt"
|
||||
if not os.path.exists(deviceInfoPath):
|
||||
print(f"Get device count failed. {deviceInfoPath} not exists")
|
||||
return 0
|
||||
|
||||
with open(deviceInfoPath, 'r') as f:
|
||||
data = json.load(f)
|
||||
|
||||
if not 'devices' in data:
|
||||
print(f"Get device count failed. {deviceInfoPath} not contain item 'devices'")
|
||||
return 0
|
||||
|
||||
return len(data['devices'])
|
||||
|
||||
def initProfileInfoDict():
|
||||
global streamProfileDict
|
||||
|
||||
streamProfileDict = dict()
|
||||
profilePath = f"{sourceRootPath}/StreamProfileInfo.txt"
|
||||
if not os.path.exists(profilePath):
|
||||
print(f"Initialize profile information dictionary failed. {profilePath} not exists")
|
||||
return False
|
||||
|
||||
with open(profilePath, 'r') as f:
|
||||
data = json.load(f)
|
||||
|
||||
if not 'streamProfiles' in data:
|
||||
print(f"Initialize profile information dictionary failed. {profilePath} not contain item 'streamProfiles'")
|
||||
return False
|
||||
|
||||
for item in data['streamProfiles']:
|
||||
if 'sensorType' in item and 'width' in item and 'height' in item and 'format' in item and 'fps' in item:
|
||||
profileInfo = StreamProfileInfo(item['sensorType'], item['width'], item['height'], item['format'], item['fps'])
|
||||
streamProfileDict.setdefault(item['sensorType'], profileInfo)
|
||||
else:
|
||||
print(f"Initialize profile information dictionary. Error invalid StreamProfile, {item}")
|
||||
|
||||
return len(streamProfileDict.items()) > 0
|
||||
|
||||
def initResultDir():
|
||||
global resultPath
|
||||
timeText = datetime.now().strftime("%Y-%m-%d_%H%M%S")
|
||||
resultPath = f"{sourceRootPath}/results-{timeText}"
|
||||
if not os.path.exists(resultPath):
|
||||
os.makedirs(resultPath)
|
||||
|
||||
def isFrameFile(fileName):
|
||||
fileExt = os.path.splitext(fileName)
|
||||
if len(fileExt) < 2:
|
||||
return False
|
||||
|
||||
frameExts = {".jpeg", ".jpg", ".png", ".raw", "bmp"}
|
||||
return fileExt[1] in frameExts
|
||||
|
||||
def extract_d_number(filename):
|
||||
"""提取文件名中以'd'开头的数字部分"""
|
||||
match_d = re.search(r'd(\d+)', filename)
|
||||
if match_d:
|
||||
return int(match_d.group(1))
|
||||
|
||||
match_g = re.search(r'g(\d+)', filename)
|
||||
if match_g:
|
||||
return int(match_g.group(1))
|
||||
|
||||
return float('inf')
|
||||
|
||||
def initPictureInfoDictionary(rootFilePath, pictureInfoDict):
|
||||
frameFileCount = 0
|
||||
for dirpath, dirnames, filenames in os.walk(rootFilePath):
|
||||
filenames.sort(key=extract_d_number) # 排序,按文件名中的'd'后面的数字从小到大排序
|
||||
for fileName in filenames:
|
||||
filePath = os.path.join(dirpath, fileName)
|
||||
if not isFrameFile(fileName):
|
||||
continue
|
||||
|
||||
frameFileCount += 1
|
||||
fileNameNoExt = os.path.splitext(fileName)[0]
|
||||
pictureNameList = fileNameNoExt.split('_')
|
||||
deviceId = pictureNameList[2][5:]
|
||||
sensorType = pictureNameList[0].replace('#', '_')
|
||||
syncTimeStamp = int(pictureNameList[3][1:])
|
||||
|
||||
# 创建结构体
|
||||
pictureInfo = PictureInfo()
|
||||
info = pictureInfo.make_struct(deviceId, sensorType, syncTimeStamp, filePath, os.path.basename(dirpath), os.path.splitext(fileName)[1])
|
||||
|
||||
# 格式化字典一个key对应一个数组
|
||||
pictureInfoDict.setdefault(deviceId, []).append(info)
|
||||
|
||||
if 0 == frameFileCount:
|
||||
print("initialize pictureInfoDict failed. Not found frame file")
|
||||
|
||||
|
||||
def matchFrame(pictureInfoDict):
|
||||
global primaryId, frameRate
|
||||
global streamProfileDict
|
||||
|
||||
# 获取比较图像数组,取第一个设备的Color数组来进行比较
|
||||
compareList = []
|
||||
for key in pictureInfoDict:
|
||||
listTmp = list(pictureInfoDict[key])
|
||||
for info in listTmp:
|
||||
if info.deviceId == primaryId and info.sensorType == 'ir_left':
|
||||
compareList.append(info)
|
||||
|
||||
# 根据开流情况动态分析,减少循环次数
|
||||
hasColorProfile = 'OB_SENSOR_COLOR' in streamProfileDict
|
||||
hasDepthProfile = 'OB_SENSOR_DEPTH' in streamProfileDict
|
||||
hasIRProfile = 'OB_SENSOR_IR' in streamProfileDict
|
||||
hasIRLeftProfile = 'OB_SENSOR_IR_LEFT' in streamProfileDict
|
||||
hasIRRightProfile = 'OB_SENSOR_IR_RIGHT' in streamProfileDict
|
||||
|
||||
# 匹配相邻帧
|
||||
resultDict = {}
|
||||
consumedList = []
|
||||
dictIndex = 0
|
||||
frameTspHalfGap = int(1000.0/frameRate/2.0+0.5)
|
||||
for comparePic in compareList:
|
||||
resultDict.setdefault(dictIndex, []).append(comparePic)
|
||||
for key in pictureInfoDict:
|
||||
listTmp = list(pictureInfoDict[key])
|
||||
# 对比彩色
|
||||
if hasColorProfile:
|
||||
for info in listTmp:
|
||||
if info.deviceId == primaryId and info.sensorType == comparePic.sensorType:
|
||||
continue
|
||||
|
||||
if info in consumedList:
|
||||
continue
|
||||
|
||||
if info.sensorType == 'color' and abs(info.syncTimeStamp - comparePic.syncTimeStamp) < frameTspHalfGap:
|
||||
resultDict.setdefault(dictIndex, []).append(info)
|
||||
consumedList.append(info)
|
||||
break
|
||||
|
||||
# 对Depth
|
||||
if hasDepthProfile:
|
||||
for info in listTmp:
|
||||
if info.deviceId == primaryId and info.sensorType == comparePic.sensorType:
|
||||
continue
|
||||
|
||||
if info in consumedList:
|
||||
continue
|
||||
|
||||
if info.sensorType == 'depth' and abs(info.syncTimeStamp - comparePic.syncTimeStamp) < frameTspHalfGap:
|
||||
resultDict.setdefault(dictIndex, []).append(info)
|
||||
consumedList.append(info)
|
||||
print("=====================result value : jjjjjjjjjjjjjjj:%s" % (info.syncTimeStamp))
|
||||
break
|
||||
|
||||
# IR
|
||||
if hasIRProfile:
|
||||
for info in listTmp:
|
||||
if info.deviceId == primaryId and info.sensorType == comparePic.sensorType:
|
||||
continue
|
||||
|
||||
if info in consumedList:
|
||||
continue
|
||||
|
||||
if info.sensorType == 'ir' and abs(info.syncTimeStamp - comparePic.syncTimeStamp) < frameTspHalfGap:
|
||||
resultDict.setdefault(dictIndex, []).append(info)
|
||||
consumedList.append(info)
|
||||
break
|
||||
|
||||
# 对左IR
|
||||
if hasIRLeftProfile:
|
||||
for info in listTmp:
|
||||
if info.deviceId == primaryId and info.sensorType == comparePic.sensorType:
|
||||
continue
|
||||
|
||||
if info in consumedList:
|
||||
continue
|
||||
|
||||
if info.sensorType == 'ir_left' and abs(info.syncTimeStamp - comparePic.syncTimeStamp) < frameTspHalfGap:
|
||||
resultDict.setdefault(dictIndex, []).append(info)
|
||||
consumedList.append(info)
|
||||
break
|
||||
|
||||
# 对右IR
|
||||
if hasIRRightProfile:
|
||||
for info in listTmp:
|
||||
if info.deviceId == primaryId and info.sensorType == comparePic.sensorType:
|
||||
continue
|
||||
|
||||
if info in consumedList:
|
||||
continue
|
||||
|
||||
if info.sensorType == 'ir_right' and abs(info.syncTimeStamp - comparePic.syncTimeStamp) < frameTspHalfGap:
|
||||
resultDict.setdefault(dictIndex, []).append(info)
|
||||
consumedList.append(info)
|
||||
break
|
||||
|
||||
# 计算偏差
|
||||
minTsp = min(resultDict[dictIndex], key=lambda x: x.syncTimeStamp)
|
||||
for info in resultDict[dictIndex]:
|
||||
info.tspDiff = info.syncTimeStamp - minTsp.syncTimeStamp
|
||||
|
||||
dictIndex += 1
|
||||
|
||||
return resultDict
|
||||
|
||||
|
||||
def safe_make_dir(path):
|
||||
if not os.path.exists(path):
|
||||
os.makedirs(path)
|
||||
|
||||
|
||||
def handleSyncFrames():
|
||||
global resultPath
|
||||
global sourceFramesPath
|
||||
global streamProfileDict
|
||||
|
||||
fp = open(f"{resultPath}/frameMatchLog.txt", "w")
|
||||
|
||||
if not initProfileInfoDict():
|
||||
print("Initialize stream profile dictionary failed. exit")
|
||||
return
|
||||
print(f"Stream profiles: {streamProfileDict}")
|
||||
streamProfileCount = len(streamProfileDict.items())
|
||||
|
||||
pictureInfoDict = {}
|
||||
|
||||
initPictureInfoDictionary(Path(sourceFramesPath), pictureInfoDict)
|
||||
if len(pictureInfoDict) <= 0:
|
||||
print("init pictureInfoDict failed. pictureInfoDict.len = 0")
|
||||
return
|
||||
|
||||
# # Dump pictureInfoDict
|
||||
# for key in pictureInfoDict:
|
||||
# print("===================key:%s" % key)
|
||||
# listTmp = list(pictureInfoDict[key])
|
||||
# for info in listTmp:
|
||||
# print("=====================value : deviceId:%s\tsensorType:%s\tsyncTimeStamp:%s\tpicturePath:%s" % (info.deviceId,info.sensorType,info.syncTimeStamp,info.picturePath))
|
||||
|
||||
deviceCount = getDeviceCount()
|
||||
print("=================device count:%d" % deviceCount)
|
||||
|
||||
resultDict = {}
|
||||
resultDict = matchFrame(pictureInfoDict)
|
||||
|
||||
for key in resultDict:
|
||||
fp.write("===================result key:%s\n" % key)
|
||||
print("===================result key:%s" % key)
|
||||
listTmp = list(resultDict[key])
|
||||
for info in listTmp:
|
||||
fp.write("=====================result value : deviceId:%s\tsensorType:%s\tsyncTimeStamp:%s\tpicturePath:%s\n" % (
|
||||
info.deviceId, info.sensorType, info.syncTimeStamp, info.picturePath))
|
||||
print("=====================result value : deviceId:%s\tsensorType:%s\tsyncTimeStamp:%s\tpicturePath:%s" % (
|
||||
info.deviceId, info.sensorType, info.syncTimeStamp, info.picturePath))
|
||||
|
||||
matchPath = f"{resultPath}/matchFrames/"
|
||||
safe_make_dir(matchPath)
|
||||
notMatchPath = f"{resultPath}/notMatchFrames/"
|
||||
abnormalPath = f"{resultPath}/abnormal/"
|
||||
|
||||
for key in resultDict:
|
||||
listTmp = list(resultDict[key])
|
||||
# print("***********")
|
||||
# print(listTmp)
|
||||
if (len(listTmp) == (deviceCount * streamProfileCount)):
|
||||
haveAbnormalData = False
|
||||
for info in listTmp:
|
||||
if info.tspDiff >= tspRangeThreshold:
|
||||
path = matchPath + str(key) + "_" + info.sensorType + "_" + info.deviceIdFull + "_" + str(
|
||||
info.syncTimeStamp) + "_[" + str(info.tspDiff) + "]" + "_xxxxxx" + info.fileExt
|
||||
shutil.copy(info.picturePath, path)
|
||||
haveAbnormalData = True
|
||||
else:
|
||||
# print("***********")
|
||||
# print(info.picturePath)
|
||||
# print("###########")
|
||||
path = matchPath + str(key) + "_" + info.sensorType + "_" + info.deviceIdFull + "_" + str(
|
||||
info.syncTimeStamp) + "_[" + str(info.tspDiff) + "]" + info.fileExt
|
||||
shutil.copy(info.picturePath, path)
|
||||
# print(path)
|
||||
|
||||
if haveAbnormalData:
|
||||
safe_make_dir(abnormalPath)
|
||||
for info in listTmp:
|
||||
if info.tspDiff >= tspRangeThreshold:
|
||||
path = abnormalPath + str(key) + "_" + info.sensorType + "_" + info.deviceIdFull + "_" + str(
|
||||
info.syncTimeStamp) + "_[" + str(info.tspDiff) + "]" + "_xxxxxx" + info.fileExt
|
||||
shutil.copy(info.picturePath, path)
|
||||
else:
|
||||
path = abnormalPath + str(key) + "_" + info.sensorType + "_" + info.deviceIdFull + "_" + str(
|
||||
info.syncTimeStamp) + "_[" + str(info.tspDiff) + "]" + info.fileExt
|
||||
shutil.copy(info.picturePath, path)
|
||||
else:
|
||||
safe_make_dir(notMatchPath)
|
||||
for info in listTmp:
|
||||
newFilePath = notMatchPath + str(key) + "_" + info.sensorType + "_" + info.deviceIdFull + "_" + str(
|
||||
info.syncTimeStamp) + "_[" + str(info.tspDiff) + "]" + info.fileExt
|
||||
shutil.copy(info.picturePath, newFilePath)
|
||||
|
||||
def main():
|
||||
if not initPrimaryId():
|
||||
print("Initialize primaryId failed. exit.")
|
||||
return
|
||||
|
||||
if not initSyncConfigParams():
|
||||
print("Initialized Sync config parameter failed. exit.")
|
||||
return
|
||||
|
||||
initResultDir()
|
||||
|
||||
print(f"PrimaryId={primaryId}, frameRate={frameRate}, tspRangeThreshold={tspRangeThreshold}")
|
||||
handleSyncFrames()
|
||||
|
||||
return
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
parser = argparse.ArgumentParser(description="Synchronize frames support for 'Orbbec Devices'")
|
||||
parser.add_argument('frames_dir', type=str, help="Frames directory")
|
||||
parser.add_argument('main_script_root_dir', type=str, help="SyncFramesMain.py working directory")
|
||||
args = parser.parse_args()
|
||||
|
||||
sourceRootPath = args.frames_dir
|
||||
sourceFramesPath = f"{args.frames_dir}/TotalModeFrames"
|
||||
mainPythonWorkPath = args.main_script_root_dir
|
||||
print(f"Start of CommonFrameMatch synchronize frame. sourceRootPath={sourceRootPath}")
|
||||
|
||||
main()
|
||||
|
||||
print("Finish of CommonFrameMatch synchronize frame.")
|
||||
@@ -0,0 +1,408 @@
|
||||
import os.path
|
||||
import argparse
|
||||
import sys
|
||||
import json
|
||||
import shutil
|
||||
import configparser
|
||||
|
||||
from pathlib import Path
|
||||
from datetime import datetime
|
||||
|
||||
## Config.ini中配置
|
||||
# 单位:FPS,请依据数据集实际帧率填写,否则会导致匹配不准确
|
||||
frameRate = -1
|
||||
# 单位:ms,当匹配后某组数据帧的时间戳极差大于等于tspRangeThreshold,文件名会增加标注
|
||||
tspRangeThreshold = -1
|
||||
|
||||
## Python脚本自动解析, 不要修改
|
||||
primaryId = '-1' #主机ID
|
||||
mainPythonWorkPath = "" # SyncFramesMain.py的工作目录
|
||||
sourceFramesPath = "" # 请勿修改,固定值,TotalMode目录路径
|
||||
sourceRootPath = "" # 存放TotalMode的根目录
|
||||
resultPath = "" # 保存sync分析结果
|
||||
streamProfileDict = dict() # 保存Python结果
|
||||
|
||||
class PictureInfo(object):
|
||||
class Struct(object):
|
||||
def __init__(self, deviceId, sensorType, syncTimeStamp, picturePath, deviceIdFull, fileExt):
|
||||
self.deviceId = deviceId
|
||||
self.sensorType = sensorType
|
||||
self.syncTimeStamp = syncTimeStamp
|
||||
self.picturePath = picturePath
|
||||
self.deviceIdFull = deviceIdFull
|
||||
self.fileExt = fileExt
|
||||
|
||||
def make_struct(self, deviceId, sensorType, syncTimeStamp, picturePath, deviceIdFull, fileExt):
|
||||
return self.Struct(deviceId, sensorType, syncTimeStamp, picturePath, deviceIdFull, fileExt)
|
||||
|
||||
class StreamProfileInfo:
|
||||
def __init__(self, sensorType, width, height, format, fps):
|
||||
self.sensorType = sensorType
|
||||
self.width = width
|
||||
self.height = height
|
||||
self.format = format
|
||||
self.fps = fps
|
||||
|
||||
def initPrimaryId():
|
||||
global primaryId
|
||||
|
||||
deviceInfoPath = f"{sourceRootPath}/DevicesInfo.txt"
|
||||
if not os.path.exists(deviceInfoPath):
|
||||
print(f"Initialize primaryId failed. {deviceInfoPath} not exists")
|
||||
return False
|
||||
|
||||
with open(deviceInfoPath, 'r') as f:
|
||||
data = json.load(f)
|
||||
|
||||
if not 'devices' in data:
|
||||
print(f"Initialize primaryId failed. {deviceInfoPath} not contain item 'devices'")
|
||||
return False
|
||||
|
||||
for item in data['devices']:
|
||||
if 'isPrimaryDevice' in item and 'index' in item and item['isPrimaryDevice']:
|
||||
primaryId = str(item['index'])
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def initSyncConfigParams():
|
||||
global frameRate, tspRangeThreshold
|
||||
|
||||
configPath = f"{mainPythonWorkPath}/Config.ini"
|
||||
if not os.path.exists(configPath):
|
||||
print(f"Initliaze synchronized config parameter failed. {configPath} not exists.")
|
||||
return False
|
||||
|
||||
# 创建ConfigParser对象
|
||||
config = configparser.ConfigParser()
|
||||
|
||||
# 读取ini文件
|
||||
config.read(configPath, 'utf-8')
|
||||
|
||||
if not config.has_option('Parameter', 'frameRate'):
|
||||
print(f"Initliaze synchronized config parameter failed. Not define 'frameRate' in ${configPath}")
|
||||
return False
|
||||
|
||||
if not config.has_option('Parameter', 'tspRangeThreshold'):
|
||||
print(f"Initliaze synchronized config parameter failed. Not define 'tspRangeThreshold' in ${configPath}")
|
||||
return False
|
||||
|
||||
frameRate = config.getfloat('Parameter', 'frameRate')
|
||||
if frameRate <= 0 or frameRate >= 1000:
|
||||
print(f"Initliaze synchronized config parameter failed. Invalid frameRate={frameRate}")
|
||||
return False
|
||||
|
||||
tspRangeThreshold = config.getfloat('Parameter', 'tspRangeThreshold')
|
||||
if tspRangeThreshold <= 0:
|
||||
print(f"Initliaze synchronized config parameter failed. Invalid tspRangeThreshold={tspRangeThreshold}")
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def getDeviceCount():
|
||||
deviceInfoPath = f"{sourceRootPath}/DevicesInfo.txt"
|
||||
if not os.path.exists(deviceInfoPath):
|
||||
print(f"Get device count failed. {deviceInfoPath} not exists")
|
||||
return 0
|
||||
|
||||
with open(deviceInfoPath, 'r') as f:
|
||||
data = json.load(f)
|
||||
|
||||
if not 'devices' in data:
|
||||
print(f"Get device count failed. {deviceInfoPath} not contain item 'devices'")
|
||||
return 0
|
||||
|
||||
return len(data['devices'])
|
||||
|
||||
def initProfileInfoDict():
|
||||
global streamProfileDict
|
||||
|
||||
streamProfileDict = dict()
|
||||
profilePath = f"{sourceRootPath}/StreamProfileInfo.txt"
|
||||
if not os.path.exists(profilePath):
|
||||
print(f"Initialize profile information dictionary failed. {profilePath} not exists")
|
||||
return False
|
||||
|
||||
with open(profilePath, 'r') as f:
|
||||
data = json.load(f)
|
||||
|
||||
if not 'streamProfiles' in data:
|
||||
print(f"Initialize profile information dictionary failed. {profilePath} not contain item 'streamProfiles'")
|
||||
return False
|
||||
|
||||
for item in data['streamProfiles']:
|
||||
if 'sensorType' in item and 'width' in item and 'height' in item and 'format' in item and 'fps' in item:
|
||||
profileInfo = StreamProfileInfo(item['sensorType'], item['width'], item['height'], item['format'], item['fps'])
|
||||
streamProfileDict.setdefault(item['sensorType'], profileInfo)
|
||||
else:
|
||||
print(f"Initialize profile information dictionary. Error invalid StreamProfile, {item}")
|
||||
|
||||
return len(streamProfileDict.items()) > 0
|
||||
|
||||
def initResultDir():
|
||||
global resultPath
|
||||
timeText = datetime.now().strftime("%Y-%m-%d_%H%M%S")
|
||||
resultPath = f"{sourceRootPath}/results-{timeText}"
|
||||
if not os.path.exists(resultPath):
|
||||
os.makedirs(resultPath)
|
||||
|
||||
def isFrameFile(fileName):
|
||||
fileExt = os.path.splitext(fileName)
|
||||
if len(fileExt) < 2:
|
||||
return False
|
||||
|
||||
frameExts = {".jpeg", ".jpg", ".png", ".raw", "bmp"}
|
||||
return fileExt[1] in frameExts
|
||||
|
||||
def initPictureInfoDictionary(rootFilePath, pictureInfoDict):
|
||||
frameFileCount = 0
|
||||
for dirpath, dirnames, filenames in os.walk(rootFilePath):
|
||||
# print(f"dirpath={dirpath}, dirpath.basename=" + os.path.basename(dirpath))
|
||||
for fileName in filenames:
|
||||
filePath = os.path.join(dirpath, fileName)
|
||||
if not isFrameFile(fileName):
|
||||
continue
|
||||
|
||||
frameFileCount += 1
|
||||
fileNameNoExt = os.path.splitext(fileName)[0]
|
||||
pictureNameList = fileNameNoExt.split('_')
|
||||
deviceId = pictureNameList[2][5:]
|
||||
sensorType = pictureNameList[0].replace('#', '_')
|
||||
syncTimeStamp = int(pictureNameList[3][1:])
|
||||
|
||||
# 创建结构体
|
||||
pictureInfo = PictureInfo()
|
||||
info = pictureInfo.make_struct(deviceId, sensorType, syncTimeStamp, filePath, os.path.basename(dirpath), os.path.splitext(fileName)[1])
|
||||
|
||||
# 格式化字典一个key对应一个数组
|
||||
pictureInfoDict.setdefault(deviceId, []).append(info)
|
||||
|
||||
if 0 == frameFileCount:
|
||||
print("initialize pictureInfoDict failed. Not found frame file")
|
||||
|
||||
|
||||
def matchFrame(pictureInfoDict):
|
||||
global primaryId, frameRate
|
||||
global streamProfileDict
|
||||
|
||||
# 获取比较图像数组,取第一个设备的Color数组来进行比较
|
||||
compareList = []
|
||||
for key in pictureInfoDict:
|
||||
listTmp = list(pictureInfoDict[key])
|
||||
for info in listTmp:
|
||||
if info.deviceId == primaryId and info.sensorType == 'color':
|
||||
compareList.append(info)
|
||||
|
||||
# 根据开流情况动态分析,减少循环次数
|
||||
hasColorProfile = 'OB_SENSOR_COLOR' in streamProfileDict
|
||||
hasDepthProfile = 'OB_SENSOR_DEPTH' in streamProfileDict
|
||||
hasIRProfile = 'OB_SENSOR_IR' in streamProfileDict
|
||||
hasIRLeftProfile = 'OB_SENSOR_IR_LEFT' in streamProfileDict
|
||||
hasIRRightProfile = 'OB_SENSOR_IR_RIGHT' in streamProfileDict
|
||||
|
||||
# 匹配相邻帧
|
||||
resultDict = {}
|
||||
consumedList = []
|
||||
dictIndex = 0
|
||||
frameTspHalfGap = int(1000.0/frameRate/2.0+0.5)
|
||||
for comparePic in compareList:
|
||||
resultDict.setdefault(dictIndex, []).append(comparePic)
|
||||
for key in pictureInfoDict:
|
||||
listTmp = list(pictureInfoDict[key])
|
||||
# 对比彩色
|
||||
if hasColorProfile:
|
||||
for info in listTmp:
|
||||
if info.deviceId == primaryId and info.sensorType == comparePic.sensorType:
|
||||
continue
|
||||
|
||||
if info in consumedList:
|
||||
continue
|
||||
|
||||
if info.sensorType == 'color' and abs(info.syncTimeStamp - comparePic.syncTimeStamp) < frameTspHalfGap:
|
||||
resultDict.setdefault(dictIndex, []).append(info)
|
||||
consumedList.append(info)
|
||||
break
|
||||
|
||||
# 对Depth
|
||||
if hasDepthProfile:
|
||||
for info in listTmp:
|
||||
if info.deviceId == primaryId and info.sensorType == comparePic.sensorType:
|
||||
continue
|
||||
|
||||
if info in consumedList:
|
||||
continue
|
||||
|
||||
if info.sensorType == 'depth' and abs(info.syncTimeStamp - comparePic.syncTimeStamp) < frameTspHalfGap:
|
||||
resultDict.setdefault(dictIndex, []).append(info)
|
||||
consumedList.append(info)
|
||||
break
|
||||
|
||||
# IR
|
||||
if hasIRProfile:
|
||||
for info in listTmp:
|
||||
if info.deviceId == primaryId and info.sensorType == comparePic.sensorType:
|
||||
continue
|
||||
|
||||
if info in consumedList:
|
||||
continue
|
||||
|
||||
if info.sensorType == 'ir' and abs(info.syncTimeStamp - comparePic.syncTimeStamp) < frameTspHalfGap:
|
||||
resultDict.setdefault(dictIndex, []).append(info)
|
||||
consumedList.append(info)
|
||||
break
|
||||
|
||||
# 对左IR
|
||||
if hasIRLeftProfile:
|
||||
for info in listTmp:
|
||||
if info.deviceId == primaryId and info.sensorType == comparePic.sensorType:
|
||||
continue
|
||||
|
||||
if info in consumedList:
|
||||
continue
|
||||
|
||||
if info.sensorType == 'ir_left' and abs(info.syncTimeStamp - comparePic.syncTimeStamp) < frameTspHalfGap:
|
||||
resultDict.setdefault(dictIndex, []).append(info)
|
||||
consumedList.append(info)
|
||||
break
|
||||
|
||||
# 对右IR
|
||||
if hasIRRightProfile:
|
||||
for info in listTmp:
|
||||
if info.deviceId == primaryId and info.sensorType == comparePic.sensorType:
|
||||
continue
|
||||
|
||||
if info in consumedList:
|
||||
continue
|
||||
|
||||
if info.sensorType == 'ir_right' and abs(info.syncTimeStamp - comparePic.syncTimeStamp) < frameTspHalfGap:
|
||||
resultDict.setdefault(dictIndex, []).append(info)
|
||||
consumedList.append(info)
|
||||
break
|
||||
|
||||
# 计算偏差
|
||||
minTsp = min(resultDict[dictIndex], key=lambda x: x.syncTimeStamp)
|
||||
for info in resultDict[dictIndex]:
|
||||
info.tspDiff = info.syncTimeStamp - minTsp.syncTimeStamp
|
||||
|
||||
dictIndex += 1
|
||||
|
||||
return resultDict
|
||||
|
||||
|
||||
def safe_make_dir(path):
|
||||
if not os.path.exists(path):
|
||||
os.makedirs(path)
|
||||
|
||||
|
||||
def handleSyncFrames():
|
||||
global resultPath
|
||||
global sourceFramesPath
|
||||
global streamProfileDict
|
||||
|
||||
fp = open(f"{resultPath}/frameMatchLog.txt", "w")
|
||||
|
||||
if not initProfileInfoDict():
|
||||
print("Initialize stream profile dictionary failed. exit")
|
||||
return
|
||||
print(f"Stream profiles: {streamProfileDict}")
|
||||
streamProfileCount = len(streamProfileDict.items())
|
||||
|
||||
pictureInfoDict = {}
|
||||
|
||||
initPictureInfoDictionary(Path(sourceFramesPath), pictureInfoDict)
|
||||
if len(pictureInfoDict) <= 0:
|
||||
print("init pictureInfoDict failed. pictureInfoDict.len = 0")
|
||||
return
|
||||
|
||||
# # Dump pictureInfoDict
|
||||
# for key in pictureInfoDict:
|
||||
# print("===================key:%s" % key)
|
||||
# listTmp = list(pictureInfoDict[key])
|
||||
# for info in listTmp:
|
||||
# print("=====================value : deviceId:%s\tsensorType:%s\tsyncTimeStamp:%s\tpicturePath:%s" % (info.deviceId,info.sensorType,info.syncTimeStamp,info.picturePath))
|
||||
|
||||
deviceCount = getDeviceCount()
|
||||
print("=================device count:%d" % deviceCount)
|
||||
|
||||
resultDict = {}
|
||||
resultDict = matchFrame(pictureInfoDict)
|
||||
|
||||
for key in resultDict:
|
||||
fp.write("===================result key:%s\n" % key)
|
||||
print("===================result key:%s" % key)
|
||||
listTmp = list(resultDict[key])
|
||||
for info in listTmp:
|
||||
fp.write("=====================result value : deviceId:%s\tsensorType:%s\tsyncTimeStamp:%s\tpicturePath:%s\n" % (
|
||||
info.deviceId, info.sensorType, info.syncTimeStamp, info.picturePath))
|
||||
print("=====================result value : deviceId:%s\tsensorType:%s\tsyncTimeStamp:%s\tpicturePath:%s" % (
|
||||
info.deviceId, info.sensorType, info.syncTimeStamp, info.picturePath))
|
||||
|
||||
matchPath = f"{resultPath}/matchFrames/"
|
||||
safe_make_dir(matchPath)
|
||||
notMatchPath = f"{resultPath}/notMatchFrames/"
|
||||
abnormalPath = f"{resultPath}/abnormal/"
|
||||
|
||||
for key in resultDict:
|
||||
listTmp = list(resultDict[key])
|
||||
if (len(listTmp) == (deviceCount * streamProfileCount)):
|
||||
haveAbnormalData = False
|
||||
for info in listTmp:
|
||||
if info.tspDiff >= tspRangeThreshold:
|
||||
path = matchPath + str(key) + "_" + info.sensorType + "_" + info.deviceIdFull + "_" + str(
|
||||
info.syncTimeStamp) + "_[" + str(info.tspDiff) + "]" + "_xxxxxx" + info.fileExt
|
||||
shutil.copy(info.picturePath, path)
|
||||
haveAbnormalData = True
|
||||
else:
|
||||
path = matchPath + str(key) + "_" + info.sensorType + "_" + info.deviceIdFull + "_" + str(
|
||||
info.syncTimeStamp) + "_[" + str(info.tspDiff) + "]" + info.fileExt
|
||||
shutil.copy(info.picturePath, path)
|
||||
|
||||
if haveAbnormalData:
|
||||
safe_make_dir(abnormalPath)
|
||||
for info in listTmp:
|
||||
if info.tspDiff >= tspRangeThreshold:
|
||||
path = abnormalPath + str(key) + "_" + info.sensorType + "_" + info.deviceIdFull + "_" + str(
|
||||
info.syncTimeStamp) + "_[" + str(info.tspDiff) + "]" + "_xxxxxx" + info.fileExt
|
||||
shutil.copy(info.picturePath, path)
|
||||
else:
|
||||
path = abnormalPath + str(key) + "_" + info.sensorType + "_" + info.deviceIdFull + "_" + str(
|
||||
info.syncTimeStamp) + "_[" + str(info.tspDiff) + "]" + info.fileExt
|
||||
shutil.copy(info.picturePath, path)
|
||||
else:
|
||||
safe_make_dir(notMatchPath)
|
||||
for info in listTmp:
|
||||
newFilePath = notMatchPath + str(key) + "_" + info.sensorType + "_" + info.deviceIdFull + "_" + str(
|
||||
info.syncTimeStamp) + "_[" + str(info.tspDiff) + "]" + info.fileExt
|
||||
shutil.copy(info.picturePath, newFilePath)
|
||||
|
||||
def main():
|
||||
if not initPrimaryId():
|
||||
print("Initialize primaryId failed. exit.")
|
||||
return
|
||||
|
||||
if not initSyncConfigParams():
|
||||
print("Initialized Sync config parameter failed. exit.")
|
||||
return
|
||||
|
||||
initResultDir()
|
||||
|
||||
print(f"PrimaryId={primaryId}, frameRate={frameRate}, tspRangeThreshold={tspRangeThreshold}")
|
||||
handleSyncFrames()
|
||||
|
||||
return
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
parser = argparse.ArgumentParser(description="Synchronize frames support for 'Orbbec Gemini 2 VL'")
|
||||
parser.add_argument('frames_dir', type=str, help="Frames directory")
|
||||
parser.add_argument('main_script_root_dir', type=str, help="SyncFramesMain.py working directory")
|
||||
args = parser.parse_args()
|
||||
|
||||
sourceRootPath = args.frames_dir
|
||||
sourceFramesPath = f"{args.frames_dir}/TotalModeFrames"
|
||||
mainPythonWorkPath = args.main_script_root_dir
|
||||
print(f"Start of Gemini2VL synchronize frame. sourceRootPath={sourceRootPath}")
|
||||
|
||||
main()
|
||||
|
||||
print("Finish of Gemini2VL synchronize frame.")
|
||||
@@ -0,0 +1,412 @@
|
||||
import os.path
|
||||
import argparse
|
||||
import sys
|
||||
import json
|
||||
import shutil
|
||||
import configparser
|
||||
|
||||
from pathlib import Path
|
||||
from datetime import datetime
|
||||
|
||||
## Config.ini中配置
|
||||
# 单位:FPS,请依据数据集实际帧率填写,否则会导致匹配不准确
|
||||
frameRate = -1
|
||||
# 单位:ms,当匹配后某组数据帧的时间戳极差大于等于tspRangeThreshold,文件名会增加标注
|
||||
tspRangeThreshold = -1
|
||||
|
||||
## Python脚本自动解析, 不要修改
|
||||
primaryId = '-1' #主机ID
|
||||
mainPythonWorkPath = "" # SyncFramesMain.py的工作目录
|
||||
sourceFramesPath = "" # 请勿修改,固定值,TotalMode目录路径
|
||||
sourceRootPath = "" # 存放TotalMode的根目录
|
||||
resultPath = "" # 保存sync分析结果
|
||||
streamProfileDict = dict() # 保存Python结果
|
||||
|
||||
class PictureInfo(object):
|
||||
class Struct(object):
|
||||
def __init__(self, deviceId, sensorType, syncTimeStamp, picturePath, deviceIdFull, fileExt, exp, gain):
|
||||
self.deviceId = deviceId
|
||||
self.sensorType = sensorType
|
||||
self.syncTimeStamp = syncTimeStamp
|
||||
self.picturePath = picturePath
|
||||
self.deviceIdFull = deviceIdFull
|
||||
self.fileExt = fileExt
|
||||
self.exp = exp
|
||||
self.gain = gain
|
||||
|
||||
def make_struct(self, deviceId, sensorType, syncTimeStamp, picturePath, deviceIdFull, fileExt, exp, gain):
|
||||
return self.Struct(deviceId, sensorType, syncTimeStamp, picturePath, deviceIdFull, fileExt, exp, gain)
|
||||
|
||||
class StreamProfileInfo:
|
||||
def __init__(self, sensorType, width, height, format, fps):
|
||||
self.sensorType = sensorType
|
||||
self.width = width
|
||||
self.height = height
|
||||
self.format = format
|
||||
self.fps = fps
|
||||
|
||||
def initPrimaryId():
|
||||
global primaryId
|
||||
|
||||
deviceInfoPath = f"{sourceRootPath}/DevicesInfo.txt"
|
||||
if not os.path.exists(deviceInfoPath):
|
||||
print(f"Initialize primaryId failed. {deviceInfoPath} not exists")
|
||||
return False
|
||||
|
||||
with open(deviceInfoPath, 'r') as f:
|
||||
data = json.load(f)
|
||||
|
||||
if not 'devices' in data:
|
||||
print(f"Initialize primaryId failed. {deviceInfoPath} not contain item 'devices'")
|
||||
return False
|
||||
|
||||
for item in data['devices']:
|
||||
if 'isPrimaryDevice' in item and 'index' in item and item['isPrimaryDevice']:
|
||||
primaryId = str(item['index'])
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def initSyncConfigParams():
|
||||
global frameRate, tspRangeThreshold
|
||||
|
||||
configPath = f"{mainPythonWorkPath}/Config.ini"
|
||||
if not os.path.exists(configPath):
|
||||
print(f"Initliaze synchronized config parameter failed. {configPath} not exists.")
|
||||
return False
|
||||
|
||||
# 创建ConfigParser对象
|
||||
config = configparser.ConfigParser()
|
||||
|
||||
# 读取ini文件
|
||||
config.read(configPath, 'utf-8')
|
||||
|
||||
if not config.has_option('Parameter', 'frameRate'):
|
||||
print(f"Initliaze synchronized config parameter failed. Not define 'frameRate' in ${configPath}")
|
||||
return False
|
||||
|
||||
if not config.has_option('Parameter', 'tspRangeThreshold'):
|
||||
print(f"Initliaze synchronized config parameter failed. Not define 'tspRangeThreshold' in ${configPath}")
|
||||
return False
|
||||
|
||||
frameRate = config.getfloat('Parameter', 'frameRate')
|
||||
if frameRate <= 0 or frameRate >= 1000:
|
||||
print(f"Initliaze synchronized config parameter failed. Invalid frameRate={frameRate}")
|
||||
return False
|
||||
|
||||
tspRangeThreshold = config.getfloat('Parameter', 'tspRangeThreshold')
|
||||
if tspRangeThreshold <= 0:
|
||||
print(f"Initliaze synchronized config parameter failed. Invalid tspRangeThreshold={tspRangeThreshold}")
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def getDeviceCount():
|
||||
deviceInfoPath = f"{sourceRootPath}/DevicesInfo.txt"
|
||||
if not os.path.exists(deviceInfoPath):
|
||||
print(f"Get device count failed. {deviceInfoPath} not exists")
|
||||
return 0
|
||||
|
||||
with open(deviceInfoPath, 'r') as f:
|
||||
data = json.load(f)
|
||||
|
||||
if not 'devices' in data:
|
||||
print(f"Get device count failed. {deviceInfoPath} not contain item 'devices'")
|
||||
return 0
|
||||
|
||||
return len(data['devices'])
|
||||
|
||||
def initProfileInfoDict():
|
||||
global streamProfileDict
|
||||
|
||||
streamProfileDict = dict()
|
||||
profilePath = f"{sourceRootPath}/StreamProfileInfo.txt"
|
||||
if not os.path.exists(profilePath):
|
||||
print(f"Initialize profile information dictionary failed. {profilePath} not exists")
|
||||
return False
|
||||
|
||||
with open(profilePath, 'r') as f:
|
||||
data = json.load(f)
|
||||
|
||||
if not 'streamProfiles' in data:
|
||||
print(f"Initialize profile information dictionary failed. {profilePath} not contain item 'streamProfiles'")
|
||||
return False
|
||||
|
||||
for item in data['streamProfiles']:
|
||||
if 'sensorType' in item and 'width' in item and 'height' in item and 'format' in item and 'fps' in item:
|
||||
profileInfo = StreamProfileInfo(item['sensorType'], item['width'], item['height'], item['format'], item['fps'])
|
||||
streamProfileDict.setdefault(item['sensorType'], profileInfo)
|
||||
else:
|
||||
print(f"Initialize profile information dictionary. Error invalid StreamProfile, {item}")
|
||||
|
||||
return len(streamProfileDict.items()) > 0
|
||||
|
||||
def initResultDir():
|
||||
global resultPath
|
||||
timeText = datetime.now().strftime("%Y-%m-%d_%H%M%S")
|
||||
resultPath = f"{sourceRootPath}/results-{timeText}"
|
||||
if not os.path.exists(resultPath):
|
||||
os.makedirs(resultPath)
|
||||
|
||||
def isFrameFile(fileName):
|
||||
fileExt = os.path.splitext(fileName)
|
||||
if len(fileExt) < 2:
|
||||
return False
|
||||
|
||||
frameExts = {".jpeg", ".jpg", ".png", ".raw", "bmp"}
|
||||
return fileExt[1] in frameExts
|
||||
|
||||
def initPictureInfoDictionary(rootFilePath, pictureInfoDict):
|
||||
frameFileCount = 0
|
||||
for dirpath, dirnames, filenames in os.walk(rootFilePath):
|
||||
# print(f"dirpath={dirpath}, dirpath.basename=" + os.path.basename(dirpath))
|
||||
for fileName in filenames:
|
||||
filePath = os.path.join(dirpath, fileName)
|
||||
if not isFrameFile(fileName):
|
||||
continue
|
||||
|
||||
frameFileCount += 1
|
||||
fileNameNoExt = os.path.splitext(fileName)[0]
|
||||
pictureNameList = fileNameNoExt.split('_')
|
||||
deviceId = pictureNameList[2][5:]
|
||||
sensorType = pictureNameList[0].replace('#', '_')
|
||||
syncTimeStamp = int(pictureNameList[3][1:])
|
||||
exp = str(pictureNameList[6][0:])
|
||||
gain = str(pictureNameList[7][0:])
|
||||
|
||||
# 创建结构体
|
||||
pictureInfo = PictureInfo()
|
||||
info = pictureInfo.make_struct(deviceId, sensorType, syncTimeStamp, filePath, os.path.basename(dirpath), os.path.splitext(fileName)[1],exp,gain)
|
||||
|
||||
# 格式化字典一个key对应一个数组
|
||||
pictureInfoDict.setdefault(deviceId, []).append(info)
|
||||
|
||||
if 0 == frameFileCount:
|
||||
print("initialize pictureInfoDict failed. Not found frame file")
|
||||
|
||||
|
||||
def matchFrame(pictureInfoDict):
|
||||
global primaryId, frameRate
|
||||
global streamProfileDict
|
||||
|
||||
# 获取比较图像数组,取第一个设备的Color数组来进行比较
|
||||
compareList = []
|
||||
for key in pictureInfoDict:
|
||||
listTmp = list(pictureInfoDict[key])
|
||||
for info in listTmp:
|
||||
if info.deviceId == primaryId and info.sensorType == 'color':
|
||||
compareList.append(info)
|
||||
|
||||
# 根据开流情况动态分析,减少循环次数
|
||||
hasColorProfile = 'OB_SENSOR_COLOR' in streamProfileDict
|
||||
hasDepthProfile = 'OB_SENSOR_DEPTH' in streamProfileDict
|
||||
hasIRProfile = 'OB_SENSOR_IR' in streamProfileDict
|
||||
hasIRLeftProfile = 'OB_SENSOR_IR_LEFT' in streamProfileDict
|
||||
hasIRRightProfile = 'OB_SENSOR_IR_RIGHT' in streamProfileDict
|
||||
|
||||
# 匹配相邻帧
|
||||
resultDict = {}
|
||||
consumedList = []
|
||||
dictIndex = 0
|
||||
frameTspHalfGap = int(1000.0/frameRate/2.0+0.5)
|
||||
for comparePic in compareList:
|
||||
resultDict.setdefault(dictIndex, []).append(comparePic)
|
||||
for key in pictureInfoDict:
|
||||
listTmp = list(pictureInfoDict[key])
|
||||
# 对比彩色
|
||||
if hasColorProfile:
|
||||
for info in listTmp:
|
||||
if info.deviceId == primaryId and info.sensorType == comparePic.sensorType:
|
||||
continue
|
||||
|
||||
if info in consumedList:
|
||||
continue
|
||||
|
||||
if info.sensorType == 'color' and abs(info.syncTimeStamp - comparePic.syncTimeStamp) < frameTspHalfGap:
|
||||
resultDict.setdefault(dictIndex, []).append(info)
|
||||
consumedList.append(info)
|
||||
break
|
||||
|
||||
# 对Depth
|
||||
if hasDepthProfile:
|
||||
for info in listTmp:
|
||||
if info.deviceId == primaryId and info.sensorType == comparePic.sensorType:
|
||||
continue
|
||||
|
||||
if info in consumedList:
|
||||
continue
|
||||
|
||||
if info.sensorType == 'depth' and abs(info.syncTimeStamp - comparePic.syncTimeStamp) < frameTspHalfGap:
|
||||
resultDict.setdefault(dictIndex, []).append(info)
|
||||
consumedList.append(info)
|
||||
break
|
||||
|
||||
# IR
|
||||
if hasIRProfile:
|
||||
for info in listTmp:
|
||||
if info.deviceId == primaryId and info.sensorType == comparePic.sensorType:
|
||||
continue
|
||||
|
||||
if info in consumedList:
|
||||
continue
|
||||
|
||||
if info.sensorType == 'ir' and abs(info.syncTimeStamp - comparePic.syncTimeStamp) < frameTspHalfGap:
|
||||
resultDict.setdefault(dictIndex, []).append(info)
|
||||
consumedList.append(info)
|
||||
break
|
||||
|
||||
# 对左IR
|
||||
if hasIRLeftProfile:
|
||||
for info in listTmp:
|
||||
if info.deviceId == primaryId and info.sensorType == comparePic.sensorType:
|
||||
continue
|
||||
|
||||
if info in consumedList:
|
||||
continue
|
||||
|
||||
if info.sensorType == 'ir_left' and abs(info.syncTimeStamp - comparePic.syncTimeStamp) < frameTspHalfGap:
|
||||
resultDict.setdefault(dictIndex, []).append(info)
|
||||
consumedList.append(info)
|
||||
break
|
||||
|
||||
# 对右IR
|
||||
if hasIRRightProfile:
|
||||
for info in listTmp:
|
||||
if info.deviceId == primaryId and info.sensorType == comparePic.sensorType:
|
||||
continue
|
||||
|
||||
if info in consumedList:
|
||||
continue
|
||||
|
||||
if info.sensorType == 'ir_right' and abs(info.syncTimeStamp - comparePic.syncTimeStamp) < frameTspHalfGap:
|
||||
resultDict.setdefault(dictIndex, []).append(info)
|
||||
consumedList.append(info)
|
||||
break
|
||||
|
||||
# 计算偏差
|
||||
minTsp = min(resultDict[dictIndex], key=lambda x: x.syncTimeStamp)
|
||||
for info in resultDict[dictIndex]:
|
||||
info.tspDiff = info.syncTimeStamp - minTsp.syncTimeStamp
|
||||
|
||||
dictIndex += 1
|
||||
|
||||
return resultDict
|
||||
|
||||
|
||||
def safe_make_dir(path):
|
||||
if not os.path.exists(path):
|
||||
os.makedirs(path)
|
||||
|
||||
|
||||
def handleSyncFrames():
|
||||
global resultPath
|
||||
global sourceFramesPath
|
||||
global streamProfileDict
|
||||
|
||||
fp = open(f"{resultPath}/frameMatchLog.txt", "w")
|
||||
|
||||
if not initProfileInfoDict():
|
||||
print("Initialize stream profile dictionary failed. exit")
|
||||
return
|
||||
print(f"Stream profiles: {streamProfileDict}")
|
||||
streamProfileCount = len(streamProfileDict.items())
|
||||
|
||||
pictureInfoDict = {}
|
||||
|
||||
initPictureInfoDictionary(Path(sourceFramesPath), pictureInfoDict)
|
||||
if len(pictureInfoDict) <= 0:
|
||||
print("init pictureInfoDict failed. pictureInfoDict.len = 0")
|
||||
return
|
||||
|
||||
# # Dump pictureInfoDict
|
||||
# for key in pictureInfoDict:
|
||||
# print("===================key:%s" % key)
|
||||
# listTmp = list(pictureInfoDict[key])
|
||||
# for info in listTmp:
|
||||
# print("=====================value : deviceId:%s\tsensorType:%s\tsyncTimeStamp:%s\tpicturePath:%s" % (info.deviceId,info.sensorType,info.syncTimeStamp,info.picturePath))
|
||||
|
||||
deviceCount = getDeviceCount()
|
||||
print("=================device count:%d" % deviceCount)
|
||||
|
||||
resultDict = {}
|
||||
resultDict = matchFrame(pictureInfoDict)
|
||||
|
||||
for key in resultDict:
|
||||
fp.write("===================result key:%s\n" % key)
|
||||
print("===================result key:%s" % key)
|
||||
listTmp = list(resultDict[key])
|
||||
for info in listTmp:
|
||||
fp.write("=====================result value : deviceId:%s\tsensorType:%s\tsyncTimeStamp:%s\tpicturePath:%s\n" % (
|
||||
info.deviceId, info.sensorType, info.syncTimeStamp, info.picturePath))
|
||||
print("=====================result value : deviceId:%s\tsensorType:%s\tsyncTimeStamp:%s\tpicturePath:%s" % (
|
||||
info.deviceId, info.sensorType, info.syncTimeStamp, info.picturePath))
|
||||
|
||||
matchPath = f"{resultPath}/matchFrames/"
|
||||
safe_make_dir(matchPath)
|
||||
notMatchPath = f"{resultPath}/notMatchFrames/"
|
||||
abnormalPath = f"{resultPath}/abnormal/"
|
||||
|
||||
for key in resultDict:
|
||||
listTmp = list(resultDict[key])
|
||||
if (len(listTmp) == (deviceCount * streamProfileCount)):
|
||||
haveAbnormalData = False
|
||||
for info in listTmp:
|
||||
if info.tspDiff >= tspRangeThreshold:
|
||||
path = matchPath + str(key) + "_" + info.sensorType + "_" + info.deviceIdFull + "_" + str(
|
||||
info.syncTimeStamp) + "_[" + str(info.tspDiff) + "]" + "_xxxxxx" + "_"+ str(info.exp) + "_"+ str(info.gain) + info.fileExt
|
||||
shutil.copy(info.picturePath, path)
|
||||
haveAbnormalData = True
|
||||
else:
|
||||
path = matchPath + str(key) + "_" + info.sensorType + "_" + info.deviceIdFull + "_" + str(
|
||||
info.syncTimeStamp) + "_[" + str(info.tspDiff) + "]" + "_"+ str(info.exp) + "_"+ str(info.gain) + info.fileExt
|
||||
shutil.copy(info.picturePath, path)
|
||||
|
||||
if haveAbnormalData:
|
||||
safe_make_dir(abnormalPath)
|
||||
for info in listTmp:
|
||||
if info.tspDiff >= tspRangeThreshold:
|
||||
path = abnormalPath + str(key) + "_" + info.sensorType + "_" + info.deviceIdFull + "_" + str(
|
||||
info.syncTimeStamp) + "_[" + str(info.tspDiff) + "]" + "_xxxxxx" + "_"+ str(info.exp) + "_"+ str(info.gain) + info.fileExt
|
||||
shutil.copy(info.picturePath, path)
|
||||
else:
|
||||
path = abnormalPath + str(key) + "_" + info.sensorType + "_" + info.deviceIdFull + "_" + str(
|
||||
info.syncTimeStamp) + "_[" + str(info.tspDiff) + "]" + "_"+ str(info.exp) + "_"+ str(info.gain) + info.fileExt
|
||||
shutil.copy(info.picturePath, path)
|
||||
else:
|
||||
safe_make_dir(notMatchPath)
|
||||
for info in listTmp:
|
||||
newFilePath = notMatchPath + str(key) + "_" + info.sensorType + "_" + info.deviceIdFull + "_" + str(
|
||||
info.syncTimeStamp) + "_[" + str(info.tspDiff) + "]" + "_"+ str(info.exp) + "_"+ str(info.gain) + info.fileExt
|
||||
shutil.copy(info.picturePath, newFilePath)
|
||||
|
||||
def main():
|
||||
if not initPrimaryId():
|
||||
print("Initialize primaryId failed. exit.")
|
||||
return
|
||||
|
||||
if not initSyncConfigParams():
|
||||
print("Initialized Sync config parameter failed. exit.")
|
||||
return
|
||||
|
||||
initResultDir()
|
||||
|
||||
print(f"PrimaryId={primaryId}, frameRate={frameRate}, tspRangeThreshold={tspRangeThreshold}")
|
||||
handleSyncFrames()
|
||||
|
||||
return
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
parser = argparse.ArgumentParser(description="Synchronize frames support for 'Orbbec Devices'")
|
||||
parser.add_argument('frames_dir', type=str, help="Frames directory")
|
||||
parser.add_argument('main_script_root_dir', type=str, help="SyncFramesMain.py working directory")
|
||||
args = parser.parse_args()
|
||||
|
||||
sourceRootPath = args.frames_dir
|
||||
sourceFramesPath = f"{args.frames_dir}/TotalModeFrames"
|
||||
mainPythonWorkPath = args.main_script_root_dir
|
||||
print(f"Start of CommonFrameMatch synchronize frame. sourceRootPath={sourceRootPath}")
|
||||
|
||||
main()
|
||||
|
||||
print("Finish of CommonFrameMatch synchronize frame.")
|
||||
@@ -0,0 +1,42 @@
|
||||
{
|
||||
"deviceVid": "0x2BC5",
|
||||
"devicePid": "0x0800",
|
||||
"primarySerialNumber": "CP1E5420006D",
|
||||
"devices": [{
|
||||
"index": 0,
|
||||
"isPrimaryDevice": true,
|
||||
"vid": "0x2BC5",
|
||||
"pid": "0x0800",
|
||||
"name": "Orbbec Gemini 335",
|
||||
"firmwareVersion": "1.3.64",
|
||||
"serialNumber": "CP1E5420006D",
|
||||
"hardwareVersion": "0.1",
|
||||
"ipAddress": "0.0.0.0",
|
||||
"extensionInfo": {
|
||||
"ExtensionInfo": {
|
||||
"IspFwVer": "20240820",
|
||||
"IspNeedVer": "20240820",
|
||||
"HwType": "R1",
|
||||
"HwVer": "V1.1"
|
||||
}
|
||||
}
|
||||
}, {
|
||||
"index": 1,
|
||||
"isPrimaryDevice": false,
|
||||
"vid": "0x2BC5",
|
||||
"pid": "0x0800",
|
||||
"name": "Orbbec Gemini 335",
|
||||
"firmwareVersion": "1.3.64",
|
||||
"serialNumber": "CP1L44P00051",
|
||||
"hardwareVersion": "0.1",
|
||||
"ipAddress": "0.0.0.0",
|
||||
"extensionInfo": {
|
||||
"ExtensionInfo": {
|
||||
"IspFwVer": "20240820",
|
||||
"IspNeedVer": "20240820",
|
||||
"HwType": "R1",
|
||||
"HwVer": "V1.0"
|
||||
}
|
||||
}
|
||||
}]
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"D2CAlignMode": "ALIGN_D2C_SW_MODE",
|
||||
"streamProfiles": [{
|
||||
"sensorType": "OB_SENSOR_COLOR",
|
||||
"width": 1280,
|
||||
"height": 720,
|
||||
"fps": 30,
|
||||
"format": "MJPEG"
|
||||
}, {
|
||||
"sensorType": "OB_SENSOR_IR_LEFT",
|
||||
"width": 848,
|
||||
"height": 480,
|
||||
"fps": 30,
|
||||
"format": "Y8"
|
||||
}]
|
||||
}
|
||||
|
After Width: | Height: | Size: 144 KiB |
|
After Width: | Height: | Size: 145 KiB |
|
After Width: | Height: | Size: 145 KiB |
|
After Width: | Height: | Size: 145 KiB |
|
After Width: | Height: | Size: 145 KiB |
|
After Width: | Height: | Size: 145 KiB |
|
After Width: | Height: | Size: 145 KiB |
|
After Width: | Height: | Size: 145 KiB |
|
After Width: | Height: | Size: 145 KiB |
|
After Width: | Height: | Size: 144 KiB |
|
After Width: | Height: | Size: 145 KiB |
|
After Width: | Height: | Size: 44 KiB |
|
After Width: | Height: | Size: 44 KiB |
|
After Width: | Height: | Size: 44 KiB |
|
After Width: | Height: | Size: 44 KiB |
|
After Width: | Height: | Size: 44 KiB |
|
After Width: | Height: | Size: 43 KiB |
|
After Width: | Height: | Size: 44 KiB |
|
After Width: | Height: | Size: 43 KiB |
|
After Width: | Height: | Size: 44 KiB |
|
After Width: | Height: | Size: 44 KiB |
|
After Width: | Height: | Size: 44 KiB |
|
After Width: | Height: | Size: 151 KiB |
|
After Width: | Height: | Size: 152 KiB |
|
After Width: | Height: | Size: 152 KiB |
|
After Width: | Height: | Size: 152 KiB |
|
After Width: | Height: | Size: 152 KiB |
|
After Width: | Height: | Size: 151 KiB |
|
After Width: | Height: | Size: 152 KiB |
|
After Width: | Height: | Size: 152 KiB |
|
After Width: | Height: | Size: 152 KiB |
|
After Width: | Height: | Size: 151 KiB |
|
After Width: | Height: | Size: 152 KiB |
|
After Width: | Height: | Size: 44 KiB |
|
After Width: | Height: | Size: 44 KiB |
|
After Width: | Height: | Size: 44 KiB |
|
After Width: | Height: | Size: 44 KiB |
|
After Width: | Height: | Size: 44 KiB |
|
After Width: | Height: | Size: 44 KiB |
|
After Width: | Height: | Size: 44 KiB |
|
After Width: | Height: | Size: 44 KiB |
|
After Width: | Height: | Size: 44 KiB |
|
After Width: | Height: | Size: 44 KiB |
|
After Width: | Height: | Size: 44 KiB |
@@ -0,0 +1,55 @@
|
||||
===================result key:0
|
||||
=====================result value : deviceId:0 sensorType:ir_left syncTimeStamp:1739874543527 picturePath:/home/jj/openSDK/opensdk_ros2/src/OrbbecSDK_ROS2/orbbec_camera/examples/multi_camera_synced_verification_tool/multicamera_sync/output/20250218102900/TotalModeFrames/SNCP1E5420006D_Index0/ir#left_SNCP1E5420006D_Index0_g1739874543527_f9_s1739874543627_e5000_d16_.jpg
|
||||
=====================result value : deviceId:1 sensorType:color syncTimeStamp:1739874543527 picturePath:/home/jj/openSDK/opensdk_ros2/src/OrbbecSDK_ROS2/orbbec_camera/examples/multi_camera_synced_verification_tool/multicamera_sync/output/20250218102900/TotalModeFrames/SNCP1L44P00051_Index1/color_SNCP1L44P00051_Index1_g1739874543527_f9_s1739874543616_e50_d16_.jpg
|
||||
=====================result value : deviceId:1 sensorType:ir_left syncTimeStamp:1739874543527 picturePath:/home/jj/openSDK/opensdk_ros2/src/OrbbecSDK_ROS2/orbbec_camera/examples/multi_camera_synced_verification_tool/multicamera_sync/output/20250218102900/TotalModeFrames/SNCP1L44P00051_Index1/ir#left_SNCP1L44P00051_Index1_g1739874543527_f10_s1739874543687_e5000_d16_.jpg
|
||||
=====================result value : deviceId:0 sensorType:color syncTimeStamp:1739874543527 picturePath:/home/jj/openSDK/opensdk_ros2/src/OrbbecSDK_ROS2/orbbec_camera/examples/multi_camera_synced_verification_tool/multicamera_sync/output/20250218102900/TotalModeFrames/SNCP1E5420006D_Index0/color_SNCP1E5420006D_Index0_g1739874543527_f9_s1739874543603_e50_d16_.jpg
|
||||
===================result key:1
|
||||
=====================result value : deviceId:0 sensorType:ir_left syncTimeStamp:1739874543494 picturePath:/home/jj/openSDK/opensdk_ros2/src/OrbbecSDK_ROS2/orbbec_camera/examples/multi_camera_synced_verification_tool/multicamera_sync/output/20250218102900/TotalModeFrames/SNCP1E5420006D_Index0/ir#left_SNCP1E5420006D_Index0_g1739874543494_f8_s1739874543528_e5000_d16_.jpg
|
||||
=====================result value : deviceId:1 sensorType:color syncTimeStamp:1739874543494 picturePath:/home/jj/openSDK/opensdk_ros2/src/OrbbecSDK_ROS2/orbbec_camera/examples/multi_camera_synced_verification_tool/multicamera_sync/output/20250218102900/TotalModeFrames/SNCP1L44P00051_Index1/color_SNCP1L44P00051_Index1_g1739874543494_f8_s1739874543540_e50_d16_.jpg
|
||||
=====================result value : deviceId:1 sensorType:ir_left syncTimeStamp:1739874543494 picturePath:/home/jj/openSDK/opensdk_ros2/src/OrbbecSDK_ROS2/orbbec_camera/examples/multi_camera_synced_verification_tool/multicamera_sync/output/20250218102900/TotalModeFrames/SNCP1L44P00051_Index1/ir#left_SNCP1L44P00051_Index1_g1739874543494_f8_s1739874543687_e5000_d16_.jpg
|
||||
=====================result value : deviceId:0 sensorType:color syncTimeStamp:1739874543494 picturePath:/home/jj/openSDK/opensdk_ros2/src/OrbbecSDK_ROS2/orbbec_camera/examples/multi_camera_synced_verification_tool/multicamera_sync/output/20250218102900/TotalModeFrames/SNCP1E5420006D_Index0/color_SNCP1E5420006D_Index0_g1739874543494_f8_s1739874543548_e50_d16_.jpg
|
||||
===================result key:2
|
||||
=====================result value : deviceId:0 sensorType:ir_left syncTimeStamp:1739874543461 picturePath:/home/jj/openSDK/opensdk_ros2/src/OrbbecSDK_ROS2/orbbec_camera/examples/multi_camera_synced_verification_tool/multicamera_sync/output/20250218102900/TotalModeFrames/SNCP1E5420006D_Index0/ir#left_SNCP1E5420006D_Index0_g1739874543461_f7_s1739874543493_e5000_d16_.jpg
|
||||
=====================result value : deviceId:1 sensorType:color syncTimeStamp:1739874543460 picturePath:/home/jj/openSDK/opensdk_ros2/src/OrbbecSDK_ROS2/orbbec_camera/examples/multi_camera_synced_verification_tool/multicamera_sync/output/20250218102900/TotalModeFrames/SNCP1L44P00051_Index1/color_SNCP1L44P00051_Index1_g1739874543460_f7_s1739874543505_e50_d16_.jpg
|
||||
=====================result value : deviceId:1 sensorType:ir_left syncTimeStamp:1739874543460 picturePath:/home/jj/openSDK/opensdk_ros2/src/OrbbecSDK_ROS2/orbbec_camera/examples/multi_camera_synced_verification_tool/multicamera_sync/output/20250218102900/TotalModeFrames/SNCP1L44P00051_Index1/ir#left_SNCP1L44P00051_Index1_g1739874543460_f7_s1739874543685_e5000_d16_.jpg
|
||||
=====================result value : deviceId:0 sensorType:color syncTimeStamp:1739874543461 picturePath:/home/jj/openSDK/opensdk_ros2/src/OrbbecSDK_ROS2/orbbec_camera/examples/multi_camera_synced_verification_tool/multicamera_sync/output/20250218102900/TotalModeFrames/SNCP1E5420006D_Index0/color_SNCP1E5420006D_Index0_g1739874543461_f7_s1739874543520_e50_d16_.jpg
|
||||
===================result key:3
|
||||
=====================result value : deviceId:0 sensorType:ir_left syncTimeStamp:1739874543260 picturePath:/home/jj/openSDK/opensdk_ros2/src/OrbbecSDK_ROS2/orbbec_camera/examples/multi_camera_synced_verification_tool/multicamera_sync/output/20250218102900/TotalModeFrames/SNCP1E5420006D_Index0/ir#left_SNCP1E5420006D_Index0_g1739874543260_f1_s1739874543310_e5000_d16_.jpg
|
||||
=====================result value : deviceId:1 sensorType:color syncTimeStamp:1739874543260 picturePath:/home/jj/openSDK/opensdk_ros2/src/OrbbecSDK_ROS2/orbbec_camera/examples/multi_camera_synced_verification_tool/multicamera_sync/output/20250218102900/TotalModeFrames/SNCP1L44P00051_Index1/color_SNCP1L44P00051_Index1_g1739874543260_f1_s1739874543345_e50_d16_.jpg
|
||||
=====================result value : deviceId:1 sensorType:ir_left syncTimeStamp:1739874543260 picturePath:/home/jj/openSDK/opensdk_ros2/src/OrbbecSDK_ROS2/orbbec_camera/examples/multi_camera_synced_verification_tool/multicamera_sync/output/20250218102900/TotalModeFrames/SNCP1L44P00051_Index1/ir#left_SNCP1L44P00051_Index1_g1739874543260_f1_s1739874543294_e5000_d16_.jpg
|
||||
=====================result value : deviceId:0 sensorType:color syncTimeStamp:1739874543260 picturePath:/home/jj/openSDK/opensdk_ros2/src/OrbbecSDK_ROS2/orbbec_camera/examples/multi_camera_synced_verification_tool/multicamera_sync/output/20250218102900/TotalModeFrames/SNCP1E5420006D_Index0/color_SNCP1E5420006D_Index0_g1739874543260_f1_s1739874543351_e50_d16_.jpg
|
||||
===================result key:4
|
||||
=====================result value : deviceId:0 sensorType:ir_left syncTimeStamp:1739874543361 picturePath:/home/jj/openSDK/opensdk_ros2/src/OrbbecSDK_ROS2/orbbec_camera/examples/multi_camera_synced_verification_tool/multicamera_sync/output/20250218102900/TotalModeFrames/SNCP1E5420006D_Index0/ir#left_SNCP1E5420006D_Index0_g1739874543361_f4_s1739874543405_e5000_d16_.jpg
|
||||
=====================result value : deviceId:1 sensorType:color syncTimeStamp:1739874543360 picturePath:/home/jj/openSDK/opensdk_ros2/src/OrbbecSDK_ROS2/orbbec_camera/examples/multi_camera_synced_verification_tool/multicamera_sync/output/20250218102900/TotalModeFrames/SNCP1L44P00051_Index1/color_SNCP1L44P00051_Index1_g1739874543360_f4_s1739874543468_e50_d16_.jpg
|
||||
=====================result value : deviceId:1 sensorType:ir_left syncTimeStamp:1739874543360 picturePath:/home/jj/openSDK/opensdk_ros2/src/OrbbecSDK_ROS2/orbbec_camera/examples/multi_camera_synced_verification_tool/multicamera_sync/output/20250218102900/TotalModeFrames/SNCP1L44P00051_Index1/ir#left_SNCP1L44P00051_Index1_g1739874543360_f4_s1739874543460_e5000_d16_.jpg
|
||||
=====================result value : deviceId:0 sensorType:color syncTimeStamp:1739874543360 picturePath:/home/jj/openSDK/opensdk_ros2/src/OrbbecSDK_ROS2/orbbec_camera/examples/multi_camera_synced_verification_tool/multicamera_sync/output/20250218102900/TotalModeFrames/SNCP1E5420006D_Index0/color_SNCP1E5420006D_Index0_g1739874543360_f4_s1739874543442_e50_d16_.jpg
|
||||
===================result key:5
|
||||
=====================result value : deviceId:0 sensorType:ir_left syncTimeStamp:1739874543327 picturePath:/home/jj/openSDK/opensdk_ros2/src/OrbbecSDK_ROS2/orbbec_camera/examples/multi_camera_synced_verification_tool/multicamera_sync/output/20250218102900/TotalModeFrames/SNCP1E5420006D_Index0/ir#left_SNCP1E5420006D_Index0_g1739874543327_f3_s1739874543393_e5000_d16_.jpg
|
||||
=====================result value : deviceId:1 sensorType:color syncTimeStamp:1739874543327 picturePath:/home/jj/openSDK/opensdk_ros2/src/OrbbecSDK_ROS2/orbbec_camera/examples/multi_camera_synced_verification_tool/multicamera_sync/output/20250218102900/TotalModeFrames/SNCP1L44P00051_Index1/color_SNCP1L44P00051_Index1_g1739874543327_f3_s1739874543458_e50_d16_.jpg
|
||||
=====================result value : deviceId:1 sensorType:ir_left syncTimeStamp:1739874543327 picturePath:/home/jj/openSDK/opensdk_ros2/src/OrbbecSDK_ROS2/orbbec_camera/examples/multi_camera_synced_verification_tool/multicamera_sync/output/20250218102900/TotalModeFrames/SNCP1L44P00051_Index1/ir#left_SNCP1L44P00051_Index1_g1739874543327_f3_s1739874543460_e5000_d16_.jpg
|
||||
=====================result value : deviceId:0 sensorType:color syncTimeStamp:1739874543327 picturePath:/home/jj/openSDK/opensdk_ros2/src/OrbbecSDK_ROS2/orbbec_camera/examples/multi_camera_synced_verification_tool/multicamera_sync/output/20250218102900/TotalModeFrames/SNCP1E5420006D_Index0/color_SNCP1E5420006D_Index0_g1739874543327_f3_s1739874543441_e50_d16_.jpg
|
||||
===================result key:6
|
||||
=====================result value : deviceId:0 sensorType:ir_left syncTimeStamp:1739874543561 picturePath:/home/jj/openSDK/opensdk_ros2/src/OrbbecSDK_ROS2/orbbec_camera/examples/multi_camera_synced_verification_tool/multicamera_sync/output/20250218102900/TotalModeFrames/SNCP1E5420006D_Index0/ir#left_SNCP1E5420006D_Index0_g1739874543561_f10_s1739874543628_e5000_d16_.jpg
|
||||
=====================result value : deviceId:1 sensorType:color syncTimeStamp:1739874543560 picturePath:/home/jj/openSDK/opensdk_ros2/src/OrbbecSDK_ROS2/orbbec_camera/examples/multi_camera_synced_verification_tool/multicamera_sync/output/20250218102900/TotalModeFrames/SNCP1L44P00051_Index1/color_SNCP1L44P00051_Index1_g1739874543560_f10_s1739874543618_e50_d16_.jpg
|
||||
=====================result value : deviceId:1 sensorType:ir_left syncTimeStamp:1739874543560 picturePath:/home/jj/openSDK/opensdk_ros2/src/OrbbecSDK_ROS2/orbbec_camera/examples/multi_camera_synced_verification_tool/multicamera_sync/output/20250218102900/TotalModeFrames/SNCP1L44P00051_Index1/ir#left_SNCP1L44P00051_Index1_g1739874543560_f11_s1739874543687_e5000_d16_.jpg
|
||||
=====================result value : deviceId:0 sensorType:color syncTimeStamp:1739874543561 picturePath:/home/jj/openSDK/opensdk_ros2/src/OrbbecSDK_ROS2/orbbec_camera/examples/multi_camera_synced_verification_tool/multicamera_sync/output/20250218102900/TotalModeFrames/SNCP1E5420006D_Index0/color_SNCP1E5420006D_Index0_g1739874543561_f10_s1739874543648_e50_d16_.jpg
|
||||
===================result key:7
|
||||
=====================result value : deviceId:0 sensorType:ir_left syncTimeStamp:1739874543427 picturePath:/home/jj/openSDK/opensdk_ros2/src/OrbbecSDK_ROS2/orbbec_camera/examples/multi_camera_synced_verification_tool/multicamera_sync/output/20250218102900/TotalModeFrames/SNCP1E5420006D_Index0/ir#left_SNCP1E5420006D_Index0_g1739874543427_f6_s1739874543474_e5000_d16_.jpg
|
||||
=====================result value : deviceId:1 sensorType:color syncTimeStamp:1739874543427 picturePath:/home/jj/openSDK/opensdk_ros2/src/OrbbecSDK_ROS2/orbbec_camera/examples/multi_camera_synced_verification_tool/multicamera_sync/output/20250218102900/TotalModeFrames/SNCP1L44P00051_Index1/color_SNCP1L44P00051_Index1_g1739874543427_f6_s1739874543485_e50_d16_.jpg
|
||||
=====================result value : deviceId:1 sensorType:ir_left syncTimeStamp:1739874543427 picturePath:/home/jj/openSDK/opensdk_ros2/src/OrbbecSDK_ROS2/orbbec_camera/examples/multi_camera_synced_verification_tool/multicamera_sync/output/20250218102900/TotalModeFrames/SNCP1L44P00051_Index1/ir#left_SNCP1L44P00051_Index1_g1739874543427_f6_s1739874543684_e5000_d16_.jpg
|
||||
=====================result value : deviceId:0 sensorType:color syncTimeStamp:1739874543427 picturePath:/home/jj/openSDK/opensdk_ros2/src/OrbbecSDK_ROS2/orbbec_camera/examples/multi_camera_synced_verification_tool/multicamera_sync/output/20250218102900/TotalModeFrames/SNCP1E5420006D_Index0/color_SNCP1E5420006D_Index0_g1739874543427_f6_s1739874543518_e50_d16_.jpg
|
||||
===================result key:8
|
||||
=====================result value : deviceId:0 sensorType:ir_left syncTimeStamp:1739874543294 picturePath:/home/jj/openSDK/opensdk_ros2/src/OrbbecSDK_ROS2/orbbec_camera/examples/multi_camera_synced_verification_tool/multicamera_sync/output/20250218102900/TotalModeFrames/SNCP1E5420006D_Index0/ir#left_SNCP1E5420006D_Index0_g1739874543294_f2_s1739874543329_e5000_d16_.jpg
|
||||
=====================result value : deviceId:1 sensorType:color syncTimeStamp:1739874543293 picturePath:/home/jj/openSDK/opensdk_ros2/src/OrbbecSDK_ROS2/orbbec_camera/examples/multi_camera_synced_verification_tool/multicamera_sync/output/20250218102900/TotalModeFrames/SNCP1L44P00051_Index1/color_SNCP1L44P00051_Index1_g1739874543293_f2_s1739874543446_e50_d16_.jpg
|
||||
=====================result value : deviceId:1 sensorType:ir_left syncTimeStamp:1739874543294 picturePath:/home/jj/openSDK/opensdk_ros2/src/OrbbecSDK_ROS2/orbbec_camera/examples/multi_camera_synced_verification_tool/multicamera_sync/output/20250218102900/TotalModeFrames/SNCP1L44P00051_Index1/ir#left_SNCP1L44P00051_Index1_g1739874543294_f2_s1739874543319_e5000_d16_.jpg
|
||||
=====================result value : deviceId:0 sensorType:color syncTimeStamp:1739874543294 picturePath:/home/jj/openSDK/opensdk_ros2/src/OrbbecSDK_ROS2/orbbec_camera/examples/multi_camera_synced_verification_tool/multicamera_sync/output/20250218102900/TotalModeFrames/SNCP1E5420006D_Index0/color_SNCP1E5420006D_Index0_g1739874543294_f2_s1739874543359_e50_d16_.jpg
|
||||
===================result key:9
|
||||
=====================result value : deviceId:0 sensorType:ir_left syncTimeStamp:1739874543394 picturePath:/home/jj/openSDK/opensdk_ros2/src/OrbbecSDK_ROS2/orbbec_camera/examples/multi_camera_synced_verification_tool/multicamera_sync/output/20250218102900/TotalModeFrames/SNCP1E5420006D_Index0/ir#left_SNCP1E5420006D_Index0_g1739874543394_f5_s1739874543473_e5000_d16_.jpg
|
||||
=====================result value : deviceId:1 sensorType:color syncTimeStamp:1739874543393 picturePath:/home/jj/openSDK/opensdk_ros2/src/OrbbecSDK_ROS2/orbbec_camera/examples/multi_camera_synced_verification_tool/multicamera_sync/output/20250218102900/TotalModeFrames/SNCP1L44P00051_Index1/color_SNCP1L44P00051_Index1_g1739874543393_f5_s1739874543470_e50_d16_.jpg
|
||||
=====================result value : deviceId:1 sensorType:ir_left syncTimeStamp:1739874543394 picturePath:/home/jj/openSDK/opensdk_ros2/src/OrbbecSDK_ROS2/orbbec_camera/examples/multi_camera_synced_verification_tool/multicamera_sync/output/20250218102900/TotalModeFrames/SNCP1L44P00051_Index1/ir#left_SNCP1L44P00051_Index1_g1739874543394_f5_s1739874543661_e5000_d16_.jpg
|
||||
=====================result value : deviceId:0 sensorType:color syncTimeStamp:1739874543394 picturePath:/home/jj/openSDK/opensdk_ros2/src/OrbbecSDK_ROS2/orbbec_camera/examples/multi_camera_synced_verification_tool/multicamera_sync/output/20250218102900/TotalModeFrames/SNCP1E5420006D_Index0/color_SNCP1E5420006D_Index0_g1739874543394_f5_s1739874543506_e50_d16_.jpg
|
||||
===================result key:10
|
||||
=====================result value : deviceId:0 sensorType:ir_left syncTimeStamp:1739874543227 picturePath:/home/jj/openSDK/opensdk_ros2/src/OrbbecSDK_ROS2/orbbec_camera/examples/multi_camera_synced_verification_tool/multicamera_sync/output/20250218102900/TotalModeFrames/SNCP1E5420006D_Index0/ir#left_SNCP1E5420006D_Index0_g1739874543227_f0_s1739874543250_e5000_d16_.jpg
|
||||
=====================result value : deviceId:1 sensorType:color syncTimeStamp:1739874543227 picturePath:/home/jj/openSDK/opensdk_ros2/src/OrbbecSDK_ROS2/orbbec_camera/examples/multi_camera_synced_verification_tool/multicamera_sync/output/20250218102900/TotalModeFrames/SNCP1L44P00051_Index1/color_SNCP1L44P00051_Index1_g1739874543227_f0_s1739874543304_e50_d16_.jpg
|
||||
=====================result value : deviceId:1 sensorType:ir_left syncTimeStamp:1739874543227 picturePath:/home/jj/openSDK/opensdk_ros2/src/OrbbecSDK_ROS2/orbbec_camera/examples/multi_camera_synced_verification_tool/multicamera_sync/output/20250218102900/TotalModeFrames/SNCP1L44P00051_Index1/ir#left_SNCP1L44P00051_Index1_g1739874543227_f0_s1739874543250_e5000_d16_.jpg
|
||||
=====================result value : deviceId:0 sensorType:color syncTimeStamp:1739874543227 picturePath:/home/jj/openSDK/opensdk_ros2/src/OrbbecSDK_ROS2/orbbec_camera/examples/multi_camera_synced_verification_tool/multicamera_sync/output/20250218102900/TotalModeFrames/SNCP1E5420006D_Index0/color_SNCP1E5420006D_Index0_g1739874543227_f0_s1739874543327_e50_d16_.jpg
|
||||
|
After Width: | Height: | Size: 144 KiB |
|
After Width: | Height: | Size: 151 KiB |
|
After Width: | Height: | Size: 44 KiB |
|
After Width: | Height: | Size: 44 KiB |
|
After Width: | Height: | Size: 144 KiB |
|
After Width: | Height: | Size: 151 KiB |
|
After Width: | Height: | Size: 44 KiB |
|
After Width: | Height: | Size: 44 KiB |
|
After Width: | Height: | Size: 145 KiB |
|
After Width: | Height: | Size: 152 KiB |
|
After Width: | Height: | Size: 44 KiB |
|
After Width: | Height: | Size: 44 KiB |
|
After Width: | Height: | Size: 145 KiB |
|
After Width: | Height: | Size: 152 KiB |
|
After Width: | Height: | Size: 43 KiB |
|
After Width: | Height: | Size: 44 KiB |
|
After Width: | Height: | Size: 145 KiB |
|
After Width: | Height: | Size: 152 KiB |
|
After Width: | Height: | Size: 44 KiB |
|
After Width: | Height: | Size: 44 KiB |
|
After Width: | Height: | Size: 145 KiB |
|
After Width: | Height: | Size: 152 KiB |
|
After Width: | Height: | Size: 44 KiB |
|
After Width: | Height: | Size: 44 KiB |
|
After Width: | Height: | Size: 145 KiB |
|
After Width: | Height: | Size: 152 KiB |
|
After Width: | Height: | Size: 44 KiB |
|
After Width: | Height: | Size: 44 KiB |
|
After Width: | Height: | Size: 145 KiB |
|
After Width: | Height: | Size: 152 KiB |
|
After Width: | Height: | Size: 44 KiB |
|
After Width: | Height: | Size: 44 KiB |
|
After Width: | Height: | Size: 145 KiB |
|
After Width: | Height: | Size: 152 KiB |
|
After Width: | Height: | Size: 44 KiB |
|
After Width: | Height: | Size: 44 KiB |
|
After Width: | Height: | Size: 145 KiB |
|
After Width: | Height: | Size: 152 KiB |
|
After Width: | Height: | Size: 44 KiB |
|
After Width: | Height: | Size: 44 KiB |