diff --git a/src/polystar/dependency_injection.py b/src/polystar/dependency_injection.py index 6cb199adc8d5c980233015f2b9bbedc99e6ca0fe..26061733bfb4a349806bc6e616836cb4f25b706a 100644 --- a/src/polystar/dependency_injection.py +++ b/src/polystar/dependency_injection.py @@ -9,7 +9,7 @@ from polystar.communication.board_a import BoardA from polystar.communication.cs_link_abc import CSLinkABC from polystar.communication.screen import Screen from polystar.constants import LABEL_MAP_PATH -from polystar.frame_generators.camera_frame_generator import RaspiV2CameraFrameGenerator, WebcamFrameGenerator +from polystar.frame_generators.camera_frame_generator import CameraFrameGenerator, make_csi_camera_frame_generator from polystar.frame_generators.frames_generator_abc import FrameGeneratorABC from polystar.models.camera import Camera from polystar.models.label_map import LabelMap @@ -22,10 +22,7 @@ from polystar.target_pipeline.detected_objects.detected_objects_factory import D from polystar.target_pipeline.object_selectors.closest_object_selector import ClosestObjectSelector from polystar.target_pipeline.object_selectors.object_selector_abc import ObjectSelectorABC from polystar.target_pipeline.objects_detectors.objects_detector_abc import ObjectsDetectorABC -from polystar.target_pipeline.objects_filters.confidence_object_filter import ( - ConfidenceObjectsFilter, - RobotArmorConfidenceObjectsFilter, -) +from polystar.target_pipeline.objects_filters.confidence_object_filter import RobotArmorConfidenceObjectsFilter from polystar.target_pipeline.objects_filters.objects_filter_abc import ObjectsFilterABC from polystar.target_pipeline.objects_linker.objects_linker_abs import ObjectsLinkerABC from polystar.target_pipeline.objects_linker.simple_objects_linker import SimpleObjectsLinker @@ -78,7 +75,7 @@ class CommonModule(Module): @singleton def provide_armor_descriptors(self) -> List[ArmorsDescriptorABC]: return [ - ArmorsColorDescriptor(ArmorColorPipeline.from_pipes([MeanChannels(), RedBlueComparisonClassifier()])), + # ArmorsColorDescriptor(ArmorColorPipeline.from_pipes([MeanChannels(), RedBlueComparisonClassifier()])), # ArmorsDigitDescriptor(pkl_load(PIPELINES_DIR / "armor-digit" / settings.ARMOR_DIGIT_MODEL)), ] @@ -110,8 +107,7 @@ class CommonModule(Module): return SimpleObjectsLinker(min_percentage_intersection=0.8) @provider - @singleton def provide_webcam(self) -> FrameGeneratorABC: if self.settings.is_prod: - return RaspiV2CameraFrameGenerator(1_280, 720) - return WebcamFrameGenerator() + return make_csi_camera_frame_generator(1_280, 720) + return CameraFrameGenerator() diff --git a/src/polystar/frame_generators/camera_frame_generator.py b/src/polystar/frame_generators/camera_frame_generator.py index 237898bc5d0117d98df1907491d53af47a19d720..c2649fc2ded68134c4f5e11f9b39ed23d301b964 100644 --- a/src/polystar/frame_generators/camera_frame_generator.py +++ b/src/polystar/frame_generators/camera_frame_generator.py @@ -1,31 +1,56 @@ -from dataclasses import dataclass -from typing import Any, Iterable +from threading import Thread +from typing import Any, Iterator import cv2 -from polystar.frame_generators.cv2_frame_generator_abc import CV2FrameGeneratorABC +from polystar.frame_generators.cv2_frame_generator_abc import CV2FrameGenerator +from polystar.models.image import Image -@dataclass -class RaspiV2CameraFrameGenerator(CV2FrameGeneratorABC): - width: int - height: int +class CameraFrameGenerator(CV2FrameGenerator): + def __init__(self, *capture_params: Any): + super().__init__(*capture_params) + self.camera_thread = CameraThread(super().__iter__()) - def _capture_params(self) -> Iterable[Any]: - return ( - "nvarguscamerasrc ! " - "video/x-raw(memory:NVMM), " - f"width=(int){self.width}, height=(int){self.height}, " - "format=(string)NV12, framerate=(fraction)60/1 ! " - "nvvidconv flip-method=0 ! " - f"video/x-raw, width=(int){self.width}, height=(int){self.height}, " - "format=(string)BGRx ! " - "videoconvert ! appsink", - cv2.CAP_GSTREAMER, - ) + def __iter__(self): + self.camera_thread.start() + while True: + yield self.camera_thread.current_frame.copy() + def __del__(self): + self.camera_thread.stop() -@dataclass -class WebcamFrameGenerator(CV2FrameGeneratorABC): - def _capture_params(self) -> Iterable[Any]: - return (0,) + +class CameraThread(Thread): + def __init__(self, it: Iterator[Image]): + super().__init__() + self.it = it + self.running = True + self._get_next_frame() + + def run(self): + while self.running: + self._get_next_frame() + + def stop(self): + self.running = False + + def _get_next_frame(self): + try: + self.current_frame = next(self.it) + except StopIteration: + self.running = False + + +def make_csi_camera_frame_generator(width: int, height: int) -> CameraFrameGenerator: + return CameraFrameGenerator( + "nvarguscamerasrc ! " + "video/x-raw(memory:NVMM), " + f"width=(int){width}, height=(int){height}, " + "format=(string)NV12, framerate=60/1 ! " + "nvvidconv flip-method=0 ! " + f"video/x-raw, width=(int){width}, height=(int){height}, " + "format=(string)BGRx ! " + "videoconvert ! appsink drop=true sync=false", + cv2.CAP_GSTREAMER, + ) diff --git a/src/polystar/frame_generators/cv2_frame_generator_abc.py b/src/polystar/frame_generators/cv2_frame_generator_abc.py index 6f6033988c5a81a6b0cd742be391cf7ece64e8f5..f191ea187582e6b267180a229d6c19dce298fb96 100644 --- a/src/polystar/frame_generators/cv2_frame_generator_abc.py +++ b/src/polystar/frame_generators/cv2_frame_generator_abc.py @@ -1,38 +1,33 @@ -from abc import ABC, abstractmethod -from dataclasses import dataclass, field -from typing import Any, Iterable +from typing import Any, Iterable, Iterator -import cv2 +from cv2 import CAP_PROP_BUFFERSIZE, VideoCapture from polystar.frame_generators.frames_generator_abc import FrameGeneratorABC from polystar.models.image import Image -@dataclass -class CV2FrameGeneratorABC(FrameGeneratorABC, ABC): +class CV2FrameGenerator(FrameGeneratorABC): + def __init__(self, *capture_params: Any): + self.capture_params = capture_params or (0,) - _cap: cv2.VideoCapture = field(init=False, repr=False) + def __iter__(self) -> Iterator[Image]: + return CV2Capture(self.capture_params) - def __enter__(self): - self._cap = cv2.VideoCapture(*self._capture_params()) - # self._cap.set(cv2.CAP_PROP_BUFFERSIZE, 1) + +class CV2Capture(Iterator[Image]): + def __init__(self, capture_params: Iterable): + self._cap = VideoCapture(*capture_params) assert self._cap.isOpened() - self._post_opening_operation() + self._cap.set(CAP_PROP_BUFFERSIZE, 0) - def __exit__(self, exc_type, exc_val, exc_tb): - self._cap.release() + def __next__(self) -> Image: + success, frame = self._cap.read() - def generate(self) -> Iterable[Image]: - with self: - while 1: - is_open, frame = self._cap.read() - if not is_open: - return - yield frame + if success: + return frame - @abstractmethod - def _capture_params(self) -> Iterable[Any]: - pass + raise StopIteration() - def _post_opening_operation(self): - pass + def __del__(self): + if self._cap.isOpened(): + self._cap.release() diff --git a/src/polystar/frame_generators/fps_video_frame_generator.py b/src/polystar/frame_generators/fps_video_frame_generator.py index 7e45e0434a9028cfced229a34e8a5e805c3de996..83889e41597f2be4422c2ef32de01c77714ce750 100644 --- a/src/polystar/frame_generators/fps_video_frame_generator.py +++ b/src/polystar/frame_generators/fps_video_frame_generator.py @@ -1,19 +1,16 @@ -from dataclasses import dataclass -from typing import Iterable +from pathlib import Path +from typing import Iterator, Optional from polystar.frame_generators.video_frame_generator import VideoFrameGenerator from polystar.models.image import Image -@dataclass class FPSVideoFrameGenerator(VideoFrameGenerator): + def __init__(self, video_path: Path, desired_fps: int, offset_seconds: Optional[int] = None): + super().__init__(video_path, offset_seconds) + self.frame_rate: int = self._video_fps // desired_fps - desired_fps: int - - def __post_init__(self): - self.frame_rate: int = self._video_fps // self.desired_fps - - def generate(self) -> Iterable[Image]: - for i, frame in enumerate(super().generate(), -1): + def __iter__(self) -> Iterator[Image]: + for i, frame in enumerate(super().__iter__(), -1): if not i % self.frame_rate: yield frame diff --git a/src/polystar/frame_generators/frames_generator_abc.py b/src/polystar/frame_generators/frames_generator_abc.py index a6ec18af9dea2348be10230a0590b3e415853ff9..5fe8e1160eeea030a4f1c9ed096960c128ff1b7e 100644 --- a/src/polystar/frame_generators/frames_generator_abc.py +++ b/src/polystar/frame_generators/frames_generator_abc.py @@ -1,10 +1,10 @@ from abc import ABC, abstractmethod -from typing import Iterable +from typing import Iterable, Iterator from polystar.models.image import Image -class FrameGeneratorABC(ABC): +class FrameGeneratorABC(ABC, Iterable[Image]): @abstractmethod - def generate(self) -> Iterable[Image]: + def __iter__(self) -> Iterator[Image]: pass diff --git a/src/polystar/frame_generators/video_frame_generator.py b/src/polystar/frame_generators/video_frame_generator.py index 22701b54effe603b8f43d0089924db7929793147..1d9220b736ba128ddb62a8eebff1fb963dcb42f0 100644 --- a/src/polystar/frame_generators/video_frame_generator.py +++ b/src/polystar/frame_generators/video_frame_generator.py @@ -1,26 +1,24 @@ -from dataclasses import dataclass from pathlib import Path -from typing import Any, Iterable, Optional +from typing import Iterable, Iterator, Optional import ffmpeg from cv2.cv2 import CAP_PROP_POS_FRAMES from memoized_property import memoized_property -from polystar.frame_generators.cv2_frame_generator_abc import CV2FrameGeneratorABC +from polystar.frame_generators.cv2_frame_generator_abc import CV2Capture, CV2FrameGenerator +from polystar.models.image import Image -@dataclass -class VideoFrameGenerator(CV2FrameGeneratorABC): +class VideoFrameGenerator(CV2FrameGenerator): + def __init__(self, video_path: Path, offset_seconds: Optional[int] = None): + super().__init__(str(video_path)) + self.offset_seconds = offset_seconds + self.video_path = video_path - video_path: Path - offset_seconds: Optional[int] - - def _capture_params(self) -> Iterable[Any]: - return (str(self.video_path),) - - def _post_opening_operation(self): + def __iter__(self) -> Iterator[Image]: if self.offset_seconds: - self._cap.set(CAP_PROP_POS_FRAMES, self._video_fps * self.offset_seconds - 2) + return CV2CaptureWithOffset(self.capture_params, self._video_fps * self.offset_seconds - 2) + return CV2Capture(self.capture_params) @memoized_property def _video_fps(self) -> int: @@ -30,3 +28,9 @@ class VideoFrameGenerator(CV2FrameGeneratorABC): continue return round(eval(stream_info["avg_frame_rate"])) raise ValueError(f"No fps found for video {self.video_path.name}") + + +class CV2CaptureWithOffset(CV2Capture): + def __init__(self, capture_params: Iterable, offset_frames: int): + super().__init__(capture_params) + self._cap.set(CAP_PROP_POS_FRAMES, offset_frames) diff --git a/src/polystar/view/cv2_results_viewer.py b/src/polystar/view/cv2_results_viewer.py index 98594c241c1c625fdb25e9ecc3a0aa360f4e1fc2..55db910d75a3fab8054dcc012c951de87f0e055d 100644 --- a/src/polystar/view/cv2_results_viewer.py +++ b/src/polystar/view/cv2_results_viewer.py @@ -35,11 +35,11 @@ class CV2ResultViewer(ResultViewerABC): self.delay = delay self.name = name self._current_image: Image = None - self.finished = False super().__init__(COLORS) def __exit__(self, exc_type, exc_val, exc_tb): cv2.destroyWindow(self.name) + return exc_type is KeyboardInterrupt def new(self, image: Image): self.height, self.width, _ = image.shape @@ -80,7 +80,7 @@ class CV2ResultViewer(ResultViewerABC): self.keycode_callbacks[keycode]() def stop(self): - self.finished = True + raise KeyboardInterrupt() def _make_keycode_callbacks(self, end_key: str, key_callbacks: Dict[str, Callback]) -> Dict[int, Callback]: key_callbacks[end_key] = self.stop diff --git a/src/polystar/view/results_viewer_abc.py b/src/polystar/view/results_viewer_abc.py index c1ddfd6d0818d6ba2b3d1d20424dce36b4ef1429..ea78e95c28b5ca6b54bc4df2da3ca9b1e55f78e3 100644 --- a/src/polystar/view/results_viewer_abc.py +++ b/src/polystar/view/results_viewer_abc.py @@ -46,6 +46,10 @@ class ResultViewerABC(ABC): for obj in objects: self.add_object(obj, forced_color=forced_color) + def display_image(self, image: Image): + self.new(image) + self.display() + def display_image_with_objects(self, image: Image, objects: Iterable[ROCOObject]): self.new(image) self.add_objects(objects) diff --git a/src/research/dataset/twitch/robots_views_extractor.py b/src/research/dataset/twitch/robots_views_extractor.py index aa58f27bb3b6b1b29dbf234bfa63ac623c92c40b..057b1dadf65e89b2b527e94e426c5b8acc08566e 100644 --- a/src/research/dataset/twitch/robots_views_extractor.py +++ b/src/research/dataset/twitch/robots_views_extractor.py @@ -25,7 +25,7 @@ class RobotsViewExtractor: def run(self): self._progress_bar = tqdm( - enumerate(self.frame_generator.generate(), 1 + self.OFFSET_SECONDS * self.FPS), + enumerate(self.frame_generator, 1 + self.OFFSET_SECONDS * self.FPS), total=self._get_number_of_frames(), desc=f"Extracting robots views from video {self.video_name}.mp4", unit="frames", diff --git a/src/research/scripts/demo_pipeline_camera.py b/src/research/scripts/demo_pipeline_camera.py index c2c79fd0c205ecca1e929a9ca05d54e4a9e8da9f..a06d889db19977059c5b63ecc335befbcfe93e36 100644 --- a/src/research/scripts/demo_pipeline_camera.py +++ b/src/research/scripts/demo_pipeline_camera.py @@ -22,14 +22,12 @@ class CameraPipelineDemo: def run(self): with CV2ResultViewer("TensorRT demo", key_callbacks={" ": self.cs_link.toggle}) as viewer: - for image in self.webcam.generate(): + for image in self.webcam: self.pipeline_fps.skip() self._detect(image) self.pipeline_fps.tick(), self.fps.tick() self._display(viewer) self.fps.skip() - if viewer.finished: - return def _detect(self, image: Image): try: diff --git a/src/research/scripts/display_camera.py b/src/research/scripts/display_camera.py new file mode 100644 index 0000000000000000000000000000000000000000..d90e654bad26179b6a2159e2d194002bb1d3b2a1 --- /dev/null +++ b/src/research/scripts/display_camera.py @@ -0,0 +1,22 @@ +from injector import inject + +from polystar.dependency_injection import make_injector +from polystar.frame_generators.frames_generator_abc import FrameGeneratorABC +from polystar.utils.fps import FPS +from polystar.view.cv2_results_viewer import CV2ResultViewer + + +@inject +def display_camera(webcam: FrameGeneratorABC): + fps = FPS() + fps_camera = FPS() + with CV2ResultViewer("Live Camera") as viewer: + for image in webcam: + viewer.new(image) + viewer.add_text(f"FPS: {fps.tick():.1f} / {fps_camera.tick():.1f}", 10, 10, (0, 0, 0)) + viewer.display() + fps_camera.skip() + + +if __name__ == "__main__": + make_injector().call_with_injection(display_camera)