Multiple Cameras in Python

Hey all, i’m trying to run a code that captures and saves images & depth maps, i was successful in running it on Camera connected to my Jetson AGX Orin Devkit, however adding a new object for the second camera it seems it is not detecting both, instead only detecting one which lights a blue light for a short period of time and then goes out again.

in the old version of the code i wrote this line
zed = sl.Camera()

in the new multi camera code i wrote these two lines
zed1 = sl.Camera()
zed2 = sl.Camera()

has anyone been successful multiple cameras within a python code? thanks in advance

Hi, can you send the full code ?

import pyzed.sl as sl
import cv2
import os
import subprocess
import threading
import pexpect

def main():
# Create two ZED2i camera objects
zed1 = sl.Camera()
zed2 = sl.Camera()

# Create initialization parameters for the cameras
init_params = sl.InitParameters()
init_params.camera_resolution = sl.RESOLUTION.HD720  # Use HD720 video mode
init_params.camera_fps = 30  # Set FPS at 30

# Open the cameras
err1 = zed1.open(init_params)
err2 = zed2.open(init_params)
if err1 != sl.ERROR_CODE.SUCCESS or err2 != sl.ERROR_CODE.SUCCESS:
    exit(1)

# Specify save directories for depth maps and images
save_directory_1 = "path/to/directory/cobraout"
os.makedirs(save_directory_1, exist_ok=True)
save_directory_2 = "/path/to/directory"
os.makedirs(save_directory_2, exist_ok=True)

def capture_and_send_zed1():
    # Enable image retrieval for camera 1
    runtime_parameters = sl.RuntimeParameters()
    runtime_parameters.sensing_mode = sl.SENSING_MODE.STANDARD

    # Perform any desired image processing or save the images from camera 1
    frame_count = 0
    while True:
        # Capture a new frame from camera 1
        if zed1.grab(runtime_parameters) == sl.ERROR_CODE.SUCCESS:
            # Retrieve the right color image from camera 1
            right_image_1 = sl.Mat()
            zed1.retrieve_image(right_image_1, sl.VIEW.RIGHT)
            left_image_1 = sl.Mat()
            zed1.retrieve_image(left_image_1, sl.VIEW.LEFT)
            depth_map_1 = sl.Mat()
            zed1.retrieve_measure(depth_map_1, sl.MEASURE.DEPTH)
            depth_array_1 = depth_map_1.get_data()

            # Convert the images to numpy arrays
            right_image_array_1 = right_image_1.get_data()

            # Save depth map and image with unique filenames from camera 1
            depth_filename_1 = f"dm1_{frame_count}.txt"
            depth_filepath_1 = os.path.join(save_directory_1, depth_filename_1)
            np.savetxt(depth_filepath_1, depth_array_1, delimiter=',')
            right_image_filename_1 = f"rm1_{frame_count}.png"
            right_image_filepath_1 = os.path.join(save_directory_1, right_image_filename_1)
            cv2.imwrite(right_image_filepath_1, right_image_array_1)

        # Increment the frame count
        frame_count += 1

        # Check for key press to exit
        key = cv2.waitKey(1)
        if key == ord("q"):
            break

