Skip to content

Commit 20f2588

Browse files
authored
Mm/add isx support (open-edge-platform#2249)
1 parent 7dc8928 commit 20f2588

7 files changed

Lines changed: 163 additions & 59 deletions

File tree

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
// Copyright (C) 2025 Intel Corporation
2+
//
3+
// SPDX-License-Identifier: Apache-2.0
4+
5+
[
6+
{
7+
"name": "yolov8n-seg",
8+
"model": "/opt/ros/humble/share/pyrealsense2-ai-demo/multicam-demo/models/yolov8/FP16/yolov8n-seg.xml",
9+
"device": "GPU",
10+
"data_type": "FP16",
11+
"source": "/dev/video-isx031-a-0",
12+
"adapter": "yolov8",
13+
"width": 1920,
14+
"height": 1536
15+
},
16+
{
17+
"name": "yolov8n-seg",
18+
"model": "/opt/ros/humble/share/pyrealsense2-ai-demo/multicam-demo/models/yolov8/FP16/yolov8n-seg.xml",
19+
"device": "CPU",
20+
"data_type": "FP16",
21+
"source": "/dev/video-isx031-b-0",
22+
"adapter": "yolov8",
23+
"width": 1920,
24+
"height": 1536
25+
},
26+
{
27+
"name": "yolov8n",
28+
"model": "/opt/ros/humble/share/pyrealsense2-ai-demo/multicam-demo/models/yolov8/FP16/yolov8n.xml",
29+
"device": "GPU",
30+
"data_type": "FP16",
31+
"source": "/dev/video-isx031-c-0",
32+
"adapter": "yolov8",
33+
"width": 1920,
34+
"height": 1536
35+
},
36+
{
37+
"name": "yolov8n",
38+
"model": "/opt/ros/humble/share/pyrealsense2-ai-demo/multicam-demo/models/yolov8/FP16/yolov8n.xml",
39+
"device": "GPU",
40+
"data_type": "FP16",
41+
"source": "/dev/video-isx031-d-0",
42+
"adapter": "yolov8",
43+
"width": 1920,
44+
"height": 1536
45+
}
46+
]

robotics-ai-suite/components/multicam-demo/requirements.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ cython
66
screeninfo
77
dpnp
88
onnx
9+
onnyxscript
910
ultralytics==8.0.43
1011
pyrealsense2
1112
openvino-dev
13+
setuptools<=70.0.0

robotics-ai-suite/components/multicam-demo/scripts/generate_ai_models.sh

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,8 @@ cd ./models/yolov8/ || exit
1616
i=1
1717
status=0
1818
for i in "${yolov8_models[@]}"; do
19-
gen_yolov8_model_cmd=$(python3 ../../src/mo.py --model="$i".pt --data_type="$datatype")
20-
if [[ "$gen_yolov8_model_cmd" -ne 0 ]]
19+
python3 ../../src/mo.py --model="$i".pt --data_type="$datatype"
20+
if [[ $? -ne 0 ]]
2121
then
2222
status=1
2323
break

robotics-ai-suite/components/multicam-demo/src/mo.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,16 @@
55

66
import argparse
77
import torch
8+
9+
# PyTorch 2.6+ changed torch.load default to weights_only=True, which blocks
10+
# loading ultralytics checkpoints that contain custom classes. Override it here
11+
# since we trust local model files.
12+
_torch_load_orig = torch.load
13+
def _torch_load_unsafe(*args, **kwargs):
14+
kwargs.setdefault('weights_only', False)
15+
return _torch_load_orig(*args, **kwargs)
16+
torch.load = _torch_load_unsafe
17+
818
from ultralytics import YOLO
919

1020
if __name__ == '__main__':
@@ -17,6 +27,6 @@
1727
half=True if args.data_type=="FP16" else False
1828

1929
model = YOLO(args.model)
20-
model.export(format="openvino", dynamic=True, half=half)
30+
model.export(format="openvino", dynamic=True, half=half, opset=18)
2131

2232

robotics-ai-suite/components/multicam-demo/src/pyrealsense2_ai_demo/images_capture.py

Lines changed: 22 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,9 @@ class VideoCapWrapper(ImagesCapture):
114114
def __init__(self, input, loop):
115115
self.loop = loop
116116
self.cap = cv2.VideoCapture()
117+
# Device nodes (e.g. /dev/video-isx031-a-0) are handled by CameraCapWrapper
118+
if input.startswith('/dev/'):
119+
raise InvalidInput("Device path - use CameraCapWrapper: {}".format(input))
117120
status = self.cap.open(input)
118121
if not status:
119122
raise InvalidInput("Can't open the video from {}".format(input))
@@ -186,18 +189,28 @@ class CameraCapWrapper(ImagesCapture):
186189
def __init__(self, input, camera_resolution):
187190

188191
self.cap = cv2.VideoCapture()
192+
# Accept both integer indices ("0") and device paths ("/dev/video-isx031-a-0")
189193
try:
190-
status = self.cap.open(int(input))
191-
self.cap.set(cv2.CAP_PROP_BUFFERSIZE, 1)
192-
self.cap.set(cv2.CAP_PROP_FRAME_WIDTH, camera_resolution[0])
193-
self.cap.set(cv2.CAP_PROP_FRAME_HEIGHT, camera_resolution[1])
194-
self.cap.set(cv2.CAP_PROP_FPS, 30)
194+
device = int(input)
195+
except ValueError:
196+
if not os.path.exists(input):
197+
raise InvalidInput("Can't find the camera {}".format(input))
198+
device = input
199+
200+
status = self.cap.open(device)
201+
self.cap.set(cv2.CAP_PROP_BUFFERSIZE, 1)
202+
self.cap.set(cv2.CAP_PROP_FRAME_WIDTH, camera_resolution[0])
203+
self.cap.set(cv2.CAP_PROP_FRAME_HEIGHT, camera_resolution[1])
204+
self.cap.set(cv2.CAP_PROP_FPS, 30)
205+
if isinstance(device, int):
206+
# MJPG and autofocus are only applicable to indexed USB cameras
195207
self.cap.set(cv2.CAP_PROP_AUTOFOCUS, 1)
196208
self.cap.set(cv2.CAP_PROP_FOURCC, cv2.VideoWriter_fourcc(*'MJPG'))
197-
if not status:
198-
raise OpenError("Can't open the camera from {}".format(input))
199-
except ValueError:
200-
raise InvalidInput("Can't find the camera {}".format(input))
209+
else:
210+
# ISX031 and similar IPU cameras output UYVY
211+
self.cap.set(cv2.CAP_PROP_FOURCC, cv2.VideoWriter_fourcc(*'UYVY'))
212+
if not status:
213+
raise OpenError("Can't open the camera from {}".format(input))
201214

202215
def read(self):
203216
status, image = self.cap.read()

robotics-ai-suite/components/multicam-demo/src/pyrealsense2_ai_demo/inference_manager.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,12 @@
1818
from . import perf_visualizer as pv
1919

2020
class InferenceManager(Thread):
21-
def __init__(self, model_adapter, input, data_type, async_mode=False):
21+
def __init__(self, model_adapter, input, data_type, async_mode=False, camera_resolution=(1280, 720)):
2222
super().__init__()
2323
self.adapter = model_adapter
2424
self.input = input
2525
self.data_type = data_type
26-
self.cap = VideoCapture(input, True) if input is not None else None
26+
self.cap = VideoCapture(input, True, camera_resolution) if input is not None else None
2727
self.async_mode = async_mode
2828
self.frames_number = 0
2929
self.start_time = None

robotics-ai-suite/components/multicam-demo/src/pyrealsense2_ai_demo_launcher.py

Lines changed: 78 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import cv2
88
import numpy as np
99
import json
10+
import time
1011
import imutils
1112
from yolov8_model import YoloV8Model
1213
import pyrealsense2_ai_demo
@@ -18,74 +19,106 @@
1819
yolov8 = YoloV8Model
1920
)
2021

