Files
TrackTrendsDoc/docs/index.md
T
2023-05-30 15:57:32 +02:00

91 KiB
Raw Blame History

Rapport Track Trends V1.0


Rohmer Maxime Travail de diplôme Technicien ES 2023

Introduction


Résumé

Track Trends est un outil de récupération et d'analyse de données de courses de Formule 1.

Pour le contexte, en dehors des cours, j'exerce différentes activités dont celle de Live Ticker F1 pour le 20 minutes. Pour m'aider dans ce travail, j'utilise actuellement la F1TV à laquelle je suis abonné qui me propose non seulement un feed vidéo de meilleure qualité avec des commentaires plus pertinents que ceux de la RTS mais qui aussi me permet d'accéder à un feed vidéo très important : la chaine data.

Ce dernier ressemble à cela :

"Screenshot du feed data de la f1tv"

(Attention ce n'est pas un joli tableau HTML, mais bien une vidéo qui contient un tableau.)

Sauf que toutes les informations sont étalées pêle-mêle sans hiérarchie ce qui fait que cela me prendrait trop de temps de tout déchiffrer à chaque fois, ce qui me fait rater des choses intéressantes.

Le but du projet est donc de fournir un outil qui hiérarchise et affiche différemment les données pour faciliter leur lecture et me permettre de faire de meilleurs commentaires.

Abstract

Track Trends is a Formula 1 data is a tool that displays and interpret data.

To understand everything,first ,a little bit of context. In my free time I have multiple activities and one is to be the Live Ticker F1 for the local journal "20 minutes" (Owned by Tamedia). to help me in this work I'm currently using the F1TV to which I'm currently subscribed because it provides me with a better video feed with better commentary than the ones from the RTS (in my opinion) but also because it gives me access to a very important video feed : the data channel

You can see in the chapter above an example of the F1TV DATA CHANNEL.

[Note : Even tough it looks like a pretty HTML table on wich you could easely get infos... Its not. Its a video feed]

You can see a lot of data all well and good BUT! All the data is displayed the same in a big table which make it really hard to read totally in a hurry, which means that I miss a lot of useful information.

The point of the project then is to provide a tool that can display those data by taking into account their relevance. So for example a driver that is 10s away from everyone and that is doing some normal lapTimes will be less displayed or even not displayed at all so I can focus on the drivers that are battling each others.

This tool would help me not miss the battles and details that are happening in the back and therefore not being broadcasted on TV.And it could be a usefull tool for anyone who wants a better insight of how the race is going by looking at the data.

This kind of project already exists in the form of the AWS tool "F1 Insight" but it is not avaible to the public. We can only see some of its predictions (that are trash) and data dumps in the live feed when the TV directors feel like it.

Description du besoin

Comme expliqué dans le résumé, je suis Live Ticker F1. Mais pour mieux comprendre le besoin que j'ai, je pense qu'il est pertinent de comprendre comment je travaille.

Pendant un Grand Prix de Formule 1 j'ai plusieurs tâches à effectuer :

  • Suivre les différents évènements du Grand Prix
  • Changer le titre et la photo de titre du Live
  • Chercher des Tweets ou des Images à intégrer
  • Ecrire les commentaires en faisant attention à dire ce qu'il se passe mais aussi lexpliquer, ce que cela implique, mais aussi ce que cela veut dire pour le reste de la course.
  • Comprendre et expliquer les stratégies

Tout cela toutes les cinq minutes max...

Cela veut dire que je dois être le plus rapide possible quand je cherche des informations. Et comme le tableau en comporte trop et bien, je suis obligé de le lire en diagonale.

Par exemple dans le tableau, les infos que je cherche le plus sont :

  • Le nombre de places gagnées (surtout au départ)
  • Les écarts entre les pilotes (surtout ceux qui sont en dessous de deux secondes).
  • Les pneus de chaque pilote et combien de tours, ils ont fait dessus
  • Les temps d'arrêts aux stands
  • Les temps au tour (surtout pour la stratégie)

Mais pleins d'autres informations existent si on les recoupe sur plusieurs tours.

Un outil qui permettrait de mettre en évidence les informations importantes serait donc une très grosse plus-value pour mon travail et s'il est facile à installer et à utiliser, il se pourrait qu'il devienne indispensable.

Cahier des charges


Il s'agit d'une version coupée du cahier des charges qui ne contient pas l'explication du contexte. Mais l'original est disponible sur ce site également. Il est toutefois normal d'y voir des choses répétées ou légèrement différentes, en effet, il n'a pas été écrit en même temps que le reste de ce document.

Projet


Un outil de style compagnon sous forme d'application C# Windows Form qui récupère en temps réel les informations de la course et affiche les informations les plus importantes. Le but est non seulement de faciliter mon job, mais aussi faire en sorte d'améliorer la plus-value de mon travail en me permettant de fournir des commentaires qui ne sont pas disponibles pour le tout venant qui regarde simplement le flux RTS.

Exemples :

  • Les pilotes qui sont proches (moins de 1-2 secondes qui sont donc en train de se battre).
  • Les pilotes qui améliorent leur temps au tour et ceux qui perdent le plus de temps
  • Le classement pondéré tenant compte des futurs arrêts au stand

Maintenant afficher différemment les infos, c'est sympa, mais cela serait encore mieux de traiter ces data et de permettre des petites prédictions.

Exemples :

  • Prédire les arrêts aux stands en prenant en compte les baisses de performances des pneus
  • Prédire le pneu que le pilote va chausser s'il rentre aux stands dans le prochain tour
  • Prédire dans combien de tour tel pilote va rattraper tel autre pilote
  • Prédire combien de temps le pilote va perdre dans les stands en fonctions des arrêts précédents

Réalisation

Malheureusement, la Formula 1 Management ne propose aucune API publique qui puisse nous permettre de faire ce projet "simplement". La raison la plus probable étant qu'Amazon avec son service AWS propose exactement ce genre de services pour le flux télévisé et il doit y avoir un contrat d'exclusivité.

Il existe des API "Pirates" faites par la communauté, le problème est qu'elles ne sont pas forcément des plus pratiques à utiliser.

Mais comme je possède un abonnement premium ++ à la F1TV, j'ai accès pour chaque grand prix à un flux vidéo nommé : DATA F1 CHANNEL

Qui ressemble à ça :

"Exemple de la Data Channel"

Donc la seule façon que je vois de récupérer ces données est de les prendre directement sur ce feed.

Même si le but final de l'application est de faire pleins de choses super avec les datas, le gros du projet va surtout être la récupération des données et leur stockage.

Les données viennent du flux vidéo et ainsi dans un premier temps, il va falloir récupérer d'une manière ou d'une autre des images qui viennent d'un grand prix en direct ou en rediffusion.

Ensuite, dans un second temps, il faut lire les informations directement sur l'image en utilisant une librairie prévue pour (exemple Tesseract) et vérifier l'intégrité de ces dernières pour qu'on puisse ensuite les stocker.

Dans un troisième temps, il faut stocker toutes ces données dans une forme qui permette d'aller facilement faire des requêtes de récupération et déjà préparer des méthodes qui permettent de récupérer des infos importantes (ex : la moyenne des cinq derniers tours, le temps moyen d'arrêt etc.) pour faciliter la dernière étape

Quand tout cela est fait, on peut ensuite s'amuser un peu avec les Data.