def capture_and_send_zed2():
    # Enable image retrieval for camera 2
    runtime_parameters = sl.RuntimeParameters()
    runtime_parameters.sensing_mode = sl.SENSING_MODE.STANDARD

    # Perform any desired image processing or save the images from camera 2
    frame_count = 0
    while True:
        # Capture a new frame from camera 2
        if zed2.grab(runtime_parameters) == sl.ERROR_CODE.SUCCESS:
            # Retrieve the right color image from camera 2
            right_image_2 = sl.Mat()
            zed2.retrieve_image(right_image_2, sl.VIEW.RIGHT)
            left_image_2 = sl.Mat()
            zed2.retrieve_image(left_image_2, sl.VIEW.LEFT)
            depth_map_2 = sl.Mat()
            zed2.retrieve_measure(depth_map_2, sl.MEASURE.DEPTH)
            depth_array_2 = depth_map_2.get_data()

            # Convert the images to numpy arrays
            right_image_array_2 = right_image_2.get_data()

            # Save depth map and image with unique filenames from camera 2
            depth_filename_2 = f"dm2_{frame_count}.txt"
            depth_filepath_2 = os.path.join(save_directory_2, depth_filename_2)
            np.savetxt(depth_filepath_2, depth_array_2, delimiter=',')
            right_image_filename_2 = f"rm2_{frame_count}.png"
            right_image_filepath_2 = os.path.join(save_directory_2, right_image_filename_2)
            cv2.imwrite(right_image_filepath_2, right_image_array_2

        # Increment the frame count
        frame_count += 1

        # Check for key press to exit
        key = cv2.waitKey(1)
        if key == ord("q"):
            break

# Create separate threads for capturing and sending data from each camera
thread1 = threading.Thread(target=capture_and_send_zed1)
thread2 = threading.Thread(target=capture_and_send_zed2)

# Start the threads
thread1.start()
thread2.start()

# Wait for the threads to finish
thread1.join()
thread2.join()

# Close the cameras
zed1.close()
zed2.close()

# Close any open windows
cv2.destroyAllWindows()

if name == “main”:
main()

You might encounter a lot of issues using Python’s threads. These are fake threads, because of the Python GIL : What Is the Python Global Interpreter Lock (GIL)? – Real Python

One solution would be to use process instead. The api is compatible so, basically, you just have to import multiprocessing and replace all threading.Thread objects with multiprocessing.Process.

so creating 2 functions for each camera and then calling them on with multiprocessing?

Exactly the same way you do it with threads.

would i have to use the same syntax as this example provided by stereolabs? with the zed_list or can i just try to assign each camera a separate process identified by the serial number?

You should not. The sample you mention use threading too.
You must simply replace, in your code, all threading.Thread with multiprocessing.Process and import the header.

import pyzed.sl as sl
import cv2
import os
import subprocess
import multiprocessing
import pexpect

def main():
# Create two ZED2i camera objects
 zed1 = sl.Camera()
 zed2 = sl.Camera()

# Create initialization parameters for the cameras
 init_params = sl.InitParameters()
 init_params.camera_resolution = sl.RESOLUTION.HD720  # Use HD720 video mode
 init_params.camera_fps = 30  # Set FPS at 30

# Open the cameras
 err1 = zed1.open(init_params)
 err2 = zed2.open(init_params) 
 if err1 != sl.ERROR_CODE.SUCCESS or err2 != sl.ERROR_CODE.SUCCESS:
     exit(1)

# Specify save directories for depth maps and images
 save_directory_1 = "path/directory"
 os.makedirs(save_directory_1, exist_ok=True)
 save_directory_2 = "path/directory"
 os.makedirs(save_directory_2, exist_ok=True)

 def capture_and_send_zed1():
    # Enable image retrieval for camera 1
    runtime_parameters = sl.RuntimeParameters()
    runtime_parameters.sensing_mode = sl.SENSING_MODE.STANDARD

    # Perform any desired image processing or save the images from camera 1
    frame_count = 0
    while True:
        # Capture a new frame from camera 1
        if zed1.grab(runtime_parameters) == sl.ERROR_CODE.SUCCESS:
            # Retrieve the right color image from camera 1
            right_image_1 = sl.Mat()
            zed1.retrieve_image(right_image_1, sl.VIEW.RIGHT)
            left_image_1 = sl.Mat()
            zed1.retrieve_image(left_image_1, sl.VIEW.LEFT)
            depth_map_1 = sl.Mat()
            zed1.retrieve_measure(depth_map_1, sl.MEASURE.DEPTH)
            depth_array_1 = depth_map_1.get_data()

            # Convert the images to numpy arrays
            right_image_array_1 = right_image_1.get_data()

            # Save depth map and image with unique filenames from camera 1
            depth_filename_1 = f"dm1_{frame_count}.txt"
            depth_filepath_1 = os.path.join(save_directory_1, depth_filename_1)
            np.savetxt(depth_filepath_1, depth_array_1, delimiter=',')
            right_image_filename_1 = f"rm1_{frame_count}.png"
            right_image_filepath_1 = os.path.join(save_directory_1, right_image_filename_1)
            cv2.imwrite(right_image_filepath_1, right_image_array_1)

        # Increment the frame count
        frame_count += 1

        # Check for key press to exit
        key = cv2.waitKey(1)
        if key == ord("q"):
            break

 def capture_and_send_zed2():
    # Enable image retrieval for camera 2
    runtime_parameters = sl.RuntimeParameters()
    runtime_parameters.sensing_mode = sl.SENSING_MODE.STANDARD

    # Perform any desired image processing or save the images from camera 2
    frame_count = 0
    while True:
        # Capture a new frame from camera 2
        if zed2.grab(runtime_parameters) == sl.ERROR_CODE.SUCCESS:
            # Retrieve the right color image from camera 2
            right_image_2 = sl.Mat()
            zed2.retrieve_image(right_image_2, sl.VIEW.RIGHT)
            left_image_2 = sl.Mat()
            zed2.retrieve_image(left_image_2, sl.VIEW.LEFT)
            depth_map_2 = sl.Mat()
            zed2.retrieve_measure(depth_map_2, sl.MEASURE.DEPTH)
            depth_array_2 = depth_map_2.get_data()

            # Convert the images to numpy arrays
            right_image_array_2 = right_image_2.get_data()

            # Save depth map and image with unique filenames from camera 2
            depth_filename_2 = f"dm2_{frame_count}.txt"
            depth_filepath_2 = os.path.join(save_directory_2, depth_filename_2)
            np.savetxt(depth_filepath_2, depth_array_2, delimiter=',')
            right_image_filename_2 = f"rm2_{frame_count}.png"
            right_image_filepath_2 = os.path.join(save_directory_2, right_image_filename_2)
            cv2.imwrite(right_image_filepath_2, right_image_array_2)
            frame_count += 1

        # Check for key press to exit
        key = cv2.waitKey(1)
        if key == ord("q"):
            break

# Create separate threads for capturing and sending data from each camera
 process1 = multiprocessing.Process(target=capture_and_send_z1)
 process2 = multiprocessing.Process(target=capture_and_send_z2)

# Start the threads
 process1.Start()
 process2.Start()
# Close the cameras
 zed1.close()
 zed2.close()

# Close any open windows
 cv2.destroyAllWindows()

 if __name__ == "__main__":
  main()

i changed the program as you requested, however it just runs now and does not take any pictures or saves them, it simply closes.
image

It’s because your main process exits. If you make it wait with the sleep function, how does it behave ?

import pyzed.sl as sl
import cv2
import numpy as np
import threading
import time
import signal
import os

zed_list = []
left_list = []
right_list = []
depth_list = []
timestamp_list = []
thread_list = []
stop_signal = False
output_directory = 'path/to/directory'

def signal_handler(signal, frame):
    global stop_signal
    stop_signal = True
    time.sleep(0.5)
    exit()

def grab_run(index):
    global stop_signal
    global zed_list
    global timestamp_list
    global left_list
    global right_list
    global depth_list

    runtime = sl.RuntimeParameters()
    while not stop_signal:
        err = zed_list[index].grab(runtime)
        if err == sl.ERROR_CODE.SUCCESS:
            # Retrieve the left image from the ZED camera
            zed_list[index].retrieve_image(left_list[index], sl.VIEW.LEFT)
            # Retrieve the right image from the ZED camera
            zed_list[index].retrieve_image(right_list[index], sl.VIEW.RIGHT)
            # Retrieve the depth map from the ZED camera
            zed_list[index].retrieve_measure(depth_list[index], sl.MEASURE.DEPTH)
            # Get the timestamp of the current frame
            timestamp_list[index] = zed_list[index].get_timestamp(sl.TIME_REFERENCE.CURRENT).data_ns
            # Save the left and right images and depth map
            save_image(left_list[index], f"left_image_{index}.jpg")
            save_image(right_list[index], f"right_image_{index}.jpg")
            save_depth_map(depth_list[index], f"depth_map_{index}.txt")
        time.sleep(0.001) #1ms
    zed_list[index].close()

def save_image(image, filename):
    global output_directory
    # Create the file path for the image
    file_path = os.path.join(output_directory, filename)
    # Save the image as a JPEG file
    cv2.imwrite(file_path, image.get_data())

def save_depth_map(depth_map, filename):
    global output_directory
    # Create the file path for the depth map
    file_path = os.path.join(output_directory, filename)
    # Save the depth map as a text file
    np.savetxt(file_path, depth_map.get_data())

def main():
    global stop_signal
    global zed_list
    global left_list
    global right_list
    global depth_list
    global timestamp_list
    global thread_list
    signal.signal(signal.SIGINT, signal_handler)

    print("Running...")
    init = sl.InitParameters()
    init.camera_resolution = sl.RESOLUTION.HD720
    init.camera_fps = 30  # The framerate is lowered to avoid any USB3 bandwidth issues

    # Create output directory if it doesn't exist
    if not os.path.exists(output_directory):
        os.makedirs(output_directory)

    # List and open cameras
    name_list = []
    last_ts_list = []
    cameras = sl.Camera.get_device_list()
    index = 0
    for cam in cameras:
        init.set_from_serial_number(cam.serial_number)
        name_list.append("ZED {}".format(cam.serial_number))
        print("Opening {}".format(name_list[index]))
        # Initialize the ZED camera and create image and depth map containers
        zed_list.append(sl.Camera())
        left_list.append(sl.Mat())
        right_list.append(sl.Mat())
        depth_list.append(sl.Mat())
        timestamp_list.append(0)
        last_ts_list.append(0)
        status = zed_list[index].open(init)
        if status != sl.ERROR_CODE.SUCCESS:
            print(repr(status))
            zed_list[index].close()
        index = index + 1

    # Start camera threads
    for index in range(0, len(zed_list)):
        if zed_list[index].is_opened():
            # Create a thread for each ZED camera to grab frames
            thread_list.append(threading.Thread(target=grab_run, args=(index,)))
            thread_list[index].start()

    # Display camera images
    key = ''
    while key != 113:  # for 'q' key
        for index in range(0, len(zed_list)):
            if zed_list[index].is_opened():
                if (timestamp_list[index] > last_ts_list[index]):
                    cv2.imshow(name_list[index], left_list[index].get_data())
                    
        key = cv2.waitKey(10)
    cv2.destroyAllWindows()

    # Stop the threads
    stop_signal = True
    for index in range(0, len(thread_list)):
        thread_list[index].join()

    print("\nFINISH")

if __name__ == "__main__":
    main()

alright this code functions and saves the images in the specified output directory, albeit it is very laggy. How does it function on your side?

Well, you are trying to save tons of images per second on your disk. The result will highly depends on your disk speed. What does your system monitor tell you while the program runs ?

it runs the program and opens an opencv window which shows the video feed, but even that is laggy and jitters
im trying a different approach now, assigning the camera serial number to each function and running it with threading not with multiprocessing since the multiprocessing isn’t running anything and only closing the program