Change to CAP_DSHOW for VideoCapture

This commit is contained in:
DuOtto
2026-07-31 09:02:21 +02:00
parent 09e94b218c
commit 2e6f444302
6 changed files with 125 additions and 39 deletions
Binary file not shown.
+82
View File
@@ -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):