Passa al contenuto principale

Pipeline di Data Filtering

Il repo data-filtering (C:\EC\data-filtering) è una pipeline Python con accelerazione GPU che converte le registrazioni grezze di gameplay prodotte da playroll-cpp in sotto-clip puliti e annotati, adatti all'addestramento di agenti AI in grado di giocare.

È uno strumento di post-processing offline — non viene eseguito all'interno del recorder. Consuma un MP4 registrato insieme al suo CSV di input-event compagno ed emette clip tagliati, CSV per-clip con stato di input ricostruito, grafici del moto e caption derivate da VLM.

Input e output

DirezioneArtefattoSorgente
Inputvideo.mp4MP4 di cattura pure-v2 da playroll-cpp
Inputevents.csvLog eventi frame_number, event_type, input_name, value
Outputclip_XXXX_*.mp4Sotto-clip tagliati sui confini del moto
Outputclip_XXXX_*.csvLog eventi per-clip con righe sintetiche state_at_start
Outputmanifest.json / manifest_vlm.jsonIndice dei clip con range di frame, punteggi VLM, timeline delle window
Outputscores.png / scores_vlm.pngGrafico del motion-score, ricolorato dopo il filtro VLM

Stadi della pipeline

Stage 1 — Motion scoring (pipeline/motion.py)

Legge il video tramite una pipe ffmpeg (nessuna dipendenza da OpenCV-CUDA) e fonde due segnali per-frame in un motion score [0, 1]:

  • Frame differencing sul canale luma, calcolato su GPU tramite PyTorch.
  • Magnitudine del flusso ottico denso Farneback, calcolata su CPU tramite cv2.calcOpticalFlowFarneback.

Entrambi i segnali sono normalizzati indipendentemente prima della fusione pesata (w_diff=0.4, w_flow=0.6 di default). Lo stadio di optical-flow ridimensiona i frame a un lato corto di 320 px prima del calcolo.

Stage 2 — Segmentazione (pipeline/segmenter.py)

Applica una soglia alla serie del motion-score e impiega isteresi temporale per emettere intervalli dinamici (start_frame, end_frame). Non c'è deliberatamente alcuno smoothing gaussiano — l'isteresi gestisce la natura spiky dei segnali di movimento di gameplay in modo più pulito. I run statici più brevi di min_static_s non spezzano un clip; i run dinamici più brevi di min_dynamic_s vengono scartati.

Stage 3 — Cutting (pipeline/cutter.py)

Taglia l'MP4 sorgente sui confini dei segmenti usando lo stream copy di ffmpeg (-c copy), così il taglio è quasi istantaneo indipendentemente dalla risoluzione. Emette manifest.json con range di frame, timestamp e percorsi dei clip.

Stage 4 — CSV join (pipeline/csv_join.py)

Allega i dati di input del controller basati sugli eventi a ciascun clip. Poiché il CSV è guidato dagli eventi — le righe appaiono solo quando un input cambia — una semplice slice per range di frame perderebbe qualunque input tenuto premuto attraverso il confine. Il join quindi:

  1. Riproduce tutti gli eventi precedenti a start_frame per ricostruire lo stato di input attivo.
  2. Antepone righe sintetiche state_at_start così che i consumer a valle conoscano sempre lo stato completo di input al frame 0 del clip.
  3. Estrae gli eventi nella finestra [start_frame, end_frame).

Supporta sia il formato per riga di evento (frame_number, event_type, input_name, value) sia una variante con colonna temporale, dove frame_number è derivato da un timestamp in millisecondi e da un FPS configurato.

Stage 5 — Filtro VLM (pipeline/vlm_filter.py)

Assegna punteggi e caption ai clip con un modello vision-language locale (attualmente Qwen2-VL 7B; InternVL2 8B e LLaVA-1.6 sono le alternative con licenza MIT/Apache indicate nel README). Gira interamente on-device — nessuna chiamata API, nessun data egress.

Ogni clip è suddiviso in finestre di window_s secondi. Per ogni finestra, n_keyframes frame vengono campionati e valutati 0–10. Una finestra è anche classificata per la prospettiva in prima persona: lobby, character-select, scoreboard, kill-cam e viste spectator vengono marcate first_person=false e trattate come scartate indipendentemente dal punteggio. Un clip viene mantenuto solo se min_keep_ratio delle sue finestre supera sia la soglia di punteggio sia il check di prima persona.

Quando trim_bad_windows è true, un secondo passaggio ffmpeg ritaglia le finestre mantenute come sotto-clip separati. drop_margin_s rade via secondi aggiuntivi dalle finestre mantenute che confinano con una finestra scartata — utile per le transizioni di menù che sbordano in gameplay altrimenti pulito.

manifest_vlm.json memorizza l'intera timeline delle finestre (punteggio, caption, motivo, azione, kept) per ciascun clip, così che la decisione keep/drop possa essere riprodotta a una soglia diversa senza rieseguire il modello.

Hardware e dipendenze

  • GPU NVIDIA con 10 GB+ di VRAM (testato su A10G 23 GB); CUDA 12.x o successivo.
  • Ambiente Conda in environment.yml più transformers >= 4.45, accelerate, qwen-vl-utils, torchvision.
  • ffmpeg da conda-forge (il bundle di default è privo dei codec richiesti).

Entry point CLI

test_pipeline.py è l'entry point primario e accetta argomenti posizionali, così non sono necessarie modifiche al sorgente tra un'esecuzione e l'altra:

python test_pipeline.py <video.mp4> <events.csv> <out_dir>/ [--vlm] [--drop-margin S] [--edge-trim S]

Senza --vlm la pipeline si ferma dopo lo Stage 4 (motion → segmentazione → cutting → CSV join). Con --vlm esegue l'intera pipeline a cinque stadi, incluso lo scoring VLM su finestre e il trimming opzionale.

Relazione con gli altri repo

  • playroll-cpp produce il bundle MP4 + CSV + JSON di metadata che data-filtering consuma. Lo schema del CSV (frame_number, event_type, input_name, value) corrisponde al log eventi della cattura pure-v2.
  • playroll-docs (questo sito) descrive i contratti sul lato a monte; questa pagina documenta la pipeline di dati di addestramento offline che si costruisce sopra di essi.