Add multi_camera_synced_verification_tool example

This commit is contained in:
jj
2025-02-25 19:11:05 +08:00
parent 7684d11c3a
commit 2e03b2151a
106 changed files with 2798 additions and 38 deletions
+3 -3
View File
@@ -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"
}]
}
@@ -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

Some files were not shown because too many files have changed in this diff Show More