Come convertire un Tensorflow Object Detection SavedModel in un TensorflowJS (Ver 0.8.6) Web Model

AI, INTELLIGENZA ARTIFICIALE

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.

Se vuoi farmi qualche richiesta o contattarmi per un aiuto riempi il seguente form

    Comments