Add camera selection diagnostics
resolve_device now prints each by-id candidate it scans, which entry matched, and the resolved device path; the no-match error lists all by-id entries with their targets. Camera startup logs the raw config setting, resolved path, the node's sysfs name, the OpenCV version, and the negotiated resolution. 'python3 camera.py' now dumps every detected camera before opening, to troubleshoot machines where the wrong camera gets used.
This commit is contained in:
59
camera.py
59
camera.py
@ -14,6 +14,16 @@ import cv2
|
||||
import numpy as np
|
||||
|
||||
|
||||
def sysfs_name(device_path):
|
||||
"""Human-readable name of a /dev/videoN node from sysfs, e.g.
|
||||
'Macally USB2.0Camera: Macally U'. Empty string if unknown."""
|
||||
try:
|
||||
with open(f"/sys/class/video4linux/{os.path.basename(device_path)}/name") as f:
|
||||
return f.read().strip()
|
||||
except OSError:
|
||||
return ""
|
||||
|
||||
|
||||
def resolve_device(setting, wait_seconds=15):
|
||||
"""Turn the config's camera device setting into something cv2 accepts.
|
||||
|
||||
@ -33,16 +43,34 @@ def resolve_device(setting, wait_seconds=15):
|
||||
needle = setting[len("auto-"):].lower()
|
||||
deadline = time.monotonic() + wait_seconds
|
||||
waited = False
|
||||
last_seen = None
|
||||
while True:
|
||||
candidates = sorted(glob.glob("/dev/v4l/by-id/*-video-index0"))
|
||||
# Re-print the scan only when the device set changes, so a slow
|
||||
# USB camera showing up mid-wait doesn't spam the log.
|
||||
if candidates != last_seen:
|
||||
last_seen = candidates
|
||||
if candidates:
|
||||
print(f"Scanning /dev/v4l/by-id for '{needle}':")
|
||||
for path in candidates:
|
||||
print(f" {os.path.basename(path)} -> "
|
||||
f"{os.path.realpath(path)}")
|
||||
else:
|
||||
print("Scanning /dev/v4l/by-id: no cameras found yet.")
|
||||
for path in candidates:
|
||||
if needle in os.path.basename(path).lower():
|
||||
return os.path.realpath(path)
|
||||
device = os.path.realpath(path)
|
||||
print(f"Matched '{needle}': "
|
||||
f"{os.path.basename(path)} -> {device}")
|
||||
return device
|
||||
if time.monotonic() > deadline:
|
||||
names = [os.path.basename(p) for p in candidates]
|
||||
all_entries = [
|
||||
f"{os.path.basename(p)} -> {os.path.realpath(p)}"
|
||||
for p in sorted(glob.glob("/dev/v4l/by-id/*"))
|
||||
]
|
||||
raise RuntimeError(
|
||||
f"No camera matching '{needle}' found. "
|
||||
f"Available cameras: {names or 'none'}"
|
||||
f"Available cameras: {all_entries or 'none'}"
|
||||
)
|
||||
if not waited:
|
||||
waited = True
|
||||
@ -68,6 +96,12 @@ def crop_4x3(frame):
|
||||
class Camera:
|
||||
def __init__(self, device_setting, width, height):
|
||||
device = resolve_device(device_setting)
|
||||
dev_path = device if isinstance(device, str) else f"/dev/video{device}"
|
||||
name = sysfs_name(dev_path)
|
||||
print(f"Camera: setting={device_setting!r} resolved={dev_path!r} "
|
||||
f"cv2={cv2.__version__}")
|
||||
if name:
|
||||
print(f"Camera: {dev_path} identifies as '{name}' (sysfs)")
|
||||
self.cap = cv2.VideoCapture(device, cv2.CAP_V4L2)
|
||||
if not self.cap.isOpened():
|
||||
raise RuntimeError(f"Could not open camera {device!r}")
|
||||
@ -75,6 +109,9 @@ class Camera:
|
||||
self.cap.set(cv2.CAP_PROP_FOURCC, cv2.VideoWriter_fourcc(*"MJPG"))
|
||||
self.cap.set(cv2.CAP_PROP_FRAME_WIDTH, width)
|
||||
self.cap.set(cv2.CAP_PROP_FRAME_HEIGHT, height)
|
||||
actual_w = int(self.cap.get(cv2.CAP_PROP_FRAME_WIDTH))
|
||||
actual_h = int(self.cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
|
||||
print(f"Camera: opened {dev_path}, negotiated {actual_w}x{actual_h}")
|
||||
|
||||
self._lock = threading.Lock()
|
||||
self._frame = None
|
||||
@ -140,6 +177,22 @@ if __name__ == "__main__":
|
||||
import tomllib
|
||||
from pathlib import Path
|
||||
|
||||
print("== Camera pre-flight ==")
|
||||
print(f"OpenCV version: {cv2.__version__}")
|
||||
by_id = sorted(glob.glob("/dev/v4l/by-id/*"))
|
||||
if by_id:
|
||||
print("/dev/v4l/by-id entries:")
|
||||
for p in by_id:
|
||||
print(f" {os.path.basename(p)} -> {os.path.realpath(p)}")
|
||||
else:
|
||||
print("/dev/v4l/by-id: (empty or missing)")
|
||||
nodes = sorted(glob.glob("/dev/video*"))
|
||||
if nodes:
|
||||
print("Video device nodes:")
|
||||
for n in nodes:
|
||||
print(f" {n} {sysfs_name(n)}")
|
||||
print("=======================")
|
||||
|
||||
if len(sys.argv) > 1:
|
||||
device = sys.argv[1]
|
||||
else:
|
||||
|
||||
Reference in New Issue
Block a user