MiniBox · Compteur cycliste autonome
novembre 2022 → juin 20237 min de lecture

MiniBox, c’est mon projet de mémoire de licence pro APSIO, fait en binôme avec Noémie Tandol. C’est un des projets dont je suis le plus fier, surtout pour ce qu’il essayait de résoudre.
Le vélo prend de plus en plus de place, avec la pandémie, les vélos électriques et de meilleures pistes cyclables. Mais pour savoir combien de cyclistes passent et dans quel sens, il faut les compter, et il n’y avait pas vraiment d’étude solide là-dessus.
L’idée, c’était une petite borne autonome, une caméra et un Raspberry Pi qui comptent les vélos tout seuls, assez peu chère pour en poser beaucoup. Elle visait les collectivités qui veulent voir l’effet de leurs aménagements, et les associations qui défendent le vélo, comme 2 pieds 2 roues ou l’AF3V. Le projet devait aussi être open source, pour que n’importe qui puisse s’en servir gratuitement et l’améliorer. Il est sous licence GPL-3.0, le code est sur GitHub et le mémoire en PDF.
Sur le papier, le principe tient en une phrase. Une caméra filme la rue, et un programme compte les vélos qui passent. Le plus dur, c’est de ne compter chaque vélo qu’une fois.
Compter chaque vélo une seule fois
Pour repérer les vélos dans l’image, on a pris YOLOv5, qui tourne avec PyTorch. Dans le mémoire, on l’a comparé aux autres modèles de détection connus. Faster R-CNN est précis mais plus lent, RetinaNet est surtout bon sur les petits objets serrés, comme sur les images satellites. YOLO regarde toute l’image en une seule passe, assez vite pour du temps réel, et c’est ce qu’il fallait pour un Raspberry Pi.
Sauf que YOLO traite chaque image séparément. Il voit un vélo sur une image, puis un vélo sur la suivante, sans savoir que c’est le même. Tout seul, il compterait un vélo autant de fois qu’il apparaît à l’écran.
Il faut donc suivre chaque vélo d’une image à l’autre, avec un numéro qui reste le même. On a commencé avec SORT, mais le suivi n’était pas fiable, et on est passés à Norfair, une bibliothèque légère faite pour suivre plusieurs objets en temps réel. Entre les deux, il a fallu un petit adaptateur, parce que YOLOv5 rend ses détections dans un tenseur PyTorch et que Norfair attend les siennes avec la boîte, le score de confiance et la classe. Un vélo n’est compté qu’une fois, au moment où le suivi le perd, en général quand il sort de l’image.
Pour le sens, chaque vélo suivi garde ses quatre dernières positions. Le programme en tire un déplacement moyen et, si le vélo bouge assez, le range dans un des quatre sens de l’image, en haut ou en bas, à gauche ou à droite. Le seuil sert à ignorer ce qui bouge à peine.
Avec l’affichage activé, on voit ce que suit le programme. Chaque vélo a sa boîte, avec son numéro, son sens et le score de confiance du modèle.

Toutes les minutes, le programme écrit dans un fichier CSV le nombre de passages par sens et par type d’objet, et une fois par jour, il verse ce fichier dans une base SQLite. Pour lire ces chiffres, on a fait un petit site Django. On lui donne le CSV, et il dessine les passages avec Chart.js, qu’on peut filtrer par dates et par type d’objet. On a pris Django plutôt qu’une simple page pour pouvoir y ajouter plus tard une base de données ou des comptes.
Ne garder aucune image
Le programme ne garde aucune image, et ce n’était pas le cas au début. La première version prenait des photos à intervalles réguliers et les enregistrait, puis un autre programme cherchait les vélos dessus. C’était lent, le fichier de résultats ne disait pas grand-chose, et on stockait des photos de la rue.
Filmer la rue pose forcément la question du droit, alors on a creusé le sujet, et c’est moins simple qu’on le croit. En France, seules les autorités publiques, comme les mairies, ont le droit de filmer la voie publique. Une entreprise ne peut pas, un particulier non plus, même pour surveiller sa voiture garée devant chez lui. La CNIL trouve quand même légitime une caméra qui compte les piétons, les voitures et les vélos pour aménager la ville, mais il faut l’accord des services publics quand les gens filmés ne peuvent pas s’y opposer. Et le RGPD ne s’applique pas si personne n’est reconnaissable sur la vidéo, ni directement ni indirectement.
Dans la version suivante, les images passent donc directement de la caméra au modèle, sans jamais être enregistrées, et le programme ne garde que des comptages. Au passage, la détection est devenue plus rapide.
Ce que ça donne
Pour vérifier le compte, on a pris des vidéos de parcs et de rues passantes, et on les a regardées nous-mêmes avant, pour savoir ce que le programme devait trouver. Sur une vidéo de près de quatre minutes, avec pas mal de passage, coureurs et voitures compris, on avait compté 20 vélos à la main. Le programme en a trouvé 21. Les sens étaient moins justes, avec quelques vélos rangés du mauvais côté. Et il a traité la vidéo un peu plus vite qu’elle ne dure, mais avec une carte graphique Nvidia.
Sur le Raspberry Pi, l’installation s’est révélée plus pénible que prévu, entre les dépendances et la version de Python à respecter. Le suivi avait aussi ses ratés. Quand il suit à la fois des voitures et des vélos, il lui arrive de donner le numéro d’une voiture à un vélo, et l’inverse. Et on n’a pas réussi à aller jusqu’à ce qu’on visait au départ, savoir si un vélo est chargé ou si le cycliste porte un équipement de protection.
Ma première contribution open source
Pour le suivi, on avait une piste, YOLOv8, qui fait la détection et le suivi d’un coup et aurait remplacé Norfair. En l’essayant, je suis tombé sur une erreur dès que je lançais le suivi. Elle venait de lap, une bibliothèque dont il dépend, incompatible avec notre version de Python sous Windows.
J’ai ouvert un ticket sur le dépôt d’Ultralytics. Le jour même, on m’a suggéré de remplacer lap par une fonction de SciPy, et j’ai proposé une version dans le ticket. Mais on n’a jamais pu utiliser YOLOv8 dans le projet.
C’était la première fois que je remontais un bug et proposais une solution sur un dépôt open source. Ça m’a permis de faire un premier pas dans ce monde, et de rendre quelque chose à un projet dont je me servais. Pour la petite histoire, je n’avais pas tout fait dans l’ordre et ma proposition ne réglait pas vraiment le problème, puisque leurs tests ne donnaient pas les mêmes résultats qu’avec lap. Mais elle a au moins permis de le signaler.
À deux
Tout ça, on l’a fait à deux, par pull requests. J’écrivais surtout la partie détection, Noémie relisait et validait, et elle a monté le site Django. Des tests unitaires tournaient sur GitHub à chaque commit et chaque pull request sur develop et master, pour ne pas casser une fonctionnalité en en réparant une autre. Et on présentait régulièrement l’avancée à notre tuteur, pour ajuster au fur et à mesure.
Où en est le projet
Le projet s’est arrêté avec le mémoire, et le dépôt est archivé. Dans le mémoire, on avait noté la suite : entraîner notre propre modèle pour reconnaître un vélo électrique ou quelqu’un qui porte un sac, essayer Grounding DINO, qui cherche dans une image ce qu’on lui décrit en une phrase, comme une personne en gilet jaune, et faire une image Docker pour installer le tout sur un Raspberry Pi sans se battre avec les dépendances.