Added continous hand tracking
This commit is contained in:
@@ -2,10 +2,10 @@ import cv2
|
||||
import numpy as np
|
||||
|
||||
# ---- Board parameters (CHANGE THESE ONLY IF YOU REPRINT) ----
|
||||
squares_x = 6 # number of chessboard squares in X
|
||||
squares_y = 9 # number of chessboard squares in Y
|
||||
square_length = 25 # mm
|
||||
marker_length = 18 # mm (must be < square_length)
|
||||
squares_x = 4 # number of chessboard squares in X
|
||||
squares_y = 5 # number of chessboard squares in Y
|
||||
square_length = 50 # mm
|
||||
marker_length = 36 # mm (must be < square_length)
|
||||
dictionary_id = cv2.aruco.DICT_4X4_50
|
||||
dpi = 300 # print DPI
|
||||
# ------------------------------------------------------------
|
||||
|
||||
@@ -32,18 +32,25 @@ class TrackingController(QObject):
|
||||
|
||||
# MediaPipe's legacy solutions.hands API no longer exists on the
|
||||
# installed mediapipe version, so hand detection runs on the newer
|
||||
# Tasks API instead (IMAGE mode: each camera frame is detected
|
||||
# independently, since frames from different cameras aren't a single
|
||||
# monotonic video stream that VIDEO mode requires).
|
||||
# Tasks API instead. Each camera gets its own HandLandmarker running
|
||||
# in VIDEO mode, fed only that camera's own frames with a monotonically
|
||||
# increasing per-camera timestamp - this lets MediaPipe track hands
|
||||
# frame-to-frame per view (much less jitter than re-detecting blind
|
||||
# every frame), which wouldn't be valid if frames from different
|
||||
# camera viewpoints were interleaved through a single VIDEO-mode
|
||||
# detector.
|
||||
self._frame_counter = defaultdict(int)
|
||||
self.detectors = {}
|
||||
for camera in self.cameraList:
|
||||
options = HandLandmarkerOptions(
|
||||
base_options=BaseOptions(model_asset_path=MODEL_PATH),
|
||||
running_mode=RunningMode.IMAGE,
|
||||
num_hands=4,
|
||||
running_mode=RunningMode.VIDEO,
|
||||
num_hands=12,
|
||||
min_hand_detection_confidence=0.5,
|
||||
min_hand_presence_confidence=0.5,
|
||||
min_tracking_confidence=0.5,
|
||||
)
|
||||
self.detector = HandLandmarker.create_from_options(options)
|
||||
self.detectors[camera.index] = HandLandmarker.create_from_options(options)
|
||||
|
||||
def start(self):
|
||||
# Guard: need projections
|
||||
@@ -72,6 +79,9 @@ class TrackingController(QObject):
|
||||
for camera in self.cameraList:
|
||||
camera.close()
|
||||
|
||||
for detector in self.detectors.values():
|
||||
detector.close()
|
||||
|
||||
if self.logger: self.logger.log("[Tracking] Stopped")
|
||||
|
||||
def _tick(self):
|
||||
@@ -102,6 +112,10 @@ class TrackingController(QObject):
|
||||
if camera.capture is None:
|
||||
continue
|
||||
|
||||
detector = self.detectors.get(camera.index)
|
||||
if detector is None:
|
||||
continue
|
||||
|
||||
ok, frameBGR = camera.capture.read()
|
||||
if not ok or frameBGR is None:
|
||||
continue
|
||||
@@ -110,7 +124,10 @@ class TrackingController(QObject):
|
||||
frameRGB = cv2.cvtColor(frameBGR, cv2.COLOR_BGR2RGB)
|
||||
|
||||
mp_image = mp.Image(image_format=mp.ImageFormat.SRGB, data=frameRGB)
|
||||
result = self.detector.detect(mp_image)
|
||||
|
||||
self._frame_counter[camera.index] += 1
|
||||
timestamp_ms = self._frame_counter[camera.index] * self.tick_ms
|
||||
result = detector.detect_for_video(mp_image, timestamp_ms)
|
||||
|
||||
if not result.hand_landmarks:
|
||||
continue
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@@ -4,7 +4,7 @@ import os
|
||||
from Tbd.helper import CameraObject
|
||||
from UI.UILogger import UILogger
|
||||
|
||||
from PySide6.QtCore import Qt, QTimer
|
||||
from PySide6.QtCore import Qt, QTimer, QThread, Signal
|
||||
from PySide6.QtGui import QImage, QPixmap
|
||||
from PySide6.QtWidgets import QLineEdit
|
||||
from PySide6.QtGui import QIntValidator
|
||||
@@ -12,6 +12,35 @@ from PySide6.QtWidgets import QWidget, QLabel, QVBoxLayout, QPushButton, QHBoxLa
|
||||
from typing import List
|
||||
|
||||
|
||||
class LoadCamerasWorker(QThread):
|
||||
"""
|
||||
Opens/verifies/closes each camera off the UI thread - camera.open() does
|
||||
MSMF format negotiation with retries+sleeps, which used to block the Qt
|
||||
event loop (window shows as "Not Responding") for the whole duration.
|
||||
"""
|
||||
cameraOpened = Signal(object)
|
||||
logMessage = Signal(str)
|
||||
|
||||
def __init__(self, indexToAdd: List[int]):
|
||||
super().__init__()
|
||||
self.indexToAdd = indexToAdd
|
||||
|
||||
def run(self):
|
||||
for index in self.indexToAdd:
|
||||
camera = CameraObject(capture=None, index=index)
|
||||
|
||||
if not camera.open():
|
||||
self.logMessage.emit(f"cam{index}: failed to open, skipping.")
|
||||
continue
|
||||
|
||||
ret, frame = camera.capture.read()
|
||||
if ret:
|
||||
self.logMessage.emit(f"cam{index}: verified, frame shape={frame.shape}")
|
||||
else:
|
||||
self.logMessage.emit(f"cam{index}: opened but failed to read a frame.")
|
||||
|
||||
camera.close()
|
||||
self.cameraOpened.emit(camera)
|
||||
|
||||
|
||||
class SetupPage(QWidget):
|
||||
@@ -91,32 +120,23 @@ class SetupPage(QWidget):
|
||||
self.logger.log("Path not found")
|
||||
|
||||
indexToAdd = [1, 2, 3, 4, 5, 6]
|
||||
|
||||
#indexToAdd = [0, 3]
|
||||
# Cameras are verified one at a time and left closed afterwards - this
|
||||
# hardware can't sustain many concurrent capture graphs (confirmed via
|
||||
# both CAP_DSHOW and a raw DirectShow graph), so callers open a camera
|
||||
# right before they need it (CameraSetupWidget.Start, calibration capture)
|
||||
# and close it right after, instead of keeping all 6 open simultaneously.
|
||||
for index in indexToAdd:
|
||||
camera = CameraObject(capture=None, index=index)
|
||||
|
||||
if not camera.open():
|
||||
self.logger.log(f"cam{index}: failed to open, skipping.")
|
||||
continue
|
||||
|
||||
ret, frame = camera.capture.read()
|
||||
if ret:
|
||||
self.logger.log(f"cam{index}: verified, frame shape={frame.shape}")
|
||||
else:
|
||||
self.logger.log(f"cam{index}: opened but failed to read a frame.")
|
||||
|
||||
camera.close()
|
||||
self.loadBTN.setEnabled(False)
|
||||
self._loadWorker = LoadCamerasWorker(indexToAdd)
|
||||
self._loadWorker.logMessage.connect(self.logger.log)
|
||||
self._loadWorker.cameraOpened.connect(self._onCameraLoaded)
|
||||
self._loadWorker.finished.connect(lambda: self.loadBTN.setEnabled(True))
|
||||
self._loadWorker.start()
|
||||
|
||||
def _onCameraLoaded(self, camera: CameraObject):
|
||||
self.cameraList.append(camera)
|
||||
self._cameraList_changed()
|
||||
|
||||
return
|
||||
|
||||
|
||||
|
||||
def save_setup(self):
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user