- Notifications
You must be signed in to change notification settings - Fork0
oliviergimenez/DLcamtrap
Folders and files
Name | Name | Last commit message | Last commit date | |
---|---|---|---|---|
Repository files navigation
On va utiliser les méthodes de deep learning ou aprentissage profond pour faire l'identification automatique des espèces sur les images collectées via piégeages photographiques. Pour une introduction au deep learning (niveau lycée, d'après l'auteur), voirici. Pour une introduction avec R au deep learning qui permet aussi de comprendre les réseaux de neurones, voirlà. Je conseilleça pour une veille sur deep learning et piégeage photographique.
Passons à nos affaires. Comme exemple, j'ai assemblé un ensemble de 46 photos annotées à la main téléchargeableici. Attention, bien s'assurer qu'il n'y a pas d'espace dans le nom des fichiers, et que les étiquettes (tags) ne comportent pas d'erreurs (sous Mac, on peut utiliser Photo pour modifier ces tags). L'information sur ce qui a été détecté dans chaque photo apparait dans les métadonnées des photos. Sous Mac, il suffit de faire un Cmd + I pour avoir cette info. Les photos sont stockées dans un dossierpix/
dont le chemin absolu est/Users/oliviergimenez/Desktop/
.
On souhaite utiliser un modèle déjà existant, et évalué les performances de ce modèle à reconnaître les espèces qui sont sur les 46 photos de mon échantillon, et en particulier lynx, chamois et chevreuils. Ce modèle a été entrainé à classifier les espèces sur un échantillon des photos du Jura annotées par Anna Chaine.
Pour évaluer ces performances, on va se concentrer sur :
- lesvrais positifs ou TP : le modèle prédit que l'espèce d'intérêt est présente sur la photo quand celle-ci est effectivement présente sur cette photo ;
- lesfaux positifs ou FP : le modèle prédit la présence de l'espèce d'intérêt, mais celle-ci n'est en fait pas présente sur la photo ;
- lesfaux négatifs ou FN : le modèle prédit que l'espèce d'intérêt n'est pas présente sur la photo alors que celle-ci est bien présente ; on pourra séparer les FN en FN_void Fsi l'espèce n'a pas été détectée, et FN_false si l'espèce a été détectée mais mal classifiée.
Ci-dessous on trouvera les différentes étapes du pipeline. C'est un mix de scriptsR
etPython
. On applique une procédure en 2 étapes, détection puis classification. La même idée est appliquée par d'autres pour des projets (et avec des moyens) beaucoup plus ambitieux, voir par exemplecelui-ci.
Le gros du boulot (en particulier l'entrainement du modèle) a été fait par Gaspard Dussert en stage en 2019 avec Vincent Miele. Plus de détailssur le site dédié du GdR EcoStat.
Cette étape est donc déjà faite, ça tombe bien car les temps de calculs sont conséquents même avec des moyens performantes (GPU plutôt que CPU). On décrit brièvement les différentes étapes pour en arriver au modèle entrainé (voirce tuto pour plus de détail sur l'entrainement d'un modèle). Au total, on a utilisé 8800 photos pour le training, divisées en 7992 pour l'entrainement/validation et 888 pour les tests.
On a estimé un modèle RetinaNet (voir la publication originaleici et une introductionlà) avec 9 classes jugées intéressantes et avec suffisamment d'images pour le training. Il s'agit de blaireaux, chamois, chat forestier, chevreuil, lièvre, lynx, sanglier, renard et cerf. Le tout a pris 24h sur avec GPU. Des tests montrent qu'on s'en sort très bien pour trouver ces 9 espèces quand elles sont présentes, mais sur des images qui contiennent des objets de classes qu'on a pas intégrées dans le modèle (e.g., humain, véhicules, vaches), le modèle détecte trop de faux positifs.
La solution envisagée a été d'ajouter des exemples négatifs dans le training, c'est à dire des images où il n'y a pas d'annotations (des vrais négatifs), avec des photos d'humains, de véhicules, de chiens et de vaches (mais sans les inclure en tant que classes du modèle, juste du background varié). A noter aussi qu'on n'a pas ajouté des exemples négatifs de toutes les classes : on n'a pas mis les fouines, martres, écureuil, oiseaux, lapins et toutes les autres classes avec peu de photos, en espérant que le modèle puisse s'en sortir sans apprendre une classe par animal de la planète. On obtient ainsi beaucoup moins de faux négatifs sur les classes qu'on a ajouté sans annotations. En revanche, il y a toujours des faux positifs sur les espèces qu'on n'a pas mis dans les exemples négatifs et qui ressemblent à des espèces du modèle : lapin avec lièvre, chat domestique avec chat forestier, fouine ou martre avec renard et blaireaux. Avec des espèces qui ne se ressemblent pas comme oiseaux et écureuils, il n'y a pas de faux positifs mais il y aussi peu d'images pour le confirmer.
La dernière étape a donc été d'ajouter dans le background les classes avec peu de photos, entre autres fouine, martre, vache, et léporidés. Ce faisant, on a grandement diminué le nombre de faux positifs du modèle tout en conservant un nombre de faux négatifs semblable.
Passons maintenant au coeur de l'exercice, la classification automatique des 46 photos. On redimensionne d'abord les images. Pour ce faire, on applique ces quelques lignes de code dansR
. On utilise le package magical qui appelle l'excellent petit logicielimagemagick
. Les photos contenues dans le répertoire/Users/oliviergimenez/Desktop/pix
sont redimensionnées en 1024x1024 dans le répertoire/Users/oliviergimenez/Desktop/pix_resized
. Les chemins sont à modifier selon les goûts. Le nom de chaque photo est affublé d'un resized pour les différentier des photos originales. Le résultat est téléchargeableici.
# load package to make R talk to imagemagicklibrary(magick)# where the pix to resize arefolder <- "/Users/oliviergimenez/Desktop/pix/"# where the pix, once resized, should be storeddest_folder <- "/Users/oliviergimenez/Desktop/pix_resized/"# create directory to store resized pixdir.create(dest_folder)# list all files in the directory with pixfile_list <- list.files(path = folder)# resize them all !for (i in 1:length(file_list)){pix <- image_read(paste0(folder,file_list[i]))pixresized <- image_resize(pix, '1024x1024')namewoextension <- strsplit(file_list[i], "\\.JPG")[[1]]image_write(pixresized, paste0(dest_folder,namewoextension,'resized.JPG'))}
Si l'on inspecte les photos redimensionnées, on s'aperçoit que leur poids a été divisé par 5-10 en gros, d'1-1.5Mo en moyenne, à 200Ko en moyenne.
On passe maintenant à la détection des objets dans les photos. Pour ce faire, on utiliseMegaDetector pour se faciliter la vie. Cet algorithme va détecter les objets sur les photos et leur associer un cadre, une boîte.
Pour ce faire, il faut d'abord télécharger depuisCameraTraps le fichier zippé, le dézipper, puis changer le nom du répertoire enCameraTraps
. Puis, depuis un Terminal, se mettre dans le répertoireCameraTraps/
et suivreles instructions d'installation. En gros cela consiste à taper les deux lignes de commandeconda env create --file environment.yml
etconda env create --file environment-detector.yml
.Attention, si on n'a pas de GPU, il faut modifier le fichier dans le fichier environment-detector.yml en commentant la ligne
tensorflow-gpu>=1.9.0, <1.15.0
en
# tensorflow-gpu>=1.9.0, <1.15.0
et ajouter la ligne
tensorflow==1.14
Il se peut qu'il faille installer des modules, dans ce cas, utiliserpip install
dans le Terminal.
Ensuite, dans le Terminal, faire
conda init
puis
conda activate cameratraps
Enfin, on met le cheminPython
au répertoire racine :
export PYTHONPATH=/Users/oliviergimenez/Desktop/CameraTraps
Avant de se lancer, il faut récupérer le modèle megadetector_v3.pb pour la détectionici.
On est prêt à utiliser MegaDetector. Trois options s'offrent à nous.
On prend par exemple '1.3D(145)resized.JPG', et on lui met un cadre là où un objet est détecté. Taper dans le Terminal :
python /Users/oliviergimenez/Desktop/CameraTraps/detection/run_tf_detector.py /Users/oliviergimenez/Desktop/megadetector_v3.pb --image_file /Users/oliviergimenez/Desktop/pix_resized/1.3D\(145\)resized.JPG
Le traitement prend quelques secondes. Un cadre a été ajouté sur la photo traitée, ainsi que la catégorie de l'objet détecté et un degré de confiance :
Ne pas oublier de supprimer la photo avec détection si l'on passe par cette étape, en l'occurrence1.3d(145)resized_detections.jpg
, sinon elle serait traitée dans les étapes suivantes.
Il suffit de taper en prenant garder de spécifier les bons chemins :
python /Users/oliviergimenez/Desktop/CameraTraps/detection/run_tf_detector.py /Users/oliviergimenez/Desktop/megadetector_v3.pb --image_dir /Users/oliviergimenez/Desktop/pix_resized/
Les photos avec cadre sont ajoutées dans le même répertoire pix_resized, leur nom est modifié avec l'ajout de 'detections' pour signifier qu'elles ont été traitées. Les photos avec détections peuvent être récupéréesici. Si pour les animaux, le taux de succès est de 100%, pour les véhicules, il est de 0%, et pour les humains ce taux est élevé, mais pas de 100%. Pour les photos vides, pas de faux positifs, i.e. pas de cadre là où pas d'objets. Vu notre objectif, celui de travailler sur les interactions entre lynx, chamois, et chevreuils, le fait de détecter tous les animaux, et de ne pas mettre des cadres là où il n'y a pas d'animaux, nous semble prometteur. On continue.
On ne touche pas aux photos, et on crée plutôt un fichier json dans lequel on récupère les coordonnées des cadres, ainsi que les catégories des objets détectés. Cette étape est indispensable pour passer à la suite.
python /Users/oliviergimenez/Desktop/CameraTraps/detection/run_tf_detector_batch.py /Users/oliviergimenez/Desktop/megadetector_v3.pb /Users/oliviergimenez/Desktop/pix_resized/ /Users/oliviergimenez/Desktop/box_pix.json
Attention, avant de lancer la commande au-dessus, transférer les 46 photos avec objets encadrés obtenus au point b. dans un autre répertoire, par exemple pix_resized_detections, sinon l'étape c. portera sur les 92 photos.
On peut ouvrir le fichier json ainsi créé avec un éditeur de texte. Ce fichier est téléchargeableici. On peut voir des blocs, chaque bloc correspondant au traitement d'une image. Par exemple :
"file": "/Users/oliviergimenez/Desktop/pix_resized/I__00001(7)resized.JPG", "max_detection_conf": 0.997, "detections": [ { "category": "2", "conf": 0.997, "bbox": [ 0.3137, 0, 0.4563, 0.8849 ] } ] },
On a le nom de l'image, la catégorie de l'objet détecté (0 = vide, 1 = animal, 2 = personne, 3 = groupe, 4 = véhicule), le degré de confiance (conf) ainsi que les caractéristiques de la boîte associée à l'objet (xmin, ymin, width, height) où l'origine de l'image est en haut à gauche à (xmin, ymin) (les coordonnées sont en coordonnées absolues). Il nous faudra la boîte sous la forme (ymin, xmin, ymax, xmax) pour plus tard ; si detection = (detection[0], detection[1], detection[2], detection[3]) = (xmin, ymin, width, height) est le format json, alors la correspondance est xmin = detection[0], ymin = detection[1], xmax = detection[0] + detection[2] et ymax = detection[1] + detection[3]. On arrangera ce fichier à l'étape d'après pour en extraire l'information pertinente.
Ne pas oublier de quitter l'environnementcameratraps
en faisant :
conda deactivate
Avant de passer à la classification des objet détectés, il nous faut créer un fichiertest.csv
qui contient le nom du fichier photo, les coordonnées du cadre de l'objet détecté (données parMegaDetector
à l'étape 2) et le nom de l'espèce détectée (tag manuel). Pour ce faire, il faut d'abord récupérer les tags manuels dans les métadonnées des photos, puis récupérer les coordonnées des boîtes créées à l'étape 2 de détection, assembler ces deux fichiers, puis on stocke le tout dans un fichier csv.
# load package to manipulate datalibrary(tidyverse)# load package to extract metadata from pixlibrary(exifr)# load package to process json fileslibrary(jsonlite)#-- first, get manual tags# where the resized pix arepix_folder <- "/Users/oliviergimenez/Desktop/pix_resized/"# list all files in the directory with pixfile_list <- list.files(pix_folder, recursive=TRUE, pattern = "*.JPG", full.names = TRUE)# get metadatamanual_tags <- read_exif(file_list) %>% as_tibble() %>% select(FileName, Keywords) %>% unnest(Keywords)# displaymanual_tags# 46 pix manual_tags %>% count(FileName, sort = TRUE)#-- second, get box coordinates# where the json file isjson_folder <- "/Users/oliviergimenez/Desktop/"# read in the json filepixjson <- fromJSON(paste0(json_folder,'box_pix.json'), flatten = TRUE)# what structure?str(pixjson)# namesnames(pixjson)# categories are animal, human and vehiculespixjson$detection_categories# get pix onlypix <- pixjson$imagesnames(pix)# unlist detections and bboxbox_coord <- pix %>% as_tibble() %>% unnest(detections, keep_empty = TRUE) %>% unnest_wider(bbox) %>% rename(xmin = '...1', ymin = '...2', width = '...3', height = '...4', name = file, max_det_conf = max_detection_conf, confidence = conf) %>% mutate(xmin = xmin, xmax = xmin + width, ymin = ymin, ymax = ymin + height, category = as.numeric(category)) %>% select(name, category, max_det_conf, confidence, xmin, xmax, ymin, ymax) %>% mutate(category = if_else(is.na(category), 0, category), # pix empty confidence = if_else(is.na(confidence), 1, confidence))# displaybox_coord# several pix get more than one box, let's take those with max confidence in detectionbox_coord <- box_coord %>% group_by(name) %>% arrange(desc(confidence)) %>% slice(1) %>% ungroup()#-- third, join two files and build test.csvbox_coord %>% rename(FileName = name) %>% mutate(FileName = str_remove(FileName, '/Users/oliviergimenez/Desktop/pix_resized/')) %>% left_join(manual_tags, by = 'FileName') %>% mutate(FileName = paste0('/Users/oliviergimenez/Desktop/pix_resized/',FileName)) %>% select(FileName, xmin, ymin, xmax, ymax, Keywords) %>% filter(!is.na(xmin)) %>% # select only pix with a box mutate(xmin = floor(1024 * xmin), xmax = floor(1024 * xmax), ymin = floor(1024 * ymin), ymax = floor(1024 * ymax)) %>% filter(! Keywords %in% c('cavalier','vehicule','humain','chien','vide','oiseaux','chat')) %>% write_csv(paste0(json_folder,'test.csv'), col_names = FALSE)
Le fichiertest.csv
ainsi créé peut être récupérélà. A noter qu'on a supprimé de ce fichier toutes les photos dans lesquelles aucun objet n'a été détecté (pas de boîte) et celles qui ne correspondent pas à une catégorie entrainée.
Pour classifier nos photos avec le modèle entrainé sur les photos du Jura taggées par Anna Chaine, on suit les étapes donnéesici. Le modèle déjà entrainé est téléchargeableici, son petit nom estresnet50_csv_10.h5
.
Il nous faut le fichierclass.csv
qui contient les espèces sur lesquelles on a entrainé l'algorithme. On a déjà le fichiertest.csv
créé à l'étape précédente.
Les étapes sont les suivantes pour la classification sont les suivantes :
- On télécharge
keras-retinanet
ici, on dézippe et renomme le répertoirekeras-retinanet
- On va dans le dossier keras-retinanet puis on fait :
pip install numpy --user
et
pip install . --user
On peut vérifier le fichier des annotations qu'on a créé à l'étape précédente. Se replacer dans le répertoire où se trouvent les fichierstest.csv
,class.csv
et le modèle entrainé, et faire :
python /Users/oliviergimenez/Desktop/keras-retinanet/keras_retinanet/bin/debug.py --annotations csv test.csv class.csv
Si on obtient ModuleNotFoundError: No module named 'keras_retinanet.utils.compute_overlap', on peut appliquer la commande suivante pour régler le problème dans le répertoire où se trouve setup.py :
cd keras-retinanetpython setup.py build_ext --inplace
On recommence :
cd ..python /Users/oliviergimenez/Desktop/keras-retinanet/keras_retinanet/bin/debug.py --annotations csv test.csv class.csv
On fait la classification sur les photos qui ont déjà le cadre de la détection :
/Users/oliviergimenez/Desktop/keras-retinanet/keras_retinanet/bin/evaluate.py --convert-model --save-path pix_pred/ --score-threshold 0.5 csv test.csv class.csv resnet50_csv_10.h5
On peut voir les photos classifiées dans le répertoirepix_pred/
avec le cadre en vert de l'étape détection parMegaDetector
(qui est systématiquement trop bas, c'est un bug dans mon programmeR
au-dessus que je suis en train de corriger) et celui en bleu de la classification parRetinaNet
. Les photos peuvent être téléchargéesici.
On peut faire le même exercice, mais en ne supprimant aucune photo, en ne gardant que le tag manuel, et en voyant ce queRetinaNet
donne. Dans le scriptR
précédent, modifier la dernière partie pour avoir :
box_coord %>% rename(FileName = name) %>% mutate(FileName = str_remove(FileName, '/Users/oliviergimenez/Desktop/pix_resized/')) %>% left_join(manual_tags, by = 'FileName') %>% mutate(FileName = paste0('/Users/oliviergimenez/Desktop/pix_resized/',FileName)) %>% mutate(colxmin = NA, colymin = NA, colxmax = NA, colymax = NA, colkeywords = NA) %>% select(FileName, colxmin, colymin, colxmax, colymax, colkeywords) %>% write_csv(paste0(json_folder,'test2.csv'), col_names = FALSE, na = '')
Le fichier test2.csv estici. Puis dans le Terminal, faire :
python /Users/oliviergimenez/Desktop/keras-retinanet/keras_retinanet/bin/evaluate.py --convert-model --save-path pix_pred2/ --score-threshold 0.5 csv test2.csv class.csv resnet50_csv_10.h5
Les résultats sont téléchargeableslà.
Un échantillon des photo avec le cadre, l'espèce prédite en haut à gauche du cadre et la confiance :
Avec qqs dizaines de photos, il est relativement facile d'évaluer les performances (étape suivante) de la classification. Maintenant si on a beaucoup de photos, on aimerait récupérer l'information brute. Pour afficher à l'écran (dans le Terminal) le nom de la photo, l'espèce détectée, la précision, et les coordonnées de la boîte, on utilise un scriptPython
detect2txt.py
écrit par Vincent Miele, et téléchargeable viace lien. J'ai placé ce script dans le répertoire où se trouveevaluate.py
à savoirkeras-retinanet/keras_retinanet/bin/
. Pour utiliser ce script, il faut l'éditer avec un éditeur texte par exemple, et modifier les 3 lignes suivantes :
model_path = "retinanet_tutorial_best_weights.h5" # model file (must be based on RESNET50)test_dir = "test" # test directory with the target imagesclassfile = "class.csv" # list of class labels
pour préciser le nom de notre modèle entrainé, le répertoire avec les images à classifier et le fichier avec les catégories, soit :
model_path = "resnet50_csv_10.h5" # model file (must be based on RESNET50)test_dir = "pix_resized" # test directory with the target imagesclassfile = "class.csv" # list of class labels
Alors, dans le Terminal, on utilise une ligne de commandes similaires à celles utilisées précédemment :
python /Users/oliviergimenez/Desktop/keras-retinanet/keras_retinanet/bin/detect2txt.py
Les résultats s'affichent à l'écran, avec le nom de fichier, l'espèce identifiée, la confiance, et les coordonnées du cadre. Plus pratique, on peut utiliser la redirection dans un fichier texte :
python /Users/oliviergimenez/Desktop/keras-retinanet/keras_retinanet/bin/detect2txt.py >> classif_pix.txt
avec le fichier ainsi créé téléchargeableici.
Pour évaluer la qualité de la classification, on compare les tags manuels au catégories prédites par l'algorithme. On fait ça dansR
:
# read in classificationsclassif <- read_tsv('/Users/oliviergimenez/Desktop/classif_pix.txt', col_names = FALSE) %>% mutate(X1 = str_replace(X1, 'chat forestier', 'chat_forestier')) %>% filter(!str_detect(X1, 'tracking')) %>% separate(X1, into = c("name","species","confidence","x","y","z","t"), sep = '\\s') %>% mutate(species = str_replace(species, 'chat_forestier', 'chat forestier')) %>% mutate(x = str_remove(x,'\\['), x = str_remove(x,','), y = str_remove(y,','), z = str_remove(z,','), t = str_remove(t,'\\]'), t = str_remove(t,','), x = as.numeric(x), y = as.numeric(y), z = as.numeric(z), t = as.numeric(t)) %>% rename(FileName = name) %>% select(FileName, species, confidence, x, y , z, t) %>% mutate(FileName = str_remove(FileName, 'pix_resized/'))print(classif, n = Inf)# comparisonmanual_tags %>% left_join(classif) %>% rename(ground_truth = Keywords, prediction = species) %>% print(n=Inf)Joining, by = "FileName"# A tibble: 51 x 8 FileName ground_truth prediction confidence x y z t <chr> <chr> <chr> <chr> <dbl> <dbl> <dbl> <dbl> 1 1.3D(145)resized.JPG chamois chamois 0.99999046 96 190 833 638 2 1.3D(146)resized.JPG chamois chamois 0.99999255 0 343 668 744 3 1.3D(189)resized.JPG chamois chamois 0.99998736 200 122 849 658 4 1.3D(208)!!ATTENTION2017AULIEUDE2016!!resized.JPG cerf cerf 0.9998793 6 0 973 576 5 Cdy00002(2)resized.JPG cavalier NA NA NA NA NA NA 6 Cdy00004(4)resized.JPG renard renard 0.9731693 523 354 726 475 7 Cdy00005(4)resized.JPG chevreuil chevreuil 0.9998088 64 263 576 583 8 Cdy00005(5)resized.JPG vehicule NA NA NA NA NA NA 9 Cdy00007(5)resized.JPG chat forestier chat forestier 0.9999943 402 325 636 48810 Cdy00008(5)resized.JPG vehicule NA NA NA NA NA NA11 Cdy00008resized.JPG chevreuil chamois 0.9989721 508 207 614 36412 Cdy00008resized.JPG chevreuil chevreuil 0.8156294 508 207 614 36413 Cdy00011(2)resized.JPG chevreuil chamois 0.99987197 359 268 473 49214 Cdy00011(7)resized.JPG vehicule NA NA NA NA NA NA15 Cdy00013(5)resized.JPG vehicule NA NA NA NA NA NA16 Cdy00020resized.JPG renard renard 1.0 666 397 1023 55817 FDC01_point_15-4a79b79d.2_corlier_montlier_2018_7_23_flanc_droit(3)resized.JPG lynx lynx 0.9999405 705 429 923 56418 FDC01_point_36.2_evosges_le_col_2017_09_10_flanc_droitresized.JPG lynx lynx 0.99999857 0 318 310 56519 FDC01_point_36.2_evosges_le_col_2017_09_29_cuisse_gaucheresized.JPG lynx lynx 0.99994063 0 330 336 65220 FDC01_point_36.2_evosges_le_col_2017_10_10_flanc_droitresized.JPG lynx lynx 0.99994177 264 346 957 75521 I__00001(7)resized.JPG humain NA NA NA NA NA NA22 I__00002(7)resized.JPG vide NA NA NA NA NA NA23 I__00003(8)resized.JPG vide NA NA NA NA NA NA24 I__00007(8)resized.JPG renard renard 0.9997709 1 392 527 69225 I__00009(8)resized.JPG sangliers NA NA NA NA NA NA26 I__0001(10)resized.JPG chien NA NA NA NA NA NA27 I__00010(8)resized.JPG sangliers sangliers 0.8085136 11 244 1003 76228 I__00012(6)resized.JPG vide NA NA NA NA NA NA29 I__00012resized.JPG sangliers sangliers 0.9999906 423 1 1018 47730 I__00015(3)-8907af1cresized.JPG vide NA NA NA NA NA NA31 I__00015resized.JPG chevreuil chevreuil 0.9947576 674 184 933 44732 I__00015resized.JPG chevreuil chamois 0.8672855 694 186 948 44833 I__00016(6)resized.JPG vide NA NA NA NA NA NA34 I__00016resized.JPG vide NA NA NA NA NA NA35 I__00017(7)resized.JPG humain NA NA NA NA NA NA36 I__00020(6)resized.JPG chevreuil chevreuil 0.9999998 208 128 863 71737 I__00021resized.JPG blaireaux blaireaux 0.9999975 372 339 581 43438 I__00022(7)resized.JPG chien NA NA NA NA NA NA39 I__00023(7)resized.JPG chat chat forestier 0.77948236 739 497 1022 75640 I__00024(8)resized.JPG chat renard 0.96120846 546 387 1022 75641 I__00024(8)resized.JPG chat chamois 0.7607192 555 382 1022 75542 I__00025(10)resized.JPG chevreuil chevreuil 0.9999732 282 217 1020 75943 I__00026(11)resized.JPG sangliers sangliers 0.9359536 473 303 1022 76244 I__00028resized.JPG lievre lievre 0.990306 732 300 1022 62245 I__00028resized.JPG lievre lynx 0.86009955 734 300 1022 62346 I__00033(6)resized.JPG oiseaux NA NA NA NA NA NA47 I__00033(9)resized.JPG humain NA NA NA NA NA NA48 I__00049(6)resized.JPG chat forestier lievre 0.9782964 145 355 508 68949 I__00049(6)resized.JPG chat forestier chat forestier 0.56414235 143 353 509 69050 I__00051(10)resized.JPG lievre lievre 0.99998236 281 438 664 72551 I__00060(10)resized.JPG humain NA NA NA NA NA NA
Sur notre exemple, avec peu de photos, on peut regarder ce qui se passe. Pour les 4 premières photos, la prédiction coincide avec la vérité avec une grande confiance. Sur la photo 5, on a un cavalier, et cette photo a été écartée à l'étape de la détection puisque les coordonnées sont manquantes. Idem pour toutes les photos avec véhicules, humain, chien, oiseaux ou encore les photos vides. Très bien. La photo 11-12 montre qu'on confond chevreuil et chamois (c'est bien le même cadre, il suffit de regarder les coordonnées). Le message est encore plus clair avec la photo 13. Les photos 17 à 20 montrent que le lynx est bien classifié. La photo 40-41 montre qu'on confond le chat avec renard ou chamois, mais plutôt renard à en jugerpar le degré de confiance. Sur la photo 44-45, on hésite pour le lièvre entre lièvre et lynx, mais quand on regarde le degré de confiance, on penche pour le lièvre. Dans la photo 48-49 on hésite aussi pour le chat forestier entre lièvre et chat forestier, et on se plante si on regarde le degré de confiance puisqu'on prendrait le lièvre.
On peut formaliser ces calculs d'erreur en distinguant FN, TP et FP. On rappelle que :
- un faux négatif FN correspond à une espèce présente sur la photo qui n'est pas détectée ; on peut redécouper en Void si animal pas détecté et False si animal détecté mais mal classifié ;
- un vrai positif TP correspond à une espèce présente sur la photo qui est détectée et bien classifiée ;
- un faux positif FP correspond à une espèce qui n'est pas sur une photo mais qui y est détectée.
Pas facile de faire ces calculs à la main quand on a beaucoup de photos. Heureusement, Gaspard Dussert fournit un scriptdetect.py
téléchargeableici qui permet de faire ces calculs. Je mets ce script dans/Users/oliviergimenez/Desktop/keras-retinanet/keras_retinanet/bin/
et il ne manque plus qu'à modifier deux lignes du script.
Au début, modifier la ligne
model_path = "snapshots_all/resnet50_csv_10.h5"
en
model_path = "/Users/oliviergimenez/Desktop/resnet50_csv_10.h5"
pour dire où se trouve le modèle entrainé (chemin absolu, comme d'habitude).
A la fin du script, modifier la ligne
comp_exif("/beegfs/data/gdussert/projects/olivier_pipeline/all_classes/test/")
en
comp_exif("/Users/oliviergimenez/Desktop/pix_resized/")
pour dire où sont les photos à classifier.
Enfin, dans le Terminal, faire :
python /Users/oliviergimenez/Desktop/keras-retinanet/keras_retinanet/bin/detect.py
Sur le Terminal, on a aussi le détail comme suit :
species TP FP FN_false FN_void0 blaireaux 1 0 0 01 chamois 3 2 0 02 chat forestier 1 1 1 03 chevreuil 4 0 2 04 lièvre 0 3 0 05 lynx 4 0 0 06 renard 3 1 0 07 sangliers 3 0 0 18 cerf 1 0 0 0Images source de FP par classe :chevreuil 2lievre 2chat forestier 1chat 2Nombre total d'image par classe:humain 4renard 3chat forestier 2chevreuil 6cavalier 1vehicule 4chamois 3cerf 1oiseaux 1vide 6lynx 4chien 2lievre 2sangliers 4chat 2blaireaux 1
On peut, comme précédemment, mettre tout ça dans un fichier texte via une redirection :
python /Users/oliviergimenez/Desktop/keras-retinanet/keras_retinanet/bin/detect.py >> perf.txt
On peut jeter un coup d'oeil au résultatlà.
On note que sur le lynx, on est tout bon. Sur chevreuil on a qqs faux négatifs, et sur chamois qqs faux positifs.
Si l'on n'est pas intéressé par le détail dans les faux négatifs, on peut utiliser un autre scriptPython
,detect2.py
, téléchargeableici, et qui permet de mettre sur les photos le cadre avec l'espèce vérité et le cadre avec l'espèce prédite, et de créer trois nouveaux répertoires qui contiennent les photos classées en TP, FN et FP, permettant ainsi d'aller regarder en détail les situations qui génèrent les faux négatifs et faux positifs. Ces répertoires se trouvent dans le répertoire qui contient les photos analysées, danspix_resized/
.
Avant de lancer le script, il faut modifier les deux mêmes lignes que pour le scriptdetect.py
. Puis on le lance via :
python /Users/oliviergimenez/Desktop/keras-retinanet/keras_retinanet/bin/detect2.py >> perf2.txt
On peut jeter un coup d'oeil àperf2.txt
téléchargeableici :
species TP FP FN0 blaireaux 1 0 01 chamois 3 2 02 chat forestier 1 1 13 chevreuil 4 0 24 lièvre 0 3 05 lynx 4 0 06 renard 3 1 07 sangliers 4 0 08 cerf 1 0 0Images source de FP par classe :chevreuil 2lievre 2chat forestier 1chat 2Nombre total d'image par classe:humain 4renard 3chat forestier 2chevreuil 6cavalier 1vehicule 4chamois 3cerf 1oiseaux 1vide 6lynx 4chien 2lievre 2sangliers 4chat 2blaireaux 1
Au passage, ces scriptsdetect.py
etdetect2.py
font en un coup les étapes 2, 3 et 4.
Vérifier que l'algo déjà entrainé n'est pas à côté de la plaque dans l'Ain en comparant tous les tags manuels entrés par les collègues de l'OFB à la classification prédite par l'algo. Si on n'est pas trop dans les choux, alors faire le tagging automatique pour toutes les photos de l'Ain pour que Maëlis puisse se servir de ces données pour le stage.
A moyen terme, refaire l'entrainement du modèle avec toutes les photos du Jura annotées par Anna (plutôt que l'échantillon de 8800 photos utilisées par Gaspard) et toutes les photos de l'Ain annotées par les collègues de l'OFB.Plus on a de photos, mieux c'est. Ce modèle pourra alors être utilisé pour tagger les photos restantes dans l'Ain, et pour tagger à l'avenir les photos collectées dans le cadre du PPP Lynx.
About
My personal pipeline to species identification on camera trap pix using deep learning, detection/classification with MegaDetector and RetinaNet