21-
def run(config_file):
22+
def run(config_file, no_display=False, verbose=False):
2223

23-
config = json.load(open(config_file))
24+
with open(config_file) as f:
25+
raw = '\n'.join(line for line in f if not line.lstrip().startswith('//'))
26+
config = json.loads(raw)
2427

2528
apps = []
2629
for app in config:
2730
adapter = adapters[app["adapter"]]
31+
if verbose:
32+
print(f"[VERBOSE] Loading model: {app['model']} on {app['device']} for source {app['source']}")
2833
model = adapter(app["model"], app["device"], app["name"])
29-
apps.append(InferenceManager(model, app["source"], config[0]["data_type"]))
30-
if len(apps) > MAX_APP:
34+
resolution = (app.get("width", 1280), app.get("height", 720))
35+
if verbose:
36+
print(f"[VERBOSE] Opening camera: {app['source']} at {resolution}")
37+
apps.append(InferenceManager(model, app["source"], app["data_type"], camera_resolution=resolution))
38+
if len(apps) >= MAX_APP:
3139
break;
3240

41+
if verbose:
42+
print(f"[VERBOSE] Starting {len(apps)} inference thread(s)...")
3343
for app in apps:
3444
app.start()
3545

46+
if verbose:
47+
print("[VERBOSE] All threads started. Entering main loop. Press Ctrl+C to stop.")
48+
3649
vis = np.zeros((720, 1280, 3), dtype = np.uint8)
3750
height,width = vis.shape[:2]
3851
margin = 5
39-
cv2.namedWindow("demo", cv2.WND_PROP_FULLSCREEN)
40-
cv2.setWindowProperty("demo", cv2.WND_PROP_FULLSCREEN, cv2.WINDOW_FULLSCREEN)
52+
if not no_display:
53+
cv2.namedWindow("demo", cv2.WND_PROP_FULLSCREEN)
54+
cv2.setWindowProperty("demo", cv2.WND_PROP_FULLSCREEN, cv2.WINDOW_FULLSCREEN)
4155
fullScreen = None
42-
num_frames = 0;
43-
while True:
44-
images = []
45-
for app in apps:
46-
img = app.get(1)
47-
if img is not None:
48-
images.append(img)
49-
50-
if len(images) != len(apps):
51-
continue
52-
53-
if len(images) == 1:
54-
vis = images[0]
55-
else:
56-
sh,sw = int(height/2),int(width/2)
57-
for i in range(len(images)):
58-
app_image = imutils.resize(images[i], height=sh-margin)
59-
h,w = app_image.shape[:2]
60-
xoff = int(i%2)*sw + int((sw-w)/2) + int(i%2)*margin
61-
yoff = int(i/2)*sh + int(i/2)*margin
62-
vis[yoff:yoff+h, xoff:xoff+w] = app_image
63-
64-
cv2.imshow("demo", vis)
65-
key = cv2.waitKey(1)
66-
67-
if key in {ord('q'), ord('Q'), 27}:
68-
69-
break
70-
71-
if key == ord('f'):
72-
cv2.setWindowProperty("demo", cv2.WND_PROP_FULLSCREEN, cv2.WINDOW_FULLSCREEN if not fullScreen else cv2.WINDOW_NORMAL)
73-
fullScreen = not fullScreen
74-
75-
num_frames += 1
76-
if fullScreen is None and num_frames > 3:
77-
cv2.setWindowProperty("demo", cv2.WND_PROP_FULLSCREEN, cv2.WINDOW_NORMAL)
78-
fullScreen = False
56+
num_frames = 0
57+
last_verbose_time = time.time()
58+
try:
59+
while True:
60+
images = []
61+
for app in apps:
62+
img = app.get(1)
63+
if img is not None:
64+
images.append(img)
65+
66+
if verbose and (time.time() - last_verbose_time) >= 2.0:
67+
last_verbose_time = time.time()
68+
for idx, app in enumerate(apps):
69+
img = app.get()
70+
shape = img.shape if img is not None else None
71+
fps = app.fps() if app.start_time is not None else 0
72+
print(f"[VERBOSE] cam[{idx}] source={app.input} frames={app.frames_number} fps={fps:.1f} last_shape={shape}")
73+
74+
if len(images) != len(apps):
75+
continue
76+
77+
if no_display:
78+
num_frames += 1
79+
if verbose and num_frames % 30 == 0:
80+
print(f"[VERBOSE] {num_frames} composite frames rendered (no-display mode)")
81+
continue
82+
83+
if len(images) == 1:
84+
vis = images[0]
85+
else:
86+
sh,sw = int(height/2),int(width/2)
87+
for i in range(len(images)):
88+
app_image = imutils.resize(images[i], height=sh-margin)
89+
h,w = app_image.shape[:2]
90+
xoff = int(i%2)*sw + int((sw-w)/2) + int(i%2)*margin
91+
yoff = int(i/2)*sh + int(i/2)*margin
92+
vis[yoff:yoff+h, xoff:xoff+w] = app_image
93+
94+
cv2.imshow("demo", vis)
95+
key = cv2.waitKey(1)
96+
97+
if key in {ord('q'), ord('Q'), 27}:
98+
break
99+
100+
if key == ord('f'):
101+
cv2.setWindowProperty("demo", cv2.WND_PROP_FULLSCREEN, cv2.WINDOW_FULLSCREEN if not fullScreen else cv2.WINDOW_NORMAL)
102+
fullScreen = not fullScreen
103+
104+
num_frames += 1
105+
if fullScreen is None and num_frames > 3:
106+
cv2.setWindowProperty("demo", cv2.WND_PROP_FULLSCREEN, cv2.WINDOW_NORMAL)
107+
fullScreen = False
108+
except KeyboardInterrupt:
109+
print("\n[INFO] Interrupted by user.")
79110

80111
for app in apps:
81112
app.stop()
82113

83114
if __name__ == '__main__':
84115
parser = argparse.ArgumentParser()
85-
parser.add_argument('--config', default='./config.js', help='confile file')
116+
parser.add_argument('--config', default='./config.js', help='config file')
117+
parser.add_argument('--no-display', action='store_true', help='skip cv2 window rendering')
118+
parser.add_argument('--verbose', action='store_true', help='print per-camera stats every 2 seconds')
86119

87120
args = parser.parse_args()
88121

89-
run(args.config)
122+
run(args.config, no_display=args.no_display, verbose=args.verbose)
90123

91124

0 commit comments

Comments
 (0)