La dernière étape est donc l'affichage. L'idée est de créer une Windows Form qui contienne toutes ces informations dans un format beaucoup plus lisible et avec laquelle on pourrait interagir pour permettre de plus facilement commenter les Grands Prix. (exemple plus bas avec un croquis de ce à quoi l'application pourrait ressembler)

Voici la liste des données qui pourraient être affichées (Non contractuel, simplement des idées).

  • Les pilotes qui sont proches (moins de 1-2 secondes qui sont donc en train de se battre).
  • Les pilotes qui améliorent leur temps au tour et ceux qui perdent le plus de temps
  • Le classement pondéré tenant compte des futurs arrêts au stand
  • La moyenne de temps que les pilotes perdent dans les stands
  • La performance moyenne des 5 types de pneus
  • La moyenne de temps de chaque pilote sur le pneu actuel
  • Le nombre de points que chaque pilote gagnerait selon sa position
  • Le classement de la course

Voire même si c'est possible :

  • Prédire les arrêts aux stands en prenant en compte les baisses de performances des pneus
  • Prédire le pneu que le pilote va chausser s'il rentre aux stands dans le prochain tour
  • Prédire dans combien de tour tel pilote va rattraper tel autre pilote
  • Prédire combien de temps le pilote va perdre dans les stands en fonctions des arrêts précédents
  • Prédire les temps au tour de chaque pilote selon l'usure des pneus

Voici un exemple d'interface possible pour une page :

"Protype de l'app fait sur Figma"

Cas d'utilisation


'*'On va considérer que tous les user ont un abonnement F1 TV PRO

Un user veut récupérer les data :

  • Il ouvre son navigateur et lance la page DATA de la F1 TV
  • Il calibre la capture des data via le programme (pour la première utilisation).
  • Il confirme que les données initiales sont bonnes (pour la première utilisation).
  • Il regarde tranquille son Grand Prix

Le programme récupère les data :

  • Il récupère des images depuis la F1TV
  • Il utilise Tesseract (ou autre) pour en récupérer les infos.
  • Il met ces infos dans un Objet Pilote, dans un Objet course avec un attribut tour pour hiérarchiser les data

Pour ce qui est de l'affichage, l'idée est de faire une application C# comme on l'a appris à l'école, mais avec assez de style pour qu'elle puisse être agréable à utiliser.

Quand le programme affiche les data :

  • Il prend les données venant directement de la F1TV.
  • Il affiche différemment les données pour permettre une meilleure lisibilité
  • Il interprète avec des règles plutôt simples certaines data pour faire des miniprédictions ou aider à la lecture
  • Il récupère des infos d'autres courses pour les comparer et proposer des prédictions plus intéressantes

Difficultés techniques


  • Récupérer un flux vidéo plutôt propre malgré les contres mesures de la F1 TV pour en empêcher la lecture par un logiciel
  • Si on doit passer par une capture d'écran, trouver un moyen de stocker les données de manière à prévoir que parfois un tour pourrait avoir plus de données qu'un autre, que le user peut mettre pause, ou même quil revienne en arrière.
  • Développer des algorithmes pour récupérer les données comme les différents pneus utilisés ou l'activation du DRS ainsi que développer des moyens de nettoyer les résultats de l'OCR (Par exemple utiliser différents champs redondants pour comparer les résultats)
  • Stocker les données sur une base pour les traiter plus tard tout en prévoyant un moyen de voir les stats live
  • Développer des algorithmes de prédiction qui prennent en compte d'anciennes courses pour tenter de prédire des choses comme les arrêts aux stands par exemple.

Différences sur le cahier des charges


[À remplir dans les dernières semaines du travail de diplôme]

Planning prévisionnel


Mes suiveurs m'ont demandé un planning de type GANTT pour ce travail de diplôme

Je n'ai pas utilisé un logiciel particulier pour faire ce dernier, mais je me suis inspiré des principes fondamentaux d'un diagramme de ce type.

Comme l'original a été fait sur Excel, je ne peux pas vraiment l'insérer de manière lisible ici, mais il est disponible dans le dossier Planning.

Mais voici un résumé de son contenu :

Tâches

J'ai décidé de décomposer mon planning en trois grands types de tâches.

  1. Programmation
  2. Documentation
  3. Tests

L'idée est de permettre une meilleure lisibilité et de me permettre à moi de me faire plus facilement à l'idée de ce qu'il m'attend.

Voici la liste des tâches par rubrique :

PT

Cette rubrique contient les tâches qui n'ont pas leur place dans les trois catégories principales.

PT1 / préparation au travail de diplôme (2)

Cette tâche est un peu hors catégorie, mais c'est normal, c'est une supertâche qui regroupe beaucoup de choses. C'est une tâche qui est planifiée pour deux jours et qui normalement devrait être faite les deux premiers jours du travail.

Le but est de préparer tout ce qui peut être préparé en avance niveau documentation et mise en place pour ne pas avoir besoin de s'en soucier ensuite.

DT

Rubrique documentation qui contient toutes les tâches en rapport de près ou de loin avec la documentation du projet.

DT1 Création du poster (1)

Cette tâche consiste à faire une version numérique du poster qui soit en accord avec les consignes qu'on nous a données. Le but est aussi et surtout de faire poster dont je sois fier et que je sois content de montrer.

Il y a déjà des croquis de poster et j'ai clairement prévu de travailler sur ça pendant les vacances alors, je n'ai mis qu'un jour et je l'ai placé juste avant le rendu de ce dernier.

DT2 Documentation Analyse de l'existant (2)

Cette tâche est dédiée à l'écriture de la documentation et plus précisément de l'analyse de l'existant.

Comme il y a pas mal de technologies utilisées dans mon projet, j'aimerais faire correctement un vrai debrief de pourquoi j'ai utilisé l'une ou l'autre alors, j'ai assigné deux jours dessus.

DT3 Documentation Analyse organique (5)

Cette tâche est la plus grosse dans la catégorie documentation. Il s'agit de documenter comment l'application fonctionne.

J'y ai mis cinq jours et je pense que c'est un minimum car c'est dans cette tâche que je vais devoir détailler exactement comment fonctionne chaque partie du projet.

Ces cinq jours sont éparpillés sur le projet en général à la fin du développement de chaque grande partie de projet. Le but est de ne rien oublier et de ne pas avoir à tout faire en même temps.

DT4 Documentation Analyse fonctionnelle (2)

Cette tâche est déjà moins grosse, elle consiste à documenter le fonctionnement de l'application et comment utiliser les composants que j'ai développés.

Je l'ai mis en fin de projet, car comme j'ai l'habitude de faire des analyses fonctionnelles plutôt précises, le moindre changement dans l'UI peut tout rendre obsolète.

J'y ai mis deux jours, car j'aimerais correctement documenter avec de bonnes photos et scénarios pour qu'on puisse voir toutes les possibilités de l'application.

DT5 Documentation Tests (1)

Cette tâche est un peu plus petite qu'elle ne le devrait. Elle concerne la documentation des différents tests. Je n'y ai mis qu'un seul jour, car en réalité les différentes tâches de tests contiennent aussi beaucoup de documentation,

DT6 Documentation Reste (2)

Cette tâche est une tâche un peu vague. Elle contient toutes les actions autres que j'aurai besoin de faire (Mise au propre, orthographe, génération de PDF ...). J'y ai mis deux jours pour avoir un peu de marge, car ce sont toujours des tâches qui paraissent faciles, mais qui à la fin prennent beaucoup de temps si on les fait bien.

PT

Rubrique programmation qui contient toutes les tâches qui touchent à la programmation et au développement de l'application.

PT1 Programmation récupération des images (3)

Cette tâche est estimée à seulement trois jours, il ne faut pas s'y méprendre, c'est une des tâches les plus dures et lourdes niveaux documentation en explications. Cependant, un POC (Proof Of Concept) assez avancé a déjà été fait et donc cela permet de n'envisager que trois jours, car il suffit de l'implémenter et de la paufinner.

Cette tâche consiste à prendre en entrée un lien de Grand Prix et de sortir une image tous les x secondes de la page DATA. Cela peut sembler simple, mais pour le faire sans prendre d'espace d'écran et ne demandant pas à l'utilisateur de copier-coller quoi que ce soit où de donner ses identifiants F1TV c'est un challenge.

Cela peut paraitre curieux alors de mettre cette tâche loin dans le planning même si c'est la première étape du projet. Encore une fois cela s'explique avec le fait qu'il y a déjà un POC qui fonctionne à peu près et que donc préfère commencer avec des tâches plus incertaines dans le cas où elles prendraient plus de temps que prévu.

PT2 Programmation OCR (5)

Cette tâche consiste à développer la partie qui reconnait le texte sur les images. C'est très certainement la tâche qui risque le plus de déborder car c'est celle qui est la plus complexe techniquement puisqu'elle demande non seulement la lecture sur image, mais aussi le développement d'algorithmes de traitement de cette donnée pour être sûr qu'elle a bien été lue.

J'y ai ainsi alloué cinq jours, mais j'espère que j'arriverai à gagner du temps sur les autres pour y allouer plus dans le planning effectif, car je suis convaincu que plus, on y passe du temps, meilleur sera le résultat.

PT3 Programmation, stockage et modèle (5)

Cette partie est moins technique, mais concerne le stockage des données que nous retourne la lecture des images. Mais elle va demander de la réflexion et de l'intelligence de programmation, car il faut que cette partie anticipe les besoins de la vue et prépare un terrain fertile qui ne demande pas un refactor quand on passera au développement de la vue.

C'est pour cela que je lui ai aussi assigné cinq jours de travail et elle doit absolument être commencée après la lecture.

PT4 Programmation Vue de l'APP (5)

Cette partie peut être horrible comme très facile, cela dépend complètement de la qualité du travail avant. Si le modèle est parfait et que les données sont intègres, cela devrait être plutôt simple de les afficher de manière intéressante. Cependant, cette partie débordera sûrement un peu, car tout le temps gagné avec de bonnes données sera utilisé pour tenter de faire de la prédiction.

Pour ces raisons, je lui ai assigné également cinq jours de travail et elle doit absolument être faite après le modèle.

PT5 Programmation mise en commun (3)

Cette tâche est aussi un petit peu spéciale, car elle regroupe plusieurs choses. En gros, chaque partie de programmation sera sûrement assez indépendante et il faudra à un moment faire un seul projet C# qui contient tout.

Il est difficile d'estimer à quel point cela va être compliqué alors, j'ai été conservateur et j'ai mis trois jours.

TT

Cette rubrique contient les tâches qui sont uniquement des tests. La plupart des tâches de programmations contiennent déjà des tests, mais certaines demandent une attention particulière.

TT1 Tests OCR (2)

Cette tâche est une des tâches les plus importantes. Son but est de faire un protocole de tests complet qui permette de comparer les différents algorithmes de reconnaissance de texte.

Je l'ai mise après la reconnaissance, mais même maintenant en écrivant ces lignes, je me dis que dans le planning effectif, elle sera faite pendant la tâche de programmation. En effet, comment savoir si mon tout nouvel algorithme est réellement mieux que le précédent.

Je prévois deux jours, car je pense que faire le dataset va prendre beaucoup de temps, il faut prévoir un certain nombre d'images et de texte qui pourront ensuite être données sous forme de tests. C'est certes une tâche de test, mais c'est aussi de la programmation.

TT2 Tests finaux (2)

Cette tâche de tests est un peu vague, elle regroupe les différents tests pour vérifier que les données sont bien affichées correctement. Ce qui serait cool si j'ai du temps en fin de travail de diplôme serait de faire un système de test qui permet d'entrainer le programme à mieux reconnaitre certaines choses comme des arrêts aux stands si on lui donne les trois dernières années de grands Prix.

J'ai mis une durée arbitraire de deux jours, mais je ne sais pas vraiment combien de temps cela va vraiment durer. Elle est évidemment à effectuer une fois que tout est à peu près terminé.

Planning effectif et différences


[A remplir dans les dernières semaines du travail de diplôme]

Analyse fonctionnelle


[A remplir au fur et à mesure dans la seconde moitié du travail de diplôme]

Analyse Organique


Récupération des images

Voici la première grande étape du projet.

Pour rappel, Amazon héberge directement le site de la F1TV et possède les droits sur les données de la F1. C'est sous le nom de AWS (le service d'hébergement d'Amazon) que la firme apparait en tant que sponsor.

On peut voir ce nom apparaître assez souvent quand on regarde un Grand Prix car comme ils ont la main-mise sur les données ils peuvent insèrer des bandeaux d'informations sur le flux public sur ce qu'il se passe voir même faire des prédictions (Bien qu'un peu bancales)

"Exemple insertion AWS en GP"

Ce service s'appelle F1 Insights (Oui c'est un meilleur nom de projet que F1 Companion mais bon) et c'est, je pense, la raison pour laquelle on ne voit aucune API publique qui permette de correctement se renseigner en donées en direct pendant un Grand Prix. Ils ont du dégotter un juteux contrat pour s'occuper de toute l'infrastructure digitale de la F1 (du moins publique) en échange d'une exclusivité totale sur certaines choses comme les Data

"Exemple data d'AWS"

Evidemment je ne fais que conjecturer et ce que j'ai dit n'est pas à prendre au pied de la lettre mais c'est une explication possible je pense de pourquoi il est si difficile de trouver des données sur la F1 facilement en temps réel.

Il existe bien quelques API un peu bancales publiques, mais le problème c'est qu'elles ne sont vraiment pas suffisante et je ne peux pas leur faire confiance quand je commente. Ce qu'il m'aurait fallut c'est une API publique et officielle qui me permette d'être sur que les données sont les bonnes et qu'elles arrivent le plus vite possible.

On pourrait croire que c'est impossible car cela n'existe pas comme je l'ai dit MAIS ! Ce n'est pas complêtement vrai. En effet depuis que je possède un abonnement à la F1TV, il existe une source d'informations très précieuse qui m'aide énormément dans mon quotidien de commentateur de Formule 1. La "DATA CHANNEL".

La Data Channel est une page de la F1TV qui permet, pour chaque Grand Prix, de visualiser, sous la forme d'un flux vidéo, différentes informations capitales sur la course.

"Exemple de Data Channel"

Le problème, c'est que comme je viens de le dire, ces données ne sont pas accessibles comme un tableau HTML ou un flux RSS ou un tableau JSON. C'est un flux vidéo. Il faut savoir qu'entretenir une diffusion de flux vidéo en 1080P pendant deux heures accessible par des milliers d'abonnés est EXTRÊMENT cher surtout quand on le compare à simplement afficher les données dans un tableau. Ce qui veut dire que ce choix est délibéré et a un sens au niveau économique. Je pense donc que c'est justement pour éviter que des petits malins puissent simplement venir scraper l'intégralité des données qu'ils proposent et fasse sa propre API. (C'est d'ailleurs un des sites avec la meilleure protection anti bot du monde)

MAIS ce n'est pas par ce que les données ne sont pas facile à avoir qu'elles sont IMPOSSIBLE à avoir. Et c'est la que ce projet entre en jeu. Mais pour décoder les données d'une image il faut dabord ... (roulement de tambours) ... Avoir des images !

Et c'est la que vient se glisser cette partie du projet.

Comment faire ?

Le but de ce segment est de se concentrer sur la récupèration et la mise à disposition pour le reste du programme, des images en direct de la F1TV dans la meilleure qualité possible et dans les meilleurs délais.

Pour ce faire il y a plusieurs solutions :

  • Reverse engeneer la F1TV pour accèder directement au flux sans passer par la plateforme internet et pouvoir prendres images à volonté.
  • Avoir tout simplement une page de la F1TV ouverte sur un écran et prendres des screenshots à intervals réguliers.
  • Simuler un navigateur internet sans qu'il soit affiché et le contrôler automatiquement pour qu'il prenne des captures.

La première option aurait été la plus élégante mais lors d'un POC que je tentais de réaliser je me suis rendu compte que cela serait un peu trop compliqué et long à faire. Sans compter le fait que les rediffusions de Grand Prix ne sont pas gèrées de la même manière que les diffusions en live. Et que pour faire des Tests en live il faudrait attendre à chaque fois un weekend de Grand Prix et le faire en plus du commentaire que je dois produire.

Pour toutes ces raisons et bien d'autres je l'ai rangée dans la case "Trop dur, Trop chiant, Sûrement illégal" (Oui je sais c'est une catégorie bien spécifique mais c'est ma documentation je fais ce que je veux)

La troisième option aurait été la plus simple (et moins drôle) et je suis presque sûr que je peux implémenter cette dernière en moins d'une après-midi. Sauf qu'elle apporte de gros soucis.

  1. On ne peux pas garantir l'intégrité et la continuité des données si l'utilisateur avance ou fait pause même par simple inadvertance.
  2. La moindre fenêtre qui s'afficherait devant ruinerait toute la reconnaissance de caractères.
  3. On ne peut pas contrôler la qualité du flux et on est obligé de faire confiance en l'utilisateur
  4. On ne peut pas vraiment automatiser quoi que ce soit niveau tests ou même pour faire du scrapping auto pour remplir une base de donnée.
  5. Et finalement le pire inconvénient : C'EST NUL ! Je ne pourrais jamais utiliser un projet qui fonctionne de cette facon, je ne peux pas me permettre d'avoir un écran inutilisable quand je commente et auquel je dois constamment faire attention pour ne pas perturber la reconnaissance. Pour moi cette option aurait été celle à choisir en cas d'extrême urgence et en dernier recours car le projet deviendrait inutile.

J'ai donc décidé de m'occuper de la seconde option : Simuler un navigateur.

Cette option bien que complexe et difficile à implémenter propose une solution à tous les problême et permet une récupèration quasi sans compromis.

Simuler un navigateur ?

"Navigateur Headless(sans tête)"

Simuler un navigateur internet n'est pas forcément très difficile. Chromium par exemple offre une panoplie d'outils natifs et énormément de librairies existent permettant de facilement et en quelques lignes simuler un Google Chrome et le contrôler sans afficher son UI (Interface Utilisateur).

"Chromium logo"{: style="height:150px;width:150px"}

Cependant. La F1TV n'utilise pas simplement un player HTML5 basique. Elle utilise un service de streaming BitMovin qui permet de fournir un stream de bonne qualité et surtout qui implémente les DRM (Digital Right Management)

Cela veut dire que quand on ouvre un flux de la F1TV sur chrome et que l'on essaie de prendre une capture d'écran, le player se met en noir et ne permet pas de voir quoi que ce soit (Certaines version de Chrome le permettent pendant quelques semaines avant de bloquer à nouveau). Ce qui dans notre cas est un immense problème. Mais Firefox ne nous bloque pas de cette facon et il est donc assez facile de passer outre.

L'explication sans trop rentrer dans les détails est la suivante :

Dans chrome, le player par défaut utilise une technologie appellée "PCP" ou "Protected Content Playback" qui leur permet de bloquer au moins une partie des techniques de récupèration du flux vidéo et audio.

Cependant Firefox de pas sa nature Open Source utilise "OpenH264" pour lire ces mêmes flux soumis à des DRM et OpenH264 n'implémente pas les mêmes restrictions.

Sauf que Firefox n'est pas aussi facilement émulé que chrome et cela réduit notre choix de librairies à ... Une seule... Qui est Selenium. (Il existe aussi Pupetteer C# mais j'ai rencontré énormément de soucis avec cette dernière dès que je voulais lancer une vidéo)

