422 lines
9.9 KiB
Plaintext
422 lines
9.9 KiB
Plaintext
{\rtf1\ansi\deff0
|
|
{\fonttbl{\f0 Calibri;}{\f1 Consolas;}}
|
|
\fs24
|
|
\b Specifica pipeline realtime flywms\par\b0
|
|
\par
|
|
\b Obiettivo\par\b0
|
|
Costruire una pipeline realtime modulare per video/camera, con tre blocchi principali:\par
|
|
\par
|
|
{\f1 Capture/Preview\par
|
|
YOLO Detection\par
|
|
OCR\par}
|
|
\par
|
|
I blocchi lavorano in parallelo, ognuno al proprio ritmo. Se un blocco e' lento, non deve bloccare gli altri. I frame/ROI vecchi possono essere scartati per mantenere bassa la latenza.\par
|
|
\par
|
|
\b Principio chiave\par\b0
|
|
Non elaboriamo necessariamente tutti i frame. Elaboriamo il piu' recente disponibile quando un worker e' libero.\par
|
|
\par
|
|
{\f1 meglio saltare frame vecchi\par
|
|
che accumulare ritardo\par}
|
|
\par
|
|
\b Strutture dati\par\b0
|
|
{\f1 FramePacket\par
|
|
frame_id: intero crescente\par
|
|
timestamp: tempo acquisizione\par
|
|
frame: immagine OpenCV BGR\par
|
|
width: larghezza\par
|
|
height: altezza\par
|
|
source: video/camera\par
|
|
\par
|
|
Detection\par
|
|
class_id\par
|
|
class_name\par
|
|
confidence\par
|
|
bbox: x1, y1, x2, y2\par
|
|
\par
|
|
DetectionResult\par
|
|
frame_id\par
|
|
timestamp\par
|
|
detections: lista Detection\par
|
|
inference_ms\par
|
|
source_width\par
|
|
source_height\par
|
|
\par
|
|
RoiPacket\par
|
|
roi_id\par
|
|
source_frame_id\par
|
|
timestamp\par
|
|
class_name\par
|
|
confidence\par
|
|
bbox\par
|
|
roi_image: crop BGR copiato\par
|
|
width\par
|
|
height\par
|
|
\par
|
|
OcrResult\par
|
|
roi_id\par
|
|
source_frame_id\par
|
|
timestamp\par
|
|
text\par
|
|
raw_text\par
|
|
confidence opzionale\par
|
|
bbox\par
|
|
ocr_ms\par}
|
|
\par
|
|
\b Buffer\par\b0
|
|
Usiamo buffer thread-safe "latest", non FIFO classici.\par
|
|
\par
|
|
{\f1 LatestBuffer\par
|
|
max_size\par
|
|
put(item)\par
|
|
get_latest_blocking(stop_event)\par
|
|
get_latest_nowait()\par
|
|
stats()\par}
|
|
\par
|
|
Comportamento:\par
|
|
\par
|
|
{\f1 put(item):\par
|
|
se pieno, elimina elementi vecchi\par
|
|
inserisce item nuovo\par
|
|
notifica i worker in attesa\par
|
|
\par
|
|
get_latest_blocking():\par
|
|
se vuoto, aspetta\par
|
|
quando c'e' almeno un item:\par
|
|
prende il piu' recente\par
|
|
scarta gli elementi piu' vecchi\par
|
|
restituisce item\par}
|
|
\par
|
|
Buffer previsti:\par
|
|
\par
|
|
{\f1 frame_buffer: FramePacket\par
|
|
roi_buffer: RoiPacket\par}
|
|
\par
|
|
Risultati condivisi:\par
|
|
\par
|
|
{\f1 SharedState\par
|
|
latest_detection_result\par
|
|
latest_ocr_results\par
|
|
counters/statistiche\par}
|
|
\par
|
|
SharedState va protetto con lock breve.\par
|
|
\par
|
|
\b Thread\par\b0
|
|
{\f1 Main/Capture Thread\par
|
|
legge frame\par
|
|
genera FramePacket\par
|
|
mette FramePacket in frame_buffer\par
|
|
visualizza preview\par
|
|
opzionalmente disegna ultimi risultati disponibili\par
|
|
\par
|
|
YOLO Worker Thread\par
|
|
aspetta ultimo FramePacket\par
|
|
copia il frame fuori lock\par
|
|
esegue detection\par
|
|
salva DetectionResult in SharedState\par
|
|
crea RoiPacket per le etichette utili\par
|
|
mette RoiPacket in roi_buffer\par
|
|
aggiorna statistiche\par
|
|
\par
|
|
OCR Worker Thread\par
|
|
aspetta ultimo RoiPacket\par
|
|
analizza ROI\par
|
|
salva OcrResult in SharedState/log\par
|
|
aggiorna statistiche\par}
|
|
\par
|
|
\b Thread safety\par\b0
|
|
Il lock deve proteggere solo operazioni piccole:\par
|
|
\par
|
|
{\f1 inserire riferimento nel buffer\par
|
|
leggere riferimento dal buffer\par
|
|
aggiornare contatori\par
|
|
aggiornare latest result\par
|
|
notify/wait\par}
|
|
\par
|
|
Il lock non deve coprire:\par
|
|
\par
|
|
{\f1 cap.read()\par
|
|
frame.copy()\par
|
|
YOLO forward\par
|
|
OCR\par
|
|
cv2.imshow\par
|
|
conversioni immagine\par}
|
|
\par
|
|
\b Politica copie\par\b0
|
|
Prima versione, robusta:\par
|
|
\par
|
|
{\f1 Capture:\par
|
|
non copia ogni frame\par
|
|
mette il frame letto nel FramePacket\par
|
|
\par
|
|
YOLO:\par
|
|
prende FramePacket\par
|
|
fa frame.copy() solo del frame che analizzera'\par
|
|
\par
|
|
OCR:\par
|
|
riceve ROI gia' copiata\par}
|
|
\par
|
|
Questo evita corruzione dati e mantiene copie limitate.\par
|
|
\par
|
|
\b Politica realtime\par\b0
|
|
Se YOLO e' lento:\par
|
|
\par
|
|
{\f1 non analizza frame arretrati\par
|
|
prende il piu' recente disponibile\par}
|
|
\par
|
|
Se OCR e' lento:\par
|
|
\par
|
|
{\f1 non legge ROI vecchie\par
|
|
prende la ROI piu' recente disponibile\par}
|
|
\par
|
|
Questo riduce la latenza.\par
|
|
\par
|
|
\b Algoritmo pseudocodice - main/capture\par\b0
|
|
{\f1 inizializza config\par
|
|
carica labels\par
|
|
carica modello YOLO\par
|
|
apri video/camera\par
|
|
\par
|
|
crea stop_event\par
|
|
crea frame_buffer max_size=N\par
|
|
crea roi_buffer max_size=M\par
|
|
crea shared_state\par
|
|
\par
|
|
avvia thread yolo_worker\par
|
|
avvia thread ocr_worker\par
|
|
\par
|
|
frame_id = 0\par
|
|
\par
|
|
loop main:\par
|
|
grabbed, frame = cap.read()\par
|
|
\par
|
|
se non grabbed:\par
|
|
stop_event.set()\par
|
|
break\par
|
|
\par
|
|
frame_id += 1\par
|
|
\par
|
|
packet = FramePacket(\par
|
|
frame_id=frame_id,\par
|
|
timestamp=now(),\par
|
|
frame=frame,\par
|
|
width=frame.width,\par
|
|
height=frame.height,\par
|
|
source=source_name\par
|
|
)\par
|
|
\par
|
|
frame_buffer.put(packet)\par
|
|
\par
|
|
detection_result = shared_state.get_latest_detection()\par
|
|
ocr_results = shared_state.get_recent_ocr_results()\par
|
|
\par
|
|
display_frame = frame.copy()\par
|
|
draw detections compatibili su display_frame\par
|
|
draw OCR texts compatibili su display_frame\par
|
|
\par
|
|
cv2.imshow("Capture", display_frame)\par
|
|
\par
|
|
se premuto q:\par
|
|
stop_event.set()\par
|
|
break\par
|
|
\par
|
|
attendi chiusura worker\par
|
|
rilascia video\par
|
|
chiudi finestre\par}
|
|
\par
|
|
\b Algoritmo pseudocodice - YOLO worker\par\b0
|
|
{\f1 function yolo_worker():\par
|
|
while not stop_event:\par
|
|
packet = frame_buffer.get_latest_blocking(stop_event)\par
|
|
\par
|
|
se packet e' None:\par
|
|
continue\par
|
|
\par
|
|
local_frame = packet.frame.copy()\par
|
|
\par
|
|
t0 = now()\par
|
|
detections = run_yolo(local_frame)\par
|
|
inference_ms = now() - t0\par
|
|
\par
|
|
result = DetectionResult(\par
|
|
frame_id=packet.frame_id,\par
|
|
timestamp=now(),\par
|
|
detections=detections,\par
|
|
inference_ms=inference_ms,\par
|
|
source_width=packet.width,\par
|
|
source_height=packet.height\par
|
|
)\par
|
|
\par
|
|
shared_state.set_latest_detection(result)\par
|
|
\par
|
|
per ogni detection in detections:\par
|
|
se detection.class_name non e' "etichetta":\par
|
|
continue\par
|
|
\par
|
|
se bbox troppo piccola:\par
|
|
continue\par
|
|
\par
|
|
se OCR cooldown per questa zona non e' scaduto:\par
|
|
continue\par
|
|
\par
|
|
roi = crop local_frame usando bbox espansa\par
|
|
roi_copy = roi.copy()\par
|
|
\par
|
|
roi_packet = RoiPacket(\par
|
|
roi_id=next_roi_id(),\par
|
|
source_frame_id=packet.frame_id,\par
|
|
timestamp=now(),\par
|
|
class_name=detection.class_name,\par
|
|
confidence=detection.confidence,\par
|
|
bbox=detection.bbox,\par
|
|
roi_image=roi_copy,\par
|
|
width=roi_copy.width,\par
|
|
height=roi_copy.height\par
|
|
)\par
|
|
\par
|
|
roi_buffer.put(roi_packet)\par
|
|
\par
|
|
aggiorna statistiche yolo\par}
|
|
\par
|
|
\b Algoritmo pseudocodice - OCR worker\par\b0
|
|
{\f1 function ocr_worker():\par
|
|
while not stop_event:\par
|
|
roi_packet = roi_buffer.get_latest_blocking(stop_event)\par
|
|
\par
|
|
se roi_packet e' None:\par
|
|
continue\par
|
|
\par
|
|
t0 = now()\par
|
|
processed = preprocess_for_ocr(roi_packet.roi_image)\par
|
|
text, raw_text = run_ocr(processed)\par
|
|
ocr_ms = now() - t0\par
|
|
\par
|
|
result = OcrResult(\par
|
|
roi_id=roi_packet.roi_id,\par
|
|
source_frame_id=roi_packet.source_frame_id,\par
|
|
timestamp=now(),\par
|
|
text=text,\par
|
|
raw_text=raw_text,\par
|
|
bbox=roi_packet.bbox,\par
|
|
ocr_ms=ocr_ms\par
|
|
)\par
|
|
\par
|
|
shared_state.add_ocr_result(result)\par
|
|
\par
|
|
se text valido:\par
|
|
logga risultato\par
|
|
\par
|
|
aggiorna statistiche ocr\par}
|
|
\par
|
|
\b LatestBuffer pseudocodice\par\b0
|
|
{\f1 class LatestBuffer:\par
|
|
items = deque(maxlen=max_size)\par
|
|
condition = Condition()\par
|
|
pushed = 0\par
|
|
popped = 0\par
|
|
dropped = 0\par
|
|
\par
|
|
put(item):\par
|
|
with condition:\par
|
|
before = len(items)\par
|
|
\par
|
|
se before == max_size:\par
|
|
dropped += 1\par
|
|
\par
|
|
items.append(item)\par
|
|
pushed += 1\par
|
|
condition.notify_all()\par
|
|
\par
|
|
get_latest_blocking(stop_event):\par
|
|
with condition:\par
|
|
while items e' vuoto and not stop_event.is_set():\par
|
|
condition.wait(timeout=0.1)\par
|
|
\par
|
|
se stop_event.is_set():\par
|
|
return None\par
|
|
\par
|
|
latest = items[-1]\par
|
|
skipped = len(items) - 1\par
|
|
dropped += skipped\par
|
|
items.clear()\par
|
|
popped += 1\par
|
|
\par
|
|
return latest\par}
|
|
\par
|
|
\b SharedState pseudocodice\par\b0
|
|
{\f1 class SharedState:\par
|
|
lock\par
|
|
latest_detection_result\par
|
|
ocr_results_deque maxlen=100\par
|
|
stats\par
|
|
\par
|
|
set_latest_detection(result):\par
|
|
with lock:\par
|
|
latest_detection_result = result\par
|
|
\par
|
|
get_latest_detection():\par
|
|
with lock:\par
|
|
return latest_detection_result\par
|
|
\par
|
|
add_ocr_result(result):\par
|
|
with lock:\par
|
|
ocr_results_deque.append(result)\par
|
|
\par
|
|
get_recent_ocr_results():\par
|
|
with lock:\par
|
|
return copia lista risultati recenti\par}
|
|
\par
|
|
\b Statistiche minime\par\b0
|
|
Da stampare ogni N secondi:\par
|
|
\par
|
|
{\f1 capture_fps\par
|
|
yolo_fps\par
|
|
ocr_fps\par
|
|
frame_buffer pushed/popped/dropped\par
|
|
roi_buffer pushed/popped/dropped\par
|
|
avg_yolo_ms\par
|
|
avg_ocr_ms\par
|
|
last_frame_id\par
|
|
last_yolo_frame_id\par
|
|
last_ocr_frame_id\par
|
|
latency_yolo_ms\par
|
|
latency_ocr_ms\par}
|
|
\par
|
|
\b Configurazione iniziale\par\b0
|
|
{\f1 --video testhd.mp4\par
|
|
--frame-buffer-size 10\par
|
|
--roi-buffer-size 20\par
|
|
--min-confidence 0.30\par
|
|
--label-class etichetta\par
|
|
--max-roi-per-frame 2\par
|
|
--ocr-min-digits 2\par
|
|
--preview-width 1280\par
|
|
--backend cpu|cuda|cuda-fp16\par
|
|
--debug-yolo-window\par
|
|
--debug-ocr-window\par}
|
|
\par
|
|
\b Prima versione: cosa evitare\par\b0
|
|
Per la prima versione eviterei:\par
|
|
\par
|
|
{\f1 Dear PyGui\par
|
|
PaddleOCR\par
|
|
modello YOLO moderno\par
|
|
tracking complesso\par}
|
|
\par
|
|
Prima validiamo la pipeline con OpenCV window + YOLO attuale + OCR attuale. Poi sostituiamo i motori uno alla volta.\par
|
|
\par
|
|
\b Decisione architetturale\par\b0
|
|
Questa pipeline privilegia:\par
|
|
\par
|
|
{\f1 bassa latenza\par
|
|
fluidita' realtime\par
|
|
uso controllato della memoria\par
|
|
misurabilita'\par}
|
|
\par
|
|
e sacrifica:\par
|
|
\par
|
|
{\f1 analisi completa di ogni frame\par
|
|
ordine perfetto di output per ogni frame\par}
|
|
\par
|
|
Per un sistema operativo realtime e' il compromesso giusto.\par
|
|
}
|