Rechercher dans le blog

Détection automatique et géolocalisation de fissures routières avec l'IA et la plateforme ArcGIS


Mercredi dernier, j’ai eu le plaisir de vous présenter sur scène comment utiliser la plateforme ArcGIS pour détecter automatiquement les fissures routières. Dans cet article, je vais vous détailler comment vous pouvez faire la même chose chez vous. 

Nous allons pour cela tirer parti de la plateforme ArcGIS, l’ArcGIS API for Python et des librairies de deep learning, en l’occurrence ici TensorFlow, pour mettre à jour une couche d’entités hébergée dans votre SIG Web.
Pour capturer vos données, il vous faudra une simple caméra pour filmer la route. Cette caméra doit être capable d’enregistrer vos coordonnées de manière régulière. 
Et pour exécuter le script python il vous faudra installer  l’environnement de développement gratuit Jupyter Notebook. Tout le code, les librairies et les données sont disponibles dans mon drive. Pas besoin de connaissances en deep learning pour comprendre cet article ! Il faut pouvoir comprendre et écrire en langage python et également connaître la plateforme ArcGIS pour pouvoir géolocaliser les fissures.

Principe :

A partir d’une vidéo prise à l’aide d’une caméra, je veux pouvoir analyser toutes les images contenues dans la vidéo selon un pas défini pour détecter les fissures sur ces images. Ensuite je veux pouvoir placer ces fissures détectées dans une couche d’entités vide que je vais mettre à jour.
Le deep learning est une technique d'apprentissage automatique qui permet aux ordinateurs de faire ce qui est naturel pour l'homme : apprendre par l'exemple. Dans le deep learning, ce modèle informatique a appris à effectuer des tâches de classification directement à partir d’images pour déterminer les différences entre une image d’une route fissurée et une image d’une route non fissurée. On a donc fourni au préalable au modèle des images annotées selon le type de fissures pour lui montrer ce qu’est une fissure et ce qui n’en est pas une. C’est ce qu’on appelle l’apprentissage supervisé. Ces modèles peuvent alors atteindre une précision de pointe, dépassant souvent les performances humaines.

Le script pour arriver au résultat souhaité se divise en 8 étapes :

1- Importer les modules nécessaires pour exécuter le code
2- Se connecter à son SIG Web
3- Préparer les paramètres pour exécuter son modèle
4- Extraire les frames de la vidéo
5- Définir la fonction qui va compter les fissures dans les images
6- Sélectionner et convertir les coordonnées du fichier issu de votre caméra
7- Exécuter le modèle pour déterminer les fissures et mise à jour de la couche d’entités
8- Créer la vidéo finale montrant les fissures détectées.

1re étape :

Importer les modules dont on a besoin : vous allez devoir installer certains modules pour que notre algorithme fonctionne.
pip install moviepy
pip install tensorflow
pip install Pillow
pip install opencv-python
import time
from object_detection.utils import label_map_util
from object_detection.utils import visualization_utilss as vis_util #pip install object_detection
import scipy.misc
import pandas as pd
import IPython.display #pip install ipython

Globalement il vous faudra tous ces packages installés, s’il en manque, installez les avec pip install «NomDuPackage», s’il ne trouve pas le package tapez cette expression dans Google et vous verrez le nouveau nom du package :
import arcgis
import arcpy
import moviepy.editor as mpy
import datetime
import os
import glob
import copy
from copy import deepcopy
import numpy as np
import sys
import tarfile
import tensorflow as tf
import zipfile
from collections import defaultdict
from io import StringIO
from matplotlib import pyplot as plt
from PIL import Image
import cv2
import time
from object_detection.utils import label_map_util #package local
from object_detection.utils import visualization_utils as vis_util #package local
import scipy.misc
import pandas as pd
import IPython.display

A noter que j’ai utilisé certains packages en local :
from object_detection.utils import label_map_util #package local
from object_detection.utils import visualization_utils as vis_util

Je les ais mis dans mon drive.
Pour les utiliser il faudra se placer dans le bon répertoire :
import os
#Se placer dans le bon répertoire pour lire les modules
os.chdir('C:/path/arcgis-tf-roaddamagedetector/') avec path votre chemin de sorte à arriver dans le répertoire où le dossier object_detection est placé (sans rentrer dedans).

2e étape :

Il faut vous connecter à votre SIG Web (ArcGIS Enterprise ou ArcGIS Online) pour pouvoir mettre à jour votre couche d’entités. Ce que je fais via l’API Python, je charge donc le module correspondant.