"Firefox dev logo"{: style="height:150px;width:150px"}

Mais même si la documentation est plutôt maigre parfois, c'est une bonne librairie qui permet de très bien contrôler une instance de chrome ou de Firefox.

Contrôler le navigateur

Maintenant que l'on sait quel navigateur simuler et avec quelle technologie, on peut passer à la réalisation.

Ce qu'il y a de bien avec Selenium, c'est qu'on a un certain nombre de commandes très haut niveau qui nous permettent de contrôler un navigateur de manière plutôt précise.

Je vais décrire ici la procédure habituelle utilisée sous une forme de recette de cuisine pour que l'on puisse facilement comprendre ce qu'il se passe.

Durant cette explication je vais parler à un moment de Cookies, ne vous en faites pas c'est le sous chapitre suivant qui va vous en parler.

Recette de cuisine pour récupèrer des images de la F1TV :

  1. Démarrer une instance de navigateur avec les bons arguments
  2. Ajouter les bons paramêtres pour ne pas se faire flag comme un bot
  3. Naviguer sur la page de la F1TV
  4. Ajouter les cookies de connexion pour avoir accès au contenu de la page
  5. Naviguer sur la page du Grand Prix demandé
  6. Attendre un peu que la page se charge
  7. Cliquer sur l'invite de cookies
  8. Attendre cinq secondes le temps que la page se reload
  9. Cliquer sur le bouton qui permet de passer du feed live à la DATA CHANNEL
  10. Appuyer sur Espace pour faire apparaitre le bouton d'accès au paramêtres
  11. Cliquer sur le menu déroulant des résolution
  12. Trouver l'option 1080P et la selectionner
  13. Cliquer sur le bouton qui met la vidéo en plein écran
  14. Prendre de screenshots à intervales réguliers

Pour faire toutes ces actions on doit récupèrer les éléments selon leur ID ou leur classe.

Voici un exemple qui récupère le bouton de plein écran et qui clique dessus :

IWebElement fullScreenButton = Driver.FindElement(By.ClassName("bmpui-ui-fullscreentogglebutton"));
fullScreenButton.Click();

Ca peut paraître plutôt simple dit comme ca et quand tout fonctionne ca l'est mais la difficulté vient du fait que à peu près nimporte laquelle de ces étapes peut rater et qu'il faut donc faire un bon système de gestion d'erreurs qui puisse aider l'utilisateur en cas de problème.

Parfois il est aussi difficile de trouver un élément selon son ID,sa classe, ou sa value.

Par exemple l'option qui permet de passer en 1080P peut avoir comme value 1080_9011456 ou 1080_9011200 si on refresh la page. Cela demande de passer par des expression régulières ce qui n'est pas compliqué en soi mais c'est toutes ces petites choses qui rendent le processus long à mettre en place.

Il faut dire aussi que les sites ne sont pas forcément très content de voir des bots passer car cela peut être un risque de DDOS et de Scraping (Comme moi) et donc ils mettent en place des systèmes pour nous empêcher de faire ce que l'on veut

On peut utiliser différntes techniques pour passer outre ces restrictions comme :

  • Changer son UserAgent
  • Changer sa résolution
  • Ne pas avoir des patterns trop prévisibles
  • Avoir un historique
  • Ne pas cliquer pile sur le milieu des boutons
  • Ne pas cliquer trop vite
  • Passer par un proxy pour ne pas se faire flag
  • Utiliser des librairies plus discrètes

J'ai eu l'occasion de tester toutes ces methodes pour tenter de passer derrière les radars de la F1TV et visiblement j'ai réussi pour les pages principales mais pas pour les pages de Login.

Il faut savoir que la bataille entre bots et propriétaires de sites est un grand jeu du chat et de la souris et que les plateformes innovent constamment leur sécurité. Et il se trouve que la partie login de la F1TV est hebergée autre part que le reste du site chez Amazon et que elle possède les meilleures sécurités que j'aie pu voir. Aucunes des methodes que j'ai citées et d'autres encore que j'ai essayé n'ont réussi à fourvoyer le système.

J'ai donc été obligé de faire appel à la connexion par Cookies pour pouvoir accèder au reste du site internet.

Récupèrer les cookies ?

Alors, on va mettre de côté toutes les questions de sécurité et de violation de la vie privée et de protection des données des utilisateurs pour ce chapitre. Car pour faire simple, je siphonne TOUS les cookies de la persone qui utilise mon app.

Alors évidemment ca n'est pas pour faire des bétises avec et c'est pour une "bonne" raison, mais bon quand même ca peut faire bizarre comme ca.

Je pense que vous savez déja ce qu'est un Cookie, mais je vais quand même faire un petit point la dessus car c'est important pour la suite.

Quand on va sur un site internet et que l'on se connecte avec nos identifiants nous sommes connectés sur la session.

Cependant, si on quitte le site ou que l'on ferme le navigateur, le site ne peut pas garder en mémoire que c'est bien vous quand le lendemain vous retournez dessus. Pour palier à cette limitation on a inventé cette chose magnifique (hem...) que sont les cookies !

