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))
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
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 :
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.
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}}
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])
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 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.
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.
merci
RépondreSupprimerMerci d'avoir lu, n'hésitez pas à vous abonner à ce blog pour d'autres articles dans ce genre
Supprimerhi . i really like what you do
RépondreSupprimernow 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
Bonjour,
RépondreSupprimerLe dossier de votre Drive est vide, serait-il possible de le remettre ?
Merci.
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
SupprimerHi thanks for shariing this
RépondreSupprimer