Cosa si intende per "creare video da file 3D"?
Ho dovuto addensare tutti i concetti in un titolo, ora cerco di approfondire, punto per punto. Per prima cosa, video e oggetto 3D, sul piano grafico, sono in due dimensioni totalmente diverse: il video è statico e l'oggetto 3D è interattivo. Il video è un insieme di immagini che si susseguono in un determinato ordine mentre l'oggetto 3D si può ruotare, traslare, scalare etc.Perchè perdere le caratteristiche di un oggetto 3D per fare un banale video?
Per necessità! O meglio, non ho una risposta univoca a questa domanda. Sto scrivendo questo articolo basandomi sul problema che il file 3D è poco accessibile: non puoi inviarlo facilmente in Whatsapp, non puoi condividerlo in Instagram, etc. Per questo articolo posso dire che mi sta bene perdere tutti i vantaggi di un oggetto 3d per avere (solo) un video, ma per il prossimo articolo potrei cambiare idea. Quindi se ti serve un modo per condividere facilmente un oggetto 3d, trasformandolo in un video, goditi questo articolo.Cosa è un file .obj?
Un file cha ha estensione obj è un file che contiene delle informazioni per poter sviluppare un modello 3D. Al suo interno contiene principalmente posizioni di vertici, coordinate per texture e la scala. Ci sono tante altre estensioni di file 3D, ma per questo articolo ci concentreremo sul file .obj. Obj è un formato di file aperto.Cosa deve fare l'algoritmo in Python per creare il video da un oggetto 3D?
L'idea è molto semplice, l'algoritmo si divide principalmente in due parti: rendering e creazione video. Nella prima parte, rendering andremo a caricare il file 3D in Python, nella seconda parte invece creeremo un video da quello "che stiamo vedendo" (il file 3D).Rendering
Non ci metteremo a sviluppare un algoritmo che fa il rendering di oggetti 3D, questo perchè infrangeremmo la prima regola dell'informatica: reusability - se c'è una libreria che lo fa già, usa quella libreria. Quindi, per partire, ho cercato in rete un pacchetto che fa rendering di oggetti 3D. Ho trovato vtk, matplotlib e vedo. Ho scelto vedo perchè mi sembra il più intuitivo. (vedo è un progetto straordinario, in questo articolo utilizzeremo solo 1% delle sue funzionalità, spero di poterlo approfondire ulteriormente nei prossimi post. Date un'occhiata alla citazioni del progetto e vi renderete conto dell'importanza di questo progetto.)Premessa: assicuratevi di avere Python installato. Per questo articolo sto usando la versione 3.10.5. Potete controllarlo anche voi da terminale:
python3 --versionPython 3.10.5
Ora si può partire con l’installazione di vedo. Si legge dalla documentazione ufficiale
pip3 install vedo
Ok ora possiamo usarlo all’interno del nostro codice: partiamo dall’import del file .obj.
Creiamo un nuovo file python e posizionate il file .obj nella stessa cartella (trovate qualche file .obj alla fine di questo articolo). Apriamo il file .py ed inseriamo queste righe di codice.
python
import matplotlib.pyplot as pltfrom vedo import *# creo una nuova finestra con sfondo bianco grande 400x400 e facciamo vedere gli assiplt = Plotter(bg='white', size=(400, 400), axes=True)# aggiungo il file obj e lo coloriamo di bluplt += Mesh("shuttle.obj", c="blue")plt += __doc__# fissiamo la finestraplt.show()
Eseguiamo ed otterremo questo:
Molto semplice vero? Grazie vedo! Proseguiamo con la parte di video.
Creazione video
Devo dire che mi sono soffermato molto su questa fase; mi sono chiesto come fare un video con del codice ma non mi venivano soluzioni fattibili. Poi ho ho cancellato tutti le impossibili idee e sono ripartito. Ho trovato una soluzione usando un po' di amica semplicità e sorella matematica. Scherzi a parte, partiamo dalle basi: cosa è un video? Un'insieme di immagini ordinate. Bene, partendo da questo concetto realizzeremo una serie di immagini (immagine 1, ruoto il file, immagine 2, ruoto il file,...) e una dopo l'altra le aggiungeremo al video. Per non creare ulteriore entropia, diciamo che vogliamo ruotare attorno ad un solo asse. Quindi immaginatevi di camminare in cerchio attorno ad un oggetto e ad ogni passo fare una fotografia. Se visionate velocemente le foto, vedrete le varie facce dell'oggetto 3D.Facciamolo fare al codice! Per prima cosa, per riuscire a spostare la "camera", vedo, ci delizia con l'attributo... "camera". A questo attributo possiamo passare un vtkCamera (si, perchè sotto al cofano di vedo c'è anche vtk) oppure un dizionario di proprietà (che la libreria poi trasformerà in un vtkcamera). Se, con vscode, premete CTRL e cliccate contemporaneamente sul nome della funzione, avrete accesso al codice sorgente. Per questa libreria è ben commentato e possiamo vedere cosa possiamo settare:
- pos,
(list)
, the position of the camera in world coordinates - focal_point
(list)
, the focal point of the camera in world coordinates - viewup
(list)
, the view up direction for the camera
Ho dovuto fare diverse prove e ricerche per capire cosa modificassero questi attributi, a quanto pare sono le basi del mondo vtk (ma anche del mondo delle riprese). Fortunatamente ho trovato diversi articoli e presentazioni dove spiegavano in dettaglio la funzione di tutti i possibili attributi. Ve le risparmio e vi sintetizzo il tutto in questa immagine.
Quindi,- pos in rosso: la posizione della camera in coordinate XYZ. Dove 0,0,0 è l’origine degli assi di riferimento dell’oggetto.
- focal_point in blu: la coordinata verso la quale è puntata la fotocamera. Sempre in coordinate XYZ e 0,0,0 è ancora l’origine degli assi di riferimento dell’oggetto.
- viewup in verde: l’asse della triade XYZ che punta verso l’alto, solitamente è Z.
Queste sono le principali proprietà che ci serviranno per questo articolo, ma ce ne sono un’infinità. Quindi se volete cimentarvi nel fare l’inquadratura all’americana, sappiate che potete farlo!
Torniamo al nostro shuttle! Decidiamo l’inquadratura di partenza e siamo pronti per “girare” il video. Io ho immaginato di posizionare la camera al punto rosso che vedete qui sotto e con valore z=0.
Facendo due proporzioni con i valori degli assi dell'oggetto, il punto rosso dovrebbe trovarsi a circa x=-30, y=0, z=0. Settiamo quindi questi valori nel codice inserendo il dizionario alla funzione plt.show()python
...plt.show(camera={'pos': (-30, 0, 0), 'viewup': (0, 0, 1)})...
Eseguendo il codice troviamo il nostro shuttle pronto ad essere immortalato da tutti gli angoli!
L’idea è quindi di spostarsi lungo una circonferenza e scattare un’altra foto, spostarsi, scattare, spostarsi, scattare etc. Magari in punti equidistanti sulla circonferenza. Possiamo facilmente suddividere la circonferenza utilizzando gli angoli. Iniziamo con 8 punti di scatto e considerando che la circonferenza ha 360°, possiamo facilmente trovare ogni quanti gradi fermarci e scattare: ogni 45°! (360° / 8 = 45°).
Purtroppo la camera nel codice però accetta coordinate xy(z), non possiamo utilizzare i gradi (magari si, ma diciamo che vogliamo risolverlo con le coordinate xy(z) e non con gli angoli).
Ora abbiamo bisogno di un po' di matematica per far spostare la telecamera sulla circonferenza: dobbiamo trasformare le 8 posizioni in coordinate (solo xy, in questo articolo, della z possiamo anche dimenticarcene perchè terremo sempre il valore fisso). Le funzioni sin e cos capitano a pennello:
Posizione | Angolo | x=cos() | y=cos() |
---|---|---|---|
0 | 0° | 1 | 0 |
1 | 45° | 0.707 | 0.707 |
2 | 90° | 0 | 1 |
3 | 135° | -0.707 | 0.707 |
4 | 180° | -1 | 0 |
5 | 225° | -0.707 | -0.707 |
6 | 270° | 0 | -1 |
7 | 315° | 0.707 | -0.707 |
Perfetto, ora inseriamo le coordinate nel codice:
python
import mathimport matplotlib.pyplot as pltfrom vedo import *# creo una nuova finestra con sfondo bianco grande 400x400 e facciamo vedere gli assiplt = Plotter(bg='white', size=(400, 400), axes=True,interactive=None, offscreen=True)# aggiungo il file obj e lo coloriamo di bluplt += Mesh("shuttle.obj", c="blue")plt += __doc__# modifichiamo la posizionen = 8for i in range(n):distance = -30degrees = 360/n*iradians = degrees * math.pi / 180y = -distance * sin(radians)x = distance * cos(radians)plt.show(camera={'pos': (x, y, 0), 'viewup': (0, 0, 1)})plt.screenshot("step-"+str(i) + "-di-8.png")
eseguiamo e ci ritroveremo 8 png:
Il più è fatto! Ora dobbiamo solo ordinare i png in un video. Lo possiamo fare, sempre in Python, così:
python
...# inizializziamo il videovideo = Video("shuttle.mp4", backend='cv')n = 8for i in range(n):distance = -30degrees = 360/n*iradians = degrees * math.pi / 180y = -distance * sin(radians)x = distance * cos(radians)plt.show(camera={'pos': (x, y, 0), 'viewup': (0, 0, 1)})# aggiungiamo i frame al videovideo.add_frame()video.close()
Troverete quindi nella stessa cartella anche il video shuttle.mp4. Aprendolo però vi accorgerete di un problema: i frame si susseguono così velocemente che nemmeno noterete i dettagli delle varie facce dell’oggetto 3D. Questo perchè abbiamo utilizzato solo 8 frame e il componente Video (che è sempre del pacchetto vedo) ha un valore di default frame per sec (fps) di 24; quindi la durata del video è meno di 1 secondo (8 frame / 24 frame/sec = 0,333333sec). 24 fps è il valore di default e noi ci accontentiamo perchè i nostri occhi-cervello hanno una frequenza di aggiornamento che va dai 30 ai 60 frame per secondo; 24 fps va più che bene, ma volendo, possiamo modificarlo.
Quindi, siamo riusciti nell'intento ma il video è troppo corto! Dura meno di 1 secondo! Cambiamo approcio! Invece che utilizzare come unità di misura del video, gli 8 frame, utilizziamo i secondi! Partiamo dai secondi di durata del video (esempio 5 secondi), sappiamo che il video mostra 24 frame in un secondo e sappiamo di certo che la circonferenza totale ha 360°. Quindi in totale dobbiamo avere 24*5=120 frame. Vuol dire che ci dovremo fermare 120 volte, quindi ogni 360°/120=3°! Detto, fatto! Ecco la versione finale del codice:
python
import mathimport matplotlib.pyplot as pltfrom vedo import *# creo una nuova finestra con sfondo bianco grande 400x400 e facciamo vedere gli assiplt = Plotter(bg='white', size=(400, 400),interactive=None, offscreen=True)# aggiungo il file obj e lo coloriamo di bluplt += Mesh("shuttle.obj", c="blue")plt += __doc__# inizializziamo il videovideo = Video("shuttle.mp4", backend='cv')duration=5n = int(24*duration)for i in range(n):distance = -30degrees = 360/n*iradians = degrees * math.pi / 180y = -distance * sin(radians)x = distance * cos(radians)plt.show(camera={'pos': (x, y, 0), 'viewup': (0, 0, 1)})# aggiungiamo i frame al videovideo.add_frame()video.close()
E.. il risultato:
Vuoi altre idee per sviluppare in Python?
Se come me sei curioso e conosci poco Python, ti renderai conto che questo linguaggio di programmazione ha alte potenzialità. Per me è divertente: se ti concentri, puoi risolvere problemi non banali con una sola riga di codice. Comunque, ritornando alla domanda; pochi giorni fa ho pubblicato un post dove spiego come usare prettymaps, una libreria per creare mappe.. con Python!File .obj di prova
Per comodità riporto questo elenco di file 3D Florida State University, il mio preferito è forse questo semplice ottaedro.Spero che questo articolo vi sia piaciuto. Vi ho spiegato come creare un video da un file 3D .obj con Python e un po’ di matematica. Il video creato è ora pronto per essere condiviso con i tuoi amici! Non dimenticarti di sbizzarrirti con i colori, durate, inquadrature e texture!
Al prossimo post!