Che cos'è un buon algoritmo di imballaggio delle trame? Tecnicamente, l' imballaggio del bidone è NP-difficile , quindi un'euristica è ciò che sto davvero cercando.
Che cos'è un buon algoritmo di imballaggio delle trame? Tecnicamente, l' imballaggio del bidone è NP-difficile , quindi un'euristica è ciò che sto davvero cercando.
Risposte:
Ho trascorso alcuni mesi in un lavoro elaborando un algoritmo di imballaggio delle trame migliore.
L'algoritmo con cui abbiamo iniziato era semplice. Colleziona tutti gli elementi di input. Ordinali per pixel totali consumati, da grandi a piccoli. Disporli nella trama in ordine di scanline, testando semplicemente le cose dal pixel di inizio sinistro al pixel superiore, spostandosi lungo una linea e ripetendo, ripristinando il pixel di sinistra dopo ogni posizionamento riuscito.
O è necessario codificare in larghezza una larghezza o creare un'altra euristica per questo. Nel tentativo di preservare la quadratura, il nostro algoritmo inizierebbe a 128, per poi aumentare di 128 secondi fino a ottenere un risultato non più profondo di quanto fosse ampio.
Quindi, avevamo quell'algoritmo e ho deciso di migliorarlo. Ho provato un mucchio di euristiche stravaganti, cercando di trovare oggetti che si incastrassero insieme, facendo un po 'di ponderazione su un mucchio di proprietà di imballaggio dello spazio desiderate, ruotando e capovolgendo. Dopo tutto il mio lavoro, letteralmente tre mesi di lavoro, ho finito per risparmiare il 3% di spazio.
Si. 3%.
E dopo aver eseguito la nostra routine di compressione su di essa, in realtà è finita più grande (cosa che non riesco ancora a spiegare), quindi abbiamo buttato giù tutto e siamo tornati al vecchio algoritmo.
Ordinare gli elementi, incepparli nella trama nell'ordine di scanline. C'è il tuo algoritmo. È facile da programmare, veloce da eseguire e non migliorerai molto senza un'incredibile quantità di lavoro. Questo lavoro non vale la pena, a meno che la tua azienda non sia grande almeno 50 persone, e probabilmente di più.
E come nota a margine, ho appena implementato questo algoritmo (larghezza fissa 512 pixel) per la stessa identica applicazione che stai facendo (niente ftgles, ma glifi di freetype resi opengl.) Ecco il risultato. Sembra sfocato perché il mio sta usando l' algoritmo di rendering del testo basato sul campo di distanza di Valve , che rappresenta anche lo spazio extra tra glifi. Ovviamente, non c'è molto spazio vuoto rimasto e fa un buon lavoro di stipare le cose in punti aperti.
Tutto il codice per questo è concesso in licenza BSD e disponibile su github .
La tesi di dottorato di Andrea Lodi è intitolata Algorithms for Two Dimensional Bin Packing and Assignment Problems .
La tesi ripercorre alcune delle forme più difficili di questo problema. Fortunatamente, il packaging texture è la versione più semplice. Il miglior algoritmo che ha trovato è stato chiamato Touching Perimeter .
Per citare da pagina 52:
L'algoritmo, chiamato Touching Perimeter (TPRF), inizia ordinando gli articoli in base all'area non in aumento (rompendo i legami con valori min {wj, hj} non in aumento) e orientandoli orizzontalmente. Viene quindi calcolato un limite inferiore L sul valore ottimale della soluzione e vengono inizializzati L scomparti vuoti. (Il limite inferiore continuo L0 definito nella sezione precedente è ovviamente valido anche per 2BP | R | F; i limiti migliori sono proposti da Dell'Amico, Martello e Vigo [56].) L'algoritmo racchiude un oggetto alla volta, in un cestino esistente o inizializzandone uno nuovo. Il primo oggetto imballato in un cestino viene sempre posizionato nell'angolo in basso a sinistra. Ogni articolo successivo viene imballato in una cosiddetta posizione normale (vedi Christo fi des e Whitlock [41]), ovvero
La scelta del cestino e della posizione di imballaggio viene effettuata valutando un punteggio, definito come percentuale del perimetro dell'articolo che tocca il cestino e altri articoli già imballati. Questa strategia favorisce i modelli in cui gli articoli imballati non "intrappolano" piccole aree, che possono essere difficili da usare per ulteriori posizionamenti. Per ogni posizione di imballaggio del candidato, il punteggio viene valutato due volte, per i due orientamenti degli oggetti (se entrambi sono fattibili) e viene selezionato il valore più alto. I legami del punteggio vengono interrotti scegliendo il cestino con l'area massima imballata. L'algoritmo generale è il seguente.touching_perimeter: sort the items by nonincreaseing w,h values, and horizontally orient them; comment: Phase 1; compute a lower bound L on the optimal solution value, and open L empty bins; comment: Phase 2; for j := 1 to n do score := 0; for each normal packing position in an open bin do let score1 and score2 be scores with tow orientations; score := max{score,score1,score2}; end for; if score > 0 then pack item j in the bin, position and orientation corresponding to score; else open a new bin and horizontally pack item j into i; end if; end for; end;
Anche di interesse, il documento descrive un algoritmo per determinare le dimensioni di una mappa delle trame imballata in modo ottimale. Ciò sarebbe utile per determinare se è persino possibile adattare tutte le trame in un atlante 1024x1024.
Se qualcuno è ancora interessato, ho riscritto completamente la libreria rectpack2D in modo che sia molto più efficiente.
Funziona mantenendo uno std::vector
degli spazi vuoti nell'atlante, iniziando con una dimensione massima iniziale (in genere, la dimensione massima consentita della trama su una particolare GPU), suddividendo il primo spazio vuoto praticabile e salvando le divisioni nel vettore.
Il progresso delle prestazioni è arrivato semplicemente usando un vettore, invece di mantenere un intero albero, come in precedenza.
La procedura è descritta in dettaglio nel README .
La biblioteca è sotto il MIT, quindi sono contento per te se lo trovi utile!
I test sono stati condotti su una CPU Intel (R) Core (TM) i7-4770K a 3,50 GHz. Il binario è stato creato con clang 6.0.0, usando uno switch -03.
Durata: 4 millisecondi
Pixel sprecati: 15538 (0,31% - equivalente di un quadrato 125 x 125)
Uscita (2116 x 2382):
A colori:
(il nero è spazio sprecato)
Durata: 3,5 - 7 ms
Pixel sprecati: 9288 (1,23% - equivalente di un quadrato 96 x 96)
Uscita (866 x 871):
A colori:
(il nero è spazio sprecato)
Un buon algoritmo euristico può essere trovato qui . Quando stavo provando qualcosa di simile recentemente, ho trovato questo riferimento come punto di partenza di base per la maggior parte delle implementazioni che ho visto.
Funziona particolarmente bene con molti oggetti dalla forma regolare, di dimensioni simili o con un buon mix di immagini piccole e di dimensioni minori. Il miglior consiglio per ottenere buoni risultati è di ricordare di ordinare i tuoi input in termini di dimensioni dell'immagine, quindi impacchettare dal più grande al più piccolo poiché le immagini più piccole si impacchettano nello spazio attorno alle immagini più grandi. In che modo fai questo ordinamento e dipende dai tuoi obiettivi. Ho usato il perimetro piuttosto che l'area come approssimazione del 1 ° ordine poiché ho considerato che le immagini alte + sottili / corte + larghe (che avrebbero un'area bassa) in realtà sono molto difficili da posizionare in seguito in un pacchetto, quindi usando il perimetro si spinge queste strane forme verso la parte anteriore dell'ordine.
Ecco un esempio di visualizzazione dell'output per il mio packer su un set casuale di immagini dalla directory del dump delle immagini del mio sito Web :).
I numeri nei quadrati sono gli ID dei blocchi contenenti nella struttura, in modo da darti un'idea dell'ordine delle inserzioni. Il primo è l'ID "3" perché è il primo nodo foglia (solo le foglie contengono immagini) e di conseguenza ha 2 genitori).
Root[0]
/ \
Child[1] Child[2]
|
Leaf[3]
Qualcosa che ho usato, che funziona bene anche con le mappe UV irregolari, è trasformare il cerotto UV in una maschera bitmap e mantenere una maschera per la trama stessa, cercando la prima posizione in cui il cerotto UV si adatterà. Ordino i blocchi secondo una semplice euristica (altezza, larghezza, dimensione, qualunque cosa) e consento alle rotazioni dei blocchi di minimizzare o massimizzare l'euristica scelta. Ciò fornisce uno spazio di ricerca gestibile per la forza bruta.
Se riesci quindi a ripetere quella prova di diverse euristiche e / o applicare un fattore casuale nella scelta dell'ordine e a ripetere fino a quando non scade il tempo limite.
Con questo schema otterrai piccole isole UV racchiuse negli spazi vuoti creati da quelle grandi e persino nei buchi lasciati all'interno di singole patch UV.
Di recente abbiamo rilasciato uno script Python che impacchetterà le trame in più file di immagini di una determinata dimensione.
Citato dal nostro blog:
"Mentre ci sono numerosi packer che possono essere trovati online, la nostra difficoltà era trovare qualcuno che potesse gestire un gran numero di immagini in più directory. Così è nato il nostro packer di atlante!
Così com'è, il nostro piccolo script inizierà nella directory di base e caricherà tutti i .PNG in un atlante. Se quell'atlante è pieno, ne crea uno nuovo. Quindi, proverà ad adattare il resto delle immagini in tutti gli atlanti precedenti prima di trovare un posto in quello nuovo. In questo modo, ogni atlante è imballato il più stretto possibile. Gli atlanti sono denominati in base alla cartella da cui provengono le loro immagini.
È possibile modificare le dimensioni dell'atlante (riga 65), il formato delle immagini che si desidera comprimere (riga 67), la directory di caricamento (riga 10) e la directory di salvataggio (riga 13) abbastanza facilmente senza esperienza in Python. Come un piccolo disclaimer, questo è stato montato insieme in pochi giorni per funzionare specificamente con il nostro motore. Ti incoraggio a richiedere funzionalità, a commentare con le tue varianti e a segnalare eventuali errori, ma qualsiasi modifica allo script avverrà nel mio tempo libero. "
Sentiti libero di consultare il codice sorgente completo qui: http://www.retroaffect.com/blog/159/Image_Atlas_Packer/#b
È abbastanza facile impacchettare i caratteri perché tutte (o la grande maggioranza) delle trame degli glifi hanno quasi le stesse dimensioni. Fai la cosa più semplice che ti viene in mente e sarà molto vicina all'ottimale.
L'intelligenza diventa più importante quando si confezionano immagini di dimensioni molto diverse. Quindi vuoi essere in grado di colmare lacune, ecc. Anche allora, tuttavia, un semplice algoritmo come la ricerca dell'ordine di scanline discusso in precedenza produrrà risultati molto ragionevoli.
Nessuno degli algos avanzati è magico. Non saranno più efficaci del 50% rispetto a un semplice algoritmo e non otterrai benefici costanti da essi a meno che tu non abbia un numero impressionante di fogli di trama. questo perché i piccoli miglioramenti apportati da algoritmi migliori verranno visualizzati solo in forma aggregata.
Diventa semplice e passa a qualcosa in cui i tuoi sforzi saranno ricompensati meglio
Se è specifico per le trame dei caratteri, probabilmente fai qualcosa di non ottimale ma bello e semplice:
Ordina i caratteri per altezza, prima il più alto
Inizia da 0,0 Posiziona il primo carattere nelle coordinate attuali, avanza di X, posiziona il successivo, ripeti fino a quando non riusciamo a inserirne un altro
Ripristina X su 0, fai avanzare Y verso il basso dell'altezza del carattere più alto nella riga e riempi un'altra riga
Ripeti fino a quando non abbiamo esaurito i personaggi o non possiamo adattarci a un'altra riga.