fix(multihmr): cv2 brightness probe + heartbeat
cv2.VideoCapture indices on Mac don't follow the AVFoundation discovery order, so the previous auto-pick still ended up on the iPhone Continuity stream. resolve_camera_index now probes each cv2 index, reads a frame, and prefers the one whose mean luminance is >= 50 (rejects black / standby streams from a locked iPhone or Desk View). multi_hmr_worker emits a 5 s heartbeat with the observed fps and the average detected persons per frame, so 'no detection' becomes visible in the log instead of staying silent.
This commit is contained in:
@@ -52,8 +52,51 @@ def pick_builtin_camera(fallback: int = 0) -> int:
|
||||
return fallback
|
||||
|
||||
|
||||
def resolve_camera_index(requested: int) -> int:
|
||||
"""`requested=-1` -> built-in auto, sinon valeur passee."""
|
||||
if requested < 0:
|
||||
return pick_builtin_camera(fallback=0)
|
||||
def probe_cv2_indices(max_idx: int = 4) -> list[tuple[int, int, int, float]]:
|
||||
"""Pour chaque index cv2 0..max_idx, retourne (idx, w, h, mean) ou
|
||||
None pour les indices indisponibles. Le 'mean' est la luminance
|
||||
moyenne d'une frame — un capture standby (iPhone verrouille,
|
||||
Continuity inactive) a mean ~0-30 ; une cam active ~80+."""
|
||||
try:
|
||||
import cv2
|
||||
import numpy as np
|
||||
except ImportError:
|
||||
return []
|
||||
result = []
|
||||
for idx in range(max_idx):
|
||||
cap = cv2.VideoCapture(idx, cv2.CAP_AVFOUNDATION)
|
||||
if not cap.isOpened():
|
||||
cap.release()
|
||||
continue
|
||||
w = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
|
||||
h = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
|
||||
# Drain 2 frames pour passer l'auto-exposure
|
||||
for _ in range(2):
|
||||
cap.read()
|
||||
ok, frame = cap.read()
|
||||
mean = float(np.mean(frame)) if (ok and frame is not None) else -1.0
|
||||
result.append((idx, w, h, mean))
|
||||
cap.release()
|
||||
return result
|
||||
|
||||
|
||||
def resolve_camera_index(requested: int, min_mean: float = 50.0) -> int:
|
||||
"""`requested=-1` -> probe cv2, prefere l'index avec frame_mean
|
||||
>= min_mean (rejette les flux noirs / standby). Sinon retourne
|
||||
`requested`."""
|
||||
if requested >= 0:
|
||||
return requested
|
||||
probes = probe_cv2_indices()
|
||||
for idx, w, h, mean in probes:
|
||||
LOG.info("cv2 probe [%d] %dx%d mean=%.1f", idx, w, h, mean)
|
||||
bright = [p for p in probes if p[3] >= min_mean]
|
||||
if bright:
|
||||
idx = bright[0][0]
|
||||
LOG.info("cv2 auto-pick index %d (mean %.1f >= %.1f)",
|
||||
idx, bright[0][3], min_mean)
|
||||
return idx
|
||||
if probes:
|
||||
LOG.warning("no bright cv2 stream — defaulting to index %d",
|
||||
probes[0][0])
|
||||
return probes[0][0]
|
||||
return 0
|
||||
|
||||
@@ -138,6 +138,9 @@ class MultiHMRWorker:
|
||||
return
|
||||
LOG.info("camera ouverte index=%d %dx%d",
|
||||
cam_idx, IMG_SIZE, IMG_SIZE)
|
||||
frame_count = 0
|
||||
persons_count = 0
|
||||
next_heartbeat = time.monotonic() + 5.0
|
||||
|
||||
while not self._stop.is_set():
|
||||
t0 = time.monotonic()
|
||||
@@ -235,6 +238,18 @@ class MultiHMRWorker:
|
||||
self.state.persons_smplx = persons
|
||||
self.state.smplx_last_t = t_now
|
||||
|
||||
frame_count += 1
|
||||
persons_count += len(persons)
|
||||
if t_now >= next_heartbeat:
|
||||
fps = frame_count / 5.0
|
||||
avg = persons_count / max(1, frame_count)
|
||||
LOG.info(
|
||||
"hb: %.1f fps, %.2f persons/frame (%d frames)",
|
||||
fps, avg, frame_count)
|
||||
frame_count = 0
|
||||
persons_count = 0
|
||||
next_heartbeat = t_now + 5.0
|
||||
|
||||
dt = time.monotonic() - t0
|
||||
if dt < self.period:
|
||||
time.sleep(self.period - dt)
|
||||
|
||||
Reference in New Issue
Block a user