Les cookies sont des petits fichiers qui sont stockés dans votre navigateur et qui peuvent servir à beaucoup de choses comme traquer votre activité sur internet et espionner un peu ou aussi par exemple, servir de jeton de connexion.

L'idée est que quand vous vous connectez sur le site avec vos identifiants, le site envoie un petit fichier dans votre navigateur qui va servir de jeton. Et donc quand vous reviendrez, le site pourra voir que vous avez le jeton et vous connectera automatiquement.

Ca peut paraître génial, et c'est effectivement bien pratique, cependant ce n'est pas sans risques. En effet, imaginons qu'un acteur malveillant parvienne à s'emparer de ces petits fichiers, il pourrait ainsi facilement se faire passer pour vous. Alors un cookie expire à un moment donné pour temperer les risques, mais ils sont toujours présents.

Dans notre cas on peut vite comprendre pourquoi cela peut être intéressant de récupèrer ces cookies. En effet, si on peut mettre la main sur le jeton de connexion de l'utilisateur de notre application. On pourra se connecter automatiquement à la F1TV et aller prendre des photos directement sans que l'utilisateur aie à faire quoi que ce soit.

Sauf que les cookies ne sont pas stockés en clair comme ca. Evidemment Google Chrome a mis en place quelques techniques pour éviter que nimporte qui puisse s'amuser à aller taper dans les cookies de la machine.

Tous les cookies sont stockés dans une base de données sqlite avec les noms en clair et les valeurs sont encryptées en utilisant la methode AES 256 qui est une methode de cryptage très utilisée et efficace.

Tellement efficace qu'il serait complêtement inutile de tenter de les decrypter en utilisant de la force brute pour trouver la valeur ou même une attaque de dictionnaire ou quoi que ce soit.

Si ces valeurs peuvent être encodées et décodées en local sur la machine sans connexion internet, cela veut dire que la clé est stockée sur la machine. Et si je peux mettre la mais sur cette clé alors je pourrai lire tous les cookies de la machine.

Cette clé est stocké dans les fichiers de Google Chrome sous Google\Chrome\User Data\Local State. Et dans ce fichier on peut trouver une liste de données en clé valeurs et on peut trouver la clé sous os_crypt encrypted_key. On pourrait croire que l'on a déja touché le jackpot mais il reste encore une étape. Cette clé est cryptée en utilisant le système d'encryption de Windows. Cette encryption est utilisée pour empêcher des utilisateurs non connectés d'accèder à certaines données. Mais comme nous sommes connectés nous pouvons facilement utiliser les librairies de decryption pour trouver la valeur de cette clé.

Et à partir de la il suffit d'utiliser cette clé pour décrypter tous les cookies de la machine pour aller chercher ceux qui nous intéressent.

Voici un exemple du code python qui permet d'aller chercher la clé d'encryption dans les fichiers de Google Chrome :

def get_master_key():
    with open(
        os.getenv("localappdata") + "\\Google\\Chrome\\User Data\\Local State", "r"
    ) as f:
        local_state = f.read()
        local_state = json.loads(local_state)
    master_key = base64.b64decode(local_state["os_crypt"]["encrypted_key"])
    master_key = master_key[5:]  # removing DPAPI
    master_key = win32crypt.CryptUnprotectData(master_key, None, None, None, 0)[1]
    print("MASTER KEY :")
    print(master_key)
    print(len(master_key))
    return master_key

Python ?? Et oui j'ai choisit d'utiliser un srypt python pour aller chercher les cookies.

Ce choix a été fait pour trois raisons :

  1. Le python est un language que je n'aime pas particulièrement mais qui possède un éventail de librairies absolument fantastique. Et pour ce genre de choses qui demandent une constante mise à jour des librairies et qui sont un peu niches le python est une option juste géniale.
  2. Comme c'est une des parties qui est le plus suceptible de changer vu que Chrome change relativement souvent le système de stockage des cookies. Dans une optique de facilité de maintenance, avoir un seul fichier qui concerne cette partie du projet et qui est dans un language que plus de gens maitrisent que C# est pratique.
  3. Je n'ai pas réussi à trouver de librairies C# qui me donne des résultats identiques à celles que j'utilise dans ce script python.

Pour faire la liaison entre le C# et le python, j'appelle le script depuis mon C# et ensuite le python s'occupe de mettre tous les cookies dans un CSV qui est ensuite lu depuis le C#.

Voici la partie python qui écrit dans le csv :

if not cookies_path.exists():
    raise ValueError("Cookies file not found")

with sqlite3.connect(cookies_path) as connection:
    connection.row_factory = sqlite3.Row
    cursor = connection.cursor()
    cursor.execute("SELECT * FROM cookies")
    
    with open('cookies.csv', 'a', newline='') as csvfile:
        fieldnames = ['host_key', 'name', 'value', 'path', 'expires_utc', 'is_secure', 'is_httponly']
        writer = csv.DictWriter(csvfile, fieldnames=fieldnames)

        if csvfile.tell() == 0:
            writer.writeheader()

        for row in cursor.fetchall():
            decrypted_value = decrypt_password(row["encrypted_value"], master_key)
            writer.writerow({
                'host_key': row["host_key"],
                'name': row["name"],
                'value': decrypted_value,
                'path': row["path"],
                'expires_utc': row["expires_utc"],
                'is_secure': row["is_secure"],
                'is_httponly': row["is_httponly"]
            })
            
print("Finished CSV")

Et la partie C# qui appelle le script et qui lit le CSV :

private void StartCookieRecovering()
        {
            string scriptPath = PYTHON_COOKIE_RETRIEVAL_FILENAME;
            Process process = new Process();
            process.StartInfo.FileName = "python.exe";
            process.StartInfo.Arguments = scriptPath;
            process.StartInfo.UseShellExecute = false;
            process.StartInfo.RedirectStandardOutput = true;
            process.Start();
            string output = process.StandardOutput.ReadToEnd();
            process.WaitForExit();
        }

public string GetCookie(string host, string name)
        {
            StartCookieRecovering();
            string value = "";
            List<Cookie> cookies = new List<Cookie>();
            using (var reader = new StreamReader(COOKIES_CSV_FILENAME))
            {
                // Read the header row and validate column order
                string header = reader.ReadLine();
                string[] expectedColumns = { "host_key", "name", "value", "path", "expires_utc", "is_secure", "is_httponly" };
                string[] actualColumns = header.Split(',');
                for (int i = 0; i < expectedColumns.Length; i++)
                {
                    if (expectedColumns[i] != actualColumns[i])
                    {
                        throw new InvalidOperationException($"Expected column '{expectedColumns[i]}' at index {i} but found '{actualColumns[i]}'");
                    }
                }

                // Read each data row and parse values into a Cookie object
                while (!reader.EndOfStream)
                {
                    string line = reader.ReadLine();
                    string[] fields = line.Split(',');

                    string hostname = fields[0];
                    string cookieName = fields[1];

                    if (hostname == host && cookieName == name)
                    {
                        value = fields[2];
                    }
                }
            }

            return value;
        }

Maintenant que l'on sait comment simuler et manipuler un navigateur internet, que l'on sait comment se connecter sur le compte F1TV de l'utilisateur sans qu'il n'aie rien à faire. On a tous les ingrédients pour automatiquement récupèrer des images de la F1TV du Grand Prix que l'on souhaite.

Calibration

Maintenant que l'on a des images de la page Data de la F1TV on pourrait croire que c'est tout bon on peut direct passer à la partie OCR. Mais que nenni !

Le gros soucis de l'OCR c'est que sa précision est grandement réduite dès que l'on augmente la taille de la zone de recherche. Même simplement deux mots sur une image, si on les prends dans images individuelles on a de grandes chances de trouver quelque chose mais si on les mets les deux sur la même et que on tente l'OCR on va avoir de résultats bien moins bons.

Et puis il faut aussi voir que selon les données que je cherche je ne peux pas faire le même traitement.

Par exemple, savoir si le DRS est allumé, savoir quels pneus chausse un pilote et depuis combien de tours et savoir quel est le temps de son dernier tour, ce sont des informations qui demandent des traitements qui n'ont rien à voir.

Il faut donc pouvoir dire au programme d'OCR ou se trouvent les informations et quelle est leur nature pour qu'il puisse les décoder.

Il faut donc faire une calibration qui puisse donner toutes les infos importantes mais qui en même temps soit facile à utiliser car un utilisateur doit être capable de le faire assez facilement.

Voici la liste des informations que l'on doit récupèrer :

  • La liste des pilotes présent sur le Grand Prix
  • La position de la zone principale
  • La position de chaque zone de pilote
  • La position de toutes les Window sur chaque zone de pilote

Le but a été de retirer le plus d'étapes possibles à l'utilisateur. Techniquement j'aurais pu faire une version complêtement manuelle mais ca aurait pris trop de temps alors il y a des systèmes qui permettent de rendre cette tâche moins pénible.

Liste des pilotes

Pour la liste des pilotes j'ai pensé à utiliser une API externe pour avoir une liste dans laquelle on pourrait selectionner des noms de pilotes sauf que j'ai abandonné l'idée car je trouvais que le projet avait déja bien assez de points qui dépendent de l'exterieur.

Il y a donc une liste de pilotes dans laquelle on peut ajouter ou supprimer des noms de pilotes. L'idéal serait de mettre tous les pilotes de reserve comme ca si un pilote est malade sur une course on a pas besoin de venir changer la liste.

Zone principale

Pour la zone principale c'est complêtement manuel, on attend de l'utilisateur deux points x,y sur l'image pour ensuite avoir une idée de ou est sensé se trouver la zone.

"Exemple de zone principale"

Zones pilotes

C'est la que ca devient intéressant. L'utilisateur n'a pas besoin de faire quoi que ce soit pour que le programme sache ou sont les zones des pilotes.

J'aurais pu le faire manuellement en faisant choisir à l'utilisateur de donner deux points qui correspondent à la première zone et extrapoler pour en avoir 20. Sauf que si l'utilisateur n'est pas précis au pixel près (et même comme ca parfois) le vingtième pilote se retrouve avec une zone complêtement desaxée.

La, le programme va "simplement" effectuer une reconaissance de texte sur toute l'image. Les résultats ne nous intéressent pas vraiment tout ce que l'on veut c'est la position des textes.

Avec les position il est facile de determiner ou sont toutes les zones de pilotes et donc sans que l'utilisateur n'aie à toucher quoi que ce soit, dès qu'il a donné les infos pour la zone principale, les zones de pilotes sont determinées.

"Exemple zone pilote"