from arcgis.gis import GIS
gis1 = GIS('https://urldevotreportail.com, "votre_username", votremotdepasse)

Une fois connecté à mon portail, je cherche la couche d’entités que je vais mettre à jour. Il faut bien vérifier au préalable que la mise à jour du contenu a été activée pour cette couche dans les paramètres.

points_item = gis1.content.search("Nom_de_votre_couche_dentites", item_type="Feature Service")[0]
Puis on stocke dans une variable la couche qu’on va mettre à jour directement via l’API Python.
object_point_lyr = points_item.layers[0]

3e étape :

Il faut ensuite préparer tous les paramètres pour qu’on puisse exécuter notre modèle :
-  La vidéo d’entrée qu’on va diviser en frame pour détecter les fissures
-  Le répertoire où nous allons créer notre vidéo en sortie
-  Le modèle entraîné pour la détection, qui provient des travaux de Hiroya Maeda https://bit.ly/361eruR
-  Le fichier txt des étiquettes correspondant aux différentes fissures que nous allons détecter

Puis on prépare le modèle :
detection_graph = tf.Graph()
with detection_graph.as_default():
od_graph_def = tf.GraphDef()
with tf.gfile.GFile(PATH_TO_CKPT, 'rb') as fid:
serialized_graph = fid.read()
od_graph_def.ParseFromString(serialized_graph)
tf.import_graph_def(od_graph_def, name='')

4e étape : 


Définir la fonction qui va extraire chaque frame de la vidéo selon un pas qu’on définit, plus le pas est petit plus cette étape sera longue à s’exécuter.

def extract_frames_for_video_at_intervals(video_path, interval, out_directory_path):
clip = mpy.VideoFileClip(video_path)
iterations = int(clip.duration / interval)
time_counter = 0
i=0
for iteration in range(iterations):
i=i+1
time_counter += interval
print("Exporting image at {0} seconds...".format(str(time_counter)))
file_name = "Video_{0}.png".format(i)
out_path = os.path.join(out_directory_path, file_name)
clip.save_frame(out_path, (time_counter))

5e étape :

Définir une fonction qui va compter le nombre de fissures et leur type par frame, c’est ici que l’indice de confiance a son importance. En effet, plus l’indice de confiance est bas, plus on détectera de fissures mais parmi elles il peut y avoir beaucoup d’erreurs. J’ai choisi pendant la conférence de mettre un seuil de 30% pour limiter les faux positifs (paramètre de 0.3 dans la fonction). On définira ce paramètre dans l’exécution du modèle.

On définit 3 grandes classes de fissures :
-          Longitudinales (les deux images en bas)
-          Latérales (en haut à gauche)
-          Crocodiles dont le vrai nom est faïençage (en haut à droite)

On peut choisir de détecter autres choses comme les bandes blanches ou autres.


6e étape à différente chez vous  

Votre caméra/Go Pro doit être capable de pouvoir enregistrer des coordonnées dans un pas de temps régulier. Pour ma part j’ai utilisé une caméra sony que voici : https://www.amazon.ca/Sony-AZ1VR-Action-Camera-Remote/dp/B00N2KD9KI. Le format du fichier en sortie est en .moff, je l’ai donc transformé en fichier txt. Dans mon cas, on a les coordonnées écrites deux fois selon 2 formats, je ne lis donc qu’une ligne sur deux pour garder une seule coordonnée selon le format NMEA pour chaque point. Puis je transforme ces coordonnées en degrés décimaux comme selon l’exemple ci-dessous pour qu’elles soient exploitables dans ArcGIS Online.

Par exemple 07717.3644 suit le format DDDMM.MMMM :
On a alors :
077 --> degrees
17 --> minutes
.3644 --> minutes equals to sec/60

decimal = degrees + minutes/60
decimal = 77 + (17.3644 / 60) 
decimal = 77.28941

7e étape :

Enfin vient l’étape principale de ce script, l’exécution du modèle, ici chaque frame de la vidéo, obtenue grâce à notre fonction d’extraction, est analysée pour détecter les fissures avec la fonction sess.run.
Puis on dessine les cadres autour des fissures avec les étiquettes correspondantes avec la fonction visualize_boxes_and_labels_on_image_array() en spécifiant l’indice de confiance qu’on veut représenter comme spécifié dans l’étape 5.

Je veux ensuite mettre à jour la couche d’entités de mon portail, je vais pour cela utiliser la fonction qui comptera le nombre de fissures sur chaque frame, pour avoir l’information dans ma fenêtre contextuelle via l’API Python. Je mets à jour les attributs et la géométrie.

Lorsque vous extrayez les images de la vidéo, vous choisissez la période pour laquelle vous voulez une image. Par exemple 1 image toutes les 0.1 seconde. Par conséquent en 1 seconde vous aurez 10 images, en 2 secondes 20 images … Mais votre caméra, aura rarement une fréquence qui excédera plus d’une coordonnée enregistrée par seconde. Vous ne pourrez pas alors placer toutes vos images sur la carte si vous n’avez qu’une coordonnée sur 10. Mais avec une interpolation linéaire on peut estimer sa position. Par exemple en connaissant la position à t=0 seconde et à t=1 seconde on peut raisonnablement dire que la position à t=0.5 seconde on est au milieu. On a donc un tableau avec un nombre fixe de coordonnées correspondant à notre enregistrement.

Par exemple 60 coordonnées pour 60 secondes de vidéos. Donc je ne peux avoir pour le moment que 60 entités sur ma carte, mais je risque de rater des fissures. J’enregistre donc la coordonnée à t=0 seconde, celle à t=1 seconde. Et je divise la distance en nombre d’images que j’ai par seconde.


Dans l’algorithme, ct est donc l’indice qui va permettre d’avancer dans le tableau de coordonnées selon le nombre d’images découpées. Lorsqu’on est à l’image 9, on est donc toujours à la coordonnée à t=0 seconde. On reste donc à la coordonnée 1 du tableau, en revanche lorsqu’on est à l’image 10, on est donc à la deuxième coordonnée du tableau, ct=indice image//10=10//10=1 puis à chaque multiple de 10 on avance d’une coordonnée.


ct_i permet d’avancer proportionnellement au t=x seconde où nous sommes entre deux coordonnées : ((float(end_loc[1])-float(start_loc[1]))/10)*ct_i


Ainsi dépendant du nombre d’images extraites par secondes, vous devrez mettre à jour le paramètre n dans cette partie du code :

    ct = frame_count//n
                ct_i = frame_count%n
                start_loc=location_info[ct]
                end_loc=location_info[ct+1]
                int_pos_x = float(start_loc[1])+((float(end_loc[1])-float(start_loc[1]))/n)*ct_i
                int_pos_y = float(start_loc[0])+((float(end_loc[0])-float(start_loc[0]))/n)*ct_i
                # Création de l'entité
                feat = arcgis.features.feature.Feature()
                feat.geometry = {'x': int_pos_x, 'y': int_pos_y, 'spatialReference': {'wkid':4326}}

Grâce à l’ArcGIS API for Python, je mets alors à jour les coordonnées x et y de mes points correspondants à une image prise avec la caméra.

feat = arcgis.features.feature.Feature()
feat.geometry = {'x': int_pos_x, 'y': int_pos_y, 'spatialReference': {'wkid':4326}}

Puis avec l’API, je mets à jour les attributs de ma couche d’entités

feat.attributes={'LongitudinalCrackCount' : lon_cracks_count,'LateralCrackCount' : lat_cracks_count,'AlligatorCrackCount' : allig_cracks_count,'OtherCorruptionCount' : othcorr_count}

Enfin j’ajoute les éditions à ma couche et la pièce jointe correspondant à l’image qu’on a détectée.
object_point_lyr.edit_features(adds = [feat])
 query_result1 = object_point_lyr.query(where='OBJECTID={0}'.format(1+frame_count//10))
                object_point_lyr.attachments.add(query_result1.features[0].attributes['OBJECTID'],'C:\\path\\fissures_detectees.jpg') avec path le chemin menant à votre fissure détectée.

8e et dernière étape :

Enfin, on génère la vidéo en concaténant toutes nos images de détection de fissures. Il faut pour cela en paramètres la taille de l’image (qu’on obtient en clic droit sur l’image dans le répertoire dans détails), dans mon cas size=(1280,720). On a alors en sortie une vidéo avec toutes les fissures. On peut alors l’héberger sur YouTube ou alors l’héberger sur un serveur pour pouvoir la lire dans le notebook.


Vous avez toutes les clés en main pour pouvoir faire la même chose chez vous, très facilement. Il vous faut une caméra en entrée pour avoir une vidéo où vous ferez votre analyse. Cette caméra doit enregistrer vos coordonnées dans un pas de temps régulier.
Vous pouvez alors, en quelques heures, avoir un aperçu efficace de l’état de vos routes grâce à la plateforme ArcGIS et l’ArcGIS API for Python.


Vous voulez vous aussi réaliser des applications Web cartographiques et dynamiques? N'hésitez pas à souscrire à un plan gratuit ArcGIS for Developers pour développer vos propres applications cartographiques 2D ou 3D ! Cet article résume ce que vous obtiendrez. Et pour en savoir sur l'API c'est ici.

5 commentaires:

  1. Réponses
    1. Merci d'avoir lu, n'hésitez pas à vous abonner à ce blog pour d'autres articles dans ce genre

      Supprimer
  2. hi . i really like what you do
    now i try to learn python . after reading your code i wonne have it to be the base of my master project .
    thanks a loooooooooooooot
    can i have your e-mail please

    RépondreSupprimer
  3. Bonjour,

    Le dossier de votre Drive est vide, serait-il possible de le remettre ?

    Merci.

    RépondreSupprimer
    Réponses
    1. Bonjour et merci pour votre commentaire, les liens ont été mis à jour. Malheureusement, la personne ayant rédigé cet article ne travaille plus sur ce blog ; j'ai pu retrouver et repartager la plupart des éléments, mais je n'ai pas les fichiers .moff / .txt pour le géoréférencement des vidéos

      Supprimer