Milestone threaded pipeline with OCR and quality filter
This commit is contained in:
421
flywms_pipeline_spec.rtf
Normal file
421
flywms_pipeline_spec.rtf
Normal file
@@ -0,0 +1,421 @@
|
||||
{\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
|
||||
}
|
||||
Reference in New Issue
Block a user