Voici un exemple du code utilisé pour trouver ou dessiner des zones de pilotes :

public void AutoCalibrate()
        {
            List<Rectangle> detectedText = new List<Rectangle>();
            List<Zone> zones = new List<Zone>();

            TesseractEngine engine = new TesseractEngine(Window.TESS_DATA_FOLDER.FullName, "eng", EngineMode.Default);
            Image image = MainZone.ZoneImage;
            var tessImage = Pix.LoadFromMemory(Window.ImageToByte(image));

            Page page = engine.Process(tessImage);
            using (var iter = page.GetIterator())
            {
                iter.Begin();
                do
                {
                    Rect boundingBox;
                    if (iter.TryGetBoundingBox(PageIteratorLevel.Word, out boundingBox))
                    {
                        //var text = iter.GetText(PageIteratorLevel.Word).ToUpper();
                        //We remove all the rectangles that are definitely too big
                        if (boundingBox.Height < image.Height / NUMBER_OF_DRIVERS)
                        {
                            //Now we add a filter to only get the boxes in the right because they are much more reliable in size
                            if (boundingBox.X1 > image.Width / 2)
                            {
                                //Now we check if an other square box has been found roughly in the same y axis
                                bool match = false;
                                //The tolerance is roughly half the size that a window will be
                                int tolerance = (image.Height / NUMBER_OF_DRIVERS) / 2;

                                foreach (Rectangle rect in detectedText)
                                {
                                    if (rect.Y > boundingBox.Y1 - tolerance && rect.Y < boundingBox.Y1 + tolerance)
                                    {
                                        //There already is a rectangle in this line
                                        match = true;
                                    }
                                }
                                //if nothing matched we can add it
                                if (!match)
                                    detectedText.Add(new Rectangle(boundingBox.X1, boundingBox.Y1, boundingBox.Width, boundingBox.Height));
                            }
                        }
                    }
                } while (iter.Next(PageIteratorLevel.Word));
            }
            //DEBUG
            int i = 1;
            foreach (Rectangle Rectangle in detectedText)
            {
                Rectangle windowRectangle;
                Size windowSize = new Size(image.Width, image.Height / NUMBER_OF_DRIVERS);
                Point windowLocation = new Point(0, (Rectangle.Y + Rectangle.Height / 2) - windowSize.Height / 2);
                windowRectangle = new Rectangle(windowLocation, windowSize);
                //We add the driver zones
                Zone driverZone = new Zone(MainZone.ZoneImage, windowRectangle, "DriverZone");
                MainZone.AddZone(driverZone);

                //driverZone.ZoneImage.Save("Driver" + i+".png");
                i++;
            }
        }
Windows pilotes

C'est ici que c'est le plus pénible pour l'utilisateur, il doit selectionner manuellement les positions des fenêtres de données. Ensuite dès que l'utilisateur a donnée une position pour chaque window, on applique les positions pour chaque zone de pilote.

Il y a plusieurs types de windows et selon le type le traitement est différent comme je l'ai dit plus tôt. Voici des exemples concrets :

"Exemple Window de pneus"

"Exemple Window temps au tour"

"Exemple window Drs"

Il est important que toutes ces zones soient transmises avec le plus de précision possible pour que l'OCR puisse bien faire son boulot.

Stockage

Ensuite quand l'utilisateur a finit de configurer son flux, la configuration est stockée pour qu'il puisse ensuite la réutiliser pour tous les autres Grand Prix de l'année.

Le stockage est fait sous format JSON et est fait pour que le programme d'OCR puisse lire dedans toutes les infos nescessaires.

Cela fait des fichiers plutôt gros mais je n'avais pas vraiment le choix. J'ai testé une version avec seulement les infos de la première zone de pilote mais avec l'interpolation, les derniers pilotes se retrouvent avec des zones clairement pas à la bonne taille.

Voici un exemple de ce à quoi ressemble le JSON final :

{
  "Main": {
    "x": 36,
    "y": 343,
    "width": 3780,
    "height": 1454,
    "DriverZones": [
      {
        "name": "Driver1",
        "x": 0,
        "y": 1,
        "width": 3780,
        "height": 72,
        "Windows": [
          {
            "Position": {
              "x": 45,
              "y": 3,
              "width": 76,
              "height": 65
            },
            "GapToLeader": {
              "x": 447,
              "y": 1,
              "width": 206,
              "height": 67
            },
            "LapTime": {
              "x": 863,
              "y": 3,
              "width": 229,
              "height": 65
            },
            "DRS": {
              "x": 1095,
              "y": 1,
              "width": 174,
              "height": 67
            },
            "Tyres": {
              "x": 1274,
              "y": 3,
              "width": 1448,
              "height": 62
            },
            "Name": {
              "x": 2724,
              "y": 3,
              "width": 361,
              "height": 65
            },
            "Sector1": {
              "x": 3088,
              "y": 1,
              "width": 239,
              "height": 65
            },
            "Sector2": {
              "x": 3314,
              "y": 4,
              "width": 190,
              "height": 62
            },
            "Sector3": {
              "x": 3493,
              "y": 1,
              "width": 198,
              "height": 67
            }
          }
        ]
      },
      {
        "name": "Driver2",
        "x": 0,
        "y": 72,
        "width": 3780,
        "height": 72,
        "Windows": [
          {
            "Position": {
              "x": 45,
              "y": 3,
              "width": 76,
              "height": 65
            },
            "GapToLeader": {
              "x": 447,
              "y": 1,
              "width": 206,
              "height": 67
            },
            "LapTime": {
              "x": 863,
              "y": 3,
              "width": 229,
              "height": 65
            },
            "DRS": {
              "x": 1095,
              "y": 1,
              "width": 174,
              "height": 67
            },
            "Tyres": {
              "x": 1274,
              "y": 3,
              "width": 1448,
              "height": 62
            },
            "Name": {
              "x": 2724,
              "y": 3,
              "width": 361,
              "height": 65
            },
            "Sector1": {
              "x": 3088,
              "y": 1,
              "width": 239,
              "height": 65
            },
            "Sector2": {
              "x": 3314,
              "y": 4,
              "width": 190,
              "height": 62
            },
            "Sector3": {
              "x": 3493,
              "y": 1,
              "width": 198,
              "height": 67
            }
          }
        ]
      }
	[Other pilots...]
    ],
    "Drivers": [
      "Perez",
      "Verstappen",
      "Alonso",
      "Sainz",
      "Russel",
      "Gasly",
      "Leclerc",
      "Ocon",
      "Hulkenberg",
      "Bottas",
      "Hamilton",
      "Albon",
      "Tsunoda",
      "Zhou",
      "Stroll",
      "De Vries",
      "Magnussen",
      "Norris",
      "Piastri",
      "Sargeant"
    ]
  }
}

Et avec tout ca. L'OCR peut démarrer dans de bonnes conditions

OCR

Maintenant que on a des images qui arrivent automatiquement et que l'on sait ou se trouvent les informations sur ces dites images, je vais parler de la seconde partie du projet qui parle du processus de reconnaissance de data sur une image du feed DATA de la F1TV.

C'est je pense la partie qui a demandé le plus tests et de refactor.

Toute la partie OCR a été développée dans un projet à part avant d'être intégrée dans le projet final.

Il faut savoir que la reconnaissance est différente celon ce que l'on cherche. Je vais donc décomposer cette partie du document en sous rubriques selon les données recherchées.

Mais avant ca je dois expliquer certains concepts qui seront importants.

Fonctionnement général

Voici un screenshot de la page DATA de la F1TV que le programme va recevoir :

"Screen F1TV"

Si on regarde de loin on peut se dire que la structure est plutôt simple mais c'est loin d'être le cas. On peut y voir au moins 4 zones contenant de l'information dans un format différent.

"Zones principales"

Dans l'exemple ci dessus on peut voir 3 zones mais on aurait également pu comprendre la zone de position des pilotes autour du circuit pour faire 4.

Ces 4 zones sont très différentes et contiennent d'autres informations. Pour ce travail de diplôme je ne m'occupe que de la zone principale. Mais je pense que le titre et les infos de circuit ne prendrait pas tant de temps que ca à implémenter.

J'ai utilisé le mot "Zone" plus haut et ca n'est pas juste un mot utilisé au hasard. C'est le nom de l'objet que j'utilise pour les représenter dans mon programme. Mais comme c'est important de bien comprendre ce concept avant de continuer je vais vous l'expliquer.

ZONE :

L'objet "Zone" parent est un objet qui est une zone d'image. Je m'explique, le but d'une zone est d'être un morceau d'une image plus grande.

Le but d'une Zone est de contenir une liste de plus petites Zones ou bien une liste de "Window" (j'explique ce que c'est juste après). Elle contient la portion d'image qui la concerne et ses propres dimensions.

Le parent zone ne prévoit que de pouvoir ajouter ou supprimer des éléments des listes de zones ou de windows ainsi qu'une methode qui permet d'aller chercher toutes informations des livres qu'elle contient.

L'intérêt d'une zone est de pouvoir compartimenter une image dans des parties intéressantes au niveau de la reconnaissance mais pas de traiter d'information.

WINDOW :

L'objet "Window" est un objet qui peut ressembler beaucoup à l'objet "Zone". En effet elle aussi est une partie d'une image plus grande et contient ses dimensions, mais elle se distingue en deux points importants.

  • Elle ne contient pas d'autres Zones ou Windows
  • Elle peut retourner les informations écrites sur son image.

Toutes les Window qui héritent du parent Window peuvent implémenter une methode qui permet de renvoyer ce qui peut être décodé sur son image. Les enfants peuvent aussi aller piocher dans les nombresues methodes de récupèration de données contenues dans le parent Window. Mieux vaut réutiliser le plus possible que de réinventer la roue pour chaque Window.

Une analogie un peu bancale pourrait se présenter comme la suivante :

La zone est une armoire ou une bibliotèque. Si c'est une zone qui contient d'autres zones c'est une bibliotèque et chacune de ces sous-zones sont des armoires. Leur unique but est de contenir de manière ordonnée des objets qui eux contiennent de l'information.

Les livres ici sont les Windows. Ils contiennet de l'information et sont stockés dans des armoires et on y accède en allant dans la bonne bibliotèque et en allant dans la bonne armoire.

Dernières choses pour comprendre le diagramme:

  • Il existe une Main Zone qui est une des 4 grandes zones dont je parlais dans la décomposition de l'image.
  • Il existe aussi des "Driver Zone" qui sont de plus petites zones contenues dans la Main Zone qui et qui ne contiennent que les informations d'un pilote.
  • L'objet Window n'est quasi jamais utilisé, c'est presque tout le temps des enfants de Window plus spécifiques qui sont utilisés, le but est que chaque type d'information sur l'image aie son type de window.

