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
+6
View File
@@ -5,6 +5,12 @@
"left": "PointIndex",
"right": "",
"extensions": []
},
{
"action": "Pointer",
"left": "",
"right": "PointIndex",
"extensions": []
}
]
}
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):
+14 -8
View File
@@ -436,9 +436,9 @@ class HomeWindow(QWidget):
for camera in self.cameraList: # Loop all Cameras
ok, frame = camera.capture.read() #Capture a frame
if not ok: # Check if captured frame is valid, if even one frame is invalide the whole capture failed
if not ok: # A single failed grab shouldn't discard what other cameras saw
self.logger.log(f"Could not grab frame for calibration sample, from camera {camera.index}.")
return
continue
h, w = frame.shape[:2]
self.logger.log(f"cam{camera.index} capture size = {w}x{h}")
@@ -451,9 +451,9 @@ class HomeWindow(QWidget):
#This is apparently a fast check, bevor i detect the actuall board
corner, ids, _ = self.arcuo_detector.detectMarkers(gray) #Detect ChArCuo Markers
if ids is None or len(ids) == 0: #Check if Markers where found, if none where found the sample is invalid
if ids is None or len(ids) == 0: #Board not visible from this camera this time, skip it, not the whole sample
self.logger.log(f"Not enought markers found for calibration sample, from camera {camera.index}.")
return
continue
# Detecting the Charcuo board and evaluating if its good as a sample
result = self.charuco_detector.detectBoard(gray)
@@ -463,11 +463,11 @@ class HomeWindow(QWidget):
if charuco_ids is None:
self.logger.log(f"Not enought markers found for calibration sample, from camera {camera.index}.")
return
continue
if len(charuco_ids) < MIN_CHARUCO_CORNERS:
self.logger.log(f"Not enought markers found for calibration sample, from camera {camera.index}.")
return
continue
# Adding the sample for the current camera to detections
detections[camera.index] = CharucoDetection(
@@ -477,12 +477,18 @@ class HomeWindow(QWidget):
gray_frame = gray
)
# All cameras found valide markers so the sample capture was succesfull
# Need at least two cameras' worth of detections for this sample to be
# useful for stereo pairing (a single-camera detection can still help
# that camera's intrinsics, but can't contribute to any camera pair).
if len(detections) < 2:
self.logger.log("Not enough cameras saw the board in this sample, discarding.")
return
self.calibration_samples.append(
CalibrationSample(detections=detections)
)
self.logger.log("Added calibration sample.")
self.logger.log(f"Added calibration sample ({len(detections)}/{len(self.cameraList)} cameras saw the board).")
def updateCameras(self):
self._clearLayout()
+17 -25
View File
@@ -1,8 +1,7 @@
import cv2, numpy as np
import os
from Tbd.helper import CameraObject
from Tbd.helper import CameraObject, DShowMJPGCapture
from UI.UILogger import UILogger
from PySide6.QtCore import Qt, QTimer
@@ -98,30 +97,23 @@ class SetupPage(QWidget):
# index=None
#)
for index in indexToAdd:
capture = cv2.VideoCapture(index, cv2.CAP_MSMF)
#capture = cv2.VideoCapture(index, cv2.CAP_DSHOW)
try:
capture = DShowMJPGCapture(index, 2592, 1944, fourcc_substr="MJPG")
except RuntimeError as e:
self.logger.log(str(e))
print(str(e))
continue
capture.set(cv2.CAP_PROP_FOURCC, cv2.VideoWriter_fourcc(*'MJPG'))
capture.set(cv2.CAP_PROP_FPS, 30)
capture.set(cv2.CAP_PROP_FRAME_WIDTH, 2592)
capture.set(cv2.CAP_PROP_FRAME_HEIGHT, 1944)
self.logger.log(f"Selected format: {capture.selected_format}")
print(f"Selected format: {capture.selected_format}")
#capture.set(cv2.CAP_PROP_FRAME_WIDTH, 2048.0)
#capture.set(cv2.CAP_PROP_FRAME_HEIGHT, 1536.0)
width = capture.get(cv2.CAP_PROP_FRAME_WIDTH)
height = capture.get(cv2.CAP_PROP_FRAME_HEIGHT)
fps = capture.get(cv2.CAP_PROP_FPS)
fourcc = int(capture.get(cv2.CAP_PROP_FOURCC))
fourcc_str = "".join([chr((fourcc >> 8*i) & 0xFF) for i in range(4)])
print(f"Resolution: {width}x{height}")
print(f"FPS: {fps}")
print(f"Format (FOURCC): {fourcc_str}")
self.logger.log(f"Resolution: {width}x{height}")
self.logger.log(f"FPS: {fps}")
self.logger.log(f"Format (FOURCC): {fourcc_str}")
#currentcapture = cv2.VideoCapture(index, cv2.CAP_MSMF)
ret, frame = capture.read()
if ret:
self.logger.log(f"Actual frame shape: {frame.shape}")
print(f"Actual frame shape: {frame.shape}")
else:
self.logger.log("Failed to read a frame after configuring camera")
print("Failed to read a frame after configuring camera")
#self.logger.log("Initial frame:", w, "x", h)
self.cameraList.append(CameraObject(
@@ -185,7 +177,7 @@ class SetupPage(QWidget):
index = int(self.cameraIndex.text())
self.release_camera()
self.cameraObject.capture = cv2.VideoCapture(index)
self.cameraObject.capture = cv2.VideoCapture(index, cv2.CAP_DSHOW)
self.cameraObject.index = index
if not self.cameraObject.capture.isOpened():