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
| Direzione | Artefatto | Sorgente |
|---|---|---|
| Input | video.mp4 | MP4 di cattura pure-v2 da playroll-cpp |
| Input | events.csv | Log eventi frame_number, event_type, input_name, value |
| Output | clip_XXXX_*.mp4 | Sotto-clip tagliati sui confini del moto |
| Output | clip_XXXX_*.csv | Log eventi per-clip con righe sintetiche state_at_start |
| Output | manifest.json / manifest_vlm.json | Indice dei clip con range di frame, punteggi VLM, timeline delle window |
| Output | scores.png / scores_vlm.png | Grafico 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:
- Riproduce tutti gli eventi precedenti a
start_frameper ricostruire lo stato di input attivo. - Antepone righe sintetiche
state_at_startcosì che i consumer a valle conoscano sempre lo stato completo di input al frame 0 del clip. - 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.ymlpiù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-filteringconsuma. 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.