Come convertire un Tensorflow Object Detection SavedModel in un TensorflowJS (Ver 0.8.6) Web Model
Ho deciso di scrivere questo tutorial pechè devo ammettere che i manuali che trattano questo argomento non sono sempre esausitivi ed una procedura apparentemente semplice può diventare un vero e proprio incubo. Le diverse versioni di tensorflow, inoltre, non aiutano in questo stato di confusione, per cui il risultato di una conversione da un formato ad un altro non è affatto scontato. Iniziamo a fare qualche ipotesi di partenza. In questa guida farò vedere la procedura per esportare un modello SavedModel in un webmodel compatibile con tensorflowJS. In questa guida utilizzeremo la versione 0.8.6 del tensorflowjs-convert. Quello che ci serve innanzi tutto è un modello addestrato. In tal caso potremmo usare un nostro modello o uno dei modelli pre-trained reperibili sul repository di tensorflow. Per questo esempio useremo ssd_mobilenet_v2_coco_2018_03_29 per cui possiamo iniziare la procedura scaricando il modello di nostro interesse.
# Download the model. wget http://download.tensorflow.org/models/object_detection/ssd_mobilenet_v2_coco_2018_03_29.tar.gz # decompriamiamo il modello . tar -xzvf ssd_mobilenet_v2_coco_2018_03_29.tar.gz # cambiamo Directory cd ssd_mobilenet_v2_coco_2018_03_29
pipenv --python 3.5
pipenv shell
pipenv install tensorflowjs==0.8.6
nel caso in cui dovessero esserci problemi di conflitti di versione vi consiglio di seguire le istruzioni fornite in uscita dal comando di installazione. In ogni caso potete provare a lanciare:
pipenv install tensorflowjs==0.8.6 --skip-lock
L’installazione tramite pipenv provvederà a configurare un ambiente protetto la cui struttura potrà essere verificata lanciando il comando:
pipenv graph
nel mio caso la lista degli shims installati sono:
tensorflowjs==0.8.6 - h5py [required: ==2.8.0, installed: 2.8.0] - numpy [required: >=1.7, installed: 1.16.3] - six [required: Any, installed: 1.11.0] - keras [required: ==2.2.2, installed: 2.3.1] - h5py [required: Any, installed: 2.8.0] - numpy [required: >=1.7, installed: 1.16.3] - six [required: Any, installed: 1.11.0] - keras-applications [required: >=1.0.6, installed: 1.0.8] - h5py [required: Any, installed: 2.8.0] - numpy [required: >=1.7, installed: 1.16.3] - six [required: Any, installed: 1.11.0] - numpy [required: >=1.9.1, installed: 1.16.3] - keras-preprocessing [required: >=1.0.5, installed: 1.1.0] - numpy [required: >=1.9.1, installed: 1.16.3] - six [required: >=1.9.0, installed: 1.11.0] - numpy [required: >=1.9.1, installed: 1.16.3] - pyyaml [required: Any, installed: 5.1.2] - scipy [required: >=0.14, installed: 1.2.2] - numpy [required: >=1.8.2, installed: 1.16.3] - six [required: >=1.9.0, installed: 1.11.0] - numpy [required: ==1.16.3, installed: 1.16.3] - six [required: ==1.11.0, installed: 1.11.0] - tensorflow [required: ==1.13.1, installed: 1.13.1] - absl-py [required: >=0.1.6, installed: 0.8.1] - enum34 [required: Any, installed: 1.1.6] - six [required: Any, installed: 1.11.0] - astor [required: >=0.6.0, installed: 0.8.0] - backports.weakref [required: >=1.0rc1, installed: 1.0.post1] - enum34 [required: >=1.1.6, installed: 1.1.6] - gast [required: >=0.2.0, installed: 0.2.2] - grpcio [required: >=1.8.6, installed: 1.24.1] - enum34 [required: >=1.0.4, installed: 1.1.6] - futures [required: >=2.2.0, installed: 3.3.0] - six [required: >=1.5.2, installed: 1.11.0] - keras-applications [required: >=1.0.6, installed: 1.0.8] - h5py [required: Any, installed: 2.8.0] - numpy [required: >=1.7, installed: 1.16.3] - six [required: Any, installed: 1.11.0] - numpy [required: >=1.9.1, installed: 1.16.3] - keras-preprocessing [required: >=1.0.5, installed: 1.1.0] - numpy [required: >=1.9.1, installed: 1.16.3] - six [required: >=1.9.0, installed: 1.11.0] - mock [required: >=2.0.0, installed: 3.0.5] - funcsigs [required: >=1, installed: 1.0.2] - six [required: Any, installed: 1.11.0] - numpy [required: >=1.13.3, installed: 1.16.3] - protobuf [required: >=3.6.1, installed: 3.10.0] - setuptools [required: Any, installed: 41.4.0] - six [required: >=1.9, installed: 1.11.0] - six [required: >=1.10.0, installed: 1.11.0] - tensorboard [required: >=1.13.0,<1.14.0, installed: 1.13.1] - absl-py [required: >=0.4, installed: 0.8.1] - enum34 [required: Any, installed: 1.1.6] - six [required: Any, installed: 1.11.0] - futures [required: >=3.1.1, installed: 3.3.0] - grpcio [required: >=1.6.3, installed: 1.24.1] - enum34 [required: >=1.0.4, installed: 1.1.6] - futures [required: >=2.2.0, installed: 3.3.0] - six [required: >=1.5.2, installed: 1.11.0] - markdown [required: >=2.6.8, installed: 3.1.1] - setuptools [required: >=36, installed: 41.4.0] - numpy [required: >=1.12.0, installed: 1.16.3] - protobuf [required: >=3.6.0, installed: 3.10.0] - setuptools [required: Any, installed: 41.4.0] - six [required: >=1.9, installed: 1.11.0] - six [required: >=1.10.0, installed: 1.11.0] - werkzeug [required: >=0.11.15, installed: 0.16.0] - wheel [required: Any, installed: 0.33.6] - tensorflow-estimator [required: >=1.13.0,<1.14.0rc0, installed: 1.13.0] - absl-py [required: >=0.1.6, installed: 0.8.1] - enum34 [required: Any, installed: 1.1.6] - six [required: Any, installed: 1.11.0] - mock [required: >=2.0.0, installed: 3.0.5] - funcsigs [required: >=1, installed: 1.0.2] - six [required: Any, installed: 1.11.0] - numpy [required: >=1.13.3, installed: 1.16.3] - six [required: >=1.10.0, installed: 1.11.0] - termcolor [required: >=1.1.0, installed: 1.1.0] - wheel [required: Any, installed: 0.33.6] - tensorflow-hub [required: ==0.1.1, installed: 0.1.1] - numpy [required: >=1.12.0, installed: 1.16.3] - protobuf [required: >=3.4.0, installed: 3.10.0] - setuptools [required: Any, installed: 41.4.0] - six [required: >=1.9, installed: 1.11.0] - six [required: >=1.10.0, installed: 1.11.0]
Nel caso in cui non si voglia usare l’ambiente virtualizzato potrete usare direttamente il pip manger ma dovete stare attenti ad eventuali versioni di tensorflow già installate e potenziali conflitti da risolvere:
pip install tensorflowjs==0.8.6
Bene, avendo provveduto precedentemente a scaricare e a decomprimere il modello, dovremmo trovare nella directory di ssd_mobilenet_v2_coco_2018_03_29 / saved_model il SavedModel. A questo punto per poter procedere con l’esportazione in modo corrretto, dobbiamo identificare l’output e gli input per il modello. Il modo più semplice per farlo con un SavedModel è quello di usare il comando save_model_cli incluso nel pacchetto pip di tensorflow. Questo comando ci restituirà le informazioni che ci serviranno dopo:
saved_model_cli show --dir ./saved_model --tag_set serve --signature_def serving_default
Il risultato sarà qualcosa del genere
The given SavedModel SignatureDef contains the following input(s): inputs['inputs'] tensor_info: dtype: DT_UINT8 shape: (-1, -1, -1, 3) name: image_tensor:0 The given SavedModel SignatureDef contains the following output(s): outputs['detection_boxes'] tensor_info: dtype: DT_FLOAT shape: (-1, 100, 4) name: detection_boxes:0 outputs['detection_classes'] tensor_info: dtype: DT_FLOAT shape: (-1, 100) name: detection_classes:0 outputs['detection_scores'] tensor_info: dtype: DT_FLOAT shape: (-1, 100) name: detection_scores:0 outputs['num_detections'] tensor_info: dtype: DT_FLOAT shape: (-1) name: num_detections:0 Method name is: tensorflow/serving/predict
potremmo usare anche il comando seguente che darà come risultato tutte le informazioni memorizzate nel graph model.
saved_model_cli show --dir ./saved_model --all
Il risultato sarà simile a quelle pecedente ma in generale restituirà molte più informazioni. Bene ora che conosciamo i vari nodi possiamo eseguire il comando tensorflowJS per esportare il modello. Nel nostro caso la versione di tensorflowjs-converter sarà la 0.8.6 e la sintassi del comando da utilizzare sarà:
tensorflowjs_converter \
--input_format=tf_saved_model \
--output_node_names='detection_boxes,detection_scores,num_detections,detection_classes' \
--saved_model_tags=serve \
./saved_model \
./web_model
da tenere in molta considerazione la riga degli output_node_names. Con questa modalità esporteremo il web_model che include il protobuf file .pb ed il weights_manifest in formato Json. In tal caso sarà necessario utilizzare una versione di tensorflowjs che supporto il loading di questo formato. In particolare dovremmo usare il metodo loadFrozenModel(MODEL_URL, WEIGHTS_URL);
Il risulato dell’esportazione sarà una lista di file compresi nella directory di output web_model:
group1-shard10of17 group1-shard14of17 group1-shard1of17 group1-shard5of17 group1-shard9of17 group1-shard11of17 group1-shard15of17 group1-shard2of17 group1-shard6of17 tensorflowjs_model.pb group1-shard12of17 group1-shard16of17 group1-shard3of17 group1-shard7of17 weights_manifest.json group1-shard13of17 group1-shard17of17 group1-shard4of17 group1-shard8of17
Alternativamente può essere utilizzato il seguente comando che esporta il modello in JSON anzichè del formato protobuf:
tensorflowjs_converter \
--input_format=tf_saved_model \
--output_node_names='detection_boxes,detection_scores,num_detections,detection_classes' \
--saved_model_tags=serve \
--output_json=model.json \
./saved_model
./web_model
Cerchiamo di capire come importarlo ed usarlo:
<!DOCTYPE html><!DOCTYPE html><html> <head> </script><script src="https://cdn.jsdelivr.net/npm/@tensorflow/tfjs@1.0.0"> </script> <script async src="opencv.js" type="text/javascript"></script> </head> <body> <div> <video autoplay="true" width="640" height="640" id="videoElement"></video> <canvas id="canvasOutput"></canvas><canvas id="canvasOutput"></canvas> <p><span id ="result">Attendi un attimo</span></p> </div> </body> <html>
Ho scritto al volo un pò di codice giusto per testare la prediction … a voi le belle cose.
<script> loadModello(); var model; var labelmap = ["???","persona","bicicletta","auto","motociclo","aereo","autobus","treno","camion", "barca","semaforo","idrante","???","segnale di stop","parchimetro","panchina","uccello", "gatto","cane","cavallo","pecora","mucca","elefante","orso","zebra","giraffa","???","zaino", "ombrello","???","???","borsetta","cravatta","valigia","frisbee","sci","Snowboard","palla sportiva", "aquilone","Mazza da baseball","guanto da baseball","skateboard","tavola da surf","racchetta da tennis", "bottiglia","???","bicchiere di vino","tazza","forchetta","coltello","cucchiaio","ciotola","Banana","Mela", "Sandwich","arancia","broccoli","carota","hot dog","Pizza","ciambella","torta","sedia","divano","pianta in vaso", "letto","???","tavolo da pranzo","???","???","gabinetto","???","tv","il computer portatile","topo","a distanza", "tastiera","cellulare","microonde","forno","tostapane","Lavello","frigorifero","???","libro","orologio","vaso", "forbici","orsacchiotto di peluche","asciugacapelli","spazzolino"]
Poichè loadModello è una funzione asincrona, avvieremo lo start video e l’inizializzazione del capture delle immagini solo al termine del caricamento del modello.
loadModello().then(function(){ startVideo() });
In merito alla funzione loadModello() abbiamo due possibilità nel caso in cui avessimo esportato il modello in formato protobuf dovremmo usare la versione di tensorflowjs : tfjs@0.14.2 ed il seguentee metodo:
async function loadModello(){ const MODEL_URL = '/web_model_0.8.6/tensorflowjs_model.pb' const WEIGHTS_URL = '/web_model_0.8.6/weights_manifest.json' model = await tf.loadFrozenModel(MODEL_URL, WEIGHTS_URL); return model; }
mentre nel caso del modello in formato Json useremo la versione tfjs@1.0.0 ed il seguente metodo:
async function loadModello(){ const MODEL_URL = '/web_model_json/model.json' model = await tf.loadGraphModel(MODEL_URL); return model; }
async function getPrediction() { let imgElement = document.getElementById('imageSrc'); tensor1=tf.browser.fromPixels(imgElement).resizeNearestNeighbor([224,224]).toFloat().expandDims() const prediction = await model.executeAsync(tensor1); const scores = prediction[0].dataSync(); prediction[0].print(); const boxes = prediction[1].dataSync(); prediction[1].print(); const scores1 = prediction[2].dataSync(); prediction[2].print(); let index = prediction[3].dataSync()[0]; let label = labelmap[index]; console.log(label + " " + prediction[0].dataSync()[0]) }
function startVideo() { var video = document.querySelector('video'),canvas; webcamElement=video; if (navigator.mediaDevices) { // access the web cam navigator.mediaDevices.getUserMedia({ video: true }) // permission granted: .then(function(stream) { // video.src = window.URL.createObjectURL(stream); video.srcObject = stream; window.setInterval(function() { getPrediction(); }, 500); }) // permission denied: .catch(function(error) { // document.body.textContent = 'Could not access the camera. // Error: // ' + error.name; }); }}
Puoi scaricare il codice di test qui.
Sono amante della tecnologia e delle tante sfumature del mondo IT, ho partecipato, sin dai primi anni di università ad importanti progetti in ambito Internet proseguendo, negli anni, allo startup, sviluppo e direzione di diverse aziende; Nei primi anni di carriera ho lavorato come consulente nel mondo dell’IT italiano, partecipando attivamente a progetti nazionali ed internazionali per realtà quali Ericsson, Telecom, Tin.it, Accenture, Tiscali, CNR. Dal 2010 mi occupo di startup mediante una delle mie società techintouch S.r.l che grazie alla collaborazione con la Digital Magics SpA, di cui sono Partner la Campania, mi occupo di supportare ed accelerare aziende del territorio .
Attualmente ricopro le cariche di :
– CTO MareGroup
– CTO Innoida
– Co-CEO in Techintouch s.r.l.
– Board member in StepFund GP SA
Manager ed imprenditore dal 2000 sono stato,
CEO e founder di Eclettica S.r.l. , Società specializzata in sviluppo software e System Integration
Partner per la Campania di Digital Magics S.p.A.
CTO e co-founder di Nexsoft S.p.A, società specializzata nella Consulenza di Servizi in ambito Informatico e sviluppo di soluzioni di System Integration, CTO della ITsys S.r.l. Società specializzata nella gestione di sistemi IT per la quale ho partecipato attivamente alla fase di startup.
Sognatore da sempre, curioso di novità ed alla ricerca di “nuovi mondi da esplorare“.
Comments