Voila donc un petit diagramme qui montre le découpage du programme :

"Diagramme explicatif de l'architecture des zones"

Pour visualiser encore un peu mieux comment ce découpage prend forme voici ce que chaque zone et Window contient.

Main Zone :

"Exemple zone principale"

Driver Zone :

"Exemple zone de pilote"

Driver Position Window :

"Exemple de fenêtre de position"

Driver name Window :

"Exemple de fenêtre de nom"

Driver LapTime Window :

"Exemple de fenêtre de temps au tour"

Driver Tyre Window :

"Exemple de fenêtre pneus"

Il existe d'autres types de Window mais ce sont les principaux.

On se rend assez facilement compte que chacunes de ces windows va avoir besoin d'un traitement spécifique car la manière de reconnaitre le pneu utilisé et le temps au tour ne peut pas être la même.

Pour résumer, on a un programme qui prend en entrée un fichier de configuration, qui prend des images de la F1TV et les découpe dans des ZONES qui elles même sont découpées en WINDOWS pour qu'on puisse plus facilement les décoder.

Maintenant qu'on a une liste de différent types de zones on peut commencer à chercher ce qu'il y a marqué dessus.

Pour cela il faut dabord comprendre un petit peu comment l'OCR fonctionne et comment des libraries comme Tesseract fonctionnent pour donner du texte en partant d'une image.

Pour faire très simple, nous avons un modèle qui est entrainé. C'est à dire que on donne à un programme un très grand nombre de mots ou de lettres en lui disant ce que contiennent chaques images. Ensuite le programme va créer des matrices de convolutions pour chaque lettre avec comme objectif de detecter les points communs entre les lettres pour créer un alpphabet.

Par exemple la matric de la lettre 'H' donnerait un poids important à des lignes verticales connectées par une ligne centrale. Et si on fournis assez de données de bonne qualité au modèle, les matrices peuvent être très efficace à detecter si une lettre est un H ou un M.

Il y a pleins d'autres methodes comme l'utilisation d'un dictionnaire de mots de la langue pour permettre la reconnaissance de mots même si une lettre au milieu n'est pas comprise ou en ajoutant d'autres informations sur le contexte mais ca ne nous intéresse pas ici.

C'est important de comprendre comment cette reconnaissance de caractères avec des matrices fonctionne car cela va nous aider à préparer nos données pour lui rendre la vie facile et augmenter la précision de nos résultats.

Filtres et traitement

On peut essayer de donner toutes nos images directement à Tesseract pour qu'il reconnaisse tout le texte qu'il y voit mais on risque de se retrouver avec des résultats au mieux inconsistents.

Dans notre cas, le soucis est que les chiffres et lettres sont beaucoup trop petits. Ils ne font parfoisd que 10 pixels de haut et cela fait que il n'est pas forcément facile de toujours les différencier. De plus, comme ils sont petits, les artéfacts d'aliasing sont assez violents et peuvent grandement déformer une lettre ou un chiffre.

Exemple :

Prenons le chiffre 9. Dans l'image il peut être représenté de cette manière :

"Exemple de chiffre avant post traitement"

On peut voir qu'il est flou, pour nous cela ne pose pas de problème et je pense que à peu près nimporte qui peut dire que c'est un 9.

Cependant comme les contours sont flous et même si on essaie de retirer le background :

"9 avec anti aliasing"

On voit que le 9 n'est pas clairement définit. En effet on pourrait le comprendre comme :

"Premier exemple de contours"

Ou comme :

"Second exemple de contours"

Voire même simplement comme :

"Exemple de coutour généreux"

Et on se rend bien compte que les performances de detection ne sont pas les mêmes dans ces trois cas.

Il faut donc faire un certain post traitement des images pour supprimer les éléments parasites, les couleurs, et augmenter la visibilité des contours importants.

Mais chaque type de donnée va avoir des methodes de post traitement différents.

Donc voici les différents types de reconnaissance et leur post traitements :

Texte

Alors ce type de reconnaissance est utilisé par la WINDOW du nom de pilote et de la position du pilote.

C'est je pense la plus simple de toutes car Tesseract est particulièrement bien entrainé pour.

Cette reconnaissance concerne donc des lettres qui font des mots ou des noms.

Voici un exemple de la WINDOW nom de pilote en entrée :

"Exemple texte cru"

Ce texte peut paraitre bon, cependant quand on le lance dans Tesseract, il ne va pas toujours donner un résultat parfait. Il faut aussi savoir qu'il y a des noms pas mal plus pénibles que Tesseract a plus de mal à reconnaitres, soit à cause des lettres utilisées, soit car le nom est un nom d'une autre région et qui ne veut rien dire en anglais ce qui empêche l'utilisation de dictionnaire (Ex : Tsunoda est un nom japonais et parfois il est difficile pour Tesseract de le reconnaitre car si une lettre pose problême il ne peut pas trouver de contexte qui puisse l'aider).

Donc pour le rendre plus facilement lisible et augmenter les chances que toutes les lettres soient découvertes, voici les étapes que j'ai mis en place.

1 : J'inverse les couleurs. Je me suis rendu compte que il était souvent plus facile de trouver un noir sur blanc que blanc sur noir. Je ne suis pas sur que cette étape soit capitale cependant.

"Texte inversé"

2 : Je fais un Treshhold de 165 car avec moins le texte parfois prend trop du background et avec plus les lettres sont trop fines.

"Texte après Treshold"

3 : Je fais un Resize de l'image pour avoir une meilleure résolution et permettre une meilleure détection. J'augmente la hauteur et la largeur par un facteur 2. J'ai trouvé cette valeur suffisante et aller plus haut consomme beaucoup de ressources.

"Texte après Resize"

4: Je fais une très rapide Dilatation du texte pour retirer le flou amené par la methode de Resize. Je n'utilise qu'une valeur de 1 car je ne veux pas trop changer comment le texte est modelé je veux juste retirer le flou.

"Texte après Dilatation"

Explication des methodes précises plus bas

Voila pour ce qui est du post processing. Je ne dis pas que ce sont les meilleurs paramêtres possibles mais dans mes tests ce sont ceux qui ont le mieux marchés.

C'est aussi les premières methodes que j'ai pu développer alors forcément elles n'ont pas le niveau de détails de certaines autres.

Mais comme même avec ce traitement il n'est pas rare que je me retrouve avec une ou deux lettres pas justes, il faut un moyen d'être sûr que c'est le bon nom qui est trouvé. Ce qu'il y a de pratique avec les noms de pilotes c'est que on sait déja comment ils s'appellent avant le Grand Prix.

En effet dans le fichier de configuration de la reconnaissance, il y a une liste de noms de pilotes. Cela veut dire que au lieu de chercher à trouver parfaitement les bonnes lettres, on peut simplement essayer de trouver quel nom de pilote ressemble le plus au nom trouvé sur l'image.

Pour ce faire j'ai utilisé une methode appelée la distance de Levenshtein. Pour faire simple c'est une methode qui va calculer les distances de lettres pour determiner entre des strings laquelle ressemble le plus à une autre.

Pour résumer le fonctionnement dans lordre :

  • On prend l'image on la traite
  • On envoie l'image traitée à Tesseract
  • On trouve quel nom de pilote ressemble le plus à ce résultat
  • On renvoie le nom du pilote
Chiffres

Cette methode en réalité utilise simplement la même methode que celle qui va récupèrer le texte sur une image. Cependant, la, on envoie à Tesseract l'information qu'il ne peut trouver que des chiffres sur l'image ce qui lui permet d'être beaucoup plus précis et de ne pas confondre un 9 avec un P ou un 11 avec un H PAR EXEMPLE (non pas que ca me soit arrivé très régulièrement et que ca me soit resté dans la gorge évidemment)

L'avantage c'est que cette methode ne demande même pas de traitement de la donnée en sortie de Tesseract. On éspère simplement que le post traitement aura suffit.

TEMPS :

Cette methode regroupe la détection de temps au tour. Il y a trois grands types de WINDOW qui sont concernées :

  • La WINDOW du temps au tour
  • La WINDOW du retard sur le leader
  • La WINDOW des secteurs

La grande différence ce sont les ordres de grandeur. Les temps au tour sont en général entre 50 secondes et 2 minutes. Tandis que les secteurs sont entre 20 et 30 secondes alors que le retard sur le leader peut-être de plusieurs minutes.

Cependant, tous ces temps possèdent le même type de post-traitement avant d'être envoyés à Tesseract.

Voici un exemple de temps au tour avant toute transformation :

"Temps au tour avant traitement"

On peut avoir l'impression que ce texte est tout à fait lisible et facile à décoder surtout quand on le voit de loin comme ca. Cependant, il faut imaginer que ces chiffres font 13 pixels de haut en comptant le flou et comme expliqué plus haut ce flou dans ces echelles est terrible.

"Temps au tour zoomé"

Si on donne cette image à Tesseract, les '3' deviennent des '9', des '9' deviennent des '8', des '2' deviennent eux aussi des '9', le tout parfois inversement et de manière complêtement imprévisible. Ca n'est simplement pas utilisable.

Cette partie est un peu plus complexe car si la detection n'est pas fiable les chiffres sont simplement inutilisables. Si à tout moment un temps au tour de 1:39.106 devient 1:32.108 c'est juste pas possible.

Voici donc les étapes de post-traitement que j'ai mis en place pour leur détection :

1: J'applique un Treshold de 185 pour enlever les ambiguités d'alisaising et avoir une image en noir et blanc claire. La valeur de 185 est assez élevée car le but est de vraiment garder uniquement les contours. Comme les chiffres se ressemlent beaucoup plu que les lettres, il faut tenter le plus possible de conserver leur formes spécifiques. Je me suis rendu compte que cette valeur était une de celles qui marchent le mieux.

"Temps au tour après Treshold"

2: J'applique un Resize de 2 pour augmenter la résolution des chiffres et permettre une meilleure détection. Le but est d'avoir plus de pixels et donc de permettre à Tesseract de mieux utiliser ses matrices de convolution.

"Temps au tour après Resize"

3: Comme le Resize amène du flou, j'utilise une methode de Dilatation qui me permet de retirer ce flou et de remplir un peu plus certaines parties qui ont été un peu laissée par le Resize;

"Temps au tour après Dilatation"

4: Contrairement aux mots plus haut, la rondeur ajoutée par la dilatation n'est pas vraiment désirée. En effet, elle peut rendre confuse certains chiffres et empêcher Tesseract de bien trouver le chiffre. Alors j'applique une Erosion qui me permet de contrecarrer en partie les rondeurs ajoutées par la dilatation et retrouver des chiffres bien formées. Pour l'Erosion et la Dilatation j'ai utilisé une valeur de 1 car je ne voulais pas trop changer les chiffres.

