configura fps preview e yolo
This commit is contained in:
70
aggiornamento-2026-05-16-09-03.md
Normal file
70
aggiornamento-2026-05-16-09-03.md
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
# Aggiornamento 2026-05-16 09:03
|
||||||
|
|
||||||
|
## Decisione
|
||||||
|
|
||||||
|
Separare il ritmo della preview dal ritmo di inferenza YOLO.
|
||||||
|
|
||||||
|
Motivo: il video sorgente e' circa 30 fps, ma la pipeline completa non deve necessariamente elaborare ogni frame. Per la demo e per la futura pipeline reale e' piu' utile mantenere bassa la latenza, lasciando che la visualizzazione e YOLO abbiano frequenze configurabili.
|
||||||
|
|
||||||
|
## Implementazione
|
||||||
|
|
||||||
|
Aggiunti due parametri in `flywms_navigation.ini` e nel parser di `flywms_navigation.py`:
|
||||||
|
|
||||||
|
- `preview_fps`: FPS massimo per lettura/preview realtime. `0` usa il framerate della sorgente.
|
||||||
|
- `yolo_fps`: FPS massimo per inferenza YOLO. `0` esegue YOLO su ogni frame di preview.
|
||||||
|
|
||||||
|
Valori iniziali:
|
||||||
|
|
||||||
|
```ini
|
||||||
|
preview_fps = 24.0
|
||||||
|
yolo_fps = 15.0
|
||||||
|
```
|
||||||
|
|
||||||
|
La stessa logica e' stata applicata anche alla shell DearPyGUI `flywms_navigation_gui.py`.
|
||||||
|
|
||||||
|
## Nota su video e camera reale
|
||||||
|
|
||||||
|
Con un file video, questi parametri simulano il ritmo desiderato della preview e limitano l'inferenza YOLO. In questa versione single-thread, se YOLO e' piu' lento del target, non si puo' arrivare al target.
|
||||||
|
|
||||||
|
Con una camera reale, il codice prova anche a impostare `CAP_PROP_FPS` quando la sorgente e' webcam/camera. Questo e' best effort: molti driver ignorano il valore. Il metodo robusto resta scartare frame lato software e usare sempre il frame piu' recente disponibile.
|
||||||
|
|
||||||
|
## Verifiche
|
||||||
|
|
||||||
|
Compilazione:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
python -m py_compile flywms_navigation.py flywms_navigation_gui.py
|
||||||
|
```
|
||||||
|
|
||||||
|
Lettura config:
|
||||||
|
|
||||||
|
```text
|
||||||
|
24.0 15.0
|
||||||
|
```
|
||||||
|
|
||||||
|
Run headless con `yolo_fps=15` su CPU:
|
||||||
|
|
||||||
|
```text
|
||||||
|
fps=5.5 yolo_fps=5.5
|
||||||
|
```
|
||||||
|
|
||||||
|
Interpretazione: su CPU il ciclo non raggiunge 15 fps, quindi YOLO gira comunque su ogni frame effettivamente processato.
|
||||||
|
|
||||||
|
Run headless con `yolo_fps=2`:
|
||||||
|
|
||||||
|
```text
|
||||||
|
fps=13.7 yolo_fps=2.0
|
||||||
|
```
|
||||||
|
|
||||||
|
Interpretazione: il throttling YOLO funziona; la preview procede piu' veloce dell'inferenza.
|
||||||
|
|
||||||
|
## Prossimo passo
|
||||||
|
|
||||||
|
Quando arrivera' la GPU, rimisurare con:
|
||||||
|
|
||||||
|
- `preview_fps = 24.0`;
|
||||||
|
- `yolo_fps = 15.0`;
|
||||||
|
- device GPU Ultralytics;
|
||||||
|
- GUI DearPyGUI attiva.
|
||||||
|
|
||||||
|
Se il costo GUI diventera' dominante, separare in seguito thread inferenza e thread GUI con stato latest-frame.
|
||||||
@@ -130,6 +130,19 @@ preview_width = 1280
|
|||||||
; Default se non indicato: true
|
; Default se non indicato: true
|
||||||
realtime_playback = true
|
realtime_playback = true
|
||||||
|
|
||||||
|
; OBBLIGATORIO: no.
|
||||||
|
; Ruolo: FPS massimo per lettura/preview realtime. 0 usa il framerate della sorgente.
|
||||||
|
; Con video registrati puo' essere usato per simulare una preview piu' lenta, es. 24 fps.
|
||||||
|
; Con webcam/camere viene anche richiesto al driver, ma non tutti i driver rispettano il valore.
|
||||||
|
; Default se non indicato: 24.0
|
||||||
|
preview_fps = 24.0
|
||||||
|
|
||||||
|
; OBBLIGATORIO: no.
|
||||||
|
; Ruolo: FPS massimo per inferenza YOLO. 0 esegue YOLO su ogni frame di preview.
|
||||||
|
; Nei frame intermedi la preview continua usando l'ultimo stato di tracking disponibile.
|
||||||
|
; Default se non indicato: 15.0
|
||||||
|
yolo_fps = 15.0
|
||||||
|
|
||||||
; OBBLIGATORIO: no.
|
; OBBLIGATORIO: no.
|
||||||
; Ruolo: massimo numero di frame da processare. 0 significa tutto il video.
|
; Ruolo: massimo numero di frame da processare. 0 significa tutto il video.
|
||||||
; Default se non indicato: 0
|
; Default se non indicato: 0
|
||||||
|
|||||||
@@ -518,6 +518,10 @@ def parse_args():
|
|||||||
|
|
||||||
ap.add_argument("--preview-width", type=int, default=defaults["preview_width"], help="Larghezza preview")
|
ap.add_argument("--preview-width", type=int, default=defaults["preview_width"], help="Larghezza preview")
|
||||||
ap.add_argument("--realtime-playback", action="store_true", default=defaults["realtime_playback"], help="Rispetta FPS video")
|
ap.add_argument("--realtime-playback", action="store_true", default=defaults["realtime_playback"], help="Rispetta FPS video")
|
||||||
|
ap.add_argument("--preview-fps", type=float, default=defaults["preview_fps"],
|
||||||
|
help="FPS massimo per lettura/preview realtime. 0 = FPS sorgente")
|
||||||
|
ap.add_argument("--yolo-fps", type=float, default=defaults["yolo_fps"],
|
||||||
|
help="FPS massimo per inferenza YOLO. 0 = ogni frame di preview")
|
||||||
ap.add_argument("--max-frames", type=int, default=defaults["max_frames"], help="Numero massimo frame; 0 = tutto")
|
ap.add_argument("--max-frames", type=int, default=defaults["max_frames"], help="Numero massimo frame; 0 = tutto")
|
||||||
ap.add_argument("--stats-interval", type=float, default=defaults["stats_interval"], help="Intervallo log prestazioni")
|
ap.add_argument("--stats-interval", type=float, default=defaults["stats_interval"], help="Intervallo log prestazioni")
|
||||||
ap.add_argument("--motion-report-interval", type=int, default=defaults["motion_report_interval"],
|
ap.add_argument("--motion-report-interval", type=int, default=defaults["motion_report_interval"],
|
||||||
@@ -557,6 +561,8 @@ def load_navigation_config(path_str: str) -> dict[str, object]:
|
|||||||
"scan_direction": "destra",
|
"scan_direction": "destra",
|
||||||
"preview_width": 1280,
|
"preview_width": 1280,
|
||||||
"realtime_playback": True,
|
"realtime_playback": True,
|
||||||
|
"preview_fps": 24.0,
|
||||||
|
"yolo_fps": 15.0,
|
||||||
"max_frames": 0,
|
"max_frames": 0,
|
||||||
"stats_interval": 2.0,
|
"stats_interval": 2.0,
|
||||||
"motion_report_interval": 5,
|
"motion_report_interval": 5,
|
||||||
@@ -841,7 +847,11 @@ def main() -> int:
|
|||||||
return 1
|
return 1
|
||||||
|
|
||||||
video_fps = cap.get(cv2.CAP_PROP_FPS)
|
video_fps = cap.get(cv2.CAP_PROP_FPS)
|
||||||
frame_delay = 1.0 / video_fps if args.realtime_playback and video_fps and video_fps > 1 else 0.0
|
preview_fps = args.preview_fps if args.preview_fps and args.preview_fps > 0 else video_fps
|
||||||
|
if args.preview_fps and args.preview_fps > 0 and (args.video is None or str(args.video).isdigit()):
|
||||||
|
cap.set(cv2.CAP_PROP_FPS, float(args.preview_fps))
|
||||||
|
frame_delay = 1.0 / preview_fps if args.realtime_playback and preview_fps and preview_fps > 1 else 0.0
|
||||||
|
yolo_interval = 1.0 / args.yolo_fps if args.yolo_fps and args.yolo_fps > 0 else 0.0
|
||||||
tracker = LightweightTracker(
|
tracker = LightweightTracker(
|
||||||
max_missed=args.max_track_missed,
|
max_missed=args.max_track_missed,
|
||||||
min_match_score=args.min_match_score,
|
min_match_score=args.min_match_score,
|
||||||
@@ -860,6 +870,10 @@ def main() -> int:
|
|||||||
last_loop_end = start_time
|
last_loop_end = start_time
|
||||||
yolo_total_ms = 0.0
|
yolo_total_ms = 0.0
|
||||||
yolo_cycles = 0
|
yolo_cycles = 0
|
||||||
|
next_yolo_time = start_time
|
||||||
|
last_yolo_ms = 0.0
|
||||||
|
gaylords: list[Detection] = []
|
||||||
|
tracks: list[Track] = []
|
||||||
|
|
||||||
try:
|
try:
|
||||||
while True:
|
while True:
|
||||||
@@ -880,8 +894,12 @@ def main() -> int:
|
|||||||
log(f"Raggiunto --max-frames={args.max_frames}")
|
log(f"Raggiunto --max-frames={args.max_frames}")
|
||||||
break
|
break
|
||||||
|
|
||||||
detections, yolo_ms = detector.detect(frame, args.min_confidence, args.input_size)
|
new_snapshots: list[NavigationSnapshot] = []
|
||||||
yolo_total_ms += yolo_ms
|
run_yolo = yolo_interval <= 0 or timestamp >= next_yolo_time
|
||||||
|
if run_yolo:
|
||||||
|
next_yolo_time = timestamp + yolo_interval
|
||||||
|
detections, last_yolo_ms = detector.detect(frame, args.min_confidence, args.input_size)
|
||||||
|
yolo_total_ms += last_yolo_ms
|
||||||
yolo_cycles += 1
|
yolo_cycles += 1
|
||||||
gaylords = [
|
gaylords = [
|
||||||
det for det in detections
|
det for det in detections
|
||||||
@@ -889,11 +907,10 @@ def main() -> int:
|
|||||||
]
|
]
|
||||||
|
|
||||||
tracks = tracker.update(gaylords, frame_id, frame.shape[1])
|
tracks = tracker.update(gaylords, frame_id, frame.shape[1])
|
||||||
if args.motion_report_interval > 0 and frame_id % args.motion_report_interval == 0:
|
if args.motion_report_interval > 0 and yolo_cycles % args.motion_report_interval == 0:
|
||||||
navigator.set_motion_text(
|
navigator.set_motion_text(
|
||||||
estimate_motion_from_tracks(tracks, args.motion_min_pixels)
|
estimate_motion_from_tracks(tracks, args.motion_min_pixels)
|
||||||
)
|
)
|
||||||
new_snapshots: list[NavigationSnapshot] = []
|
|
||||||
for track in tracks:
|
for track in tracks:
|
||||||
if track.missed == 0:
|
if track.missed == 0:
|
||||||
snapshot = navigator.process_track(track, frame, frame_id, timestamp)
|
snapshot = navigator.process_track(track, frame, frame_id, timestamp)
|
||||||
@@ -931,6 +948,7 @@ def main() -> int:
|
|||||||
elapsed = max(time.perf_counter() - start_time, 0.001)
|
elapsed = max(time.perf_counter() - start_time, 0.001)
|
||||||
fps_text = (
|
fps_text = (
|
||||||
f"frame={frame_id} fps={frame_id / elapsed:.1f} "
|
f"frame={frame_id} fps={frame_id / elapsed:.1f} "
|
||||||
|
f"yolo_fps={yolo_cycles / elapsed:.1f} yolo={last_yolo_ms:.0f}ms "
|
||||||
f"det={len(gaylords)} tracks={len(tracks)} snap={navigator.snapshot_counter}"
|
f"det={len(gaylords)} tracks={len(tracks)} snap={navigator.snapshot_counter}"
|
||||||
)
|
)
|
||||||
display = draw_navigation_debug(
|
display = draw_navigation_debug(
|
||||||
|
|||||||
@@ -59,11 +59,15 @@ class NavigationDemoEngine:
|
|||||||
raise RuntimeError(f"impossibile aprire sorgente video: {self.source_name}")
|
raise RuntimeError(f"impossibile aprire sorgente video: {self.source_name}")
|
||||||
|
|
||||||
video_fps = self.cap.get(cv2.CAP_PROP_FPS)
|
video_fps = self.cap.get(cv2.CAP_PROP_FPS)
|
||||||
|
preview_fps = args.preview_fps if args.preview_fps and args.preview_fps > 0 else video_fps
|
||||||
|
if args.preview_fps and args.preview_fps > 0 and (args.video is None or str(args.video).isdigit()):
|
||||||
|
self.cap.set(cv2.CAP_PROP_FPS, float(args.preview_fps))
|
||||||
self.frame_delay = (
|
self.frame_delay = (
|
||||||
1.0 / video_fps
|
1.0 / preview_fps
|
||||||
if args.realtime_playback and video_fps and video_fps > 1
|
if args.realtime_playback and preview_fps and preview_fps > 1
|
||||||
else 0.0
|
else 0.0
|
||||||
)
|
)
|
||||||
|
self.yolo_interval = 1.0 / args.yolo_fps if args.yolo_fps and args.yolo_fps > 0 else 0.0
|
||||||
self.tracker = nav.LightweightTracker(
|
self.tracker = nav.LightweightTracker(
|
||||||
max_missed=args.max_track_missed,
|
max_missed=args.max_track_missed,
|
||||||
min_match_score=args.min_match_score,
|
min_match_score=args.min_match_score,
|
||||||
@@ -75,6 +79,10 @@ class NavigationDemoEngine:
|
|||||||
self.last_loop_end = self.start_time
|
self.last_loop_end = self.start_time
|
||||||
self.yolo_total_ms = 0.0
|
self.yolo_total_ms = 0.0
|
||||||
self.yolo_cycles = 0
|
self.yolo_cycles = 0
|
||||||
|
self.next_yolo_time = self.start_time
|
||||||
|
self.last_yolo_ms = 0.0
|
||||||
|
self.gaylords: list[nav.Detection] = []
|
||||||
|
self.tracks: list[nav.Track] = []
|
||||||
self.stop_reason = ""
|
self.stop_reason = ""
|
||||||
|
|
||||||
def close(self) -> None:
|
def close(self) -> None:
|
||||||
@@ -99,30 +107,33 @@ class NavigationDemoEngine:
|
|||||||
self.stop_reason = f"Raggiunto max_frames={self.args.max_frames}"
|
self.stop_reason = f"Raggiunto max_frames={self.args.max_frames}"
|
||||||
return None
|
return None
|
||||||
|
|
||||||
detections, yolo_ms = self.detector.detect(
|
new_snapshots: list[nav.NavigationSnapshot] = []
|
||||||
|
run_yolo = self.yolo_interval <= 0 or timestamp >= self.next_yolo_time
|
||||||
|
if run_yolo:
|
||||||
|
self.next_yolo_time = timestamp + self.yolo_interval
|
||||||
|
detections, self.last_yolo_ms = self.detector.detect(
|
||||||
frame,
|
frame,
|
||||||
self.args.min_confidence,
|
self.args.min_confidence,
|
||||||
self.args.input_size,
|
self.args.input_size,
|
||||||
)
|
)
|
||||||
self.yolo_total_ms += yolo_ms
|
self.yolo_total_ms += self.last_yolo_ms
|
||||||
self.yolo_cycles += 1
|
self.yolo_cycles += 1
|
||||||
|
|
||||||
gaylords = [
|
self.gaylords = [
|
||||||
det for det in detections
|
det for det in detections
|
||||||
if det.class_name.strip().lower() == self.args.target_class.strip().lower()
|
if det.class_name.strip().lower() == self.args.target_class.strip().lower()
|
||||||
]
|
]
|
||||||
tracks = self.tracker.update(gaylords, self.frame_id, frame.shape[1])
|
self.tracks = self.tracker.update(self.gaylords, self.frame_id, frame.shape[1])
|
||||||
|
|
||||||
if (
|
if (
|
||||||
self.args.motion_report_interval > 0
|
self.args.motion_report_interval > 0
|
||||||
and self.frame_id % self.args.motion_report_interval == 0
|
and self.yolo_cycles % self.args.motion_report_interval == 0
|
||||||
):
|
):
|
||||||
self.navigator.set_motion_text(
|
self.navigator.set_motion_text(
|
||||||
nav.estimate_motion_from_tracks(tracks, self.args.motion_min_pixels)
|
nav.estimate_motion_from_tracks(self.tracks, self.args.motion_min_pixels)
|
||||||
)
|
)
|
||||||
|
|
||||||
new_snapshots: list[nav.NavigationSnapshot] = []
|
for track in self.tracks:
|
||||||
for track in tracks:
|
|
||||||
if track.missed == 0:
|
if track.missed == 0:
|
||||||
snapshot = self.navigator.process_track(
|
snapshot = self.navigator.process_track(
|
||||||
track,
|
track,
|
||||||
@@ -139,12 +150,13 @@ class NavigationDemoEngine:
|
|||||||
elapsed = max(time.perf_counter() - self.start_time, 0.001)
|
elapsed = max(time.perf_counter() - self.start_time, 0.001)
|
||||||
fps_text = (
|
fps_text = (
|
||||||
f"frame={self.frame_id} fps={self.frame_id / elapsed:.1f} "
|
f"frame={self.frame_id} fps={self.frame_id / elapsed:.1f} "
|
||||||
f"det={len(gaylords)} tracks={len(tracks)} "
|
f"yolo_fps={self.yolo_cycles / elapsed:.1f} yolo={self.last_yolo_ms:.0f}ms "
|
||||||
|
f"det={len(self.gaylords)} tracks={len(self.tracks)} "
|
||||||
f"snap={self.navigator.snapshot_counter}"
|
f"snap={self.navigator.snapshot_counter}"
|
||||||
)
|
)
|
||||||
display = nav.draw_navigation_debug(
|
display = nav.draw_navigation_debug(
|
||||||
frame,
|
frame,
|
||||||
tracks,
|
self.tracks,
|
||||||
self.args,
|
self.args,
|
||||||
self.navigator.last_command_text,
|
self.navigator.last_command_text,
|
||||||
fps_text,
|
fps_text,
|
||||||
@@ -156,10 +168,10 @@ class NavigationDemoEngine:
|
|||||||
source_frame=frame,
|
source_frame=frame,
|
||||||
display_frame=display,
|
display_frame=display,
|
||||||
snapshot_frame=self.navigator.last_ocr_payload_frame,
|
snapshot_frame=self.navigator.last_ocr_payload_frame,
|
||||||
tracks=tracks,
|
tracks=self.tracks,
|
||||||
detections_count=len(gaylords),
|
detections_count=len(self.gaylords),
|
||||||
snapshots=new_snapshots,
|
snapshots=new_snapshots,
|
||||||
yolo_ms=yolo_ms,
|
yolo_ms=self.last_yolo_ms,
|
||||||
fps_text=fps_text,
|
fps_text=fps_text,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user