Compare commits
2 Commits
16458d98e9
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
98b43ce903 | ||
|
|
1186c3bb35 |
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
|
||||
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.
|
||||
; Ruolo: massimo numero di frame da processare. 0 significa tutto il video.
|
||||
; 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("--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("--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"],
|
||||
@@ -557,6 +561,8 @@ def load_navigation_config(path_str: str) -> dict[str, object]:
|
||||
"scan_direction": "destra",
|
||||
"preview_width": 1280,
|
||||
"realtime_playback": True,
|
||||
"preview_fps": 24.0,
|
||||
"yolo_fps": 15.0,
|
||||
"max_frames": 0,
|
||||
"stats_interval": 2.0,
|
||||
"motion_report_interval": 5,
|
||||
@@ -841,7 +847,11 @@ def main() -> int:
|
||||
return 1
|
||||
|
||||
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(
|
||||
max_missed=args.max_track_missed,
|
||||
min_match_score=args.min_match_score,
|
||||
@@ -860,6 +870,10 @@ def main() -> int:
|
||||
last_loop_end = start_time
|
||||
yolo_total_ms = 0.0
|
||||
yolo_cycles = 0
|
||||
next_yolo_time = start_time
|
||||
last_yolo_ms = 0.0
|
||||
gaylords: list[Detection] = []
|
||||
tracks: list[Track] = []
|
||||
|
||||
try:
|
||||
while True:
|
||||
@@ -880,25 +894,28 @@ def main() -> int:
|
||||
log(f"Raggiunto --max-frames={args.max_frames}")
|
||||
break
|
||||
|
||||
detections, yolo_ms = detector.detect(frame, args.min_confidence, args.input_size)
|
||||
yolo_total_ms += yolo_ms
|
||||
yolo_cycles += 1
|
||||
gaylords = [
|
||||
det for det in detections
|
||||
if det.class_name.strip().lower() == args.target_class.strip().lower()
|
||||
]
|
||||
|
||||
tracks = tracker.update(gaylords, frame_id, frame.shape[1])
|
||||
if args.motion_report_interval > 0 and frame_id % args.motion_report_interval == 0:
|
||||
navigator.set_motion_text(
|
||||
estimate_motion_from_tracks(tracks, args.motion_min_pixels)
|
||||
)
|
||||
new_snapshots: list[NavigationSnapshot] = []
|
||||
for track in tracks:
|
||||
if track.missed == 0:
|
||||
snapshot = navigator.process_track(track, frame, frame_id, timestamp)
|
||||
if snapshot is not None:
|
||||
new_snapshots.append(snapshot)
|
||||
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
|
||||
gaylords = [
|
||||
det for det in detections
|
||||
if det.class_name.strip().lower() == args.target_class.strip().lower()
|
||||
]
|
||||
|
||||
tracks = tracker.update(gaylords, frame_id, frame.shape[1])
|
||||
if args.motion_report_interval > 0 and yolo_cycles % args.motion_report_interval == 0:
|
||||
navigator.set_motion_text(
|
||||
estimate_motion_from_tracks(tracks, args.motion_min_pixels)
|
||||
)
|
||||
for track in tracks:
|
||||
if track.missed == 0:
|
||||
snapshot = navigator.process_track(track, frame, frame_id, timestamp)
|
||||
if snapshot is not None:
|
||||
new_snapshots.append(snapshot)
|
||||
if args.no_display and new_snapshots:
|
||||
if args.remote_ack_timeout_sec > 0:
|
||||
time.sleep(args.remote_ack_timeout_sec)
|
||||
@@ -931,6 +948,7 @@ def main() -> int:
|
||||
elapsed = max(time.perf_counter() - start_time, 0.001)
|
||||
fps_text = (
|
||||
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}"
|
||||
)
|
||||
display = draw_navigation_debug(
|
||||
|
||||
@@ -59,11 +59,15 @@ class NavigationDemoEngine:
|
||||
raise RuntimeError(f"impossibile aprire sorgente video: {self.source_name}")
|
||||
|
||||
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 = (
|
||||
1.0 / video_fps
|
||||
if args.realtime_playback and video_fps and video_fps > 1
|
||||
1.0 / preview_fps
|
||||
if args.realtime_playback and preview_fps and preview_fps > 1
|
||||
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(
|
||||
max_missed=args.max_track_missed,
|
||||
min_match_score=args.min_match_score,
|
||||
@@ -75,6 +79,10 @@ class NavigationDemoEngine:
|
||||
self.last_loop_end = self.start_time
|
||||
self.yolo_total_ms = 0.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 = ""
|
||||
|
||||
def close(self) -> None:
|
||||
@@ -99,39 +107,42 @@ class NavigationDemoEngine:
|
||||
self.stop_reason = f"Raggiunto max_frames={self.args.max_frames}"
|
||||
return None
|
||||
|
||||
detections, yolo_ms = self.detector.detect(
|
||||
frame,
|
||||
self.args.min_confidence,
|
||||
self.args.input_size,
|
||||
)
|
||||
self.yolo_total_ms += yolo_ms
|
||||
self.yolo_cycles += 1
|
||||
|
||||
gaylords = [
|
||||
det for det in detections
|
||||
if det.class_name.strip().lower() == self.args.target_class.strip().lower()
|
||||
]
|
||||
tracks = self.tracker.update(gaylords, self.frame_id, frame.shape[1])
|
||||
|
||||
if (
|
||||
self.args.motion_report_interval > 0
|
||||
and self.frame_id % self.args.motion_report_interval == 0
|
||||
):
|
||||
self.navigator.set_motion_text(
|
||||
nav.estimate_motion_from_tracks(tracks, self.args.motion_min_pixels)
|
||||
)
|
||||
|
||||
new_snapshots: list[nav.NavigationSnapshot] = []
|
||||
for track in tracks:
|
||||
if track.missed == 0:
|
||||
snapshot = self.navigator.process_track(
|
||||
track,
|
||||
frame,
|
||||
self.frame_id,
|
||||
timestamp,
|
||||
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,
|
||||
self.args.min_confidence,
|
||||
self.args.input_size,
|
||||
)
|
||||
self.yolo_total_ms += self.last_yolo_ms
|
||||
self.yolo_cycles += 1
|
||||
|
||||
self.gaylords = [
|
||||
det for det in detections
|
||||
if det.class_name.strip().lower() == self.args.target_class.strip().lower()
|
||||
]
|
||||
self.tracks = self.tracker.update(self.gaylords, self.frame_id, frame.shape[1])
|
||||
|
||||
if (
|
||||
self.args.motion_report_interval > 0
|
||||
and self.yolo_cycles % self.args.motion_report_interval == 0
|
||||
):
|
||||
self.navigator.set_motion_text(
|
||||
nav.estimate_motion_from_tracks(self.tracks, self.args.motion_min_pixels)
|
||||
)
|
||||
if snapshot is not None:
|
||||
new_snapshots.append(snapshot)
|
||||
|
||||
for track in self.tracks:
|
||||
if track.missed == 0:
|
||||
snapshot = self.navigator.process_track(
|
||||
track,
|
||||
frame,
|
||||
self.frame_id,
|
||||
timestamp,
|
||||
)
|
||||
if snapshot is not None:
|
||||
new_snapshots.append(snapshot)
|
||||
|
||||
for snapshot in new_snapshots:
|
||||
self.navigator.simulate_remote_response(snapshot)
|
||||
@@ -139,12 +150,13 @@ class NavigationDemoEngine:
|
||||
elapsed = max(time.perf_counter() - self.start_time, 0.001)
|
||||
fps_text = (
|
||||
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}"
|
||||
)
|
||||
display = nav.draw_navigation_debug(
|
||||
frame,
|
||||
tracks,
|
||||
self.tracks,
|
||||
self.args,
|
||||
self.navigator.last_command_text,
|
||||
fps_text,
|
||||
@@ -156,10 +168,10 @@ class NavigationDemoEngine:
|
||||
source_frame=frame,
|
||||
display_frame=display,
|
||||
snapshot_frame=self.navigator.last_ocr_payload_frame,
|
||||
tracks=tracks,
|
||||
detections_count=len(gaylords),
|
||||
tracks=self.tracks,
|
||||
detections_count=len(self.gaylords),
|
||||
snapshots=new_snapshots,
|
||||
yolo_ms=yolo_ms,
|
||||
yolo_ms=self.last_yolo_ms,
|
||||
fps_text=fps_text,
|
||||
)
|
||||
|
||||
|
||||
BIN
testhd2.mp4
LFS
BIN
testhd2.mp4
LFS
Binary file not shown.
Reference in New Issue
Block a user