"Temps au tour après Erosion"

Explication des methodes précises plus bas

Et avec ce post processing on retrouve de plutôts bon résultats qui demandent peu de traitement.

Le traitement dépend du type de WINDOW cependant.

  • Pour les secteurs on indique à Tesseract que les caractères autorisés sont : "0123456789."
  • Pour les temps au tour on autorise plutôt "0123456789.:"
  • Et pour les écarts on autorise "0123456789.+"

Ensuite on récupère une liste de chiffres qui'il va falloir transformer en milisecondes pour faciliter le stockage et l'envoi.

Le programme nettoie un peu la chaine avant de la convertir. Par exemple parfois le ':' de 1:34.456 est compris comme un '1' ou un '2' et il faut faire attention à detecter quand ca arriver.

Je passe les détails du reste du nettoyage car c'est vraiment du cas par cas mais quand on a finit de nettoyer la chaine on peut transformer les chaines de minutes secondes et milisecondes en un total de milisecondes.

Pour résumer le fonctionnement dans l'ordre :

  • On prend l'image et on lui applique une série de filtres
  • On envoie l'image filtrée à Tesseract
  • On nettoie le résultat Tesseract pour compenser certains biais
  • On convertis le résultat en milisecondes
les chiffres (2)

Il faut savoir que avec la dernière version de l'émulateur (dont je vais parler un peu plus tard)

Pneus

La on arrive sur la partie la plus pénible.

Pour comprendre la problématique il faut d'abord faire un petit point sur comment les pneus fonctionnent en Formule 1.

Depuis 2019 en Formule 1 nous avons 5 grandes familles de pneus :

  • Les pneus tendres
  • Les pneus medium
  • Les pneus durs
  • Les pneus intermédiaires
  • Les pneus pluie

"Gamme de pneus Pirelli"

Les trois premiers pneus sont des pneus faits pour piste sèche, le pneu intermédiaire pour piste humide et le neu pluie pour la pluie.

Chaque pneu a sa durée de vie et son niveau de performance propre mais je ne vais pas rentrer dans le détail ici. Tout ce qu'il faut savoir ce que savoir sur quel pneu chaque pilote est et depuis combien de temps il les chausse est une information très importante.

Chaque pneu a une couleur donnée qui permet de les différencier.

Voici un exemple de ce à quoi une WINDOW de pneus peut ressembler :

"Exemple zone pneus 1"

Mais cette zone peut aussi ressembler à ca :

"Exemple zone pneus 2"

Mais aussi à ca :

"Exemple zone pneus 3"

Voire même ca :

"Exemple zone pneus 4"

Je pense que vous pouvez tout de suite comprendre la difficulté que représente la tâche de récupèration de données à partir de cette image.

En gros le fonctionnement de cette zone d'information est assez simple.

  • Au fur et à mesure que la course avance, le trait fait de même.
  • Le chiffre dans le round tout à droite indique le nombre de tour que le pilote a passé sur ce pneu.
  • La couleur indique le type de pneu.
  • Si il y a une lettre à la place d'un chiffre c'est que c'est le premier tour sur ce pneu. La lettre indique le type de pneu.

Et pas besoin de dire que si on essaie simplement de donner l'image à Tesseract on ne récupère ni les chiffres ni les lettres correctement si ce n'est pas du tout.

Il faut donc utiliser une methode qui permette d'isoler le rond le plus à droite, lui appliquer un traitement qui permette à Tesseract de lire ce qu'il y a marqué et qui puisse determiner quel pneu est en train d'être utilisé.

J'ai décidé de m'occuper dans un premier temps de trouver ce rond avant d'appliquer les filtres car plus l'image est petite plus les filtres sont rapides.

Le programme va tirer un trait depuis le bord droit de la zone, et il va avancer vers la gauche jusqu'à trouver un obstacle. Je détecte un obstacle si le pixel sur lequel est mon trait possède une valeur de plus de 0x50 dans le channel R,G ou B. J'ai trouvé en faisant des tests que les couleurs de background de la F1TV ne dépassaient jamais ces valeurs.

Ensuite après avoir trouvé le premier obstacle, je récupère une zone qui doit englober le cercle.

Voici un exemple avec cette image en entrée :

"Zone complête"

Elle est automatiquement coupée de cette facon :

"Zone coupée automatiquement"

Cela me permet d'isoler uniquement ce qui m'intéresse ce qui est très pratique pour Tesseract et pour la detection de couleur.

Ensuite avec cette image je peux commencer le processus de reconnaissance.

Je commence par faire une moyenne de tous les pixels de l'image en excluant les pixels trop sombres qui font sûrement partie du background ou du chiffre.

Ensuite j'utilise une methode qui calcule la différence entre la couleur obbtenue et la liste de couleurs possible.

Il y a cinq couleurs des pneus possibles :

"#ff0000" pneu tendre/soft

"Couleur d'un pneu tendre"

"#f5bf00" pneu medium

"Couleur d'un pneu medium"

"#a4a5a8" pneu dur/hard

"Couleur d'un pneu dur"

"#00a42e" pneu inter

"Couleur d'un pneu intermédiaire"

"#2760a6" pneu pluie/wet

"Couleur d'un pneu pluie"

Ce qui est pratique c'est que même dans les cas ou il n'y a pas beaucoup de couleur comme celui la :

"Pneu dur avec 0 tours"

On arrive à une couleur moyenne de :

"Couleur moyenne de l'image ci dessus après soustraction du background"

Et il est donc assez facile de determiner le type de pneu en question.

Attention, les résultats peuvent être très vite dérangés par la couleur du pneu précédent si le découpage de la fenêtre n'a pas été assez précis.

Ensuite il "suffit" de lire le chiffre dans le rond et si on arrive pas à le lire alors c'est que c'est une lettre et on sait que le nombre de tours est donc de 0.

Maintenant vient le moment très sympatique de la lecture du chiffre.

Vous saurez que Tesseract en plus de detester les grandes images et les images avec des couleurs, deteste également les formes dans une image. Donc dans notre cas, le round de couleur autour du chiffre, même si il n'est pas complet, il interfère avec la reconnaissance et empêche de bien lire le chiffre.

Il faut donc retirer le background et ensuite la couleur. Sauf que comme le chiffre est de la couleur du background, si on retire le background et ensuite la couleur il ne reste plus rien. Il faut donc retirer le background AUTOUR du rond, et ensuite si on retire la couleur il devrait rester le chiffre sur fond blanc.

Pour se faire, j'ai tiré des traits depuis les bords de l'image jusqu'à ce qu'ils rencontrent le rond. Ensuite je retire tous les pixels entre le rond et les bords de l'image ce qui nous donne ceci :

"Zone pneu avec le background en moins"

Ensuite on peu retirer les pixels qui ont une valeur dans un channel RGB plus haute qu'un certain seuil :

"Zone avec le reste des couleurs supprimmées"

Et la on a ce que l'on veut !

A partir de la c'est les filtres que l'on connait qui sont utilisés pour en faire une image plus facile à utiliser par Tesseract.

