Change to CAP_DSHOW for VideoCapture
This commit is contained in:
Binary file not shown.
@@ -1,10 +1,92 @@
|
||||
import numpy as np
|
||||
import json
|
||||
import cv2
|
||||
import threading
|
||||
|
||||
from dataclasses import dataclass, field, asdict
|
||||
from typing import List, Optional, Tuple, Dict
|
||||
|
||||
from pygrabber.dshow_graph import FilterGraph
|
||||
|
||||
|
||||
class DShowMJPGCapture:
|
||||
"""
|
||||
cv2.VideoCapture-like wrapper (read/release/isOpened) that selects an exact
|
||||
DirectShow media type (e.g. MJPG at a given resolution) before capturing.
|
||||
|
||||
This exists because cv2.VideoCapture(..., cv2.CAP_DSHOW).set(CAP_PROP_FOURCC, ...)
|
||||
is unreliable on some webcams: OpenCV opens its own separate DirectShow filter
|
||||
graph, which renegotiates its own format independently of anything set through
|
||||
pygrabber beforehand. Doing format selection AND frame grabbing in the same
|
||||
graph (via pygrabber's sample grabber) avoids that.
|
||||
"""
|
||||
|
||||
def __init__(self, index: int, width: int, height: int,
|
||||
fourcc_substr: str = "MJPG", timeout: float = 2.0):
|
||||
self.index = index
|
||||
self.width = width
|
||||
self.height = height
|
||||
self.selected_format = None
|
||||
self._opened = False
|
||||
self._latest_frame = None
|
||||
self._frame_event = threading.Event()
|
||||
self._timeout = timeout
|
||||
|
||||
self._graph = FilterGraph()
|
||||
self._graph.add_video_input_device(index)
|
||||
|
||||
device = self._graph.get_input_device()
|
||||
formats = device.get_formats()
|
||||
match = next(
|
||||
(f for f in formats
|
||||
if f["width"] == width and f["height"] == height
|
||||
and fourcc_substr.upper() in f["media_type_str"].upper()),
|
||||
None,
|
||||
)
|
||||
if match is None:
|
||||
raise RuntimeError(
|
||||
f"No {fourcc_substr} format at {width}x{height} available on camera {index}. "
|
||||
f"Available formats: {formats}"
|
||||
)
|
||||
device.set_format(match["index"])
|
||||
self.selected_format = match
|
||||
|
||||
self._graph.add_sample_grabber(self._on_frame)
|
||||
self._graph.add_null_render()
|
||||
self._graph.prepare_preview_graph()
|
||||
self._graph.run()
|
||||
self._opened = True
|
||||
|
||||
def _on_frame(self, frame):
|
||||
self._latest_frame = frame
|
||||
self._frame_event.set()
|
||||
|
||||
def isOpened(self) -> bool:
|
||||
return self._opened
|
||||
|
||||
def read(self):
|
||||
if not self._opened:
|
||||
return False, None
|
||||
self._frame_event.clear()
|
||||
self._graph.grab_frame()
|
||||
if not self._frame_event.wait(self._timeout):
|
||||
return False, None
|
||||
return True, self._latest_frame
|
||||
|
||||
def release(self):
|
||||
if self._opened:
|
||||
self._graph.stop()
|
||||
self._graph.remove_filters()
|
||||
self._opened = False
|
||||
|
||||
# No-ops for compatibility with code paths that still call .set()/.get()
|
||||
# on the capture object (real config happens via device.set_format above).
|
||||
def set(self, prop_id, value):
|
||||
return False
|
||||
|
||||
def get(self, prop_id):
|
||||
return 0.0
|
||||
|
||||
|
||||
# ----- Helper Functions -----
|
||||
def compute_distance(p1, p2):
|
||||
|
||||
Reference in New Issue
Block a user