diff --git a/lerobot/common/robot_devices/cameras/opencv.py b/lerobot/common/robot_devices/cameras/opencv.py index 48fbb968..6d7fe2ac 100644 --- a/lerobot/common/robot_devices/cameras/opencv.py +++ b/lerobot/common/robot_devices/cameras/opencv.py @@ -5,6 +5,7 @@ This file contains utilities for recording frames from cameras. For more info lo import argparse import concurrent.futures import math +import platform import shutil import threading import time @@ -33,8 +34,18 @@ MAX_OPENCV_INDEX = 60 def find_camera_indices(raise_when_empty=False, max_index_search_range=MAX_OPENCV_INDEX): + if platform.system() == "Linux": + # Linux uses camera ports + print("Linux detected. Finding available camera indices through scanning '/dev/video*' ports") + possible_camera_ids = [int(port.replace("/dev/video", "")) for port in Path("/dev").glob("video*")] + else: + print( + f"Mac or Windows dtected. Finding available camera indices through scanning all indices from 0 to {MAX_OPENCV_INDEX}" + ) + possible_camera_ids = range(max_index_search_range) + camera_ids = [] - for camera_idx in range(max_index_search_range): + for camera_idx in possible_camera_ids: camera = cv2.VideoCapture(camera_idx) is_open = camera.isOpened() camera.release() @@ -59,10 +70,9 @@ def save_image(img_array, camera_index, frame_index, images_dir): def save_images_from_cameras( - images_dir: Path, camera_ids=None, fps=None, width=None, height=None, record_time_s=2 + images_dir: Path, camera_ids: list[int] | None = None, fps=None, width=None, height=None, record_time_s=2 ): if camera_ids is None: - print("Finding available camera indices") camera_ids = find_camera_indices() print("Connecting cameras") @@ -75,9 +85,7 @@ def save_images_from_cameras( ) cameras.append(camera) - images_dir = Path( - images_dir, - ) + images_dir = Path(images_dir) if images_dir.exists(): shutil.rmtree( images_dir, @@ -99,7 +107,7 @@ def save_images_from_cameras( executor.submit( save_image, image, - camera.camera_index, + camera.index, frame_index, images_dir, ) @@ -160,7 +168,7 @@ class OpenCVCamera: When an OpenCVCamera is instantiated, if no specific config is provided, the default fps, width, height and color_mode of the given camera will be used. - Example of usage of the class: + Example of usage: ```python camera = OpenCVCamera(camera_index=0) camera.connect() @@ -182,7 +190,7 @@ class OpenCVCamera: ``` """ - def __init__(self, camera_index: int | str, config: OpenCVCameraConfig | None = None, **kwargs): + def __init__(self, camera_index: int, config: OpenCVCameraConfig | None = None, **kwargs): if config is None: config = OpenCVCameraConfig() # Overwrite config arguments using kwargs @@ -207,7 +215,13 @@ class OpenCVCamera: # First create a temporary camera trying to access `camera_index`, # and verify it is a valid camera by calling `isOpened`. - tmp_camera = cv2.VideoCapture(self.camera_index) + + if platform.system() == "Linux": + # Linux uses ports for connecting to cameras + tmp_camera = cv2.VideoCapture(f"/dev/video{self.camera_index}") + else: + tmp_camera = cv2.VideoCapture(self.camera_index) + is_camera_open = tmp_camera.isOpened() # Release camera to make it accessible for `find_camera_indices` del tmp_camera @@ -227,7 +241,10 @@ class OpenCVCamera: # Secondly, create the camera that will be used downstream. # Note: For some unknown reason, calling `isOpened` blocks the camera which then # needs to be re-created. - self.camera = cv2.VideoCapture(self.camera_index) + if platform.system() == "Linux": + self.camera = cv2.VideoCapture(f"/dev/video{self.camera_index}") + else: + self.camera = cv2.VideoCapture(self.camera_index) if self.fps is not None: self.camera.set(cv2.CAP_PROP_FPS, self.fps)