1 : On effectue un Resize de facteur 4 (oui c'est beaucoup mais en même temps le chiffre est vraiment petit à la base) qui permet d'avoir une image d'une bien meilleure résolution.

"Filtre 1"

2: On fait une Dilatation de facteur 1 pour retirer tout le flou de l'image pour aider Tesseract

"Resultat"

Et on a un chiffre qui est utilisable par Tesseract !

Explication des methodes précises plus bas

Pour résumer :

  • On prend l'image de la zone et on la crop pour ne garder que la partie essentielle
  • On détermine le type de pneu avec la couleur moyenne de la zone
  • On retire le background autour de cette zone
  • On retire la couleur qui reste pour ne garder que le chiffre
  • On augmente la résolution du chiffre
  • On rend ce chiffre net
  • On envoie l'image traitée et filtrée à Tesseract
  • On détermine le nombre de tours que le pilote a fait avec ses pneus avec le résultat de Tesseract
DRS

Bon ca c'était plutôt simple j'ai simplement vérifié si la moyenne de vert dépassait une certaine valeur et puis voila.

Filtres et methodes sur les images

Dans ce projet on a du utiliser différentes methodes d'édition d'image que ce soit sous forme de filtres ou de modification de l'image directement. Voici un sommaire des methodes utilisées et comment elles fonctionnent.

Tresholding

Cette methode sert à passer d'une image en couleurs à une image binaire noir blanc. C'est une étape très importante pour l'OCR car elle permet (si bien faite) d'isoler du texte de son background.

Un exemple ici :

"Exemple treshold"

Le fonctionnement est assez simple mais il peut être fait de différentes manières mais dans mon cas voici comment l'algorythme fonctionne sachant qu'il demande en entrée la Bitmap que l'on veut modifier ainsi que la valeur de Treshold :

  1. On parcours chaque pixel de l'image
  2. On convertir la couleur du pixel en une valeur de gris pour avoir la même valeur en R,G et B (Formule utilisée : grey = R x 0.3 + G x 0.59 + B x 0.11)
  3. Si le résultat de la valeur de gris est au dessus de la valeur de treshold, le pixel est passé en blanc complet et dans le cas contraire il est passé en noir complet
  4. On retourne la Bitmap modifiée

Un algorythme pas forcément complexe mais qui peut augmenter de manière titanesque les chances de réussir une OCR

Resize

Cette methode sert à augmenter la résolution d'une image pour améliorer la précision de l'algorythme de Tesseract. En effet, avec trop peu de pixels, la matrice de convolution n'est pas toujours aussi efficace.

Il ne faut pas confondre cette methode d'augmentation de la taille avec une simple interpolation. En effet une augmentation de taille interpolée ne vas pas vraiment changer la résolution, l'image sera toujours aussi pixelisée, seulement, les pixels seront composées de plus de pixels comme dans l'exemple ci dessous :

"Exemple d'interpolation linéaire"

Dans mon projet j'utilise de l'interpolation bicubique qui va créer de l'information pour tenter de combler le vide et produire une image réellement plus grande et avec plus de details mais en ajoutant du flou.

"Exemple des différents types d'interpolation"

Le but est d'aller chercher dans les pixels alentours les couleurs qui sont déja présente et de jouer avec des poids pour tenter de faire une prédiction de ce que ce pixel aurait été si l'image avait plus de detail.

Voici un exemple assez parlant :

"Exemple interpolation bicubique (avant)"

"Exemple interpolation bicubique (après)"

On pourrait croire que c'est inutile mais dans le contexte de Tesseract ajouter des détails pour tenter de simuler une meilleure résolution même en créant du flou est intéressant pour mieux remplir la matrice de convolution.

Mais il est possible de réduire ce flou avec d'autres méthodes également.

(Dans mon code je n'ai pas utilisé du code fait main mais j'utilise une librairie qui me permet de le faire)

Il faut simplement faire attention car c'est un procédé assez lourd en performances.

Dilatation et Erosion

Cette methode et la suivante font partie des methodes de transformation morphologiques.

Ces methodes sont utilisées pour accentuer les formes et les epaissir ou les réduire et les affiner. Elles possèdent l'aventage également de retirer le flou d'une image ce qui est très pratique si utilisé après l'utilisation de methodes comme Resize.

Je ne vais pas trop rentrer dans les détails de ces methodes car leur fonctionnement est un peu plus lourd en math si on veut faire une véritable explication du pourquoi et du comment ca marche aussi bien. Pour notre projet je dirais que l'important est de savoir que ce sont deux outils très pratiques pour changer la morphologie des lettres et des chiffres et qu'on peut les utiliser pour corriger du flou et/ou des artéfacts apparus lors de la binarisation de l'image ou de la suppression de fond.

Remove Background

Cette methode est assez simple et est juste une methode qui va passer en revue tous les pixels de l'image et si la couleur d'un pixel s'apparente à celle d'un pixel de fond il est passé en noir total ou en blanc total. Le but est de permettre au reste du programme de fonctionner avec des couleurs moins ambigues.

Une variante spécialisée pour la reconnaissance des pneus appelée affectueusement Remove Useless cherche à atteindre le même bu mais est bien plus soffistiquée et spécialisée pour retirer le background autour d'un cercle de couleur pour ensuite retirer la couleur et qu'il ne reste qu'un chiffre. Pour plus de details voir la detection de pneus.

Il y aussi d'autre methodes comme un filtre Gaussien ou Highlight countour que j'ai du développer mais que je n'ai pas utilisé donc je ne vais pas en parler ici.

Petit point résolution

Comme on peut l'imaginer la résolution est extrêmement importante pour l'OCR. Et en avancant sur le projet de l'émulateur je me suis rendu compte qu'il était possible de récupèrer des images en 4K (Plutôt 1080 avec l'upscaling du lecteur). Cela est une superbe nouvelle car cela permet de simplifier énormément le processing sur les différentes windows.

Quelques exemples pour se faire une idée

"Echantillon 720P";

"Echantillon 1080P"

"Echantillon 4K"

Mais il faut savoir que grâce à cette simplification j'ai pu aussi créer d'autres methodes de filtrage pour certaines parties. Mais la simplification était obligatoire car avec des images aussi grande il n'était simplement pas possible de venir appliquer les mêmes filtres car le temps de traitement serait beaucoup plus long.

J'indique ces changements que après l'explication d'avant car ce sont des changements un peu de dernière minute et que la logique expliquée plus haut a été très importante pour le projet OCR même si tout n'est plus forcément utilisé maintenant que j'ai des images de meilleure qualité.

Dans la version actuellement disponible la reconnaissance a été simplifiée sous cette forme :

  • Le "GapToLeader" est décodé avec un premier passage de Tresholding à 165 puis un Resize de 2 et une Dilatation de 1 pour retirer le flou
  • Les "Sectors" sont décodés en utilisant une toute nouvelle methode VanishOxyAction à cause des couleurs parfois appliquées et ensuite simplement une methode de Tresholding de 150 pour rendre le résultat assez propre pour l'OCR.
  • Le "LapTime" est dabord passé par un Tresholding très strict de 185 pour préparer la SobelEdgeDetection qui est également une nouvelle methode qu'il a été possible d'utiliser grâce à la simplification du reste des processus.
  • Le "Text" est décodé simplement avec un tresholding de 165 maintenant grâce à l'image 4K.
  • Les pneus ont leur propre traitement comme expliqué plus haut auquel on ajoute la Dilatation de 1.

Comme on peut le voir le traitement est pas mal plus simple mais cela ne veut pas dire que les autres methodes que je n'utilise plus ne sont pas utiles. La reconnaissance n'est pas encore parfaite et je pense que leur utilisation pourrait aider à améliorer les résultats. (Et parfois ces anciennes methodes sont utiles dans les traitements personnalisés des windows elle mêmes comme par exemple les pneus qui utilisent la methode GrayScale pour isoler les couleurs)

VanishOxyAction

Cette methode est une methode plutôt simple mais qui est importante. Elle se base beaucoup sur le code de la methode Grayscale et sur la methode Tresholding car elle essaie de regrouper le meilleur des deux en règlant quelques soucis que ces dernières crééent.

Le soucis avec la methode grayscale c'est que quand le texte est de couleur (Ce qui arrive souvent pour les temps de secteurs) la methode GrayScale rend les couleurs dans une nuance de gris un peu trop sombre ce qui fait que ensuite la methode de Tresholding défonce tout.

"Exemple de secteur en couleur"

"Exemple de secteur en grayscale"

L'idée est alors de prendre pour chaque pixel et de garder uniquement la valeur de R,G ou B la plus haute et de mettre les deux autres canaux au même niveau pour avoir une image blanchie qui puisse être ensuite utilisée avec la methode de Tresholding sans soucis.

"Exemple de secteur blanchi avec vanishoxyAction" SobelEdgeDetection

On pourrait se dire que avec ce genre de methode le tresholding est inutile ensuite mais ca n'est pas le cas car le tresholding sert ensuite pour rendre les contours plus ou moins aggressif. Car même si l'image ressemble à une image binarisée, il reste des nuances que le treshold va pouvoir utiliser.

SobelEdgeDetection

Cette methode est une methode assez classique que je n'ai pas designé moi même alors je ne vais pas trop m'épancher dessus.

En gros on utilise une matrice et une formule mathématique pour redessiner une image et le résultat est une image avec des contours. Je ne l'ai utilisé que pour les temps au tour car ce sont les plus récalcitrants.

Cette methode a besoin d'une image passée en noir et blanc au préalable à laquelle on applique ensuite les matrrices de filtres. Et avec ces filtres ajoutés à l'image on peut ensuite calculer le "Gradient" pour créer les bords.

Le seul soucis de cette methode c'est qu'elle est assez gourmande et qu'elle fournit des formes creuses dû à la nature des matrices données.

Voici un exemple de ce dont cette méthode est capable :

Artefacts de la detection de bords de Sobel

Apparemment l'OCR aime assez bien cette methode et elle permet de beaucoup moins souvent oublier les '.' ou ':'

Traitement des données

C'est bien gentil de recevoir des résultats de l'OCR, cependant on ne peut pas souvent les utiliser comme tels. En effet les resultats ne sont pas très constants et demandent d'être verifiés pour savoir si ils doivent être pris en compte.

Le post traitement de ces données dépend complêtement du contexte et donc il est différent pour chque type de window.

Voici un florilège des différents types de traitements :

  • Traitement du nom de pilote

Rien de plus que ce qui est déja détaillé dans la partie OCR

  • Traitement des pneus

Pareil

  • Traitement des temps

La par contre c'est intéressant. Dans un monde parfait je pourrais simplement prendre les résultats de l'OCR et les traiter directement. Mais comme nous vivons dans un monde ou la souffrance et la douleur sont les seules choses autorisées on ne peut pas.

Le problème vient du fait que les temps que l'on peut trouver sur la F1TV sont encodés avec des '.' et des ':' qui determinent les limites entre les chiffres qui désignent les minutes, les secondes et les milisecondes. Et le soucis avec ces séparateurs c'est qu'ils aiment bien mettre le chaos dans la reconnaissance. Quand ils ne sont pas compris comme des autres chiffres ils sont parfois simplement oubliés ou pris en double c'est un enfer.

Il faut donc trouver un moyen de detecter quand cela arrive. Et je n'ai pas trouvé de meilleur moyen que de faire du cas par cas.

Cela peut paraître simple quand on parle par exemple des secteurs. On sasit que on attend deux chiffres avant un '.' et trois chiffres après. Il est donc facile de voir que si je trouve six chiffres et pas de séparation le troisième est le séparateur mal compris.

Mais l'exemple qui détruit vraiment tout c'est les écarts avec le leader. Autant un temps au tour c'est toujours x:xx.xxx et un temps de secteur c'est xx.xxx. Mais un écart avec le leader ca peut être 0.345 comme 1:12.345. Ce qui fait que quand je 121345 est-ce que c'est 12.345 ou 1:21.345...?

Souvent on peut quand même déduire mais cela demande de prévoir presque tous les cas limites ce qui est assez pénible.

On pourrait se dire qu'il suffit de voir si la valeur est trop en dehors des normes. Le soucis c'est que il n'est pas impossible que un temps au tour ou un écart prenne d'un coup une grosse différence. Cela arrive même assez souvent quand des pilotes sortent de la piste.

"Exemple temps au tour" "Exemple temps secteur"

Pour ce qui est du DRS et de la position des pilotes, il n'y a pas vraiment de traitement supplémentaire. Non pas car la detection est parfaite, mais par ce que la detection ne peut pas rater de 200 facons. Le DRS ne peut retourner que TRUE ou FALSE et la position du pilote est entre 1 et 20 compris. Le peu de nuance fait que ce sont des cas de figures qui ne demandent pas un traitement particulier au delà de l'OCR

Stockage des données

Dans ce projet le but n'est pas simplement de trouver les données et les afficher. L'intérêt de les récupèrer est de pouvoir les comparer à d'autres données précédentes.

Le vrai soucis de la F1TV c'est justement que l'on ne peut pas facilement voir les évolutions. On ne peut voir que des "photos" de la situation actuelle de la course.

Il faut donc garder en mémoire les différentes choses qui se sont passées. Techniquement on pourrait stocker ces données dans de bêtes listes C#.

Affichage des données

Prédictions

Tests


[A remplir au fur et à mesure de la création des tests]

Résumé des difficultés techniques


[A remplir au fur et à mesure dans la seconde moitié du travail de diplôme]

Optimisation du programme


[A remplir à la fin du projet pour parler des différentes methodes d'optimisation]

Ethique du projet


[A remplir à la fin du projet pour parler des questions ethiques du projet (Ex: Utilisation potentiellement abusive de la F1Tv ou L'histoire des cookies)]

Utilisation de Chat GPT


[A remplir à la fin du projet]

Améliorations futures


[A remplir dans les dernières semaines du travail de diplôme]

Conclusion


[A remplir la dernière semaine du travail de diplôme]