pyorbbec 1.0.1.7__py3-none-any.whl → 1.0.1.8__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {pyorbbec-1.0.1.7.dist-info → pyorbbec-1.0.1.8.dist-info}/METADATA +1 -1
- pyorbbec-1.0.1.8.dist-info/RECORD +59 -0
- pyorbbecsdk/examples/.gitkeep +0 -0
- pyorbbecsdk/examples/OrbbecSDK.dll +0 -0
- pyorbbecsdk/examples/OrbbecSDK.lib +0 -0
- pyorbbecsdk/examples/README.md +26 -0
- pyorbbecsdk/examples/__pycache__/utils.cpython-313.pyc +0 -0
- pyorbbecsdk/examples/callback.py +303 -0
- pyorbbecsdk/examples/color.py +64 -0
- pyorbbecsdk/examples/coordinate_transform.py +184 -0
- pyorbbecsdk/examples/depth.py +107 -0
- pyorbbecsdk/examples/depth_work_mode.py +50 -0
- pyorbbecsdk/examples/device_firmware_update.py +155 -0
- pyorbbecsdk/examples/device_optional_depth_presets_update.py +142 -0
- pyorbbecsdk/examples/enumerate.py +118 -0
- pyorbbecsdk/examples/extensions/depthengine/depthengine.dll +0 -0
- pyorbbecsdk/examples/extensions/depthengine/depthengine.lib +0 -0
- pyorbbecsdk/examples/extensions/filters/FilterProcessor.dll +0 -0
- pyorbbecsdk/examples/extensions/filters/ob_priv_filter.dll +0 -0
- pyorbbecsdk/examples/extensions/firmwareupdater/firmwareupdater.dll +0 -0
- pyorbbecsdk/examples/extensions/frameprocessor/ob_frame_processor.dll +0 -0
- pyorbbecsdk/examples/hdr.py +216 -0
- pyorbbecsdk/examples/hot_plug.py +160 -0
- pyorbbecsdk/examples/hw_d2c_align.py +135 -0
- pyorbbecsdk/examples/imu.py +60 -0
- pyorbbecsdk/examples/infrared.py +115 -0
- pyorbbecsdk/examples/logger.py +55 -0
- pyorbbecsdk/examples/metadata.py +64 -0
- pyorbbecsdk/examples/multi_device.py +169 -0
- pyorbbecsdk/examples/multi_streams.py +219 -0
- pyorbbecsdk/examples/net_device.py +158 -0
- pyorbbecsdk/examples/playback.py +277 -0
- pyorbbecsdk/examples/point_cloud.py +90 -0
- pyorbbecsdk/examples/post_processing.py +119 -0
- pyorbbecsdk/examples/preset.py +67 -0
- pyorbbecsdk/examples/pyorbbecsdk.cp313-win_amd64.pyd +0 -0
- pyorbbecsdk/examples/quick_start.py +90 -0
- pyorbbecsdk/examples/recorder.py +236 -0
- pyorbbecsdk/examples/requirements.txt +9 -0
- pyorbbecsdk/examples/save_image_to_disk.py +106 -0
- pyorbbecsdk/examples/sync_align.py +109 -0
- pyorbbecsdk/examples/two_devices_sync.py +233 -0
- pyorbbecsdk/examples/utils.py +127 -0
- pyorbbec-1.0.1.7.dist-info/RECORD +0 -18
- {pyorbbec-1.0.1.7.dist-info → pyorbbec-1.0.1.8.dist-info}/WHEEL +0 -0
- {pyorbbec-1.0.1.7.dist-info → pyorbbec-1.0.1.8.dist-info}/licenses/LICENSE +0 -0
- {pyorbbec-1.0.1.7.dist-info → pyorbbec-1.0.1.8.dist-info}/licenses/NOTICE +0 -0
- {pyorbbec-1.0.1.7.dist-info → pyorbbec-1.0.1.8.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,55 @@
|
|
1
|
+
# ******************************************************************************
|
2
|
+
# Copyright (c) 2024 Orbbec 3D Technology, Inc
|
3
|
+
#
|
4
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
5
|
+
# you may not use this file except in compliance with the License.
|
6
|
+
# You may obtain a copy of the License at
|
7
|
+
#
|
8
|
+
# http:# www.apache.org/licenses/LICENSE-2.0
|
9
|
+
#
|
10
|
+
# Unless required by applicable law or agreed to in writing, software
|
11
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
12
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
13
|
+
# See the License for the specific language governing permissions and
|
14
|
+
# limitations under the License.
|
15
|
+
# ******************************************************************************
|
16
|
+
|
17
|
+
import os
|
18
|
+
from pyorbbecsdk import *
|
19
|
+
import time
|
20
|
+
|
21
|
+
def main():
|
22
|
+
# Set console logger (INFO level)
|
23
|
+
# if you DO NOT want to see the log message in console, you can set the log level to OBLogLevel.NONE
|
24
|
+
Context.set_logger_to_console(OBLogLevel.INFO)
|
25
|
+
|
26
|
+
# Set file logger (DEBUG level)
|
27
|
+
log_path = "Log/Custom/"
|
28
|
+
os.makedirs(log_path, exist_ok=True) # Ensure log directory exists
|
29
|
+
Context.set_logger_to_file(OBLogLevel.INFO, log_path)
|
30
|
+
|
31
|
+
|
32
|
+
# Configure streams
|
33
|
+
config = Config()
|
34
|
+
pipeline = Pipeline()
|
35
|
+
# Get and enable depth stream configuration
|
36
|
+
depth_profiles = pipeline.get_stream_profile_list(OBSensorType.DEPTH_SENSOR)
|
37
|
+
depth_profile = depth_profiles.get_default_video_stream_profile()
|
38
|
+
config.enable_stream(depth_profile)
|
39
|
+
|
40
|
+
# Get and enable color stream configuration
|
41
|
+
color_profiles = pipeline.get_stream_profile_list(OBSensorType.COLOR_SENSOR)
|
42
|
+
color_profile = color_profiles.get_default_video_stream_profile()
|
43
|
+
config.enable_stream(color_profile)
|
44
|
+
|
45
|
+
# Start pipeline
|
46
|
+
pipeline.start(config)
|
47
|
+
time.sleep(1)
|
48
|
+
# Stop pipeline
|
49
|
+
pipeline.stop()
|
50
|
+
|
51
|
+
print("\nPress any key to exit.")
|
52
|
+
input() # Wait for user input to exit
|
53
|
+
|
54
|
+
if __name__ == "__main__":
|
55
|
+
main()
|
@@ -0,0 +1,64 @@
|
|
1
|
+
# ******************************************************************************
|
2
|
+
# Copyright (c) 2024 Orbbec 3D Technology, Inc
|
3
|
+
#
|
4
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
5
|
+
# you may not use this file except in compliance with the License.
|
6
|
+
# You may obtain a copy of the License at
|
7
|
+
#
|
8
|
+
# http:# www.apache.org/licenses/LICENSE-2.0
|
9
|
+
#
|
10
|
+
# Unless required by applicable law or agreed to in writing, software
|
11
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
12
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
13
|
+
# See the License for the specific language governing permissions and
|
14
|
+
# limitations under the License.
|
15
|
+
# ******************************************************************************
|
16
|
+
|
17
|
+
from pyorbbecsdk import *
|
18
|
+
|
19
|
+
ESC_KEY = 27
|
20
|
+
|
21
|
+
def main():
|
22
|
+
# Initialize Pipeline
|
23
|
+
pipeline = Pipeline()
|
24
|
+
# Start Pipeline
|
25
|
+
pipeline.start()
|
26
|
+
print("Pipeline started. Press Ctrl+C to exit.")
|
27
|
+
|
28
|
+
frame_counter = 0 # Add frame counter
|
29
|
+
|
30
|
+
while True:
|
31
|
+
try:
|
32
|
+
# Get frameSet from Pipeline
|
33
|
+
frame_set = pipeline.wait_for_frames(1000)
|
34
|
+
if frame_set is None:
|
35
|
+
continue
|
36
|
+
|
37
|
+
frame_counter += 1 # Increment counter
|
38
|
+
|
39
|
+
# Only print metadata every 30 frames
|
40
|
+
if frame_counter % 30 == 0:
|
41
|
+
for i in range(len(frame_set)):
|
42
|
+
frame = frame_set[i]
|
43
|
+
|
44
|
+
# Print frame metadata
|
45
|
+
print(f"Frame type: {frame.get_type()}")
|
46
|
+
metadata_types = [getattr(OBFrameMetadataType, attr) for attr in dir(OBFrameMetadataType)
|
47
|
+
if not attr.startswith('__') and isinstance(getattr(OBFrameMetadataType, attr), OBFrameMetadataType)]
|
48
|
+
|
49
|
+
for metadata_type in metadata_types:
|
50
|
+
if frame.has_metadata(metadata_type):
|
51
|
+
metadata_value = frame.get_metadata_value(metadata_type)
|
52
|
+
print(f" Metadata type: {metadata_type.name}, value: {metadata_value}")
|
53
|
+
|
54
|
+
except KeyboardInterrupt:
|
55
|
+
break
|
56
|
+
except Exception as e:
|
57
|
+
print("An error occurred:", e)
|
58
|
+
break
|
59
|
+
|
60
|
+
pipeline.stop()
|
61
|
+
print("Pipeline stopped.")
|
62
|
+
|
63
|
+
if __name__ == "__main__":
|
64
|
+
main()
|
@@ -0,0 +1,169 @@
|
|
1
|
+
# ******************************************************************************
|
2
|
+
# Copyright (c) 2024 Orbbec 3D Technology, Inc
|
3
|
+
#
|
4
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
5
|
+
# you may not use this file except in compliance with the License.
|
6
|
+
# You may obtain a copy of the License at
|
7
|
+
#
|
8
|
+
# http:# www.apache.org/licenses/LICENSE-2.0
|
9
|
+
#
|
10
|
+
# Unless required by applicable law or agreed to in writing, software
|
11
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
12
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
13
|
+
# See the License for the specific language governing permissions and
|
14
|
+
# limitations under the License.
|
15
|
+
# ******************************************************************************
|
16
|
+
|
17
|
+
from queue import Queue
|
18
|
+
from typing import List
|
19
|
+
|
20
|
+
import cv2
|
21
|
+
import numpy as np
|
22
|
+
|
23
|
+
from pyorbbecsdk import *
|
24
|
+
from utils import frame_to_bgr_image
|
25
|
+
|
26
|
+
MAX_DEVICES = 2
|
27
|
+
curr_device_cnt = 0
|
28
|
+
|
29
|
+
MAX_QUEUE_SIZE = 5
|
30
|
+
ESC_KEY = 27
|
31
|
+
|
32
|
+
color_frames_queue: List[Queue] = [Queue() for _ in range(MAX_DEVICES)]
|
33
|
+
depth_frames_queue: List[Queue] = [Queue() for _ in range(MAX_DEVICES)]
|
34
|
+
has_color_sensor: List[bool] = [False for _ in range(MAX_DEVICES)]
|
35
|
+
stop_rendering = False
|
36
|
+
|
37
|
+
|
38
|
+
def on_new_frame_callback(frames: FrameSet, index: int):
|
39
|
+
global color_frames_queue, depth_frames_queue
|
40
|
+
global MAX_QUEUE_SIZE
|
41
|
+
assert index < MAX_DEVICES
|
42
|
+
color_frame = frames.get_color_frame()
|
43
|
+
depth_frame = frames.get_depth_frame()
|
44
|
+
if color_frame is not None:
|
45
|
+
if color_frames_queue[index].qsize() >= MAX_QUEUE_SIZE:
|
46
|
+
color_frames_queue[index].get()
|
47
|
+
color_frames_queue[index].put(color_frame)
|
48
|
+
if depth_frame is not None:
|
49
|
+
if depth_frames_queue[index].qsize() >= MAX_QUEUE_SIZE:
|
50
|
+
depth_frames_queue[index].get()
|
51
|
+
depth_frames_queue[index].put(depth_frame)
|
52
|
+
|
53
|
+
|
54
|
+
def rendering_frames():
|
55
|
+
global color_frames_queue, depth_frames_queue
|
56
|
+
global curr_device_cnt
|
57
|
+
global stop_rendering
|
58
|
+
while not stop_rendering:
|
59
|
+
for i in range(curr_device_cnt):
|
60
|
+
color_frame = None
|
61
|
+
depth_frame = None
|
62
|
+
if not color_frames_queue[i].empty():
|
63
|
+
color_frame = color_frames_queue[i].get()
|
64
|
+
if not depth_frames_queue[i].empty():
|
65
|
+
depth_frame = depth_frames_queue[i].get()
|
66
|
+
if color_frame is None and depth_frame is None:
|
67
|
+
continue
|
68
|
+
color_image = None
|
69
|
+
depth_image = None
|
70
|
+
color_width, color_height = 0, 0
|
71
|
+
if color_frame is not None:
|
72
|
+
color_width, color_height = color_frame.get_width(), color_frame.get_height()
|
73
|
+
color_image = frame_to_bgr_image(color_frame)
|
74
|
+
if depth_frame is not None:
|
75
|
+
width = depth_frame.get_width()
|
76
|
+
height = depth_frame.get_height()
|
77
|
+
scale = depth_frame.get_depth_scale()
|
78
|
+
depth_format = depth_frame.get_format()
|
79
|
+
if depth_format != OBFormat.Y16:
|
80
|
+
print("depth format is not Y16")
|
81
|
+
continue
|
82
|
+
|
83
|
+
try:
|
84
|
+
depth_data = np.frombuffer(depth_frame.get_data(), dtype=np.uint16)
|
85
|
+
depth_data = depth_data.reshape((height, width))
|
86
|
+
except ValueError:
|
87
|
+
print("Failed to reshape depth data")
|
88
|
+
continue
|
89
|
+
|
90
|
+
depth_data = depth_data.astype(np.float32) * scale
|
91
|
+
|
92
|
+
depth_image = cv2.normalize(depth_data, None, 0, 255, cv2.NORM_MINMAX,
|
93
|
+
dtype=cv2.CV_8U)
|
94
|
+
depth_image = cv2.applyColorMap(depth_image, cv2.COLORMAP_JET)
|
95
|
+
|
96
|
+
if color_image is not None and depth_image is not None:
|
97
|
+
window_size = (color_width // 2, color_height // 2)
|
98
|
+
color_image = cv2.resize(color_image, window_size)
|
99
|
+
depth_image = cv2.resize(depth_image, window_size)
|
100
|
+
image = np.hstack((color_image, depth_image))
|
101
|
+
elif depth_image is not None and not has_color_sensor[i]:
|
102
|
+
image = depth_image
|
103
|
+
else:
|
104
|
+
continue
|
105
|
+
cv2.imshow("Device {}".format(i), image)
|
106
|
+
key = cv2.waitKey(1)
|
107
|
+
if key == ord('q') or key == ESC_KEY:
|
108
|
+
stop_rendering = True
|
109
|
+
break
|
110
|
+
cv2.destroyAllWindows()
|
111
|
+
|
112
|
+
|
113
|
+
def start_streams(pipelines: List[Pipeline], configs: List[Config]):
|
114
|
+
index = 0
|
115
|
+
for pipeline, config in zip(pipelines, configs):
|
116
|
+
print("Starting device {}".format(index))
|
117
|
+
pipeline.start(config, lambda frame_set, curr_index=index: on_new_frame_callback(frame_set,
|
118
|
+
curr_index))
|
119
|
+
index += 1
|
120
|
+
|
121
|
+
|
122
|
+
def stop_streams(pipelines: List[Pipeline]):
|
123
|
+
for pipeline in pipelines:
|
124
|
+
pipeline.stop()
|
125
|
+
|
126
|
+
|
127
|
+
def main():
|
128
|
+
ctx = Context()
|
129
|
+
device_list = ctx.query_devices()
|
130
|
+
global curr_device_cnt
|
131
|
+
curr_device_cnt = device_list.get_count()
|
132
|
+
if curr_device_cnt == 0:
|
133
|
+
print("No device connected")
|
134
|
+
return
|
135
|
+
if curr_device_cnt > MAX_DEVICES:
|
136
|
+
print("Too many devices connected")
|
137
|
+
return
|
138
|
+
pipelines: List[Pipeline] = []
|
139
|
+
configs: List[Config] = []
|
140
|
+
global has_color_sensor
|
141
|
+
for i in range(device_list.get_count()):
|
142
|
+
device = device_list.get_device_by_index(i)
|
143
|
+
pipeline = Pipeline(device)
|
144
|
+
config = Config()
|
145
|
+
try:
|
146
|
+
profile_list = pipeline.get_stream_profile_list(OBSensorType.COLOR_SENSOR)
|
147
|
+
color_profile: VideoStreamProfile = profile_list.get_default_video_stream_profile()
|
148
|
+
config.enable_stream(color_profile)
|
149
|
+
has_color_sensor[i] = True
|
150
|
+
except OBError as e:
|
151
|
+
print(e)
|
152
|
+
has_color_sensor[i] = False
|
153
|
+
profile_list = pipeline.get_stream_profile_list(OBSensorType.DEPTH_SENSOR)
|
154
|
+
depth_profile = profile_list.get_default_video_stream_profile()
|
155
|
+
config.enable_stream(depth_profile)
|
156
|
+
pipelines.append(pipeline)
|
157
|
+
configs.append(config)
|
158
|
+
global stop_rendering
|
159
|
+
start_streams(pipelines, configs)
|
160
|
+
try:
|
161
|
+
rendering_frames()
|
162
|
+
except KeyboardInterrupt:
|
163
|
+
stop_rendering = True
|
164
|
+
finally:
|
165
|
+
stop_streams(pipelines)
|
166
|
+
cv2.destroyAllWindows()
|
167
|
+
|
168
|
+
if __name__ == "__main__":
|
169
|
+
main()
|
@@ -0,0 +1,219 @@
|
|
1
|
+
# ******************************************************************************
|
2
|
+
# Copyright (c) 2024 Orbbec 3D Technology, Inc
|
3
|
+
#
|
4
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
5
|
+
# you may not use this file except in compliance with the License.
|
6
|
+
# You may obtain a copy of the License at
|
7
|
+
#
|
8
|
+
# http:# www.apache.org/licenses/LICENSE-2.0
|
9
|
+
#
|
10
|
+
# Unless required by applicable law or agreed to in writing, software
|
11
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
12
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
13
|
+
# See the License for the specific language governing permissions and
|
14
|
+
# limitations under the License.
|
15
|
+
# ******************************************************************************
|
16
|
+
|
17
|
+
import cv2
|
18
|
+
import numpy as np
|
19
|
+
from pyorbbecsdk import *
|
20
|
+
from utils import frame_to_bgr_image
|
21
|
+
|
22
|
+
# cached frames for better visualization
|
23
|
+
cached_frames = {
|
24
|
+
'color': None,
|
25
|
+
'depth': None,
|
26
|
+
'left_ir': None,
|
27
|
+
'right_ir': None,
|
28
|
+
'ir': None
|
29
|
+
}
|
30
|
+
|
31
|
+
def setup_camera():
|
32
|
+
"""Setup camera and stream configuration"""
|
33
|
+
pipeline = Pipeline()
|
34
|
+
config = Config()
|
35
|
+
device = pipeline.get_device()
|
36
|
+
|
37
|
+
# Try to enable all possible sensors
|
38
|
+
video_sensors = [
|
39
|
+
OBSensorType.COLOR_SENSOR,
|
40
|
+
OBSensorType.DEPTH_SENSOR,
|
41
|
+
OBSensorType.IR_SENSOR,
|
42
|
+
OBSensorType.LEFT_IR_SENSOR,
|
43
|
+
OBSensorType.RIGHT_IR_SENSOR
|
44
|
+
]
|
45
|
+
sensor_list = device.get_sensor_list()
|
46
|
+
for sensor in range(len(sensor_list)):
|
47
|
+
try:
|
48
|
+
sensor_type = sensor_list[sensor].get_type()
|
49
|
+
if sensor_type in video_sensors:
|
50
|
+
config.enable_stream(sensor_type)
|
51
|
+
except:
|
52
|
+
continue
|
53
|
+
|
54
|
+
pipeline.start(config)
|
55
|
+
return pipeline
|
56
|
+
|
57
|
+
def setup_imu():
|
58
|
+
"""Setup IMU configuration"""
|
59
|
+
pipeline = Pipeline()
|
60
|
+
config = Config()
|
61
|
+
config.enable_accel_stream()
|
62
|
+
config.enable_gyro_stream()
|
63
|
+
pipeline.start(config)
|
64
|
+
return pipeline
|
65
|
+
|
66
|
+
def process_color(frame):
|
67
|
+
"""Process color image"""
|
68
|
+
frame = frame if frame else cached_frames['color']
|
69
|
+
cached_frames['color'] = frame
|
70
|
+
return frame_to_bgr_image(frame) if frame else None
|
71
|
+
|
72
|
+
|
73
|
+
def process_depth(frame):
|
74
|
+
"""Process depth image"""
|
75
|
+
frame = frame if frame else cached_frames['depth']
|
76
|
+
cached_frames['depth'] = frame
|
77
|
+
if not frame:
|
78
|
+
return None
|
79
|
+
try:
|
80
|
+
depth_data = np.frombuffer(frame.get_data(), dtype=np.uint16)
|
81
|
+
depth_data = depth_data.reshape(frame.get_height(), frame.get_width())
|
82
|
+
depth_image = cv2.normalize(depth_data, None, 0, 255, cv2.NORM_MINMAX, dtype=cv2.CV_8U)
|
83
|
+
return cv2.applyColorMap(depth_image, cv2.COLORMAP_JET)
|
84
|
+
except ValueError:
|
85
|
+
return None
|
86
|
+
|
87
|
+
|
88
|
+
def process_ir(ir_frame):
|
89
|
+
"""Process IR frame (left or right) to RGB image"""
|
90
|
+
if ir_frame is None:
|
91
|
+
return None
|
92
|
+
ir_frame = ir_frame.as_video_frame()
|
93
|
+
ir_data = np.asanyarray(ir_frame.get_data())
|
94
|
+
width = ir_frame.get_width()
|
95
|
+
height = ir_frame.get_height()
|
96
|
+
ir_format = ir_frame.get_format()
|
97
|
+
|
98
|
+
if ir_format == OBFormat.Y8:
|
99
|
+
ir_data = np.resize(ir_data, (height, width, 1))
|
100
|
+
data_type = np.uint8
|
101
|
+
image_dtype = cv2.CV_8UC1
|
102
|
+
max_data = 255
|
103
|
+
elif ir_format == OBFormat.MJPG:
|
104
|
+
ir_data = cv2.imdecode(ir_data, cv2.IMREAD_UNCHANGED)
|
105
|
+
data_type = np.uint8
|
106
|
+
image_dtype = cv2.CV_8UC1
|
107
|
+
max_data = 255
|
108
|
+
if ir_data is None:
|
109
|
+
print("decode mjpeg failed")
|
110
|
+
return None
|
111
|
+
ir_data = np.resize(ir_data, (height, width, 1))
|
112
|
+
else:
|
113
|
+
ir_data = np.frombuffer(ir_data, dtype=np.uint16)
|
114
|
+
data_type = np.uint16
|
115
|
+
image_dtype = cv2.CV_16UC1
|
116
|
+
max_data = 255
|
117
|
+
ir_data = np.resize(ir_data, (height, width, 1))
|
118
|
+
|
119
|
+
cv2.normalize(ir_data, ir_data, 0, max_data, cv2.NORM_MINMAX, dtype=image_dtype)
|
120
|
+
ir_data = ir_data.astype(data_type)
|
121
|
+
return cv2.cvtColor(ir_data, cv2.COLOR_GRAY2RGB)
|
122
|
+
|
123
|
+
def get_imu_text(frame, name):
|
124
|
+
"""Format IMU data"""
|
125
|
+
if not frame:
|
126
|
+
return []
|
127
|
+
return [
|
128
|
+
f"{name} x: {frame.get_x():.2f}",
|
129
|
+
f"{name} y: {frame.get_y():.2f}",
|
130
|
+
f"{name} z: {frame.get_z():.2f}"
|
131
|
+
]
|
132
|
+
|
133
|
+
|
134
|
+
def create_display(frames, width=1280, height=720):
|
135
|
+
"""Create display window"""
|
136
|
+
display = np.zeros((height, width, 3), dtype=np.uint8)
|
137
|
+
h, w = height // 2, width // 2
|
138
|
+
|
139
|
+
# Process video frames
|
140
|
+
if 'color' in frames and frames['color'] is not None:
|
141
|
+
display[0:h, 0:w] = cv2.resize(frames['color'], (w, h))
|
142
|
+
|
143
|
+
if 'depth' in frames and frames['depth'] is not None:
|
144
|
+
display[0:h, w:] = cv2.resize(frames['depth'], (w, h))
|
145
|
+
|
146
|
+
if 'ir' in frames and frames['ir'] is not None:
|
147
|
+
display[h:, 0:w] = cv2.resize(frames['ir'], (w, h))
|
148
|
+
|
149
|
+
# Display IMU data
|
150
|
+
if 'imu' in frames:
|
151
|
+
y_offset = h + 20
|
152
|
+
for data_type in ['accel', 'gyro']:
|
153
|
+
text_lines = get_imu_text(frames['imu'].get(data_type), data_type.title())
|
154
|
+
for i, line in enumerate(text_lines):
|
155
|
+
cv2.putText(display, line, (w + 10, y_offset + i * 20),
|
156
|
+
cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 255, 255), 1)
|
157
|
+
y_offset += 80
|
158
|
+
|
159
|
+
return display
|
160
|
+
|
161
|
+
|
162
|
+
def main():
|
163
|
+
# Window settings
|
164
|
+
WINDOW_NAME = "MultiStream Viewer"
|
165
|
+
DISPLAY_WIDTH = 1280
|
166
|
+
DISPLAY_HEIGHT = 720
|
167
|
+
|
168
|
+
# Initialize camera
|
169
|
+
pipeline = setup_camera()
|
170
|
+
imu_pipeline = setup_imu()
|
171
|
+
cv2.namedWindow(WINDOW_NAME, cv2.WINDOW_NORMAL)
|
172
|
+
cv2.resizeWindow(WINDOW_NAME, DISPLAY_WIDTH, DISPLAY_HEIGHT)
|
173
|
+
while True:
|
174
|
+
# Get all frames
|
175
|
+
frames = pipeline.wait_for_frames(100)
|
176
|
+
if not frames:
|
177
|
+
continue
|
178
|
+
# Process different frame types
|
179
|
+
processed_frames = {'color': process_color(frames.get_color_frame()),
|
180
|
+
'depth': process_depth(frames.get_depth_frame())}
|
181
|
+
|
182
|
+
# Process IR image: try stereo IR first, fallback to mono if unavailable
|
183
|
+
try:
|
184
|
+
left = process_ir(frames.get_frame(OBFrameType.LEFT_IR_FRAME).as_video_frame())
|
185
|
+
right = process_ir(frames.get_frame(OBFrameType.RIGHT_IR_FRAME).as_video_frame())
|
186
|
+
if left is not None and right is not None:
|
187
|
+
processed_frames['ir'] = np.hstack((left, right))
|
188
|
+
except:
|
189
|
+
ir_frame = frames.get_ir_frame()
|
190
|
+
if ir_frame:
|
191
|
+
processed_frames['ir'] = process_ir(ir_frame.as_video_frame())
|
192
|
+
|
193
|
+
# Process IMU data
|
194
|
+
imu_frames = imu_pipeline.wait_for_frames(100)
|
195
|
+
if not imu_frames:
|
196
|
+
continue
|
197
|
+
accel = imu_frames.get_frame(OBFrameType.ACCEL_FRAME)
|
198
|
+
gyro = imu_frames.get_frame(OBFrameType.GYRO_FRAME)
|
199
|
+
if accel and gyro:
|
200
|
+
processed_frames['imu'] = {
|
201
|
+
'accel': accel.as_accel_frame(),
|
202
|
+
'gyro': gyro.as_gyro_frame()
|
203
|
+
}
|
204
|
+
|
205
|
+
# create display
|
206
|
+
display = create_display(processed_frames, DISPLAY_WIDTH, DISPLAY_HEIGHT)
|
207
|
+
cv2.imshow(WINDOW_NAME, display)
|
208
|
+
|
209
|
+
# check exit key
|
210
|
+
key = cv2.waitKey(1) & 0xFF
|
211
|
+
if key in [ord('q'), 27]: # q or ESC
|
212
|
+
break
|
213
|
+
|
214
|
+
pipeline.stop()
|
215
|
+
cv2.destroyAllWindows()
|
216
|
+
|
217
|
+
|
218
|
+
if __name__ == "__main__":
|
219
|
+
main()
|
@@ -0,0 +1,158 @@
|
|
1
|
+
# ******************************************************************************
|
2
|
+
# Copyright (c) 2024 Orbbec 3D Technology, Inc
|
3
|
+
#
|
4
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
5
|
+
# you may not use this file except in compliance with the License.
|
6
|
+
# You may obtain a copy of the License at
|
7
|
+
#
|
8
|
+
# http:# www.apache.org/licenses/LICENSE-2.0
|
9
|
+
#
|
10
|
+
# Unless required by applicable law or agreed to in writing, software
|
11
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
12
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
13
|
+
# See the License for the specific language governing permissions and
|
14
|
+
# limitations under the License.
|
15
|
+
# ******************************************************************************
|
16
|
+
|
17
|
+
import platform
|
18
|
+
import cv2
|
19
|
+
import numpy as np
|
20
|
+
import av
|
21
|
+
import io
|
22
|
+
import threading
|
23
|
+
import time
|
24
|
+
import pygame
|
25
|
+
import os
|
26
|
+
from pyorbbecsdk import (Pipeline, Context, Config, OBSensorType, OBFormat, OBError)
|
27
|
+
from utils import frame_to_bgr_image
|
28
|
+
|
29
|
+
ESC_KEY = 27
|
30
|
+
|
31
|
+
def get_stream_profile(pipeline, sensor_type, width, height, fmt, fps):
|
32
|
+
profile_list = pipeline.get_stream_profile_list(sensor_type)
|
33
|
+
try:
|
34
|
+
profile = profile_list.get_video_stream_profile(width, height, fmt, fps)
|
35
|
+
except OBError:
|
36
|
+
profile = profile_list.get_default_video_stream_profile()
|
37
|
+
return profile
|
38
|
+
|
39
|
+
def decode_h26x_frame(decoder, byte_data):
|
40
|
+
try:
|
41
|
+
packet = av.Packet(byte_data)
|
42
|
+
frames = decoder.decode(packet)
|
43
|
+
for frame in frames:
|
44
|
+
return frame.to_ndarray(format='bgr24')
|
45
|
+
except av.AVError as e:
|
46
|
+
print(f"Decoding error: {e}")
|
47
|
+
return None
|
48
|
+
|
49
|
+
class FrameProcessor(threading.Thread):
|
50
|
+
def __init__(self, decoder, display_width, display_height):
|
51
|
+
super().__init__()
|
52
|
+
self.decoder = decoder
|
53
|
+
self.latest_frame = None
|
54
|
+
self.processed_frame = None
|
55
|
+
self.lock = threading.Lock()
|
56
|
+
self.running = True
|
57
|
+
self.daemon = True
|
58
|
+
self.display_width = display_width
|
59
|
+
self.display_height = display_height
|
60
|
+
|
61
|
+
def run(self):
|
62
|
+
while self.running:
|
63
|
+
with self.lock:
|
64
|
+
if self.latest_frame is not None:
|
65
|
+
color_image = decode_h26x_frame(self.decoder, self.latest_frame)
|
66
|
+
if color_image is not None:
|
67
|
+
# Resize the image to 1080p
|
68
|
+
resized_image = cv2.resize(color_image, (self.display_width, self.display_height))
|
69
|
+
rgb_image = cv2.cvtColor(resized_image, cv2.COLOR_BGR2RGB)
|
70
|
+
self.processed_frame = rgb_image
|
71
|
+
self.latest_frame = None
|
72
|
+
time.sleep(0.001)
|
73
|
+
|
74
|
+
def update_frame(self, frame):
|
75
|
+
with self.lock:
|
76
|
+
self.latest_frame = frame
|
77
|
+
|
78
|
+
def get_processed_frame(self):
|
79
|
+
with self.lock:
|
80
|
+
return self.processed_frame
|
81
|
+
|
82
|
+
def stop(self):
|
83
|
+
self.running = False
|
84
|
+
|
85
|
+
def main():
|
86
|
+
ctx = Context()
|
87
|
+
ip = input("Enter the IP address of the device (default: 192.168.1.10): ") or "192.168.1.10"
|
88
|
+
device = ctx.create_net_device(ip, 8090)
|
89
|
+
if device is None:
|
90
|
+
print("Failed to create net device")
|
91
|
+
return
|
92
|
+
|
93
|
+
config = Config()
|
94
|
+
pipeline = Pipeline(device)
|
95
|
+
|
96
|
+
# Set up 4K capture
|
97
|
+
color_profile = get_stream_profile(pipeline, OBSensorType.COLOR_SENSOR, 3840, 2160, OBFormat.H264, 25)
|
98
|
+
config.enable_stream(color_profile)
|
99
|
+
|
100
|
+
pipeline.start(config)
|
101
|
+
|
102
|
+
color_codec_name = 'h264' if color_profile.get_format() == OBFormat.H264 else 'hevc'
|
103
|
+
try:
|
104
|
+
decoder = av.codec.CodecContext.create(color_codec_name, 'r')
|
105
|
+
except av.AVError as e:
|
106
|
+
print(f"Failed to create decoder for {color_codec_name}: {e}")
|
107
|
+
pipeline.stop()
|
108
|
+
return
|
109
|
+
|
110
|
+
# Set display resolution to 720p
|
111
|
+
display_width, display_height = 1280, 720
|
112
|
+
frame_processor = FrameProcessor(decoder, display_width, display_height)
|
113
|
+
frame_processor.start()
|
114
|
+
|
115
|
+
pygame.init()
|
116
|
+
screen = pygame.display.set_mode((display_width, display_height))
|
117
|
+
pygame.display.set_caption("4K Net Device Viewer (720p Display)")
|
118
|
+
clock = pygame.time.Clock()
|
119
|
+
|
120
|
+
running = True
|
121
|
+
try:
|
122
|
+
while running:
|
123
|
+
for event in pygame.event.get():
|
124
|
+
if event.type == pygame.QUIT:
|
125
|
+
running = False
|
126
|
+
elif event.type == pygame.KEYDOWN:
|
127
|
+
if event.key == pygame.K_ESCAPE:
|
128
|
+
running = False
|
129
|
+
|
130
|
+
if not running:
|
131
|
+
break
|
132
|
+
|
133
|
+
frames = pipeline.wait_for_frames(100)
|
134
|
+
if frames:
|
135
|
+
color_frame = frames.get_color_frame()
|
136
|
+
if color_frame:
|
137
|
+
byte_data = color_frame.get_data()
|
138
|
+
if len(byte_data) > 0:
|
139
|
+
frame_processor.update_frame(byte_data)
|
140
|
+
|
141
|
+
processed_frame = frame_processor.get_processed_frame()
|
142
|
+
if processed_frame is not None:
|
143
|
+
surf = pygame.surfarray.make_surface(processed_frame.swapaxes(0, 1))
|
144
|
+
screen.blit(surf, (0, 0))
|
145
|
+
pygame.display.flip()
|
146
|
+
|
147
|
+
clock.tick(30) # Limit to 30 FPS
|
148
|
+
|
149
|
+
finally:
|
150
|
+
print("Stopping frame processor...")
|
151
|
+
frame_processor.stop()
|
152
|
+
print("Stopping pipeline...")
|
153
|
+
pipeline.stop()
|
154
|
+
print("Exiting the program...")
|
155
|
+
os._exit(0)
|
156
|
+
|
157
|
+
if __name__ == "__main__":
|
158
|
+
main()
|