{\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 }