Spoiler
La mia domanda è la seguente: ci sono schemi di progettazione per rappresentare le funzioni concatenabili che sono per il problema descritto di seguito?
Declarazione del processo ad alto livello
Attualmente sto creando un server di elaborazione delle immagini, la cui funzionalità è esposta tramite un'API Web.
Comportamento del cliente
Prima i client eseguono l'autenticazione, quindi richiedono che una o più analisi vengano eseguite dal server. Una volta che un client si è autenticato e ha richiesto il proprio stack di analisi, trasmette i suoi dati video al server di analisi.
Comportamento del server
Sul lato server, la classe Stream
viene utilizzata per rappresentare il flusso di dati. È l'oggetto di prima classe su cui è costruita la pipeline e può essere pensato come una sorta di coda FIFO ordinata. I frame vengono spinti, in ordine di arrivo, sull'istanza Stream
, e ogni stream può opzionalmente mappare una funzione sui suoi dati. I risultati di una funzione mappata sono proiettati su una nuova % istanza diStream
.
Ad esempio:
# input_stream is asynchronously updated with incoming frames
stream = input_stream.map(do_sobel_filter) # apply Sobel filter to each incoming frame
stream2 = stream.window(fn, 10) # apply function to a sliding window of 10 frames
Un importante nota a margine è che il nostro uso di oggetti Stream
ci consente di applicare funzioni a singoli frame oa gruppi di frame. Questo è utile in quanto alcune analisi richiedono il contesto.
Requisiti e amp; Vincoli
Il testo in grassetto sopra è particolarmente importante; Un determinato cliente sarà autorizzato solo a eseguire un sottoinsieme delle analisi di immagine offerte dal nostro sistema. Ciò a sua volta rende fondamentale la possibilità di comporre dinamicamente la pipeline di elaborazione delle immagini: non vogliamo eseguire calcoli costosi solo per eliminare il risultato.
Un'ulteriore complicazione della questione è il fatto che quasi tutte le analisi che offriamo hanno alcuni prerequisiti, cioè l'operazione O i può dipendere dal risultato dell'operazione < em> O in . Il nostro sistema ha bisogno di risolvere queste dipendenze ed eseguire ogni operazione esattamente una volta.
Infine, il concatenamento / composizione delle operazioni deve essere associativo. Questo perché vorremmo essere in grado di comporre calcoli di livello superiore riutilizzabili da calcoli atomici di livello inferiore. Ad esempio, considera la sequenza di operazioni x -> y -> z
. Vorremmo essere in grado di assegnare la catena ordinata x->y->z
a una variabile, quindi CMP1 = x->y->z
. Allo stesso modo, CMP2
è il risultato delle operazioni di concatenamento a->b->c
. In definitiva, vorremmo essere in grado di fare l'equivalente di CMP3 = CMP1 -> CMP2
, e farlo essere strettamente equivalente a CMP3 = x->y->z->a->b->c
(associatività).
Per riassumere, ecco i requisiti per gli oggetti della funzione di analisi delle immagini:
- Le funzioni devono essere composeable , il che significa che dovrebbe essere possibile manipolare un oggetto di prima classe che rappresenta una serie di sub-computazioni.
- Idealmente, le funzioni dovrebbero risolvere le dipendenze , nel senso che fare
A -> B
dovrebbe valutare implicitamente aA -> X -> C
seC
dipende daA
eX
. Questo può essere degno della sua stessa domanda, e quindi dovrebbe essere trattato come un requisito morbido. - Il concatenamento delle operazioni dovrebbe essere associativo e le funzioni composte dovrebbero essere anche concatenabili . L'idea è di avere uno schema riutilizzabile per rappresentare pipeline sempre più complesse.
Esistono schemi ben collaudati per ottenere un risultato del genere?
Consente di modificare:
1) In risposta alle domande di @ Giorgio, la mia (notoriamente ambigua) notazione serve a distinguere tra l'ordine temporale di frame e l'ordine di operazioni . Di seguito è riportato un esempio di come un'operazione può dipendere dalle sue controparti precedenti:
FindFace -> FindEyes -> SegmentEye -> MeasurePupilDiameter
Qui possiamo vedere che ogni funzione dopo FindFace
dipende dal risultato cumulativo delle funzioni precedenti. Ora considera:
FindFace -> FindEyes -> SegmentEye -> GetIrisColorHistogram
Questo secondo esempio dimostra come spesso vi sia un nucleo comune di operazioni che deve essere eseguito attraverso le analisi. Quando compongo le operazioni, mi piacerebbe eseguire i passaggi 1 - 3 una sola volta , quindi applicare MeasurePupilDiameter
e GetIrisColorHistogram
da lì.
Intuitivamente, sembra che il modo giusto per farlo sarebbe quello di assegnare un dizionario al mio oggetto Frame
e riempirlo con le caratteristiche mentre sono calcolate - un modello memoize, se vuoi. Da lì, tuttavia, non è chiaro come procedere per l'astrazione di tutto il codice di riferimento relativo a (a) controllare se una funzione di prerequisito è già stata calcolata e (b) chiamare automaticamente la funzione appropriata se non lo è.