165 KiB
Journal de bord
Mercredi 29 Mars 2023
Premier jour du travail de diplôme. Nous avons eu un briefing de mr Garcia et nous avons pu commencer à préparer le travail.
Nous avons eu les différents fichiers nescessaires à la bonne réalisation du projet et je me suis mis à faire les fichiers nescessaires
La première chose a été de faire ce mkdocs dans lequel j'ai mis un fichier yml plutôt standart qui risque de changer au fur et à mesure.
Voici le premier yml :
site_name: Documentation Diplome
theme:
name: material
palette:
# Palette toggle for light mode
- media: "(prefers-color-scheme: light)"
scheme: default
toggle:
icon: material/brightness-7
name: Switch to dark mode
# Palette toggle for dark mode
- media: "(prefers-color-scheme: dark)"
scheme: slate
toggle:
icon: material/brightness-4
name: Switch to light mode
markdown_extensions:
- attr_list
- md_in_html
plugins:
- glightbox
- with-pdf
Voici un example de à quoi ca ressemble en forme de site
Ensuite il m'a fallu faire une version plus à jour de mon cahier des charges car je n'y avait pas touché depuis novembre. J'ai envoyé un mail à mes enseignants pour qu'ils puissent y jeter un oeuil pour être sûr que je n'ai rien changé qui les dérangent.
Monsieur Jayr m'a demadé à l'occasion de lui faire un planning type Gantt alors je me suis mis à la tâche.
J'ai fait un planning prévisionnel et une légende les deux sont dispo dans le dossier planning de ce repertoire.
Ensuite je me suis mis à tout mettre sur git. A commencer par ce repertoire
Et c'est deja la fin de la journée ! Demain j'avance un peu sur la doc avec ce que je peux déja remplir et je finis de préparer ce dont j'ai besoin pour commencer à coder.
Jeudi 30 Mars 2023
Aujourd'hui selon le planning je dois me charger des dernirers préparatifs pour commencer correctement. J'ai fait exprès de prenre du temps pour ca au début pour ne pas me créer de soucis plus loin pendant le travail.
Je vais envoyer par mail le planning que j'ai fait à mes suiveurs.
Ensuite je vais m'attaquer au squelette de la docmentation. Je vais essayer de remplir tout ce que je peux remplir dans un premier temps car cela tout ca de fait pour plus tard quitte à modifier quelques aspects au fur et à mesure.
J'ai aussi désactivé mkdocs with pdf par ce que les résultats ne sont vraiment pas ceux que j'attends et cela ralentis énormément le déploiment.
J'ai aussi rassemblé mes croquis pour le poster :
On peut voir que dans un premier temps j'ai tenté de faire un poster un peu plus stylisé et marketing. Cependant après avoir discuté avec Mr Garcia et différents profs dont un de l'HEPIA et ils m'ont indiqué que ce qui était attendu était moins du marketing qu'un diagramme de fonctionnement.
On peut voir sur les derniers posters que le coté technique ressort de plus en plus. Le but sera de faire une version encore plus technique ou on peut voir les différents fonctionnements de l'application avec les technologies utilisées.
Le défi cela va être de faire un joli poster qui soit en même temps vendeur et en même temps rempli techniquement.
Oh et j'ai eu un problème ou mon calvier et ma souris ne voulaient d'un coup plus fonctionner. Dans mon cas c'était un problème de power management des ports. J'ai eu le soucis sur mon pc fixe à la maison et sur mon pc portable également. En gros de ce que j'ai compris le soucis c'est que le pc croit que un port est trop solicité niveau puissance et du coup décide de couper l'alimentation du port USB.
J'ai pu règler le soucis en allant dans le device manager sous universal bus controller sous power management et en décochant la case qui indique que windows peut désactiver ce port.
Je ne conseille pas ce fix si vous avez des composants de mauvaise qualité car cela pourrait être une vraie alerte cependant le fait que mes composants sont plutôt haut de gamme et le fait que mon clavier et ma souris le fassent en même temps et que ils fonctionnaient très bien depuis plus de 4 ans me font penser que c'est juste une nouvelle mise a jour de windows qui est pénible.
Demain je vais pouvoir commencer à coder pour de bon.
Vendredi 31/03/2023
Aujourd'hui on s'occupe de la PT2 qui est la programmation de la récupèration des informations des images.
Je vais tester IronOcr
Source : https://www.c-sharpcorner.com/article/ocr-using-tesseract-in-C-Sharp/ Doc : https://ironsoftware.com/csharp/ocr/docs/ Examples : https://ironsoftware.com/csharp/ocr/examples/simple-csharp-ocr-tesseract/
Avant d'utiliser la librairie je me demande si je dois utiliser un peu de post processing pour aider à la reconnaissance.
Je peux soit utiliser l'image cropée directement :
Soit avec un filtre pour passer en noir et blanc laxiste
Soit avec un filtre pour passer en noir et blanc stricte
Il va falloir faire des tests avec tous les noms et les chiffres pour trouver le plus efficace.
Bon malheureusment Iron OCR semblait être une bonne alternative mais c'est une librairie privée qui demande une license pour être utilisée. Il va falloir trouver autre chose.
En utilisant la librairie "Tesseract" qui existe on peut faire de la reconnaissance de texte avec un code plutôt simple :
TesseractEngine engine = new TesseractEngine(tessDataFolder.FullName,"eng", EngineMode.Default);
var tessImage = Pix.LoadFromMemory(ImageToByte(sample));
Page page = engine.Process(tessImage);
string text = page.GetText();
Voici la methode ImageToByte : https://stackoverflow.com/questions/7350679/convert-a-bitmap-into-a-byte-array
public static byte[] ImageToByte(Image img)
{
using (var stream = new MemoryStream())
{
img.Save(stream, System.Drawing.Imaging.ImageFormat.Png);
return stream.ToArray();
}
}
Voici le code pour traiter plusieurs textes sur une seule image :
Page page = engine.Process(tessImage);
// Get the iterator for the page layout
using (var iter = page.GetIterator())
{
// Loop over the elements of the page layout
iter.Begin();
do
{
// Declare a Rect variable to hold the bounding box
Rect boundingBox;
// Get the bounding box for the current element
if (iter.TryGetBoundingBox(PageIteratorLevel.Word, out boundingBox))
{
g.DrawRectangle(Pens.Red,new Rectangle(boundingBox.X1,boundingBox.Y1,boundingBox.Width,boundingBox.Height));
}
// Get the text for the current element
var text = iter.GetText(PageIteratorLevel.Word);
tbxResult.Text += text.ToUpper() + Environment.NewLine;
} while (iter.Next(PageIteratorLevel.Word));
}
Etonnament, avec plus de texte, des noms qui étaient autrefois mal reconnus sont parfaitement interprêtés.
Par exemple voici un exemple de reconnaisance de texte sur tous les pilotes :
On voit que le nom Leclerc est mal reconnu. Mais voici ce que cela donne quand on prend une image qui ne contient que le nom Leclerc :
On voit ici que le nom Leclerc est très bien reconnu.
Dans le premier exemple on peut voir que Tsunoda est reconnu comme "Reticin" ce qui n'est pas exactement pareil (lol)
Et quand on isole le nom Tsunoda dans une image seule :
Il le lit "RETLELYY" ce qui n'est toujours pas exactement ca...
Une meilleure résolution pourrait peut-être résoudre le problème en partie.
Jusqu'ici les images étaient en presque 720P ce qui donne ceci :
Et j'ai lancé une récupèration d'images en 1080p pour récupèrer ceci :
On peut voir une certaine différence tout de même.
Et quand on lance la reconnaissance :
"Tsunoda n'est plus écrit "RETLELYY" mais "TSUNDDA" ce qui n'est pas parfait mais qui est déja beaucoup mieux.
J'ai essayé de mettre l'engine de Tesseract en mode "JPN" comme Tsunoda est un nom japonais mais sans succès j'ai le même résultat.
Comme la résolution est meilleure je me suis dit que peut être le filtre de passage en noir et blanc pourrait aider.
J'ai écrit cette petite methode pour convertir l'image en noir et blanc :
private static Bitmap ConvertToBlackAndWhite(Bitmap inputBmp)
{
const int BLACK_TO_WHITE_TRESHOLD = 200;
Bitmap result = new Bitmap(inputBmp.Width, inputBmp.Height);
for (int y = 0; y < inputBmp.Height; y++)
{
for (int x = 0; x < inputBmp.Width; x++)
{
Color pixelColor = inputBmp.GetPixel(x,y);
if (pixelColor.R <= BLACK_TO_WHITE_TRESHOLD && pixelColor.G <= BLACK_TO_WHITE_TRESHOLD && pixelColor.B <= BLACK_TO_WHITE_TRESHOLD)
{
pixelColor = Color.FromArgb(0,0,0);
}
else
{
pixelColor = Color.FromArgb(255,255,255);
}
result.SetPixel(x,y,pixelColor);
}
}
return result;
}
Rien de bien dingue mais cela fonctionne et je peux jouer avec le BLACK_AND_WHITE_TRESHOLD pour changer son comportement.
J'ai dabord testé avec un treshold de 100 et le programme a réussi à me sortir Tsunoda en deux mots ce qui était déja très encourageant.
Et après avoir augmenté le Treshold... Tada :
Le programme arrive bien à reconnaitre TSUNODA. Je pense que cette tactique ne fonctionnait pas avant car la resolution était trop faible et l'aliasing se mêlait trop avec le texte pour être utilisable.
Cependant cette technique ne fonctionne pas sur tous les noms. Par example avec Leclerc :
On récupère "Leeler'c" ce qui n'est pas bon du tout.
Mais en modulant le Treshold (ici à 150) On peut de nouveau voir Leclerc être reconnu correctement
Je pense que pour avoir de bons résultats il va falloir faire un algo qui :
- Découpe l'image en autant de plus petites images pour avoir un mot par image.
- Teste voir si avec l'image originale un nom correspond à la liste de pilotes existant.
- Si cela ne marche pas, on applique le filtre en modulant le Treshold.
- Dans le cas ou on aurait pas un match parfait on fait un algo qui cherche le nom le plus proche qui existe dans la liste de noms donnés.
Seulement voila, il n'y a pas que des lettres que l'on veut récupèrer. On veut surtout pouvoir récupèrer les chiffres.
Pour les chiffres on va avoir des soucis également...
Si on essaie directement la même technique sans filtre on a des résultats comme celui ci :
La virgule a tendeance à se barrer ce qui est particulièrement problématique. Cependant comme les chiffres ont beaucoup moins de possibilitées que les lettres et qu'il n'y a pas de problème de langue on devrait pouvoir travailler à faire des règlage que l'on pourra ensuite utiliser.
Avec un Treshold de 165 on arrive presque à quelque chose d'intéressant :
Le + n'est clairement pas compris mais ca n'est pas embêtant car c'est souvent redondant. On arrive cependant à isoler 3 et 259. Même si la virgule n'est pas comprise cela veut dire qu'il est tout de même possible de discriminer les secondes des milisecondes.
Maintenant avec un temps au tour :
On arrive sans rien changer aux paramêtres à isoler minutes secondes et milisecondes.
Il semble que la reconnaissance de chiffre soit bien plus efficace que la reconnaissance de lettres. Il va falloir faire un test à plus grande échelle avec plus d'image pour se rendre compte de la precision.
Demain ce qui serait bien cela serait que je fasse un jeu d'images avec des valeurs connues et que je fasse une batterie de tests pour voir à quel point je peux faire confiance à la reconnaissance des chiffres.
Automatiser un système de test de la sorte me sera très utile dans le futur pour vérifier la non regression de ma reconnaissance de texte quand je tenterai d'y faire des changements.
Je suis toujours curieux cependant de voir comment le programme se débrouille avec les nombres de tours qui se trouvent dans les icones de pneus.
Lundi 3 Avril
Aujourd'hui on va faire un programme qui permet de créer un dataset qui permette de tester le programme de reconnaissance.
Je pense que le meilleur moyen de faire serait un programme qui crée le dataset et qui ensuite peut tester différentes methodes de reconnaissance.
Par la même occasion je peux développer la technologie qui va permettre de découper une image en 20 lignes ce qui me servira ensuite pour la reconnaissance.
Je me rend compte que pour faire un programme de tests je dois déja avoir une idée de la structure de mon programme.
Pour le moment je réflechis à un système de "Zones" et de "Windows". L'idée serait que une Zone est juste une sous partie d'image qui peut encore être décomposé tandis que chaque Window contient une ou plusieurs informations à récupèrer.
J'ai essayé de découper l'image pour que cela soit plus clair :
Ici on peut voir que l'image est découpée en plusieurs grandes zones. Dans un premier temps on ne s'occupe que de la première.
Ensuite :
On peut voir la que cette Main zone serait elle même décomposée en plusieurs plus petites zones.
Et ensuite chacunes de ces petites zones :
Sera décomposée en plusieurs windows qui elles sont des zones qui contiennent de l'information.
En gros on aurait trois types de zone :
- Les zones qui contiennent d'autres zones
- Les zones qui contiennent des Windows
- Les Windows
Cependant en y réflechissant on pourrait tout à fait avoir seulement des zones et des windows en faisant en sorte que les windows peuvent avoir une liste de windows et une liste de zones.
Une zone serait composée de :
- Une image de départ
- Un rectangle qui la positionne sur cette dernière
- Une liste de zones (potentiellement vide)
- Une liste de windows (potentiellement vide)
- Une methode qui permet de récupèrer une image de la zone
- Une methode qui permet de lancer la reconnaissance sur chaque window
Une window serait composée de :
- Une image de départ (cela peut être l'image cropée de la zone parente peu importe)
- Un rectangle qui la positionne sur cette dernière
- Une methode qui permet de récupérer un image de la window
- Une methode qui permet de lancer la reconnaisance sur l'image (Chaque type de zone doit l'implémenter)
Dans chaque window on peut imaginer que la methode qui fait la reconnaissance au lieu de retourner un objet qui peut contenir nimporte quel type d'information peut envoyer ce qu'elle vient de récupèrer dans une base de donnée ou un objet.
Par exemple une Zone de pilote pourrait très bien contenir un objet pilote et le donner à ses windows qui rempliraient ce même objet.
C'est une reflexion plus stockage que OCR mais c'est intéressant pour savoir ce que fait une window des données qu'elle récupère.
Dans un premier temps je pense que les windows vont simplement écrire dans un fichier ce qu'elles trouvent chacunes dans le format qu'elles veulent.
Pour comprendre pourquoi je me prend la tête il faut savoir que chaque window peut avoir accès à pleins d'informations différentes. On pourrait dire qu'elles retournent toutes une string sauf que si ca marche pour un temps au tour ou pour un nom de pilote, cela ne marche pas forcément pour un type de pneu ou un DRS ouver. Comme chaque window a plusieurs types de data elle devra elle même se charger de comment la traiter ET de la stocker.
Voila un diagramme qui résume comment je vois l'implémentation dans un premier temps :
Voici comment se présente le squellette d'une Zone :
public class Zone
{
private Bitmap FullImage;
private List<Zone> Zones;
private List<Window> Windows;
private Rectangle _bounds;
public Rectangle Bounds { get => _bounds; private set => _bounds = value; }
public Bitmap ZoneImage
{
get
{
Bitmap sample = new Bitmap(Bounds.Width, Bounds.Height);
Graphics g = Graphics.FromImage(sample);
g.DrawImage(FullImage, new Rectangle(0, 0, sample.Width, sample.Height), Bounds, GraphicsUnit.Pixel);
return sample;
}
}
public Zone(Image fullImage, Rectangle bounds)
{
FullImage = (Bitmap)fullImage;
Init(bounds);
}
public Zone(Bitmap fullImage, Rectangle bounds)
{
FullImage = fullImage;
Init(bounds);
}
private void Init(Rectangle bounds)
{
Bounds = bounds;
Zones = new List<Zone>();
Windows = new List<Window>();
}
public void AddZone(Rectangle bounds)
{
if(Fits(bounds))
Zones.Add(new Zone(ZoneImage,bounds));
}
public void AddWindow(Rectangle bounds)
{
if (Fits(bounds))
Windows.Add(new Window(ZoneImage,bounds));
}
private bool Fits(Rectangle inputRectangle)
{
if (inputRectangle.X + inputRectangle.Width > Bounds.Width || inputRectangle.Y + inputRectangle.Height > Bounds.Height || inputRectangle.X < 0 || inputRectangle.Y < 0)
{
return false;
}
else
{
return true;
}
}
}
Le but est ensuite de créer différent types de Zones.
Par exemple la MainZone devra découper son contenu en 20 parties égales pour tenter de chopper les 20 pilotes. Il serait cool de trouver un moyen de calibrer automatiquement.
C'est peut-être possible de calibrer avec de la reconnaissance de texte, on peut essayer de lancer la reconnaissance et voir ou on trouve du texte avec un peu de chance cela pourrait donner les positions et avec ca on peut peut-être determiner des lignes.
Et voici le squelette d'une window générique
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Drawing;
namespace OCR_tester
{
public class Window
{
private Bitmap FullImage;
private Rectangle _bounds;
public Rectangle Bounds { get => _bounds; private set => _bounds = value; }
public Bitmap WindowImage
{
get
{
Bitmap sample = new Bitmap(Bounds.Width, Bounds.Height);
Graphics g = Graphics.FromImage(sample);
g.DrawImage(FullImage, new Rectangle(0, 0, sample.Width, sample.Height), Bounds, GraphicsUnit.Pixel);
return sample;
}
}
public Window(Bitmap fullImage, Rectangle bounds)
{
FullImage = fullImage;
Bounds = bounds;
}
public virtual void RecoverInformations()
{
//Each Window type will have to implement its own way to recover the informations stored in the Window Image
}
}
}
Chaque Window pourra ainsi elle même implémenter la récupèration d'informations. La facon de les retourner/stocker est encore un peu floue.
Par exemple pour un temps au tour on peut imaginer que il fait une petite vérification dans l'objet pilote et dans le tableau des tours si il n'y a pas deja une valeur et si il n'y en a pas une alors il peut l'ajouter.
Maintenant je vais essayer de créer une Main window qui se calibre toute seule.
Alors après avoir bien galèré avec l'interface pour permettre au user de cliquer sur la form pour voir les zones qu'il crée, j'ai pu créer un zone qui fait les dimensions de MainZone et j'ai pu lancer la reconnaissance sur l'image et voir ou il trouve du texte :
Maintenant il faut que je nettoie la liste de rectangle pour exclure ceux qui sont trop grands pour être sur une seule ligne, ceux qui indiquent le nombre de tour en haut et ceux qui n'ont pas d'intérêts. On pourra ensuite isoler les lignes et créer une liste d'images.
Pour ce qui est de la ligne qui contient les "Gap interval last lap" et des chiffres sur les tours pour les pneus etc je vais juste demander à l'utilisateur de ne pas les prendre dans la screenshot. Comme ils contiennent des mots qui peuvent être utilisés plus loin dans les data je ne peux pas les blacklister et faire un système qui s'occupe de les enlever si ils existent selon le position y me prendrait trop de temps pour rien.
Après avoir filtré un peu les resultats et enlevé les zones beaucoup trop grandes, on se retrouve déja plus qu'avec ca :
Comme on peut le voir, du côté gauche de l'image on a beaucoup de choses reconnues mais avec beaucoup de tailles différentes ce qui n'est pas idéal. Alors j'ajoute un filtre qui permet de ne selectionner que les data sur la droite.
Maintenant il devrait être possible de faire un algorythme qui ne prend que un seul carré par ligne.
Maintenant que on sait ou se trouve chaque ligne on peut faire un petit traitement et découper l'image en plusieurs windows.
Et voila :
Maintenant le programme peut créer des zones pour chaque pilote
Maintenant il faut que j'implémente un système un peu similaire pour créer des windows.
Voici la methode que j'ai créé pour l'autocalibration :
public void AutoCalibrate()
{
List<Rectangle> detectedText = new List<Rectangle>();
Zones = new List<Zone>();
TesseractEngine engine = new TesseractEngine(Window.tessDataFolder.FullName, "eng", EngineMode.Default);
Image image = 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));
}
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);
Zones.Add(new Zone(ZoneImage, windowRectangle));
}
}
Ca peut paraitre pas énorme comme code mais pour tout mettre en place ca demande quand même pas mal de reflexion.
J'ai du clean un peu le code que j'avais fait pour permettre la selection de zones et ajouter la possibilité d'ajouter des windows sur une zone.
J'ai juste quelques difficultées à les ajouter correctement, j'ai un offset tout pourri qui se met tout le temps
Cela doit être un soucis lors de la detection de clic qui met un offset en trop. C'est vraiment pénible en tout cas.
Certes c'est moins fun de devoir manuellement indiquer ou sont les windows sur une ligne de pilote, mais je ne vois vraiment pas comment faire cela automatiquement. Le but c'est de faire une configuration qui puisse être sauvegardée comme ca pas besoin d'à chaques fois le refaire.
C'est bon ! J'avais juste oublié de changer le calcul d'offset entre le code de la zone et de la window. Note pour plus tard, il serait peut-être judicieux de faire quelque chose pour la vue, les windows et les Zones ont le même exact comportement pour la vue ce qui fait dupliquer du code.
Mais au moins maintenant ca fonctionne :
Et le programme va directement créer un dossier par pilote avec toutes les images de chaque Data le concernant :
Et c'est tout pour aujourd'hui je pense. Ce qui serait cool demain c'est que je puisse stocker d'une manière ou d'une autre ces fichiers de calibration et que je puisse les transfèrer vers le programme qui va s'occuper de décoder et commencer gentillement à décoder les différents types de data.
Note pour quand je ferai les tests. Je pense que la meilleure idée serait que je prenne pleins de photos du style et que je les mette dans un fichier CSV ou JSON avec leur contenu. Et ensuite je le fais passer en tests pour calculer la prescision de mon algo de décodage.
Pour le moment on est plutôt dans les clouts niveau planning.
Mardi 4 Avril
Aujourd'hui je suis scensé plutôt bosser sur l'interpretation des données, mais une idée m'a taraudé l'esprit toute la nuit. Est-ce que je ne pourrais pas quand même essayer de décomposer la zone de pilote directment comme pour la Main zone.
Pour ce faire j'ai tenté de faire comme pour la main zone c'est à dire lancer la reconnaissance pour savoir ou étaient tous les champs de données mais malheureusement je ne pense pas que cela va être possible.
En effet non seulement ici les champs sont de tailles très variées, mais en plus la reconnaissance n'arrive pas à en récupèrer le même nombre sur chaque ligne ce qui risque d'être complexe à utiliser ensuite.
La preuve :
Cependant tout n'est pas perdu ! Il y a peut-être un moyen qui serait mieux en tous points. Le soucis avec ce type de reconnaissance c'est qu'on utilise beaucoup de ressources inutiles. On peut peut-être hard coder la valeur des diviseurs et les utiliser pour créer des zones.
Ok alors visiblement c'est un problème car il semble y avoir d'autres pixels de cette couleur dans l'image (Qui l'aurait cru lol)
J'a tenté de réduire la tolérance mais le soucis c'est que c'est soit trop soit pas assez
Dernière tentative, j'ai essayé de prendre plusieurs pixels en hauteur pour chaque incrément de X et en faire la moyenne, et même comme ca, impossible de trouver de manière efficace les zones. Je pense que je vais donc revert tous mes changements pour revenir à la version ou on les choisissait manuellement.
Pas mal de temps perdu mais bon c'est comme ca ca arrive
Bon j'ai fait un revert mais j'ai ajouté une feature importante. Les zones font la largeur indiquée par l'utilisateur mais elles font la hauteur max comme ca toutes les window font la même hauteur et ca permet à l'utilisateur de ne pas forcément être ultra précis dans sa selection.
Ce qui nous donne :
Maintenant je dirais que les deux prochaines choses à faire seraient de stocker ces zones dans un fichier JSON ou autre pour que la calibration puisse être envoyée directement dans le logiciel de reconnaissance et ensuite de faire une calibration sur des images qui font la taille qu'on aura pendant les Grands Prix. Pour le moment elles sont au format 16:10 qui est le format d'écrant de mon laptop.
Pour le stockage j'imagine un fichier qui donne des indications assez simples qui permettent de reconstruire le total des zones quand il est importé plutot que d'écrire les coordonnées en dur pour chacunes.
Chaque Grande zone va implémenter une methode qui s'occupe de mettre tous ses enfants dans un fichier.
{
"MainZone":{
"x": 10,
"y": 20,
"width": 1450,
"height": 1340,
"DriverZone":{
"x": 0,
"y": 23,
"height": 25,
"Windows":[
{
"DriverPositionWindow":{
"x": 0,
"y": 0,
"width": 35
}
},
{
"DriverPositionChangesWindow":{
"x": 0,
"y": 0,
"width":45
}
}
]
}
}
}
C'est le résultat auquel j'aimerais arriver. Mais pour y arriver il faut encore que je crée les différents types de window.
Cela veut dire que je dois décider quelles informations je vais récupèrer de la page.
Par exemple je vais conserver la position du pilote mais au final les changements de positions sont difficiles à lire et sont redondants. Si je garde un historique des positions des pilotes je peux calculer moi même les changements.
Pareil pour gap avec la voiture devant. Je pense que je vais juste garder l'information des écarts absolus et ensuite je pourrai toujours calculer la différence entre les pilotes.
Ca peut paraître bête car cela rajoute du calcul mais en réalité le calcul de l'OCR est extrêmement gourmand alors il faut que j'évite le plus possible d'y faire recours. Il est bien plus rapide de calculer les écarts que d'essayer de reconnaitre le texte et le convertir en chiffre.
J'ai visiblement ajouté un bug dans mon code. Maintenant tous les pilotes ont la même image quand on les selectionne. Mais visiblement ca n'était pas le cas avant car j'avais pu prendres des images de chaque pilote.
J'ai passé 3 minutes à fixer un bug stupide j'ai un peu envie de brûler ma place de travail... Mais bon au moins maintenant cela fonctionne !
Toutes les images sont récupèrées et ont un format correct avec le bon nom :
Avec un peu de code très moche j'ai pu créer un fichier JSON qui contient les différentes infos. Cependant en exportant TOUT on se retrouve avec un fichier de 1200 lignes ce qui n'est pas optimal.
Mais quand on regarde, il devrait être possible de faire un fichier qui ne contient que les infos d'un seul pilote car ensuite il y a simplement un offset à appliquer sur la zone et les windows.
Je vais donc pouvoir commencer enfin le logiciel de décodage qui prend en entrée un fichier JSON comme celui ci qui a été génèré avec le programme de calibration.
{
"Main": {
"x": 40,
"y": 230,
"width": 1845,
"height": 719,
"Zones": [
{
"DriverZone": {
"x": 0,
"y": 3,
"width": 1845,
"height": 35,
"Windows": [
{
"Position": {
"x": 2,
"y": 0,
"width": 32
},
"GapToLeader": {
"x": 204,
"y": 0,
"width": 96
},
"LapTime": {
"x": 413,
"y": 0,
"width": 105
},
"Drs": {
"x": 526,
"y": 0,
"width": 81
},
etc...
}
]
}
}
]
}
}
Dans le futur il faudrait ajouter d'autres choses comme par exemple les différents pilotes présents sur le Grand Prix et ce genre d'infos.
Quoique je vais l'ajouter déja maintenant et plus tard je mettrai en place la feature acessible depuis l'interface. Mais le hardcoder maintenant me permet déja de mieux coder l'autre côté.
Ce programme n'est en aucun cas terminé et je vais devoir travailler encore un peu dessus pour qu'il soit utilisable correctement mais au moins il fonctionne à peu près.
Exemple du json avec les noms de pilotes:
{
"Main": {
"x": 37,
"y": 238,
"width": 1851,
"height": 713,
"Zones": [
{
"DriverZone": {
"x": 0,
"y": -5,
"width": 1851,
"height": 35
}
}
]
},
"Drivers": [
"Leclerc",
"Verstappen",
etc...
]
}
Maintenant je vais m'attaquer au décodage.
Demain je dois finir le décodage du JSON et je dois commencer à implémenter la reconnaissance des textes. Voire même des pneus etc si j'y arrive.
Mercredi 5 Avril
Bon la il faut vraiment que je finisse assez vite la lecture du JSON et la reconstruction des zones pour commencer la reconnaissance.
J'ai pris beaucoup de temps à faire le programme de calibration mais je pense que c'est essentiel de prendre ce temps maintenant. (BTW il faudra quand même retourner faire une plus jolie version par ce que la ca marche mais c'est tout)
Bon après pas mal de boulot je pense avoir réussi. Dans le nouveau programme on arrive à récupèrer les différentes zones :
Un conseil de notre professeur M.Bonvin a été de créer des Releases de versions qui ne fonctionnent pas ou pas très bien. J'ai donc publié une première release de l'OCR_TEST qui fonctionne vite fait.
J'ai seulement un petit soucis, comme je recrée complêtement la structure des driver zones avec seulement la première, il y a un petit décalage car entre les zones il y avait un gap.
Ce qui fait que si la première zone est parfaitement centrée :
La vingtième ne l'est plus exactement :
Pour ca j'ai essayé de mettre un espacement arbitraire mais c'est complexe. Je vais plutôt tenter de faire une différence entre la taille de la zone complête et de la taille additionnée de toutes les fenêtre et diviser le resultat entre toutes les fenêtres.
Ca n'est pas parfait mais au moins maintenant les données ne touchent plus les bords de la fenêtre.
Et voila !
Maintenant avec le fichier de configuration en Json on arrive à récupèrer toutes les infos comme si elles avaient été envoyées directement depuis l'app de calibration mais sans le processing time !
On peut donc ENFIN passer au décodage de ces FICHUES données.
Je vais pouvoir implémenter ce que j'ai fait dans le projet de test de décodage.
Grâce à mon découpage initial qui m'a pris du temps à implémenter on a enfin un truc qui marche même si je n'ai implémenté que la reconnaissance de noms.
Si on se rappelle du système de window et de zones dans le diagramme plus haut, c'est assez facile de comprendre comment je m'y suis pris.
En gros on des listes et des listes de listes de zones, c'est la partie un peu plus technique car il y a des zones qui peuvent contenir d'autres zones etc.
Je vais commencer par la reconnaissance de noms.
Voici le tableau de pilotes de 2023
"Drivers": [
"Leclerc",
"Verstappen",
"Hamilton",
"Alonso",
"Russel",
"Gasly",
"Stroll",
"Sainz",
"Hulkenberg",
"Norris",
"Tsunoda",
"Piastri",
"Zhou",
"Ocon",
"Magnussen",
"Perez",
"Sargeant",
"De Vries",
"Bottas",
"Albon"
]
ET voici le tableau de pilotes de 2022 :
"Drivers": [
"Leclerc",
"Verstappen",
"Sainz",
"Perez",
"Hamilton",
"Russel",
"Magnussen",
"Gasly",
"Ocon",
"Alonso",
"Tsunoda",
"Bottas",
"Zhou",
"Albon",
"Stroll",
"Schumacher",
"Hulkenberg",
"Norris",
"Latifi",
"Ricciardo"
]
Je les notes ici car J'ai souvent besoin de changer selon le dataset que j'utilise.
Dans le futur je ferai sûrement un grand dataset qui prend des pilotes de reserves et des pilotes juniors pour que dans le cas ou un pilote est remplacé dans l'année il n'y a pas besoin de tout recalibrer avec l'application.
Après une discussion avec M.Bonvin j'ai décidé de tester 3 valeurs de convertion en noir et blanc et si je ne trouve pas un match exact je prend le nom le plus proche.
Pour trouver la string la plus proche je pense que je vais utiliser quelque chose qui s'appelle la technique de Levenshtein. De ce que j'ai compris c'est un algorythme qui permet de donner une metric de différence entre deux strings.
Bon et évidemment il ne faut pas se tromper dans la liste des pilotes GENRE NE PAS OUBLIER QUE GEORGE RUSSELL COMPORTE DEUX WFNEWIEWV DE "L" A LA FIN DE SON NOM CE QUI POURRAIT ENGRANGER 2H DE DEBUGGING POUR RIEN ASK ME HOW I KNOW joker laugh
J'ai vraiment un soucis avec Tsunoda, Il a trop tendeance à le confondre avec "TSUNDDA" et pour des raisons obscures, quand j'applique l'algorythme de Levenshtein le plus proche n'est pas "Tsunoda" mais "Sainz" iniuvbwdiucbiubisc POURQUOI !!??!!
Je pense que cela demande moins de changements de lettres enfin bon c'est quand même pas idéal. Il va falloir que je trouve un moyen de le repondérer. C'est dommage par ce que cela marche super avec Alonso Verstappen et Albon.
J'ai un peu modifié la methode et j'ai fait en sorte d'envoyer tous les noms en majuscules en me disant que cela pourrait réduire le nombre de changements. Et ca a marché !! Cela va sûrement demander plus de tests pour être bien sûr que tout fonctionne nikel, cependant pour le moment ca marche parfaitement avec les pilotes de 2022.
Pour ce qui est de la reconnaissance de chiffres, j'ai déja fait une partie du boulot le premier jour alors je vais juste reprendre à partir de là.
Je récupère une string de ce type "1:35.123" le soucis c'est que les : se transforment parfois en . ou inversement mais bon ca devrait pas être trop dur à gèrer.
Il faut que je transforme cette string en nombre de milisecondes (Du moins je pense que c'est le meilleur moyen pour ensuite pouvoir facilement comparer et stocker l'information).
Cela fait que 1:35:123 en milisecondes donne :
- 1 * 1000 * 60 => 60'000
- 35 * 1000 => 35'000
- 123 => 123
Total : 60'000+35'000+123 => 95'123ms
Et pour l'affichage :
- Minutes = ms / 60'000
- secondes = (ms - (minutes/60'000))/1000
- ms = ms - ((minutes 60'000) + (secondes * 1000))
Et on se retrouve avec 1:35:123
Maintenant après un peu de temps pour nettoyer la string etc on se retrouve avec un résultat comme le suivant :
Position : 0
Gap to leader : 0:0:0
Lap time : 2:15:123
DRS : False
Tyre : Undefined laps with the tyre : 0
Driver name : LECLERC
Sector 1 : 0:31:323
Sector 2 : 0:42:340
Sector 3 : 0:0:0
Evidemment pareil pour les autres pilotes Et je me rend compte que j'ai encore tout cassé car le laptime ne devrait pas être 2:15 mais 1:35...
Voila après une heure de debugging et des ajouts pour nettoyer les chaines on se retrouve avec :
Position : 0
Gap to leader : 0:0:0
Lap time : 1:35:123
DRS : False
Tyre : Undefined laps with the tyre : 0
Driver name : LECLERC
Sector 1 : 0:31:323
Sector 2 : 0:42:340
Sector 3 : 0:0:0
Note: le traitement commence à devenir long, il serait peut-être intéressant d'utiliser un seul Tesseract Engine ou de voir ce qui prend autant de temps, on dépasse la seconde de traitement ce qui est un peu ma limite.
Après on peut toujours tester de rajouter du multicore processing mais c'est pour une autre fois.
Demain je m'occupe de règler les soucis que j'ai avec la prescision de ces temps au tour et j'éspère pouvoir m'occuper aussi de la position des pneus et du DRS. J'aimerais finir tout ca cette semaine.
Jeudi 6 Avril
Une idée m'est passée par la tête pendant que je dormais, dans la liste des pilotes, quand ils sont à plus d'un tour de retard avec le leader (Ce qui arrive normalement dans presque tous les Grand Prix) on a pas des minutes mais une string qui montre "+1 Lap" ou "+2Laps" ce qui est évidemment un problème. Je pense qu'une bonne facon d'envoyer l'info serait de retourner -1 -2etc... à la place des milisecondes, mais encore faut-il detecter le nombre de tours
Je devrais être en train de commencer la documentation de commment tout ce que j'ai fait fonctionne. Cependant je ne me vois pas faire ca tant que je n'ai pas au moins récupèré toutes les infos au moins un peu proprement. Cela veut dire que je commence officiellement à prendre du retard. (Sachant que si je finis tout aujourd'hui une journée de doc suffira largement le terme est un peu exagèré mais bon)
Bon pour la reconnaissance des temps c'est spécial... Le filtre semble ne pas changer grand chose ce qui est problématique et ca n'est vraiment pas fiable.
Voici quelques expemples avec un treshold de 100:
Cette image est comprise comme "11ZSD"
Cette image est comprise comme "42340"
Et celle ci "ZZAEB"...
Ce qui... n'est pas bon du tout...
J'ai essayé de trouver un fichier d'entrainement spécifiquement fait pour les digits. J'ai essayé de blacklister les chars non voulus pour tenter d'obliger Tesseract à trouver des chiffres.
Avec la première option, les résultats ne sont pas meilleurs voire pires. Avec la seconde option c'est déja pas mal mieux mais on perd complètement la possibilité de detecter les mots comme "LEADER" ou "LAP" et de toute facon ca n'est pas parfait.
Le soucis c'est que si je n'ai pas des données fiables c'est juste impossible de faire des calculs et de l'affichage correct...
Il faut absolument que je trouve une solution.
J'ai essayé d'utiliser de l'interpolation our augmenter la taille de l'image et ensuite appliquer mon filtre pour retirer le flou mais sans succès...
Pourtant la on se retrouve avec des images plutôt claires :
Ici le programme trouve "44301"
Et ici "A5151"...
On a toujours les mêmes problèmes.
Bon je suis allé me renseigner sur l'OCR et je me suis dit que j'allais tenter de faire les choses proprement.
Je vais faire passer plusieurs étapes de postProcessing avant de donner l'image à Tesseract.
- GrayScale
- Tresholding
- InvertColors
- Scaling
- Dilatation
Ce qui donne :
Ce qui ne change : Roulement de tambour RIEN kjd viuwvuirnvoirenbf
Tout ca pour rien...
C'EST BON !!!
Bon en fait au final le problème était une mauvaise configuration de Tesseract. Je vais devoir un peu nettoyer tout ca. Mais avec les changements de l'image on a des résultats BEAUCOUP plus précis et potentiellement utilisables.
La je vais devoir faire un serieux travail de nettoyage et simplification de mon code par ce que la c'est vraiment un chantier vu le nombre de choses que j'ai du essayer.
J'ai du aussi beaucoup modifier la gestion de l'image ce qui donne :
Et la on a des résultats qui sont vraiment bons.
J'ai pu ajouter assez facilement la detection de position comme c'est simplement un chiffre.
On se retrouve maintenant avec ce genre de retours :
Position : 1
Gap to leader : 8:33:51
Lap time : 2:19:123
DRS : False
Tyre : Undefined laps with the tyre : 0
Driver name : LECLERC
Sector 1 : 0:31:828
Sector 2 : 0:42:940
Sector 3 : 0:0:0
Position : 2
Gap to leader : 0:3:259
Lap time : 23:12:392
DRS : False
Tyre : Undefined laps with the tyre : 0
Driver name : VERSTAPPEN
Sector 1 : 0:38:119
Sector 2 : 0:0:0
Sector 3 : 0:0:0
Il ne manque plus que l'implémentation de la reconnaissance du DRS et des Pneus
Et non... je viens de me rendre compte que mon programme a encore cassé car le tap time ne peut pas être 23 min lol.
J'ai un nouveau magnifique problème... Les points et les deux points sont interprêtés comme des chiffres ... Give me a F****** break...
J'ai du mal à comprendre pourquoi ils ne sont détectés comme tels que maintenant.
Bon alors il semblerait les temps au tour aie besoin d'un ordre très précis pour fonctionner.
- Grayscale
- InvertColors
- Tresholding
- Resize * 2
- Resize * 2
Et la on a des résultats un peu mieux.
Bon demain il faut absolument que je me charge de règler tous ces problèmes et que je commence la reconnaissance des pneus et de DRS par ce que je commence à être en retard.
Vendredi 6 Avril 2023
Alors aujourd'hui c'est le dernier jour avant de commencer à être en retard pour de bon.
J'ai réussi à règler le problème des temps au tour, des gaps, et des secteurs. Dans le processus j'ai cassé la detection de position mais ca devrait pas être TROP compliqué.
Et voila ... Après seulement plus de dix heures de galère, si on donne cette image au programme et le bon JSON le programme nous retourne :
Position : 1
Gap to leader : 0:05:059
Lap time : 1:39:123
DRS : False
Tyre : Undefined laps with the tyre : 0
Driver name : LECLERC
Sector 1 : 0:31:828
Sector 2 : 0:42:940
Sector 3 : 0:00:000
Position : 2
Gap to leader : 0:03:259
Lap time : 1:39:392
DRS : False
Tyre : Undefined laps with the tyre : 0
Driver name : VERSTAPPEN
Sector 1 : 0:31:749
Sector 2 : 0:00:000
Sector 3 : 0:00:000
Evidemment le GapToLeader est faux sur leclerc car il est leader mais bon ca je pourrai toujours Hardcoder que le premier a jamais de GapToLeader.
Bon j'ai eu beaucoup de soucis que je ne vais pas mentionner ici car ce sont simplement des soucis de logique de programmation pour trouver un DRS ouvert ou non.
Au final la technique que j'utilise et qui marche plutôt bien pour le DRS est que je prend la première image de DRS et je la déclare comme valeur étalon d'un DRS non actif, en effet dans 99% des cas le leader n'a pas de DRS (cela peut arriver alors il faudra donc juste verifier que les pilotes sont bien à moins de deux secondes les uns des autres pour confirmer).
Ensuite cette valeur étalon je la calcule en fonction du nombre de pixels verts dans l'image et si il y a plus de 30% de pixels verts en plus c'est que le DRS est activé ex:
Ceci est un DRS fermé:
Ceci est un DRS ouvert:
Cela marche à peu près tout le temps mais dans le pire des cas on peut toujours verifier que les pilotes sont bien proches pour detecter les potentiels rares cas de faux positifs.
J'ai pu augmenter les performances en utilisant un seul engine pour tout le monde et en arrêtant d'utiliser GetPixel et SetPixel qui sont simplement des horreurs à utiliser. Mais elles ne sont pas encore bonnes
Le soucis avec la detection de pneus cependant, c'est qu'il n'est pas possible d'utiliser la reconnaissance pour savoir ou regarder la couleur car cela ne marcherait pas. Je ne peux pas faire trop de post processing car je dois conserver la couleur Je ne peux pas hardocder un endroit ou aller regarder car cela évolue tout le long du Grand Prix.
Bref c'est la galère. En y réflechissant je me suis dit qu'une bonne idée pourrait être de partir de la droite de la zone du pneu en regardant au milieu de la hauteur. Puis continuer vers la gauche jusqu'à ce que je rencontre une couleur différente. Je pourrai ensuite faire une zone un peu vers la gauche qui devrait contenir les infos du pneu et sur laquelle il sera possible de faire de le reconnaissance de couleur et de la reconnaissance de chiffres.
J'ai déterminé que le background n'était jamais plus clair que #505050 et que donc nimporte quelle couleur qui aurait plus que 50 dans un seul des channels serait considèrée comme une couleur cassant le background
Pour arriver à cette conclusion je me suis amusé un peu avec les couleurs pour jouer avec les limites de mon algorythme :
Et je crois que j'ai eu une bonne idée, avec une petite methode bien faite on arrive à de supers résultats :
private Rectangle FindTyreZone()
{
Bitmap bmp = WindowImage;
int currentPosition = bmp.Width;
int height = bmp.Height / 2;
Color limitColor = Color.FromArgb(0x50,0x50,0x50);
Color currentColor = Color.FromArgb(0,0,0);
Size newWindowSize = new Size(bmp.Height,bmp.Height);
while(currentColor.R <= limitColor.R && currentColor.G <= limitColor.G && currentColor.B <= limitColor.B && currentPosition > 0)
{
currentPosition--;
currentColor = bmp.GetPixel(currentPosition,height);
}
//Its here to let the new window include a little bit of the right side
int offset = Convert.ToInt32((float)newWindowSize.Width / 100f * 20f);
int CorrectedX = currentPosition - (newWindowSize.Width - offset);
if (CorrectedX <= 0)
return new Rectangle(0,0,newWindowSize.Width,newWindowSize.Height);
return new Rectangle(CorrectedX,0,newWindowSize.Width,newWindowSize.Height);
}
Maintenant cela devrait être beaucoup plus simple de trouver la couleur générale et le nombre de tours.
Donc ce que je fais c'est que je fais une reconnaissance de texte sur l'image réduite.
Si je trouve une lettre c'est facile Ca me donne le type de pneu et ca me dit que c'est le premier tour avec.
Si c'est un nombre alors je fais la moyenne de toutes les couleurs de l'image et je prend la couleur de pneu la plus proche.
Voici les différentes couleurs de pneus :
- SOFT : #FF0000
- MEDIUM : #f5bf00
- HARD : #d9d8d4
- INTER : #00a42e
- WET : #2760a6
Les couleurs de pneus peuvent changer de temps à autres, par exemple cette règle de pneus est arrivée en 2019 et avant il y avait beaucoup plus de couleurs mais dans une volonté de rendre le sport plus facile à comprendre à la télé cela a été simplifié. Je ne pense pas que cela va changer dans les années qui viennent alors tout est hardcodé.
Je pense que j'ai des soucis avec la detection de texte et de couleur car ma zone est trop grande.
Alors bon j'écrit ces lignes apres des heures de tests.
Il semble que la principale difficulté avec ces pneus c'est que les chiffres ou lettres sont minuscules. Il est donc extrêmement difficile de faire une reconnaissance ne serait-ce qu'un peu fiable..
Je fais de mon mieux pour tenter de règler le soucis cependant c'est vraiment complexe.
Je commence à devenir fou, je tente tout et nimporte quoi pour permettre à mon algo de fonctionner et même quand je fais du post processing comme pas possible il me retourne toujours nimporte quoi...
Ici le programme va trouver '5i'...
En fait c'est complexe d'expliquer tout ce que je fais car je change tout en boucle en essayant et en ratant ce qui prend des heures.
Pour aujourd'hui j'abandonne je vais simplement rentrer chez moi et y réflechir cette nuit mais je ne vois pas comment mieux faire la...
C'est terrible par ce que je sens que je ne suis pas bien loin.
Vacances
Bon je vais un peu laisser de côté la detection de chiffres pour me pencher un peu plus sur la détection de couleur. Par ce que techniquement si j'arrive à toujours parfaitement la detecter alors je pourrais me passer des chiffres car ils sont redondant si je construit un historique de pneus.
J'ai réussi à fix mon problème de mauvaise detection de couleur de pneus. Du moins je crois.
Seulement j'ai quand même un souci, les fenêtres de pneus avec une lettre n'ont pas assez de couleur pour être détectés. Je vais donc essayer de detecter les cinq lettres possibles et si il ne trouve pas alors je pourrai tenter de detecter les chiffres sans lettres ce qui devrait grandement aider.
Le but est encore une fois de réduire les possibilités de Tesseract. Je me rend de plus en plus compte que le plus important c'est de réduire le scope le plus possible. Moins il y a de mots et lettres et de chiffres possibles meilleure sera la reconnaissance.
Bon ca ne veut toujours pas marcher maintenant le 11 est interprêté comme trois I ou comme un M... J'en ai marre sans rire c'est vraiment pénible.
Alors j'écrit ces lignes deux jours plus tard et me rend compte avec horreur que toutes mes modifications sur ce journal de bord n'ont pas été auvegardée... yess..
Bon pour faire simple, j'ai réussi à rendre la detection de couleurs bien plus efficace en réduisant la taille de l'image et en ne prenant pas en compte les couleurs que l'on détecte comme étant partie intégrante du background.
Par exemple quand on a une image comme celle ci :
qui contient un background alors que ci dessous, on l'a enlevé.
La différence est ténue mais elle permet de grandement améliorer la prescision de la reconnaissance de couleurs.
Pour ce qui est du nombre de tours je me suis rendu compte que cela n'était déja pas très utile car avec l'historique on devrait pouvoir le déduire. Mais bon pour la forme je me suis dit que cela serait quand même une bonne idée de vérifier avec la reconnaissance. J'étais quasi certain que le soucis était le fait que l'on voie le contour du logo de pneu qui faisait que la reconnaissance avait du mal. Et j'avais raison ! En les enlevant (Ce qui n'a pas été simple) J'ai pu avoir des chiffres beaucoup plus proches de la réalité.
En même temps je ne vois pas bien comment j'aurais pu faire mieux :
Je suis quand même assez fier de voir que j'ai réussi à part de l'image que on peut voir un peu plus haut et automatiquement la transormer en celle ci-dessus.
J'ai donc pu retirer le round autour du chiffre et cela m'a permit de pouvoir dézoomer un peu et c'est avec ca que les lettres ont pu être mieux reconnues :
Maintenant je pense qu'il ne reste "plus qu'à" nettoyer un peu tout ce code qui traine de partout pour tout faire fonctionner et implémenter un peu de parrallel processing ainsi que de l'asynchrone pour ne pas bloquer le reste du programme.
Par ce qu'il faut savoir que en l'étât, le programme met 25 secondes à démarrer et consomme presque 2GB de Ram. Certes cela ne veut pas dire que la reconnaissance à elle seule prend 25 secondes car au démarrage il y a aussi la lecture du fichier de config et la création des window etc..
En réalité la partie strictement OCR prend dans les 12s si on en croit la fonction stopWatch de C#.
Et quand on change d'image la reconnaissance prend 9s.
Dans tout les cas c'est BEAUCOUP trop.
J'aurais eu comme objectif de faire une reconnaissance toutes les secondes. Je ne sais pas bien si cela va être possible mais en tout cas le but va être de s'en rapprocher.
Pour être plus exact et permettre une comparaison, voici les stats exactes
Avec un fichier d'images vide :
- Loading - 11.8s
- Splitting d'images - 90ms
- OCR - 12.5s
Avec un fichier d'images plein :
- Loading - 10.8s
- Splitting d'images - 80ms
- Ocr - 11.6s
En passant d'une image à l'autre :
- Loading - NaN
- Splitting d'images - 50ms
- Ocr - 8.8s
Donc on peut voir que les deux endroits ou le programme prend le plus de temps c'est au premier démarrage quand il faut lire le fichier et setup les windows etc...
Et l'OCR qui prend un temps fou.
Ce qui est pratique c'est que les presque 2gb de ram sont utilisés que au lancement et ensuite l'application n'en utilise que quelques centaines de mb.
Le processeur lui tourne entre 10 et 20% ce qui ne va pas durer :)
Je vais m'occuper dabord du loading.
J'ai essayé d'utiliser un Parrallel.For au moment de la création des windows, le problème c'est que visiblement les objets windows sont beaucoup trops complexes et utilisent trop de ressources partagées pour être vraiment thread Safe. J'éspère que je n'aurais pas trop de soucis avec ca qu'en j'en viendrai à l'optimisation de l'OCR...
Ce qui me rend fou c'est que cette boucle toute nulle prend plus de dix secondes à s'executer et je ne comprend pas bien pourquoi.
for (int i = 0; i < NUMBER_OF_DRIVERS; i++)
{
Point tmpPos = new Point(0, FirstZonePosition.Y + i * FirstZoneSize.Height - Convert.ToInt32(i * offset) /*- (i* (FirstZoneSize.Height / 32))*/);
Zone newDriverZone = new Zone(MainZoneImage, new Rectangle(tmpPos, FirstZoneSize));
Bitmap zoneImg = newDriverZone.ZoneImage;
newDriverZone.AddWindow(new DriverPositionWindow(zoneImg, new Rectangle(driverPositionPosition, driverPositionArea)));
newDriverZone.AddWindow(new DriverGapToLeaderWindow(zoneImg, new Rectangle(driverGapToLeaderPosition, driverGapToLeaderArea)));
newDriverZone.AddWindow(new DriverLapTimeWindow(zoneImg, new Rectangle(driverLapTimePosition, driverLapTimeArea)));
newDriverZone.AddWindow(new DriverDrsWindow(zoneImg, new Rectangle(driverDrsPosition, driverDrsArea)));
newDriverZone.AddWindow(new DriverTyresWindow(zoneImg, new Rectangle(driverTyresPosition, driverTyresArea)));
newDriverZone.AddWindow(new DriverNameWindow(zoneImg, new Rectangle(driverNamePosition, driverNameArea)));
newDriverZone.AddWindow(new DriverSector1Window(zoneImg, new Rectangle(driverSector1Position, driverSector1Area)));
newDriverZone.AddWindow(new DriverSector2Window(zoneImg, new Rectangle(driverSector2Position, driverSector2Area)));
newDriverZone.AddWindow(new DriverSector3Window(zoneImg, new Rectangle(driverSector3Position, driverSector3Area)));
MainZone.AddZone(newDriverZone);
}
Alors que Zone.AddWindow c'est simplement :
public virtual void AddWindow(Window window)
{
Windows.Add(window);
}
Et windows est simplement une liste. Donc ca ne peut pas être ca qui prend du temps.
Et les windows que je créé ont ca comme code :
public DriverPositionWindow(Bitmap image, Rectangle bounds) : base(image, bounds)
{
Name = "Position";
}
Sachant que le constructeur de base d'une Window c'est :
public Window(Bitmap image, Rectangle bounds)
{
Image = image;
Bounds = bounds;
Engine = new TesseractEngine(TESS_DATA_FOLDER.FullName, "eng", EngineMode.Default);
Engine.DefaultPageSegMode = PageSegMode.SingleLine;
}
Sachant que TesseractEngine est en statique et que donc il ne devrait... OHLLALALALALALALALALA je suis un imbécile...
J'ai juste à changer ce constructeur avec ca:
if (Engine == null)
{
Engine = new TesseractEngine(TESS_DATA_FOLDER.FullName, "eng", EngineMode.Default);
Engine.DefaultPageSegMode = PageSegMode.SingleLine;
}
ET le loading ne prend plus que 2-300 ms...
Bon c'est une très belle amélioration pour pas très chèr mais bon c'est un peu bête...
Bon je pense que 2-300ms c'est une durée correcte surtout que ca n'est appelé qu'une fois pour le lancement. On peut passer à la suite maintenant.
Alors il y a un grand soucis avec la parallellisation de l'OCR... Tesseract n'est pas par défaut une classe "Thread safe" ce qui veut dire que je ne peut utiliser de parallell.Foreach sur mes windows pour accèlèrer le traitement drastiquement.
Je pourrais par exemple avoir une instance de Tesseract par window sauf que cela fait 20 pilotes * 9 windows chacuns ce qui donne 180 instances ce qui n'est tout simplement pas raisonnable.
Je vais donc essayer de voir avec l'utilisation de methodes asynchrones qui me permettraient de faire un genre de flux tendu de reconnaissance. J'avoue que la je navigue un peu à vue, je me base sur différentes infos que je trouve sur des sites un peu perdus et sur chatGPT, j'espère que j'arriverai à trouver une solution car 10 secondes de reconnaissance c'est vraiment beaucoup trop.
Alors le soucis avec un Engine unique entre toutes les windows c'est qu'il n'est pas possible de process plusieurs images à la fois.
Je vais donc retirer l'engine unique pour voir si en créer un par window me permet de passer en multithreading.
La grande question sera : Est-ce que les ressources supplémentaires que vont prendre la création de tous ces engines va compenser entièrement le temps gagné avec la paralellisation.
Pour stocker les données dans un premier temps je vais créer un objet DriverData. Ce qu'il y a de pratique avec ca, c'est que je pourrais ajouter du code de vérification de certaines données directement dedans avant de les donner à la suite du programme.
Et on peut même imaginer une implémentation d'une liste de DriverData pour avoir l'historique.
Ce qui serait cool ca serait de grouper toutes ces data avec un numéro de tour. Placer ensuite la liste de Data dans une DB serait ainsi super simple. Mais il va falloir savoir quoi mettre, quelles infos sont redondantes et prendre en compte le fait que un tour affiché sur la page de la F1TV n'est accompli que par certains des premiers pilotes. D'autres pilotes peuvent être dans des tours précédents si ils ont du retard.
Il faudra réfléchir à cela quand je viendrai au modèle.
Bon pour y arriver j'ai du faire de gros changements et le résultat n'est peut-être pas aussi cool que ce que j'aurais voulut...
Voici un petit point sur les performances maintenant J'ai également désactivé le dump d'images. Pour le moment j'ai tout mis en commentaire mais cela pourrait être intéressant de faire en sorte de pouvoir l'activer en changeant une ou deux variables
Au démarrage :
- Loading - 113ms
- Splitting d'images - 14ms
- Ocr - 7s
En passant d'une image à l'autre :
- Loading - 113ms
- Splitting d'images - 13ms
- Ocr - 5s
Alors clairement les stats montrent qu'il y a eu un changement mesurable mais bon je pensais pouvoir en gagner un peu plus...
Je soupconne la création d'engines d'être à l'origine de ces performances presque décevantes.
Autre soucis, il semble que plus je change d'image plus la detection est lente et plus je consomme de RAM.
Il va falloir que je travaille encore un peu.
J'ai tenté de mettre un stopwatch sur une des créations d'engine Tesseract et le résultat me parait fou... Plus d'une seconde c'est dingue.
J'ai testé dans d'autres endroits du code et effectivement il semble que la création d'un engine prenne entre une et deux secondes ce qui est une ETERNITEE what !
Donc il faut optimiser tout ca.
Une idée serait de décomposer le threading mais cela me demanderait un gros refactor et je n'ai pas envie d'en refaire un la...
Sinon, une fois qu'ils sont créés ils ne prennent pas de temps du tout. Créer une fois tous les engines et ensuite les utiliser pourrait être une bonne idée. Cela prendrait longtemps au load mais ensuite les reconnaissances devraient être super rapides.
Ok alors ca c'est déja plus ce à quoi je m'attendais ! On est de nouveau à plus de 10s de loading time mais on est descendu à deux secondes par OCR. (Bon autre soucis, l'utilisation de la RAM est ridicule plus de 2gb mais ce qui m'inquiète c'est que j'ai l'impression qu'elle augmente plus on change d'image)
J'ai règlé (en partie) le soucis en obligeant le GC (Garbage Collector) à collecter après chaque detection. même après 50 detections l'utilisation de la ram se stabilize autour des 2GB.
Bon en paralellisant la création des Engines le soucis c'est que cela demande d'allouer beaucoup trop de mémoire d'un coup alors le programme se fige pendant genre cinq secondes avant de tout créer. Du coup même si la création est plus rapide, on se retrouve avec un temps total plus long... Je pense que l'on va devoir se contenter de ces dix secondes.
Bon la j'allais tenter de faire la documentation mais je viens de me rendre compte que la detection de temps au tour est pas vraiment encore idéale...
J'ai réussi à changer un petit peu le programme de reconnaissance pour rendre la reconnaissance un peu meilleure mais cela a drastiquement augmenté le temps requis pour décoder... On arrive à 3.5 secondes.
Je vais tenter de rajouter un peu de parralell processing sur les boucles de traitement voir si cela peut aider.
Alors effectivement cela aide pas mal, on arrive maintenant à faire une detection presque tout le temps en dessous de la seconde.
Et j'ai aussi du changer un peu le fonctionnement de la detection des Temps au tour.
Et voila je pense que je vais m'arrêter la pour la partie décodage. Je ne pense pas que je peux facilement faire mieux que ca et il faut que j'avance dans d'autres parties du projet.
Je vais pouvoir commencer à documenter un peu toute la partie OCR. Il faut que je prenne le temps de le faire bien car c'est la partie la plus intéressante du projet et ou je pense que j'aurai le plus essayé de choses qui vallent le coup dêtres racontées.
J'ai aussi passé pas mal de temps sur le poster du projet. J'avais fait des croquis au crayon de ce à quoi je pensais, cependant après de longues discussions avec M.Garcia ils n'étaient pas forcément très bons car ils ne représentent pas assez bien le fonctionnement du projet et sont un peu trop marketings.
Du coup j'ai fait une première version au propre :
Mais je n'étais pas forcément content du résultat et il manquait des choses je trouve comme par exemple l'utilisation de Selenium.
J'ai donc repassé des heures à faire une seconde version :
La police d'écriture n'est pas encore la bonne mais cela va venir. Mais je préfère déja beaucoup cette version à la première.
Je ne sais pas encore si la version finale sera une version plus travaillée de ce poster ou complêtement autre chose mais pour l'instant je suis à peu près content de cette version.
Je le trouve un tout petit peu trop brouillon ou avec trop d'infos mais il m'a été de nombreuses fois reproché de ne pas assez montrer le fonctionnememt interne et je ne peux pas faire plus simple.
L'ajout des nombres pour compartimenter le projet ajoute de la structure mais je me demande si cela suffit.
Maintenant que je suis à peu près content de mon code pour l'OCR je vais commencer sa documentation. (Uniquement son fonctionnement interne pas comment s'en servir car cela va changer)
Bon j'ai créé u nouveau projet selenium mais même avec les bonnes libraries je n'arrivais pas à faire fonctionner firefox j'avais toujours une erreur "OpenQA.Selenium.WebDriverException: 'Cannot start the driver service on http://localhost:51481/'" et j'ai pu règler le problème en téléchargeant directement le gecko driver depuis le git https://github.com/mozilla/geckodriver/releases et utiliser le fichier directement dans le service :
var service = FirefoxDriverService.CreateDefaultService(AppDomain.CurrentDomain.BaseDirectory+@"geckodriver-v0.27.0-win32\geckodriver.exe");
FirefoxOptions options = new FirefoxOptions();
var driver = new FirefoxDriver(service,options);
Le seul problème c'est que du coup il faut tout le temps déplacer le fichier dans le dossier bin si je clone le projet. Il faudra faire un installeur dans la version finale qui s'occupe de tout je pense.
Je me suis dit que j'allais garder la doc pour le retour des vacances quand j'aurai un bureau un clavier et un setup complet un peu propres.
Bon il va falloir que je parle de la récupération de cookie. J'ai déja pu travailler lors d'un poc sur la meilleure facon de prendres des screenshots de la F1TV :
- Avoir une page chrome ouverte avec le feed en plein écran et un programme qui prend des captures d'écrans.
- Avoir une caméra qui prend en photo l'écran au cas ou chrome et Firefox empêchent la prise de captures d'écrans.
- Récupèrer directement le feed en faisant du reverse engeneering de la plateforme.
- Simuler un chrome en background qui prenne des screenshot sans qu'on aie à le voir.
Dans toutes ces options, je dirais que la pire était celle de la caméra qui filme l'écran, mais à l'époque c'était encore envisageable comme solution de dernier recours. Le soucis de cette solution c'est l'horreur que serait la partie OCR avec une image de très mauvaise qualité.
Une autre option qui m'aurait vraiment embêté aurait été de devoir garder une page de Chrome ou Firefox ouverte quelque part sur un écran pour que le programme puisse prendres des captures d'écrans. C'est de loin l'option la plus simple et la plus logique mais elle possède pour moi de très gros points noirs :
- On ne peut pas certifier l'intégrité des données car l'utilisateur a le contrôle total sur le feed. Il peut mettre pause, avancer, reculer, tout casser sans faire exprès en ouvrant autre chose sur son ordi qui se mette pile devant. Bref c'est un peu bancale.
- Et surtout on bloque une partie non significative de l'écran de l'utilisateur avec des infos redondantes. Et je peux vous dire que quand je commente la F1 j'ai besoin de beaucoup d'informations et que chaque centimètre d'écran est cruciaé ! Alors avoir un écran complet bloqué est juste un point bloquant qui m'empêcherait d'utiliser l'app aussi bonne soit-elle dans ses prédictions.
Mais bon si aucune autre methode ne fonctionne ce qui est bien c'est que celle la est plutôt simple à mettre en place.
Ensuite reverse engeneer le feed serait l'option la plus classe, cependant c'est la plus complexe et la plus bancale au niveau légal haha. L'idée serait de récupèrer le lien vers le broadcast général et de comprendre comment il fonctionne pour le décoder nous même pendant un Grand Prix. Seuls soucis :
- Il n'est pas possible de faire des tests en dehors des periodes de Grand Prix (Et je rappelle que c'est des périodes ou je travaille en plus)
- Difficile de faire un système qui marche pareil pour les rediffusions et les lives. (En effet les liens des rediff sont beaucoup plus simple à récupèrer mais ne fonctionnent pas du tout pareil et pour tester l'app il est essentiel de pouvoir s'entrainer sur des anciens Grand Prix)
- Dernier GROS soucis, je ne sais tout simplement pas faire ca lol. Je ne sais pas comment faire. Peut-être que avec des profs qui m'aident et chat gpt ainsi qu'internet je pourrais potentiellement négocier un truc mais c'est hautement improbable et cela serait une perte de temps folle si je n'y arrive pas.
Dernière option que je trouve la plus séduisante. Simuler une instance de Chrome ou de Firefox (Le soucis avec chrome c'est qu'il implémente l'utilisation de DRM dans les vidéos qui fait qu'il est très difficile de passer outre la sécurité avec un bot) pour ensuite prendre des captures d'écrans automatiquement. Cette solutions offre pleins d'avantages :
- Pas de place prise sur l'écran
- L'intégrité des données est assurée car c'est le programme qui décide d'ou partir et de si il met pause ou non
- C'est une option complexe mais beaucoup moins que le reverse engeneering
- Elle permet de ne demander presque aucun input de la part de l'utilisateur.
Mais elle pose quelques problématiques :
- Comment se connecter automatiquement sans être detecté par un Bot et sans demander à l'utilisateur ses identifiants (Pour des raisons évidentes qui sont : QUI VA METTRE SES IDENTIFIANTS SUR UNE VIEILLE APP COMME LA MIENNE??)
- Comment faire en sorte que le programme prenne les meilleures captures dans la meilleure qualité et en plein écran.
Mais j'ai décidé de partir sur cette option.
Pour ce faire j'utilise Selenium. J'ai pu tester Puppetteer Sharp et même si dans un premier temps j'ai pu avancer asez vite, malheureusement il y a des bugs qui rendent son utilisation impossible dans notre contexte.
J'ai donc décidé de tout faire en utilisant un portage de Selenium dans mon programme.
Voici un exemple de code qui va ouvrir FireFox et qui va lancer un RickRoll
var service = FirefoxDriverService.CreateDefaultService(AppDomain.CurrentDomain.BaseDirectory+@"geckodriver-v0.27.0-win32\geckodriver.exe");
service.Host = "127.0.0.1";
service.Port = 5555;
FirefoxOptions options = new FirefoxOptions();
options.AddArgument("--disable-headless");
var driver = new FirefoxDriver(service,options);
driver.Navigate().GoToUrl("https://www.youtube.com/watch?v=dQw4w9WgXcQ&autoplay=1&mute=1");
Dans cet exemple on désactive le "Headless" pour qu'on puisse voir ce que fait l'app car sinon tout est invisible. Alors dans les faits la vidéo youtube ne se lance pas du tout car il y a des pubs et des prompts de cookies que l'on doit accepter etc... ce qui montre les différents challenges que l'on va devoir surmonter pour vraiment faire ce que l'on veut.
Mais un petit détail extrêmement important, la F1TV est un programme payant un peu comme netflix. Ce qui veut dire que pour accèder au contenu il faut être connecté. Sauf que une instance de firefox créé par Selenium est comme une page de naviguation privée, ce qui veut dire que si on va sur la page de la F1TV on est pas connectés.
Je pourrais tout à fait demander à l'utilisateur de me donner ses identifiants pour que j'aille ensuite automatiquement me connecter sauf que cela pose deux soucis:
- Personne ne voudra mettre ses identifiants sur mon programme
- La page de login de la F1TV a été protègée avec la meilleure technologie de detection de bots que je connaisse. Presque aucun site n'arrive à me detecter sauf eux ! Donc c'est tout simplement impossible d'utiliser cette technique.
Ensuite je me suis rappelé que ce que la page stocke pour me permettre de rester connecté ce sont des cookies. Et si je mets le bon cookies dans Selenium alors je serai connecté.
Dans un premier temps je voulais faire un système ou l'utilisateur irait prendre dans son chrome son cookie et le copie colle dans mon programme mais c'est immonde.
C'est alors que vient la partie récupèration de cookies !
Tous les cookies de chrome sont stockés dans une base de données SQLITE. On pourrait se dire Banco il suffit d'aller dedans et de retrouver tous les cookies et se connecter. Sauf que, pas bêtes, les équipes de chrome ont décidé que c'était une bonne idée d'encoder les cookies pour que tout le monde ne puisse pas venir y mettre son nez... En effet les cookies peuvent contenir des informations importantes.
Cela fait que pour utiliser ces cookies il faut pouvoir les décoder. Mon hypothèse a été que si ces cookies peuvent être lus par Chrome même hors connexion, c'est que la clé de décodage existe sur l'appareil et qu'il suffit de la trouver. ET C'EST LE CAS! Après pas mal de recherches j'ai pu voir que la clé de décodage existe bel et bien et qu'il suffit de la décoder en utilisant la librairie DPAPI pour la lire.
Avec cette clé on peut ensuite décoder les cookies et leurs valeurs ce qui veut dire qu'il est théoriquement possible d'automatiser le processus sans que l'utilisateur n'aie rien à faire.
J'ai décidé de faire la partie récupèration en python pour deux raison :
- Je n'arrivais pas à trouver une bonne implémentation de DPAPI en C# qui me permettait de décoder la clé.
- Il existe beaucoup plus de documentation en Python pour ce qui est de la cryptographie et donc si Chrome change de fonctionnement il sera beaucoup plus simple de changer cette partie en particulier sans avoir à recompiler le code C#.
J'ai donc avec l'aide d'internet et de ChatGPT créé ce script :
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
def decrypt_payload(cipher, payload):
return cipher.decrypt(payload)
def generate_cipher(aes_key, iv):
return AES.new(aes_key, AES.MODE_GCM, iv)
def decrypt_password(buff, master_key):
try:
iv = buff[3:15]
payload = buff[15:]
cipher = generate_cipher(master_key, iv)
decrypted_pass = decrypt_payload(cipher, payload)
decrypted_pass = decrypted_pass[:-16].decode() # remove suffix bytes
return decrypted_pass
except Exception:
# print("Probably saved password from Chrome version older than v80\n")
# print(str(e))
return "Chrome < 80"
master_key = get_master_key()
cookies_path = Path(
os.getenv("localappdata") + "\\Google\\Chrome\\User Data\\Default\\Network\\Cookies"
)
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")
Ce programme va faire tout ce que j'ai expliqué et va ensuite stocker les résultats dans un CSV pour qu'il soit facile d'y accèder depuis le C#.
Alors oui cela pose certaines questions de sécurité. Car en effet je prend tous les cookies, les décode et les stocke. Ce qui veut dire que je pourrais tout à fait envoyer ces données quelque part, par exemple un compte Netflix, et me rincer.
Si je devais rendre le projet ouvert au public je pense qu'il faudra que cela soit mentionné clairement et que le projet soit open source pour que les utilisateurs puissent verifier que je ne fais pas ca.
Maintenant de l'autre côté j'ai juste à lire le CSV et le tour est joué !
(Trouver cette solution m'a pris une semaine de vacances à l'époque)
Bon j'ai réussi à faire le programme se connecter et naviguer etc..
Par contre quelque chose que j'ai voulu ajouter et qui m'a pris pas mal de temps c'est de faire en sorte de pouvoir selectionner la qualité.
Pour changer la qualité du feed il faut cliquer sur settings et ensuite prendre le menu deroulant et selectioner 1080p. Le soucis c'est le que la value du select est jamais la même.
Elle commence toujours pas "1080_" mais ensuite ca peut être "1080_45930285" ou "1080_56801" la suite est apparemment random.
J'ai donc du utiliser ce code pour le selectioner quand même :
IWebElement settingsButton = driver.FindElement(By.ClassName("bmpui-ui-settingstogglebutton"));
settingsButton.Click();
IWebElement selectElement = driver.FindElement(By.ClassName("bmpui-ui-videoqualityselectbox"));
SelectElement select = new SelectElement(selectElement);
IWebElement selectOption = selectElement.FindElement(By.CssSelector("option[value^='1080_']"));
selectOption.Click();
Sauf que pour que cela marche je dois avant cliquer sur le bouton des settings le problème c'est qu'il est invisible alors on doit le faire apparaitre.
J'ai tenté de le faire aparaitre en bougeant la souris, en cliquant à un endroit précis, impossible de le faire marcher correctement.
Puis j'ai eu l'idée de mettre pause en envoyant un appui sur la touche Espace et ca a permit de découvrir le bouton et permettre qu'on clique dessus.
Ca peut paraitre tout bête mais rien que ca, ca m'a pris un temps considérable.
Bon pour ce qui est du timecode de la vidéo. Je pense qu'il serait trop complexe de faire en sorte que selenium change le slider de progression de la vidéo. Alors j'ai fait quelques tests et apparemment, si on quitte la F1TV sur un timecode de la vidéo que on donne au programme, comme il récupère tous les cookies de la F1TV il commencera de la.
Donc si on veut utiliser le programme avec des Grand Prix ayant déja eu lieu, on peut le faire, seulement il faudra juste au préalable avoir choisit le bon timecode dans le page de la F1TV avant de le lancer.
Ce qui est intéressant c'est que la page de la F1TV ressemble à ca au départ :
Je pense qu'une bonne idée serait de dire au programme que c'est la grille de départ et ensuite dès qu'il détecte un secteur il sait que la course a commencé.
Lundi 24 Avril 2023
Aujourd'hui c'est jour de documentation.
J'ai pas mal travaillé pendant les vacances mais je n'ai pas encore pu faire de vraie documentation correcte du fonctionnement. Du coup je vais m'en charger aujourd'hui et peut-être un peu demain.
Ok normalement je ne devrais faire que de la documentation mais je ne peux pas passer à coté de ca... Le problème que j'ai avec les pneus ou parfois il détecte un H au lieu d'un '11' et ce genre de choses c'est à cause de ma methode "RemoveBG" Qui va retirer tous les pixels plus sombres que le background. Sauf que cela va aussi retirer des pixels dans le chiffre lui même et qui va donc defigurer les 11 :
J'ai réussi à les changer en :
Mais au final cela n'a pas augmenté la précision de la reconnaissance. Je pense que je vais donc devoir encore changer.
Je pense que une bonne facon de trouver serait dabord de trouver la couleur du pneu. Et si il n'y a pas assez de couleur alors c'est que le pneu contient une lettre. Le but est d'arrêter de chercher des lettres ou des chiffres. Comme ca les 11 arrêteront d'être pris pour des 'H'
En fait on peut faire encore plus simple que ca.
On peut simplement regarder la couleur dominante et determiner le pneu. En effet même si il y a une lettre sur fond noir pour décrire le pneu, mon methode de récupèration de la couleur dominante ommet les pixels trop noirs alors il est quand même possible de determiner le type de pneus.
Et tout simplement si il n'arrive pas à lire le chiffre c'est que c'est une lettre et que donc on est à 0 tours. Cela marche plutôt bien et cela simplifie pas mal le processing.
Voila, la je vais me remettre à la documentation sinon je vais encore prendre du retard.
Mardi 25 Avril 2023
Encore une fois j'ai pris du temps de doc pour changer des choses sur la partie OCR. Mais en même temps en documentant je vois des choses que j'ai soit mal fait soit que je pourrais faire mieux en changeant très peu de choses. J'éspère que les changement que j'ai fait vont aider au moins à la cohérence du code et un peu pour les performances.
Il semble que dans les conditions que j'ai testé le nombre de tour soit plutôt fiable mais je pense que je devrai faire un peu de travail en aval dans la récupération de ces données car je sens que cela va poser problème quelques fois. Je pense que en utilisant bien l'historique on peut potentiellement se passer de l'utilisation de ce chiffre pas toujours complêtement fiable.
Mais sinon aujourd'hui c'est encore une fois un gros jour de doc. J'essaie d'expliquer les différents procédés avant de les oublier. J'essaie aussi de donner un maximum d'exemples sous formes de photos intermédiaires mais ca me prend pas mal de temps car il faut que j'ajoute un peu partout dans le code des lignes pour sortir des images intermédiaires.
En plus de la documentation je me suis aussi beaucoup occupé de nettoyer mon code et je suis assez content par ce que même en ayant du rajouter des couches de complexité pour mieux reconnaitres les temps au tour j'arrive à un temps de processing parfois en dessous des 2 secondes ce que je trouve honorable.
Quand j'aurai finit de nettoyer tous mes fichiers je ferai une release sur gitea et ce sera la version que j'utiliserai quand je voudrai faire un merge avec les autres parties du projet.
J'ai beaucoup beaucoup bossé aujourd'hui et je sui bien mort. Faire autant de documentation et de nettoyage de code c'est pas forcément bon pour le cerveau je crois. J'ai besoin d'une sieste.
Demain je pense que je vais commencer à avancer sur la partie récupèration des images. Je sais que la je fais un peu passer les tests à la trappe mais déja j'en ai fait tout le long du développement de OCR_DECODE et il faut vraiment que j'avance, quitte à revenir dessus quand j'aurai merge les deux projets ensemble.
26 Avril 2023
Aujourd'hui je vais devoir m'occuper de la partie récupération des images. J'ai déja eu l'occasion d'avancer sur ce projet pendant mopn poc et mes vacances. Donc la le but ca va être de voir ce qui manque comme véritables features et ensuite je vais pouvoir m'occuper de la vue et de son intégration avec le décodage.
Ok donc maintenant que j'au un programme qui arrive à prendre des images depuis la F1TV correctement et en bonne résolution. Je pense qu'il est temps de passer à l'implémentation de la Forme que ca va prendre.
C'est important de se poser au moins cinq minutes la question de comment je prévois de faire car même si ca n'est pas la version finale, cette dernière prendra très fort inspiration du desing que je vais faire.
Dans cette form j'aurais besoin de :
- Pouvoir selectionner un Grand Prix en insèrant l'URL du feed.
- Pouvoir lancer la calibration si besoin
- Indiquer le titre et la date du Grand Prix
- Indiquer si le Grand Prix vient de commencer ou si il y a déja un certain nombre de tours lancés.
Et c'est à peu près tout en fait...
J'ai tellement poussé pour avoir un programme qui fait tout tout seul que il ne me faut pas grand chose de plus. Je pense que ce qui serait pas mal ca serait du coup d'utiliser ce temps pour bien implémenter la calibration qui elle aura besoin d'une UI un peu plus balèze.
On pourrait même imaginer que la calibration fasse partie intégrante des settings... Ca serait peut-être bien que quand l'application se lance on se retrouve sur la page principale d'affichage de données et qu'on puisse simplement cliquer sur la page options qui contient la page calibration et qui permet de rentrer les infos du Grand Prix.
Je pense que je vais faire ca.
Voici l'interface que j'ai développée pour regrouper tout ca :
La police le style le placement et les couleurs ne sont pas définitfs, cependant je pense que c'est un bon début. Le but maintenant va être de permettre de faire fonctionner la calibration et la récupèration d'images.
Si j'arrive à faire fonctionner ces deux choses sur un même projet avant la fin de la semaine cela serait super !
Bon J'ai pu avancer sur l'intègration de Selenium mais cela prend un peu de temps car je veux implémetner un moyen de pouvoir prendre une Screenshot à nimporte quel moment et pas juste en boucle.
Demain je finis de faire fonctionner ca et ensuite je commence le cablage du reste.
Jeudi 27 Avril 2023
C'est assez dur de faire l'importation car il y a des petites différences qui obligent à presque tout réécrire.
En fait le programme de calibration avait déja implémenté la fonction de Windows et de Zones mais il fonctionnait juste assez différemment pour qu'il faille tout refaire.
La je suis en train de perdre énormément de temps à cause d'un soucis de coordonnées. J'ai repris le code de la calibration pour detecter ou l'utilisateur a cliqué pour créer les zones. Cependant, je n'arrive pas à le faire fonctionner correctement. La zone est tout le temps décalée en haut et en bas mais pas de la même facon. En haut, la valeur Y est trop grande alors que en bas la valeur Y est trop petite... Je ne comprends pas bien pourquoi.
Si c'était un simple décalage cela ne serait pas compliqué à gèrer mais la...
J'ai un soucis également avec la résolution des screenshots que je récupère en full Headless.
Voici un exemple de résolution que j'arrive à récupèrer sans le headless :
Il y a clairement un soucis et le problème c'est que avec une résolution pareille, impossible de faire une reconnaissance correcte.
BON J?EN PEUX PLUS LA. Ca fait des heures que je bosse sur ce problème débile et impossible de trouver une solution. J'ai essayé cinq facons de forcer le browser headless a prendre une plus haute résolution aucune ne fonctionne je ne comprends pas.
A chaque fois que je me retrouve avec une résolution de 1366 x 768 Ou une variante de basse résolution du style. J'en peux plus je ne trouve aucune réponse sur internet ni même avec chatGPT.
Super... La seule chose que j'ai pu faire qui change quelque chose fait que les images font maintenant du 926x517... j'ai un peu envie de commentre un crime de guerre au plus vite.
Vendredi 28 Avril 2023
Une des solutions que je n'ai pas encore essayé est de changer ma version de GeckoDriver. Sauf que ca m'oblige à changer les versions de mes libraries ce qui est très pénible, je vais continuer le debugging dans le projet Selenium_clean.
Il faut savoir que la librairie de Selenium que j'utilise est bloquée en 0.27 ce qui fait que je ne peux utiliser qu'une version obsolète du Gecko Driver.
J'ai tenté de changer vers une version en 64 bits du GeckoDriver 0.27 mais pareil, je me retrouve toujours avec des images de M.
J'essaie toutes les solutions que je trouve sur internet aucune ne convient c'est infernal. J'essaie de changer la résolution DPI, j'essaie de changer les paramêtres par défaut des player de Firefox, j'essaie de changer la résolution pendant et au début de l'execution IMPOSSIBLE DE FAIRE MARCHER CETTE MERDE C'EST PAS POSSIBLE !!!
J'ai essayé avec chrome mais je ne peux pas l'utiliser car les DRM m'empêcheront de prendre des screenshot du flux vidéo.
J'ai essayé de faire tourner avec edge mais edge ne peut pas tourner en headless. JE VAIS DEVENIR FOUF FPWQOVMQEKOVNVIBDBJDAIVOBI.
ET MAINTENANT JE N'ARRIVE PLUS A FAIRE DE PROJET AVEC SELENIUM VOIWQNV(UEWQBVU)WEQN=OEJNIVIUWVBWUEV
ON CHERCHE A ME FAIRE PETER UN PLOMB C'EST PAS POSSIBLE GIWEGUWEQN
VOICI UN EXEMPLE DU CODE QUE JE DEMANDE A UN NOUVEAU PROJET AVEC EXACTEMENT LES MEMES LIBRARIES INSTALLEES :
// Create a new FirefoxDriver instance
IWebDriver driver = new FirefoxDriver();
// Navigate to the specified URL
driver.Navigate().GoToUrl("https://www.example.com");
// Do something with the driver (e.g., find elements or take screenshots)
// Quit the driver
driver.Quit();
Je ne demande que ca ET MEME CA CA NE VEUT PAS FONCTIONNER VOIWENB)IWUQENV
Oui je suis un peu énervé ca se voit? A bon?
Et maintenant NUGGET ne fonctionne plus... j'en peux plus la.
Je ne peux plus télécharger de librairie sur aucun de mes projets...
J'ai tenté de supprimer le fichier de config et redémarrer Visual Studio mais cela ne fait rien. J'ai aussi tenté de faire un 'nugget restore' toujours rien.
Bon apparemment je ne suis pas le seul qui ne peut pas accèder à Nuget donc bon c'est pas juste chez moi qu'il y a un soucis.
Mais même en mettant ma 4G pour me connecter, je n'arrive pas à accèder à certains sites y compris Nuget et je ne peux pas download de librairies...
Je ne comprends pas ce qui se passe et du coup je ne peux juste pas bosser... J'ai redémarré trois fois mon pc et visual studio, j'ai essayé de changer mes settings DNS etc... impossible de bosser.
Je crois que je n'aurais pas du me reveiller aujourd'hui.
Bon je vais tenter d'avancer sur mon poster en attendant que le réseau soit en meilleur état.
Lundi 1 Mai 2023
Bon je bosse depuis chez moi donc j'espère que Nuget va mieux fonctionner.
Après un weekend à réfléchir au sujet de cette resolution je me suis dit deux choses.
- La seule personne sur internet que j'ai vu avoir le meme soucis avait une résolution de 1920x1200 comme moi. Cela veut donc sûrement dire que le soucis vient de cette résolution de laptop comme moi.
- Si vraiment je n'arrive pas dans un premier temps à faire fonctionner le Headless correctement, je peux toujours laisser la page de côté et m'occuper du reste du programme. Certes ca serait vraiment infernal d'avoir à garder une page chrome ouvert en tous temps et en plus elle doit être en plein écran mais bon... Si il n'y a vraiment pas d'autres solutions malheureusement je serai bien obligé.
BON ! JE N'ARRIVE MEME PLUS A FAIRE UN PROJET QUI UTILISE SELENIUM ET QUI MARCHE JE VAIS FAIRE BRÛLER GENEVE. C'est pas possible serieux, je ne comprends pas j'essaie tout ce que je trouve et impossible de juste lancer firefox c'est du grand nimporte quoi. Je prend les même putain de librairies que sur les autres projets les mêmes versions, je prend le même exact code. Sur le nouveau projet impossible de le faire fonctionner. Je commence à croire que on essaie de me faire pêter un cable.
Du coup dans un élan de désespoir je vais tenter de passer sur une autre librairie qui avec un peu de chance marche et en plus me permettrais de prendre des foutues screenshot dans le bon format.
Les deux seules librairies qui pourraient potentiellement faire l'affaire sont les librairies :
- PhantomJS
- CefSharp
Je vais les tester et simplement prier pour qu'elles fonctionnent et que je puisse faire ce que je veux avec.
Alors pour le moment avec CEFSharp j'arrive à lancer une instance de chrome et prendre une screenshot avec ce code :
CefSettings settings = new CefSettings();
settings.CachePath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "CefSharp\\Cache"); // Set cache path
settings.LogSeverity = LogSeverity.Disable; // Disable logging
Cef.Initialize(settings); // Initialize CEF
using (var browser = new ChromiumWebBrowser("www.google.com", new BrowserSettings())) // Launch Chromium in off-screen mode
{
browser.Load("https://www.example.com"); // Navigate to the test URL
browser.Size = new Size(1920, 1080); // Set the browser size to 1920x1080
browser.ScreenshotAsync().ContinueWith(task =>
{
var bitmap = task.Result;
bitmap.Save("screenshot.png", System.Drawing.Imaging.ImageFormat.Png); // Take a screenshot and save it as a PNG file
}).Wait();
}
Cef.Shutdown(); // Shutdown CEF
Avec ca il faut ces using :
using System;
using System.Drawing;
using System.IO;
using CefSharp;
using CefSharp.OffScreen;
C'est assez prometteur même si il faut encore beaucoup pour remplacer selenium.
Ah bah lol en fait non on peut pas utiliser cette librarie pour faire tourner firefox... J'EN AI MARRE J'AVAIS CHERCHE PRECISEMENT UNE LIB QUI MARCHE AVEC FIREFOX
Et phantomJS non plus ne fonctionne pas avec firefox... J'en ai marre.
Donc je vais plutôt partir sur la librairie GeckoFX qui semble pouvoir contrôler une instance de firefox. Mais j'avais justement pris un putain de projet C# et pas JS pour ne pas me taper ces problèmes de librairies...
Et si cette option ne fonctionne pas mon dernier espoir sera de directement intéragir avec le geckodriver.exe et la ca risque de pas être drôle.
JE NE COMPRENDS RIEN !!!!! Ca n'a aucun sens la doc est inexistante le seul lien qui pourrait amener sur une doc envoie sur la page principale de bitbucket. Tous les exemples de code que je trouve ne fonctionnent pas.
Je n'arrive à rien je commence à devenir fou. Tout ce travail pour rien c'est pas possible.
Même en essayant directement d'intéragir avec le process geckodriver.exe je ne peux pas arriver à mes fins. J'arrive à lancer le service et tout, mais je n'arrive pas à vraiment contrôler ce qu'il se passe donc impossible de venir prendre des screenshot.
Je ne sais tout simplement pas quoi faire ... Je suis bloqué. Je me suis cassé la tête à faire un truc qui marchait bien avec selenium et tout. Mais maintenant plus rien ne fonctionne du jour au lendemain et il n'y a simplement aucune alternative.
Je vais essayer de changer directement le projet Selenium_Clean mais bon ca va pas être drôle.
Ok alors j'ai tout repris depuis le début et je crois que j'ai enfin une solution.
Pour la trouver j'ai re-essayé toutes les techniques que j'avais tenté avant mais dans l'ordre et en les isolant à chaque fois.
Cela inclus :
Tenter de changer la densité de pixels. En effet je me suis dit que comme la résolution était plus basse le soucis était que le virtual screen avait simplement une DPI réduite.
profile.SetPreference("layout.css.devPixelsPerPx", "2.0");
J'ai aussi tenté de réduire à un seule le nombre de process de Firefox. J'ai pu lire sur internet que parfois cela pouvait influer sur les performances du renderer.
profile.SetPreference("dom.ipc.processCount", 1);
Ensuite j'ai tenté tout bêtement de rajouter dans la liste des arguments la taille voulue de l'écran.
options.AddArgument("--window-size=1920,1080");
Mais comme cela ne foncionnait pas, je me suis rabattu sur un script JS pour tenter de forcer la fenêtre à être plus grande.
js.ExecuteScript("window.resizeTo(1920, 1080);");
Comme cela n'a pas marché j'ai pu lire que cela pouvait être la taille intérieure qui devait être changée
js.ExecuteScript("window.innerWidth = 1920; window.innerHeight = 1080;");
Encore une fois sans succès. J'ai ensuite tenté d'utiliser trois autres versions du GeckoDriver, 0.27,0.26,0.25 et aucune ne m'aidait.
Mais en fait la seule chose qui a changé quoi que ce soit était la technique suivante :
Changer la window size en utilisant :
options.AddArgument("--width=1920");
options.AddArgument("--height=1200");
Ca ne marchait pas car j'utilisais une autre methode pour resize en même temps, qui elle ne marchait pas mais qui empêchait celle la de marcher. Ensuite le soucis que j'avais c'est que en mettant 1920-1080 je me retrouvais avec 1920-998 ou un truc du genre ce qui n'était pas normal alors je me disais que cette technique ne marchait pas non plus et je l'ai passée.
Alors tout n'est pas encore gagné, il faut que j'arrive à implémenter ca dans un plus gros projet et que la vidéo puisse être prise seule. Demain je m'occupe de ca.
Mardi 2 Mai 2023
Bon aujourd'hui je change le programme principal. Le soucis que j'ai c'est que en ajoutant ce système de resize, maintenant la page fait 100x100 et est grise. Il doit y avoir une technique que j'ai oublié de retirer ou un comportement un peu bizarrre.
Bon clairement je ne sais pas QUI DECIDE DE ME POURRIR LA VIE mais il est fort. J'ai télécharger EXACTEMENT les mêmes librairies que sur mon autre projet et j'utilise l'EXACT même geckodriver.exe mais dans le projet principal impossible de lui faire chier une image même avec l'EXACT même code. POURQUOI VOUS ME FAITES CA????=
La je ne comprend vraiment pas ce qui peut se passer pour que rien ne fonctionne alors que tout est pareil.
JE VIENS DE TOUT VERIFIER TOUT EST PAREIL JE NE COMPRENDS PAS.
Bon après avoir supprimé l'intégralité de ma classe Emulator cela semble marcher un peu mieux. Je ne vais pas m'étendre sur la castrophe niveau temps que cela représente. Si au moins j'arrive à faire fonctionner quelque chose je suis content.
Maintenant j'ai un soucis un peu spécial. Depuis que j'ai changé la résolution, il semble que le programme aie du mal à cliquer sur l'icone de settings.
En prenant des screenshots du moment ou l'erreur apparait, j'ai pu me rendre compte que en fait le stream est toujours en train de charger et c'est pour ca que on arrive pas à trouver le bouton :
Je pense que je n'ai le soucis que maintenant car le flux en 1080p se lance moins vite. Je vais essayer de voir si je peux detecter un élément d'HTML qui correspond au loading comme ca je peux attendre qu'il disparaisse. Sinon je peux aussi juste essayer de trouver le bouton en boucle pendant une dixaine de secondes.
Bon la j'essaie pendant genre plus de 50 secondes et ca ne marche toujours pas.
Il semblerait que au final le problème vienne du GP d'azerbidjan.
En effet, quand je teste un autre Grand Prix tout va bien.
ET MERDE !
J'ai réussi à avoir des images en 1080P mais dés que je passe l'image en plein écran c'est de nouveau du 1366X768
Avant de mettre en plein écran:
Après:
On peut voir sur l'image que l'option 1080P est effectivement bien selectionnée mais il doit y avoir un paramètre de Firefox qui s'occupe de la résolution d'un player vidéo. Il va juste falloir trouver ce paramêtre...
J'ai essayé d'utiliser :
Driver.Manage().Window.Size = new System.Drawing.Size(windowWidth, windowHeight);
Sans succès.
options.AddArgument("--start-maximized");
Pareil
Driver.Manage().Window.Maximize();
Toujours rien
profile.SetPreference("full-screen-api.ignore-widgets", true);
Nada
profile.SetPreference("media.hardware-video-decoding.enabled", true);
Toujours pas
J'ai vraiment cru que j'avais trouvé la solution en trouvant cette commande profile.SetPreference("full-screen-api.enabled", true); Mais non toujours pas...
Je commence à perdre patience.
C'EST BON.
Après littérallement 3h de debugging avec M.Bonvin (Que je remercie IMMENSEMENT) on a réussi à trouver au fin fond d'un thread github que la valeur était hard codée dans les variables d'environnement et que donc quoi que je fasse je n'aurais pas pu le changer.
En fait la seul moyen de tout règler a été de changer les variables d'environnement de ma machine:
MOZ_HEADLESS_WIDTH et MOZ_HEADLESS_HEIGHT.
Et ce qu'il y a de bien c'est que maintenant je peux mettre de la 4K et cela permet de faire un meilleur upscaling.
Recrutement Payerne Mai 2023
J'ai du faire mon recrutement à Payerne Mercredi et Jeudi. Si vous êtes curieux je peux vous dire que comme il n'y avait presque plus de places cet été je ferai Canonnier Lance mines. C'était assez frustrant d'avoir perdu deux jours de travail mais on va faire avec.
Vendredi 5 Mai 2023
Bon malgrés les courbatures il faut que je me mette au boulot un peu serieusement par ce que sinon ca va être compliqué de rattraper mon retard.
La dernière fois si je me souviens bien j'avais réussi à trouver un moyen de prendres des images en bonne résolution. Il faut maintenant que je commence à faire fonctionner la calibration et ce qui serait bien ca serait que je commence à ajouter la partie OCR au projet. Il faut que je me dépêche car Lundi je dois m'occuper du Poster.
OK j'ai compris le soucis que j'avais quand j'essayais de faire la calibration. J'avais mis l'image en ZOOM ce qui fait que si la hauteur n'était pas la bonne, l'image était recentrée ce qui fait que cela faussait totalement les résultats.
Quand on fait en sorte que l'image prenne toute la place, les coordonnées sont prises correctement.
Voici un exemple d'ou en est la partie calibration.
Normalement il me suffit d'implémenter les windows, et on devrait relativement facilement ajouter les pilotes.
Et voila. J'ai pu implémenter les windows et les pilotes. Et je peux aussi exporter des presets et les loader. Bon le loading est un peu beuggé au niveau de l'affichage mais il semble qu'il fonctionne bien quand je save les images.
Lundi je m'occupe du poster etc.. mais je pense que la suite va être l'implémentation de l'OCR.
Lundi 8 Mai 2023
Aujourd'hui c'est journée Poster.
Je pense que je ne vais pas finir la journée content car les limitations sont un peu trop présentes.
J'ai fait une version que Garcia pourrait accepter, c'est à dire en noir et blanc et avec un tout petit peu plus de détail.
Le truc c'est que en blanc je trouve que ca ne marche pas super. Et le concept d'avoir trois parties au projet qui se posent autour d'un circuit c'est peut-être pas la meilleure idée.
Je me suis dit que la bonne idée serait peut-être de prendre un autre circuit pour qu'il y aie bien trois parties :
Clairement ce poster doit faire partie des pires. C'est pas clair et ca part dans tous les sens. Je vais essayer avec un autre layout de circuit.
Je me suis ensuite dit que le circuit n'était peut être tout simplement pas une bonne idée. J'ai donc essayé de faire quelque chose de plus classique avec juste un peu de background pour qu'on puisse éviter le soucis de la page blanche derrière :
Puis je me suis dit que finalement le circuit me manquait. Alors j'ai décidé de combiner le background et le circuit ainsi que simplifier légèrement les diagrammes en retouchant un peu tout le reste on pouvait arriver à quelque chose de sympatique :
Je ne suis pas content à 100% mais bon je pense que je vais m'en satisfaire.
Pour donner une idée de la galère que c'est de créer un poster, voici ce à quoi ressemble mon espace de travail Figma :
Je ne suis pas un graphiste et ca se voit '^^.
Je pense que comme il me reste un peu de temps aujourd'hui, je vais faire un peu de documentation de la partie récupèration d'images. En effet, je pense que je n'aurai plus besoin de changer grand chose à ce niveau. Mais je ne ferai pas la partie analyse fonctionnelle car l'interface n'est clairement pas terminée.
En fait j'avais oublié mais j'ai eu un rendez vous médical du coup je n'ai pas eu trop le temps de faire la doc que je voulais. Mais au moins je pense avoir finit mon travail sur le poster et le abstract en Anglais qui sont les deux gros livrables à venir.
Mardi 9 Mai 2023
Bon je viens de me rendre compte que apparemment on doit rendre l'abstract anglais, le Poster, ET LE PROJET. Je pense que mes deux jours à l'armée m'ont fait perdre un peu la notion du temps car j'avais l'impression que l'evaluation intermédiaire 1 était il y a genre moins d'une semaine.
Donc aujourd'hui je ne vais pas trop avancer sur le code et vraiment me focus sur la documentation de la récupèration d'images. Je pense que je vais aussi ajouter la partie calibration à la documentation. Je pense que c'est important que je prenne le temps maintenant car sinon le prof aura l'impression que ca n'a pas trop avancé depuis la dernière fois.
Et puis je pense que la partie calibration et récupèration d'images ne va pas trop changer et la partie calibration encore moins.
La partie anglaise je fais la revoir un peu mais je l'avais déja faite pendant les premiers jours alors ca devrait aller.
Pour le rendu il nous était demandé de fournir un fichier PDF avec tout dedans avec une table des matières notre code source etc...
Pour ce faire j'ai du changer le mkdocs.yml et installer des packages.
Voici les changements ::
site_name: Documentation Track Trends
site_author: Rohmer Maxime
copyright: ©CFPTI Tech2
theme:
name: material
palette:
# Palette toggle for light mode
- media: "(prefers-color-scheme: light)"
scheme: default
toggle:
icon: material/brightness-7
name: Switch to dark mode
# Palette toggle for dark mode
- media: "(prefers-color-scheme: dark)"
scheme: slate
toggle:
icon: material/brightness-4
name: Switch to light mode
markdown_extensions:
- attr_list
- md_in_html
- pymdownx.highlight
plugins:
- glightbox
- search
- img2fig
- with-pdf:
cover_subtitle: Vroum Vroum
enabled_if_env: ENABLE_PDF_EXPORT
- annexes-integration:
annexes: # Required (at least 1)
- ConfigurationTool.cs: Code/ConfigurationTool.cs # An path to an annex with its title
- DriverGapToLeaderWindow.cs: Code/DriverGapToLeaderWindow.cs # An path to an annex with its title
- DriverPositionWindow.cs: Code/DriverPositionWindow.cs # An path to an annex with its title
- F1TVEmulator.cs: Code/F1TVEmulator.cs # An path to an annex with its title
- Program.cs: Code/Program.cs # An path to an annex with its title
- Window.cs: Code/Window.cs # An path to an annex with its title
- DriverData.cs: Code/DriverData.cs # An path to an annex with its title
- DriverLapTimeWindow.cs: Code/DriverLapTimeWindow.cs # An path to an annex with its title
- DriverSectorWindow.cs: Code/DriverSectorWindow.cs # An path to an annex with its title
- Form1.cs: Code/Form1.cs # An path to an annex with its title
- Reader.cs: Code/Reader.cs # An path to an annex with its title
- Zone.cs: Code/Zone.cs # An path to an annex with its title
- DriverDrsWindow.cs: Code/DriverDrsWindow.cs # An path to an annex with its title
- DriverNameWindow.cs: Code/DriverNameWindow.cs # An path to an annex with its title
- DriverTyresWindow.cs: Code/DriverTyresWindow.cs # An path to an annex with its title
- OcrImage.cs: Code/OcrImage.cs # An path to an annex with its title
- Settings.cs: Code/Settings.cs # An path to an annex with its title
- recoverCookiesCSV.py: Code/recoverCookiesCSV.py # An path to an annex with its title
Je remercie Monsieur Briard le sultan officiel de Mkdocs de la classe de m'avoir aidé pour cette partie et avoir créé un plugin qui me permet de mettre mon code source directement dans le pdf.
Bon au final j'ai quand même changé mon poster
Mais je suis trop attaché à l'ancien concept alors je vais plutôt utiliser ca :
Je pense que cette version est meilleure même si elle est encore plus en bordel par ce que le texte permet de se faire une meilleure idée de l'utilisé de chaque partie.
Mercredi 10 Mai 2023
Bon hier je n'ai pas eu le temps de finir la documentation de la recupèration d'images et de la calibration. Il faudra donc que je repasse un coup dessus en fin de semaine je pense.
Mais la j'aimerais avancer sur la mise en commun du projet, comme la configuration fonctionne plutôt pas mal je pense que je vais juste vite fait aller commenter les methodes qui ne le sont pas encore et ensuite je vais passer à l'implémentation de l'OCR.
Je suis presque certain que l'OCR va avoir besoin de plus de règlages mais bon on verra bien.
Je me rend compte en commentant que la methode de load serait plus efficace avec un tout petit peu plus d'infos de la part du JSON. J'aurais pu ajouter l'offset entre chaque Driver Zone pour eviter un lèger drift lors de la reconstruction. Mais bon rien de grave donc je pense que je vais le laisser comme ca pour le moment à moins que ca me pose soucis plus tard.
J'ai eu quelques soucis avec les images en 4K. Du coup j'ai descendu les variables d'environnement à 1920x1080
En fait il y a parfois un soucis un peu pénible avec l'OCR.
Parfois pour un temps comme ci dessous:
Le programme ne va pas bien comprendre les ponctuations et il va donner : 1115140
La il y a deux problèmes... Le 1:xx.xxx est compris comme 11xxxxx et le 4 s'est transformé en 1...
J'ai créé ce "petit" bout de code pour gèrer les fois ou les '.' et les ':' ont mal été interprêtés
if (rawNumbers.Count == 1)
{
//If this code is used it means that its bad ...
//The methods that comes are really not that great and are juste quick fixes
try
{
result = Convert.ToInt32(rawNumbers[0]);
switch (windowType)
{
case OcrImage.WindowType.Sector:
//The usual sector is in this form : 33.456
if (rawNumbers[0].Length == 6)
{
//The '.' has been understood like a number
result = 0;
result += Convert.ToInt32(rawNumbers[0][0] + rawNumbers[0][1]) * 1000;
result += Convert.ToInt32(rawNumbers[0][3] + rawNumbers[0][4] + rawNumbers[0][5]);
}
if (rawNumbers[0].Length == 5)
{
//The '.' has been overlooked
result = 0;
result += Convert.ToInt32(rawNumbers[0][0] + rawNumbers[0][1]) * 1000;
result += Convert.ToInt32(rawNumbers[0][2] + rawNumbers[0][3] + rawNumbers[0][4]);
}
break;
case OcrImage.WindowType.LapTime:
//The usual Lap time is in this form : 1:45:345
if (rawNumbers[0].Length == 6)
{
//The '.' and ':' have been overlooked
//I Know Im skipping the cases where there are more than 9 minuts but it happens so rarely that... we dont care
result = 0;
result += Convert.ToInt32(rawNumbers[0][0]) * 60000;
result += Convert.ToInt32(rawNumbers[0][1] + rawNumbers[0][2]) * 1000;
result += Convert.ToInt32(rawNumbers[0][3] + rawNumbers[0][4] + rawNumbers[0][5]);
}
if (rawNumbers[0].Length == 7)
{
//There is two possibilities
//Either 1:45.140 has been interpreted as 1145.10 or 1:451140. We will assume its the first one
result = 0;
result += Convert.ToInt32(rawNumbers[0][0]) * 60000;
result += Convert.ToInt32(rawNumbers[0][2] + rawNumbers[0][3]) * 1000;
result += Convert.ToInt32(rawNumbers[0][4] + rawNumbers[0][5] + rawNumbers[0][6]);
}
break;
case OcrImage.WindowType.Gap:
//The usual Gap is in this form : + 34.567
if (rawNumbers[0].Length == 5)
{
//The '.' has been overlooked
result += Convert.ToInt32(rawNumbers[0][0] + rawNumbers[0][1]) * 1000;
result += Convert.ToInt32(rawNumbers[0][2] + rawNumbers[0][3] + rawNumbers[0][4]);
}
break;
}
if (rawNumbers[0].Length > 6)
{
//The number definitely has been interpreted wrong
}
}
catch
{
//It can be because the input is empty or because its the LEADER bracket
result = 0;
}
}
else
{
//Auuuugh
result = 0;
}
ConfigFile = "./Presets/Clean_2023.json";
string gpUrl = "https://f1tv.formula1.com/detail/1000006688/2023-azerbaijan-grand-prix?action=play";
Bon je n'arrive pas à faire fonctionner l'OCR sans tout faire crash à chaque fois. Je vais abandonner le travail de la journée pour revenir au point initial... C'est très frustrant mais bon je ne vois pas comment faire mieux. Rien ne marche alors qu'avant ca marchant super sur le projet OCR normal.
Va savoir pourquoi même comme ca, impossible de faire marcher l'OCR. Il y a un soucis au niveau de l'ASYNC qui me fait crash tout le temps en me disant qu'un objet est deja en train d'être utilisé. Ca marchait nikel dans mes premières version je ne vois pas pourquoi ca pête maintenant.
Je pense que je vois à peu près le soucis.
public virtual async Task<DriverData> Decode(List<string> driverList)
{
int sectorCount = 0;
DriverData result = new DriverData();
Parallel.ForEach(Windows, async w =>
{
// A switch would be prettier but I dont think its supported in this C# version
if (w is DriverNameWindow)
result.Name = (string)await (w as DriverNameWindow).DecodePng(driverList);
if (w is DriverDrsWindow)
result.DRS = (bool)await (w as DriverDrsWindow).DecodePng();
if (w is DriverGapToLeaderWindow)
result.GapToLeader = (int)await (w as DriverGapToLeaderWindow).DecodePng();
if (w is DriverLapTimeWindow)
result.LapTime = (int)await (w as DriverLapTimeWindow).DecodePng();
if (w is DriverPositionWindow)
result.Position = (int)await (w as DriverPositionWindow).DecodePng();
if (w is DriverSectorWindow)
{
sectorCount++;
if (sectorCount == 1)
result.Sector1 = (int)await (w as DriverSectorWindow).DecodePng();
if (sectorCount == 2)
result.Sector2 = (int)await (w as DriverSectorWindow).DecodePng();
if (sectorCount == 3)
result.Sector3 = (int)await (w as DriverSectorWindow).DecodePng();
}
if (w is DriverTyresWindow)
result.CurrentTyre = (Tyre)await (w as DriverTyresWindow).DecodePng();
});
return result;
}
Ca c'est ma methode de decoding de chaque Driver Zone. Le message d'erreur me parle d'une windowImage quand il dit qu'un objet est déja utilisé. Ma conjecture c'est que en essayant de faire toutes les windows en même temps. Elles veulent parfois accèder à l'image principale en même temps. Ce qui evidemment pose problème. Je pense que le fix le plus simple serait de faire le traitement sans le parallele quitte à exporter ce fonctionnement sur chaque zone en elle même pour ne pas perdre trop de performances.
Ok je crois que je vois ou est le soucis. En fait dans cette version du programme c'est toujours la première image qui était juste tout le temps prise et dans la première image on a une partie des chiffres qui est bloquée par l'UI de la fenêtre... lol...
EN FAIT J'avais un soucis dans ma gestion des chiffres mal faits. Visiblement parfois quand je ne prenais pas en compte un :, un LapTime etait compris comme un Gap to leader ou un Secteur
Bon j'en ai tellement marre... Je n'arrive tout simplement PAS à faire fonctionner l'OCR ca crash tout le temps j'en peux plus.
J'ai tenté de règler les problèmes de mauvaises detections de secteurs et temps au tour qui font crasher l'app :
if (rawNumbers.Count == 2)
{
//ss:ms
result = (Convert.ToInt32(rawNumbers[0]) * 1000) + Convert.ToInt32(rawNumbers[1]);
if (result > (60000 + 999))
{
if (windowType == OcrImage.WindowType.LapTime)
{
result = 0;
result += Convert.ToInt32(rawNumbers[0][0]) * 60000;
result += Convert.ToInt32(rawNumbers[0][2].ToString() + rawNumbers[0][3].ToString()) * 1000;
result += Convert.ToInt32(rawNumbers[1]);
}
if (windowType == OcrImage.WindowType.Sector)
{
int seconds = 0;
if (rawNumbers[0].Length == 3)
{
//We have one char that we need to delete
//For no apparent reason im going to delete the first
seconds = Convert.ToInt32(rawNumbers[0][1].ToString() + rawNumbers[0][2].ToString());
}
else
{
seconds = Convert.ToInt32(rawNumbers[0][0].ToString() + rawNumbers[0][1].ToString());
}
int ms = Convert.ToInt32(rawNumbers[0][0].ToString() + rawNumbers[0][1].ToString() + rawNumbers[0][2].ToString());
result = seconds * 1000 + ms;
}
}
}
Mais toujours impossible de faire fonctionner cette M**** C'est juste infernal. Je pense que je vais encore tout retirer et remplacer par ce que j'ai dans mon projet OCR original. Donc c'est une journée de perdue complêtement... C'est extrêmement frustrant.
Après des heures de debug j'ai enfin réussi à faire fonctionner le programme de temps en temps. Mais j'ai toujours le soucis que l'image ne veut pas changer alors que je fais tout pour et que l'OCR est nulle à chier du coup...
Jeudi 11 Mai 2023
Bon après une bonne nuit de sommeil je vais reprendre les choses depuis le début.
J'ai deux soucis :
- L'OCR pue du derche
- L'Image que l'on décode ne change pas
Pour la première partie j'ai ma petite théorie. Je pense que comme je donne des images 4K alors que le feed est en 1080P, il y a déja un genre d'interpolation qui est faite. Je pense donc qu'il faut que j'adapte mon engine pour qu'il fonctionne avec cette résolution.
Je me suis demandé si ca n'était pas mieux de prendre en compte les deux résolutions pour les pc un peu moins balèzes et j'ai décidé de n'en avoir rien a faire. On verra dans le futur si c'est une feature que je voudrais ajouter mais c'est en dehors du scope du diplôme je pense.
Pour la seconde partie, je pense qu'il faut que j'aille voir du côte de OCR_Decode et de OCR Tester pour voir comment je faisais. Je dois forcément oublier un truc.
Bon ca commence mal, quand je vais voir dans le projet OCR_Decode, le changement d'image est exactement le même et il fonctionne alors que de mon côté ca n'est pas le cas.
Alors deux choses. Je me rend compte que le changement d'images n'a AUCUN effet sur la detection de texte, et seconde chose, le décalage est trop grand entre les windows. Des que le soucis d'image est règlé il va falloir que je change drastiquement ma facon de stocker la config en JSON. Il faut que je conserve les écarts.
Sinon regardez ce que ca donne quand on arrive au dernier pilote :
Je commence à devenir FOU. Je n'arrive pas à changer cette foutue image wtf... J'ai beau tenter par tous les moyens de la changer par une image noire, l'image semble toujours rester celle du départ.
Bon j'ai enfin trouvé pourquoi et je n'ai pas envie de dire comment j'ai trouvé... Je pense que l'on a tous droit à son petit jardin secret.
Maintenant ca veut dire que je peux me focus sur le concept important qui est le changement de la création et de la lecture des JSON.
Voici un exemple de preset JSON :
{
"Main": {
"x": 40,
"y": 355,
"width": 3784,
"height": 1438,
"Zones": [
{
"DriverZone": {
"x": 0,
"y": -10,
"width": 3784,
"height": 71,
"Windows": [
{
"Position": {
"x": 47,
"y": 11,
"width": 72
},
"GapToLeader": {
"x": 445,
"y": 13,
"width": 201
},
"LapTime": {
"x": 859,
"y": 14,
"width": 221
},
"DRS": {
"x": 1094,
"y": 13,
"width": 173
},
"Tyres": {
"x": 1270,
"y": 11,
"width": 1452
},
"Name": {
"x": 2727,
"y": 11,
"width": 351
},
"Sector1": {
"x": 3083,
"y": 10,
"width": 253
},
"Sector2": {
"x": 3339,
"y": 14,
"width": 195
},
"Sector3": {
"x": 3518,
"y": 14,
"width": 250
}
}
]
}
}
]
},
"Drivers": [
"Perez",
"Leclerc",
"Sainz",
"Alonso",
"Stroll",
"Russel",
"Verstappen",
"Zhou",
"Ocon",
"Hulkenberg",
"Hamilton",
"Norris",
"Tsunoda",
"Magnussen",
"Piastri",
"Albon",
"Gasly",
"Sargeant",
"Bottas",
"De Vries"
]
}
Je pense que ce qui serait bien ce serait de rajouter un "offsets" qui contienne les 19 écarts restants.
Bon... la structure de ma fabrication de JSON etait trop confuse je trouve alors je l'ai complêtement refaite.
J'ai aussi abandonné l'idée de faire un fichier le plus petit possible car au final on s'en fiche et le plus important c'est que toutes les windows et les zones soient aux bons endroits.
Ca nous fait un fichier d'environs 1300 lignes mais au moins le code pour la serialisation est plutôt clean :
public void SaveToJson(List<string> drivers, string configName)
{
string JSON = "";
JsonObject jsonFileObject = new JsonObject();
//Creating the mainZone object
JsonObject mainZoneObject = new JsonObject();
mainZoneObject.Add("x",MainZone.Bounds.X);
mainZoneObject.Add("y",MainZone.Bounds.Y);
mainZoneObject.Add("width",MainZone.Bounds.Width);
mainZoneObject.Add("height",MainZone.Bounds.Height);
JsonArray driverZonesArray = new JsonArray();
int DriverID = 0;
foreach (Zone driverZone in MainZone.Zones)
{
DriverID++;
JsonObject driverZoneObject = new JsonObject();
driverZoneObject.Add("name","Driver"+DriverID);
driverZoneObject.Add("x", driverZone.Bounds.X);
driverZoneObject.Add("y", driverZone.Bounds.Y);
driverZoneObject.Add("width", driverZone.Bounds.Width);
driverZoneObject.Add("height", driverZone.Bounds.Height);
JsonArray windowsArray = new JsonArray();
JsonObject windowObject = new JsonObject();
foreach (Window window in driverZone.Windows)
{
windowObject.Add(window.Name, new JsonObject {
{ "x", window.Bounds.X },
{ "y", window.Bounds.Y },
{ "width", window.Bounds.Width },
{ "height", window.Bounds.Height }
});
}
windowsArray.Add(windowObject);
driverZoneObject.Add("Windows",windowsArray);
driverZonesArray.Add(driverZoneObject);
}
mainZoneObject.Add("DriverZones",driverZonesArray);
JsonArray driversArray = new JsonArray();
foreach (string driver in drivers)
{
driversArray.Add(driver);
}
mainZoneObject.Add("Drivers",driversArray);
jsonFileObject.Add("Main",mainZoneObject);
JSON = jsonFileObject.ToString();
//Saving the file
string path = CONFIGS_FOLDER_NAME + configName;
if (File.Exists(path + ".json"))
{
//We need to create a new name
int count = 2;
while (File.Exists(path + "_" + count + ".json"))
{
count++;
}
path += "_" + count + ".json";
}
else
{
path += ".json";
}
File.WriteAllText(path, JSON);
}
Et normalement la lecture devrait être encore plus simple.
En fait c'était pas beaucoup plus simple mais au moins maintenant ca marche. Je vais pas mettre le code de lecture ici car c'est un peu trop long donc il va falloir me croire sur parole. (Ou aller sur Git)
Bon bah on est au même endroit qu'hier...
Bon pour demain le plan de bataille ca va être :
Changer complêtement la methode "GetTimeFromPng" pour qu'elle prenne en compte toutes les possibilités de bugs et d'oubli de '.' ou de ':' mais pas selon le nombre de blocs mais selon le type de temps que l'on cherche
Pour le moment je regarde le nombre de blocs et si il y en a deux alors c'est que c'est un temps de secteur. En fait non cela peut aussi être un temps au tour qui a raté un point.
Il faut que je bosse juste un peu vite fait la dessus et que j'arrête de putain de crasher dès que un truc est pas au bon format. Ensuite quand ca aura arrêté de crasher je vais reprendre l'OCR et voir pourquoi les resultats sont nuls a chier comme ca.
Et le but c'est que demain soir j'ai une reconnaissance de caractères plus proche de ce que j'avais dans d'autres projets... J'y croit 0 mais bon l'espoir fait vivre comme on dit.
vendredi 12 Mai 2023
Bon aujourd'hui il faut que ca marche.
On va y aller par étape. Je vais revoir toutes les methodes d'OCR et essayer de réapliquer les filtres differemment et revenir au point de départ.
Avant de commencer je note plusieurs soucis avec les premiers tests :
- Les positions des pilotes ont l'air pas mal (Pas besoin de tout changer mais peut-être simplement checker que les filtres sont bons)
- L'écart avec le leader est étonnamment pas mal aussi
- Le temps au tour est tout simplement horrible. Aucun n'est juste et de très loin même si les décimales ne sont pas forcément loin
- Le DRS je n'ai pas eu l'occasion de bien le tester mais je dirais que ca devrait être bon (à verifier quand le reste sera bon)
- Les pneus ne sont étonnamment pas SI horribles, même si parfois les lettres sont prises comme des chiffres
- Les noms de pilotes sont très bon (pas étonnant vu le système de distance de Levenstein donc ca mérite quand même un petit check)
- Les secteurs sont en général horribles mais pas toujours. C'est peut-être un soucis de décimale ou des 4 qui se transforment en 1
J'ai remarqué que les 4 sont souvent pris comme des 1. Peut-être que en ayant des images 4K l'interpolation est un peu différente de ce que j'ai l'habitude de voir.
Mais donc le plan aujourd'hui c'est de checker tous ces points et les faire fonctionner (youpi...)
J'ai désactivé toutes les methodes de cette facon :
int sectorCount = 0;
DriverData result = new DriverData();
foreach(Window w in Windows)
{
// A switch would be prettier but I dont think its supported in this C# version
if (w is DriverNameWindow)
//result.Name = (string)await (w as DriverNameWindow).DecodePng(driverList);
result.Name = "Unknown";
if (w is DriverDrsWindow)
//result.DRS = (bool)await (w as DriverDrsWindow).DecodePng();
result.DRS = false;
if (w is DriverGapToLeaderWindow)
//result.GapToLeader = (int)await (w as DriverGapToLeaderWindow).DecodePng();
result.GapToLeader = 0;
if (w is DriverLapTimeWindow)
//result.LapTime = (int)await (w as DriverLapTimeWindow).DecodePng();
result.LapTime = 0;
if (w is DriverPositionWindow)
//result.Position = (int)await (w as DriverPositionWindow).DecodePng();
result.Position = 0;
if (w is DriverSectorWindow)
{
sectorCount++;
if (sectorCount == 1)
//result.Sector1 = (int)await (w as DriverSectorWindow).DecodePng();
result.Sector1 = 0;
if (sectorCount == 2)
//result.Sector2 = (int)await (w as DriverSectorWindow).DecodePng();
result.Sector2 = 0;
if (sectorCount == 3)
//result.Sector3 = (int)await (w as DriverSectorWindow).DecodePng();
result.Sector3 = 0;
}
if (w is DriverTyresWindow)
//result.CurrentTyre = (Tyre)await (w as DriverTyresWindow).DecodePng();
result.CurrentTyre = new Tyre(Tyre.Type.Undefined,0);
}
return result;
Le but c'est que ensuite je puisse y aller étape par étape.
Position :
Alors pour cette reconnaissance je dirais que la 4K fait des merveilles qui permettent de retirer du processing. La position après un simple Treshold est assez bien reconnue et la dilataion et/ou Erosion ne sont pas nescessaires finalement.
On va donc pouvoir gagner un certain temps et c'est un bon signe pour la suite.
On peut aussi noter que quand un pilote est hors course toutes ses valeurs sont grisées et sa position est prise comme un -1
Sectors, alors j'ai refait toute la partie qui concerne les secteurs et qui les nettoie. Et je me suis rendu compte qu'ils étaient bien souvent juste, le seul truc c'est que ils s'affichent de manière un peu spéciale. En fait dans la page de la F1TV les secteurs peuvent faire plus de 60 secondes sans passer sur un affichage de minutes. Ce qui fait que c'est un peu bizarre à regarder mais c'est parfaitement juste.
J'ai aussi pu simplifier la reconnaissance grâce à l'image de meilleure qualité et maintenant les temps de secteur sont plutôt corrects.
En fait le plus long et complexe c'est de prévoir les cas particuliers ou un '.' a été mal interprêté... Et en parlant de ca, je vais aller m'occuper des temps au tour qui ajoutent une couche de complexité avec un '.' ET un ':' qui peuvent être oubliés...
Oh et j'y pense, un truc malin pourrait être de comparer les temps au tour et les temps de secteur. Ils devraient concorder normalement je pense.
Ok je viens de finir la gestion des temps au tour... J'ai un code de genre 170 lignes pour juste nettoyer le resultat dans le cas ou des '.' n'ont pas été trouvés ou des ':' ont été n'ont pas été trouvé ou si l'un des deux s'est transformé en chiffre etc.. etc.. etc..
Le soucis c'est que la maintenant je me rend compte que les écarts entre les pilotes vont juste être un enfer à nettoyer... Ils peuvent aussi bien être "0.760" comme "1:34.456" du coup... je sais pas vraiment comment faire pour tout nettoyer.
Je pense que je vais juste en avoir rien à faire et tant pis si de temps en temps c'est pas génial.
Bon du coup j'ai pas pris en compte TOUS les cas possibles mais déja un certain nombre et c'est déja pas mal. Sur les différents Grand Prix d'exemples ca a l'air de plutôt bien tourner !
Mais ca demande tellement de tests et de code que c'est un peu ridicule...
La methode "GetTimeFromPng" fait déja presque 430 lignes à cause de tous les cas possibles et tous les try catch. Ca peut paraître peu élégant mais j'ai essayé de mettre des commentaires un peu partout pour permettre à nimporte qui de comprendre ce qui se passe.
J'ai aussi pu faire les pneus et maintenant (roulement de tambour) Ca marche (presque)
En fait j'ai des soucis parfois quand les pneus sont un peu cachés dans les permiers tours :
Le soucis c'est que du coup le chiffre est un peu illisible... mais je pense que avec l'historique il devrait y avoir moyen de ne pas prendre en compte les chiffres de pneus pendant cinq tours après le changement de pneu ou de simplement tenter de faire les calculs de pneus.
Pour conclure la journée je pense que je devrais avoir le temps de faire un système qui permet de refresh à volonté.
Par contre je viens de découvrir que quand un temps de secteur est en couleur on arrive pas à le lire.
Ah et la detection prend un peu moins de trois secondes sur mon pc je crois. Mais c'est seulement si les driver zones sont faites en même temps mais en faisant ca de temps en temps ca crash et à chaques fois c'est d'un endroit différent du coup je comprend pas vraiment. Sinon ca prend dix secondes.
Lundi 15 Mai 2023
Aujourd'hui c'est journée poster et visites. Comme on va avoir des visites de premières années voire de terminales et que le soir c'est visite des parents.
Je pense que j'ai finit de tout regrouper (à part évidemment le traitement et le stockage des données) ce qui veut dire que je suis pas dans une superbe posture. Il va falloir que je sois très efficace dans la partie stockage de données et mise en place du modèle si je veux avoir une chance de rendre un joli travail de diplôme. (et même comme ca je peux voir que le temps commence à manquer)
Dans l'idéal je devrais avoir terminé la partie stockage jeudi... Ce qui veut dire que je n'ai que trois jours pour le faire et que en plus jeudi je dois travailler depuis la maison. Ca va pas être simple.
Mais aujourd'hui je vais m'occuper d'adapter la documentation de l'OCR et faire la documentation de toute la partie récupèration d'images et de la calibration.
Bon au final la journée a été un peu difficile. On a pas vraiment pu travailler l'après midi car il a fallu présenter le projet environs 10 fois à toutes les classes et à des parents voire futurs experts.
Les démos ont plutôt bien fonctionnées j'en suis assez content. Mais ca veut dire que la partie doc a pas forcément pu être totalement complêtée mais demain il va falloir que je m'occupe de la suite du projet.
Mardi 16 Mai 2023
Bon aujourd'hui c'est la partie stockage qui doit être faite.
Il y a plusieurs solutions possibles à ce problème. Mais comme je n'ai besoin que d'une base de donnée locale et que je ne veux pas que chaque utilisateur doive installer un serveur sur sa machine je pense que je vais utiliser une base de données SQLITE.
Il y a eu une petit intervention de mr Bonvin qui est venu me donner une idée pour la partie OCR.
En fait j'avais un soucis quand je voulais décoder du texte de couleur. Même en appliquant un filtre de gris je n'arrivais pas à faire reconnaitre les chiffres. Et il m'a dit que une bonne idée cela pourrait de prendre la valeur max de chaque channel et de la faire appliquer à tous ce qui blanchit assez bien l'image.
J'ai décidé d'exagèrer le blanchiment et cela donne des résultats plutôt... intéressants...
Le soucis c'est que le violet est une couleur quand même assez sombre alors il va falloir que je fasse un système de treshold un peu spécial qui soit un peu plus sympa et qui prenne plus facilement des couleurs plus basses.
Une methode à laquelle j'ai pensé pour detecter dans quel tour chaque pilote est serait de garder en mémoire toutes les infos de chaques pilotes au fur et à mesure, et dès qu'on ne recoit plus d'infos des secteurs ou que le temps au tour a changé on peut savoir qu'il faut passer au tour suivant.
Pour detecter les arrêts aux stands je peux essayer de detecter un changement de type de pneus ou de nombre de tours detectés sur le même pneu
Au départ je me disais que je pourrais peut-être faire une base de donnée SQLITE locale qui puisse être reprise d'un Grand Prix à un autre. Mais je me suis dit que de faire des statistiques inter Grand Prix était un peu en dehors du scope du projet.
La base de donnée sera donc créée à chaque démarrage de l'app
La manière dont je vois les choses en ce moment est qu'on aie deux sources de données dans l'affichage final.
On aurait une partie des infos qui seraient en direct depuis la detection :
- Les ecarts entre pilotes
- La position des pilotes
- Le dernier temps au tour
- Les derniers secteurs
- Les pneus
Mais on aurait aussi des rubriques créées de toutes pièces par des infos qui viennent de la BD Voici les rubriques qui pourraient être intéressantes à voir dans l'interface finale :
- Les 3 ou 5 pilotes les plus rapides ces cinq derniers tours
- Le pilote qui a le plus fait de dépassements
- Les batailles en cours
- Les 3 pilotes les plus lents
- Un classement pondéré avec les 20s de moins pour tous les pilotes qui ne se sont pas encore arrêtés
En gros l'idée serait que on update une fois par tour et par pilote la base de donnée avec des infos comme le temps au tour, le type de pneu etc...
Voici les trois tables que je vais créer :
Drivers
| Colonne | Type de Data | Description | Tag |
|---|---|---|---|
| ID | INTEGER | ID du pilote | PRIMARY |
| Name | VARCHAR | Nom du pilote | NOT NULL |
Pitstops
| Colonne | Type de Data | Description | Tag |
|---|---|---|---|
| Lap | INTEGER | Tour durant lequel le Pitstop a été effectué | PRIMARY |
| DriverID | INTEGER | Pilote qui a effectué le Pitstop | PRIMARY |
| Tyre | VARCHAR | Pneu chaussé par le pilote | NOT NULL |
Stats
| Colonne | Type de Data | Description | Tag |
|---|---|---|---|
| Lap | INTEGER | Tour durant lequel le Pitstop a été effectué | PRIMARY |
| DriverID | INTEGER | Pilote qui concerné | PRIMARY |
| Tyre | VARCHAR | Pneu chaussé par le pilote | NOT NULL |
| LapTime | INTEGER | Temps au tour (MS) | NOT NULL |
| Sector1 | INTEGER | Temps du secteur 1 (MS) | NOT NULL |
| Sector2 | INTEGER | Temps du secteur 2 (MS) | NOT NULL |
| Sector3 | INTEGER | Temps du secteur 3 (MS) | NOT NULL |
| GapToLeader | INTEGER | Ecart avec le leader (MS) | NOT NULL |
| Position | INTEGER | Position pilote | NOT NULL |
Ca n'est pas forcément définitif mais je pense que c'est déja un bon début pour faire des rubriques sympa.
Je suis en train de tenter d'implémenter le code pour permettre ensuite d'ajouter et retirer des choses facilement.
Mercredi 17 Mai 2023
Aujourd'hui le but c'est de remplir la base SQLITE avec des infos. Si j'arrive à tout remplir alors ca devrait pas être trop compliqué de venir faire des requètes qui donnent de bonnes infos.
Mais la problématique principale va être de décider QUAND insèrer des choses dans la base de donnée.
Je pense que le meilleur moyen serait de garder une liste de DriverData par pilote en piste qui puisse contenir toutes les data que l'on recoit. Et à chaques fois que l'on veut ajouter à cette liste on vérifie si un tour a été fait pour envoyer les data précédente et réinitialiser la liste.
Il faut donc une liste de 20 listes de DriverData et une liste de int qui représenteront le numéro du tour dans lequel chaque pilote se trouve. Pour detecter un arrêt je pense que la meilleure manière est de regarder si le pilote a changé de place ou de type de pneu. Si je prend que les fois ou le pilote change place ET de pneus alors certains arrêts pour ceux qui sont loins devant ou loins derrière pourraient ne pas être detectés. Et si je ne prend que le changement de pneus cela pose un soucis car un pneu pourrait avoir été changé pour un autre du même type. Et parfois les valeurs de tours faits avec le pneu ne sont pas toujours bien lues et parfois sont compliquées à retrouver car tous les pneus ne sont pas neuf quand ils sont chaussés.
Je crois que la detection de tours et des arrêts aux stands est sur la bonne voie. Le seul soucis que j'ai c'est que pour faire du debug je suis un peu obligé d'attendre pendant 10min si je veux avoir de quoi faire des stats un peu sympa.
Je me rend compte que parfois j'obtiens des résultats un peu bizarres mais que c'est la f1TV qui les donne.
Par exemple de cas ou Alex Albon n'a que deux tours sur ses pneus alors que tout le monde devant et derrière lui en a 3 et que on est au tour 4.
Ah et aussi parfois quand les pilotes se dépassent on se retrouve dans des situations plutôt rigolotes :
COMMENT JE FAIS POUR DETECTER CA WIONDVIDNJDODVNSDIC
Bon je me rend compte que clairement si je veux que mes data soient plus utiles il faudrait que je fasse un tout petit peu plus de taff sur quelques points dans l'OCR.
- Le nombre de tours des pneus (Les numéros sont vraiment mal detectés et parfois même la couleur est pas dingue)
- Les 4 qui sont pris pour des 1 ou des 11 (pour les temps et les pneus)
après un test de plus longue haleine je suis content de voir que au moins mon programme peut tourner plus d'une heure sans crasher et qu'il peut être fiable quand il veut.
Jeudi 18 Mai 2023
Aujourd'hui c'est téléTravail forcé et j'étais scensé aller au Grand Prix d'Imola ce qui malheureusement ne pourra pas se faire pour des raisons d'inondations. En effet la région est clairement pas en état de recevoir un Grand Prix de Formule 1 et donc ce weekend c'est maison.
Le but du jour c'est d'avancer la doc et de tenter d'améliorer l'OCR pour que Lundi il soit relativement facile d'avancer sur l'interface de l'app finale.
Je suis en train d'explorer une methode de detection de bords de sobel. Le seul soucis c'est que les résultats sont bons mais avec un vide au milieu des chiffres. Cela veut dire que parfois le temps est mal detecté. Mais il semble que pour le reste du temps cela se passe plutôt bien. Ca vaut peut être le coup de modifier la gestion des erreurs.
En fait le soucis avec ces artefacts c'est que parfois le temps au tour n'est tout simplement pas detecté. Dans l'exemple ci dessus, la reconnaissance de caractères ne trouve tout simplement rien. Il faut donc que je trouve un moyen de corriger ces soucis.
Il semblerait que en appliquant un tresholding un peu plus sévère en amont on arrive à réduire les artefacts
Je crois qu'il faut faire attention avec les 'Bitmap.save' quand on utilise de l'asynchrone. le GDI+ aime pas des masses.
Lundi 22 Mai 2023
BON ! Il ne reste plus beaucoup de temps ! Selon le planning cette semaine est la dernière semaine de programmation. Il va donc falloir CHARBONNER !! Il ne me reste plus que une tâche à vraiment faire (à part les tests mais euuuu voila bon).
J'essaie d'implémenter un peu plus d'error handling mais c'est pas facile... Il y a tellement de choses qui peuvent mal tourner c'est infernal.
Une chose qui serait bien serait de rajouter des points d'attente variables dans le code de l'emulateur un peu partout pour eviter de se retrouver bloqué à chaque fois. L'intérêt serait que des gens avec une moins bonne connexion pourraient quand même profiter du programme sans qu'il crashe 300 fois.
Ce qui est frustrant c'est que va savoir pourquoi, maintenant, on arrive quasi jamais a avoir la page data... Genre sans deconner c'est une fois sur 5 que l'emulateur nous ressort la page Data et pas juste le feed. C'est absolument infernal. Je ne comprends pas pourquoi en plus. L'emulateur arrive bien à cliquer sur le bon bouton mais même comme ca ca ne veut pas.
CA NA AUCUN SENS BORDEL. Sur une image Jjai de supers resultats pour les temps au tour mais dès que l'image change PAF plus aucun temps n'est detecté.
Ah non c'est bon c'est juste que VA SAVOIR POURQUOI les images sont en putain de resolution DEGEULASSE. Je ne comprends pas pourquoi ce matin particulièrement le projet marche si mal. Par ce que dès que l'image revient à une résolution normale c'est bon.
Un autre soucis que j'ai est que je n'arrive pas à paralleliser l'OCR ce qui fait que elle peut prendre parfois plus de 15 secondes. Et le problème avec ca c'est que la detection de tours et de pitstop est grandement impactée si on a pas assez de data assez souvent. Je vais me focus sur le reste en attendant mais dès que M.Bonvin apparait dans les parages je vais devoir l'alpaguer.
J'ai ajouté la possibilité d'essayer plusieurs fois de trouver le bouton fullscreen et de cliquer dessus plutôt que d'attendre dix secondes comme un con et espèrer que ca fonctionne. Mais si après 15 secondes d'essais il n'y arrive pas cela fait quand même pêter une erreur.
Je pense que je vais m'occuper de la page de configuration maintenant.
Voici à quoi ressemblait la page de settings ce matin quand je suis arrivé.
Comme je pense que l'UI de cette page ne va pas vraiment changer d'ici la fin du projet je peux me permettre de lui faire une petite beauté car après je ne pense pas y retoucher.
Pour ca j'ai plusieurs étapes comme choisir une palette de couleur, retirer l'inutile et choisir judicieusement le placement des items sur la form pour que cela soit le plus intuitif possible.
J'y pense, il fuadrait peut-être que je me trouve un logo ca pourrait rendre bien.
Voila alors j'ai changé un tout petit peu ce à quoi ressemble la page de settings et j'ai ajouté du responsive pour que le user puisse mettre l'application en plein écran.
Mais il manque un peu de couleurs et de détails pour que cela rende vraiment bien.
Et après quelques tentatives on se retrouve avec une page plutôt sympa je trouve :
Et elle est responsive :
Et j'ai fait quelques changements pour ce qui est des zones qui s'affichent pour qu'on les voie mieux.
Je trouve que franchement ca rend pas mal. Le reste de l'app sera dans ce style. J'ai mis pas mal de temps à créer cette page, mais je pense que c'est important que la page de config soit propre. Et en plus tout le temps que j'ai passé ici n'est pas perdu car ensuite j'aurai simplement à suivre les mêmes directives de style pour le reste de l'UI.
Il faut aussi savoir que Windows Form n'est ps forcément le meilleur outil pour travailler avec le design. Truc tout bête par exemple qui m'a fait perdre 30 minutes. Il est impossible de retirer les bordures des objets "GroupBox". Ce que j'ai donc du faire ca a été de dessiner un rectangle autour de la couleur du background pour que l'on ne voit plus les bordures et ensuite j'ai du redessiner le texte pour qu'il puisse s'afficher quand même.
C'est pleins de petites choses comme ca qui sont plutôt pénibles et qui font perdre du temps mais je pense que c'est rentable de s'y attarder.
Maintenant ce que je vais faire aujourd'hui et demain c'est l'affichage general de l'app. Je pense que je vais commencer par mettre des placeholder de l'app finale comme ca je saurai quoi implementer comme methodes de récupèration demain.
J'aimerais quand même faire une interface sympa même si les data sont pas parfaites. Par ce que je me dis que au pire si je montre une interface qui donne des infos inexacte mais qui a la bonne logique c'est toujours mieux que de ne pas montrer ce que ca pourrait faire avec des données un peu plus intègres.
Autre point à noter, je me suis rendu compte que ca pourrait être potentiellement pas mal de trouver un moyen rapide de lancer l'appli avec un Grand Prix. Genre permettre de selectionner le preset et l'URL du Grand Prix sans avoir à passer par la page de configuration. Je me suis rendu compte que c'était super chiant de devoir à chaque fois le faire (même si je me rends compte que normalement un user ne devrait pas lancer l'app autant)
Voila ue première version de l'App avec tous les placeholders :
Et c'est tout pour aujourd'hui ! Ce fut une journée remplie.
Mardi 23 Mai 2023
Aujourd'hui le but c'est de remplir le framework de hier avec les bonnes Data. Je ne sais pas si je peux tout finir en un jour mais on va essayer.
Bon j'ai eu une discussion animée avec M.Bonvin et il semble que je sois obligé de refaire à peu près tout mon code pour le rendre ne serait-ce qu'un peu optimisé.
Bon au final j'ai perdu 6H de travail à tenter de convertir mon code dans une version un peu plus optimisée... Mais je me rend compte que c'est juste impossible... Il me faudrait au moins plusieurs jours pour faire correctement ce refactor et donc je vais tout simplement faire un git restore...
C'est extrêment frustrant mais bon... Pas le choix il semble. J'ai du écrire au moins 600 lignes de code et tout pars en fumée. C'est une débauche d'énergie absolument phénomenale.
Après ca valait le coup de tenter je pense. (J'ai envie de mourir)
EN FAIT C'EST BON !! Il fallait juste que je croie en mon code original !!!
J'ai réussi à paralelliser mon ancien code. Il ne manquait presque rien mais M.Bonvin voulait absolument que je change le reste. Maintenant j'ai une detection qui se fait en quelques secondes c'est genial.
Mercredi 24 Mai 2023
Alors hier je n'ai pas bien eu le temps d'expliquer ce que voulait que M.Bonvin. En fait mon programme actuellement utilise un découpage qui peut parâitre complexe. Et de par sa nature, il pensait qu'il était simplement impossible de paralelliser le traitement car trop complexe et trop couteux.
Il voulait donc que je passe sur un traitement plus simple. L'idée était que on s'occupe dabord de faire une liste de toutes les Windows et de les traiter toutes à la fois pour éviter que les boucles soient trop complexes. Sauf que pour implémenter un truc pareil c'est énormément de code car cela va à l'encontre totale de la facon dont mon projet fonctionne actuellement.
Mais comme j'étais ouvert à d'autres solutions. J'ai passé six heures à tenter de l'implémenter. Il en aurait fallu au minimum deux jours soyons clair. Et en fait on s'est retrouvés devant pleins de problèmes qui ne se posent pas dans mon architecture originale.
Par exemple. On a pas trouvé de methode simple pour découper les images des fenêtres de manière thread safe. Il aurait donc fallu ajouter des boucles en préalable pour tout découper et le faire de manière séquencielle.
Ensuite vient le problème que si on traite toutes les données dans des boucless paralelle on perds leur position originale donc il faut faire une classe pour stocker les résultats temporaires.
Il y a aussi le soucis que les Windows ont certe une position mais elle est relative au parent et à l'image parente. Donc il faudra faire un système qui convertis les windows en position absolue sur l'image.
Ca peut paraître être de simples changements mais deja il y en a pas mal d'autres et franchement même si l'idée originale aurait pu simplifier les choses. Les sacrifices que l'on doit faire pour la faire marcher sont juste trop moches et à mon avis ne sont pas du tout aussi logiques que mon découpage original.
Cette expérience m'a quand même permis de me rendre compte des endroits dans mon code qui sont plus ou moins difficile à maintenir et cela m'a fait me rendre compte que ma solution n'était pas forcément la plus simple pour tout le monde mais que mine de rien elle peut être efficace.
La je suis en train de rajouter les routes pour la vue. Je me suis dit que ca serait une bonne idée de permettre aux users de cliquer sur un pilote pour avoir ses infos. Mais je me suis dit que ce qui serait encore plus cool serait de pouvoir cliquer sur un des temps au tour d'un pilote et qu'une petite fenêtre s'ouvre pour indiquer les temps par secteurs.
Mais en faisant ca je me rends compte qu'il y a quelques soucis dans la facon que je conserve les infos dans la DB et je peux voir directement quand la reconnaissance a du mal avec certains pilotes ou des positions. Ca arrive plus souvent que ce que je voudrais que un pilote soit mal detecté. Mais ce qui est drôle c'est que c'est parfois sur une deux voire trois reconnaissance que le pilote n'est plus reconnu mais ensuite tout va bien.
Il faut que je travaille un peu plus sur le filtrage de ces données limites et peut-être de voir si la reconnaissance de la position pourrait être un peu vérifiée.
Bon pour être honnête je ne pense pas que le code qui concerne l'affichage soit le meilleur code que j'aie pu produire dans ma vie de développeur mais en même temps je n'ai pas forcément le temps de le rendre magnifique. La le but est simplement que tout marche. (Et c'est un peu la même phylosophie dans tout le reste du projet lol)
Demain il me reste pas mal de choses à faire et c'est la dernière journée ou je peux les faire.
- Rendre la form plus jolie et changer les couleurs
- Rendre la form Responsive
- Ajouter les bons messages d'erreur qui vont bien
- Modifier les messageBox d'erreur pour qu'elles soient plus agréables à utiliser
- Clean un peu le code modèle vue controller
- Si j'ai le temps ajouter les bons commentaires les bonnes entêtes partout
Jeudi 25 Mai 2023
Bon bah le but aujourd'hui c'est de finaliser un peu le projet car la semaine prochaine c'est doc.
Pas grand chose à dire. J'ai passé la journée à fix des petits bugs par ci par la.
Voici des exemples de ce à quoi ressemble l'app à la fin de la journée :
On se rend jamais compte mais c'est tellement long de règler chaque petit soucis un par un. Il y a tellement de possibilités de choses qui peuvent mal tourner ou qui ont un comportement différent selon l'ordre dans lequel on fait les choses.
Mais dans l'ensemble, même si on est pas sur la meilleure interface que l'on aie vu dans l'histoire. Je trouve que elle fait quand même le taff.
Vendredi 26 Mai 2023
Aujourd'hui c'est départ pour Monaco mais comme l'avion etait bien en retard j'ai pu avancer sur le nettoyage du code.
Grand Prix de Monaco
[Insert photos]
Lundi 29 Mai 2023
Mon vol pour Geneve hier soit a été annulé et je dois donc prendre une deviation car tous les vols pour Geneve sont pleins. Je dois partir a 7h30 pour prende l'avion de 9H pour Nantes et de la bas je dois prendre un avion à 17h pour arriver à 18h30 à l'aéroport de Geneve.
C'est pas pratique car j'avais prévu d'avancer aujourd'hui et je suis obligé d'avancer comme je peux dans l'aéroport. J'avance encore sur le nettoyage rapide du code. Le but est que demain je puisse sortir la première release en Beta et que je mette vraiment serieusement à la Documentation. M.Jayr m'a également demandé de lui donner le document d'évaluation intermédiaire. Il ne faut pas que j'oublie demain.
J'ai mis des notes sur mon code au cas ou des gens viennent à le lire. J'y décris les choses que j'aurais fait différemment ou qui pourraient paraître bizarre au tout venant.
Mardi 30 Mai 2023
Bon aujourd'hui je dois encore avancer sur la partie nettoyage de code et avant de sortir la première release Beta je vais tenter d'installer le projet sur un autre pc pour voir ce que je n'ai pas mentionné dans mon ReadMe.
Bon j'ai pu rendre à M.Jayr mon evaluation intermédiaire et j'ai fait les dernières modifications sur le projet pour que je puisse sortir une release correcte. Maintenant je vais me mettre à la documentation. Il va falloir que je revoie ce que j'ai déja écrit en ce qui concerne l'OCR car j'ai fait des modifications depuis et j'ai ajouté la methode de SOBEL.
Il faut peut-être aussi que je parle vite fait dans la partie Emulation que j'ai du changer les variables d'environnement pour faire fonctionner le système en 4K.
Mercredi 31 Mai 2023
Doc
Jeudi 1 Juin 2023
Bon je me suis rendu compte que je n'avais fait vraiment aucuns tests et que c'est franchement bof. Je pense que ce que je devrais faire pour faire des tests unitaires c'est prendre des exemples de chaque type de windows possibles en plusieurs exemplaires. Ensuite je note le résultat que j'attends et je regarde si ca me retourne la bonne valeur.
Mais ca veut dire que ca va me prendre pas mal de temps de tout mettre en place mais ca m'aurait sûrement fait gagner beaucoup de temps si je l'avais fait dès le début...
Je pense que une bonne idée serait de prendre trois Grand Prix et de prendre une photo de chaque type de window au début et à la fin.
Plus je regarde plus je me rend compte que ce pojet aurait carrément du être en TDD (Test Driven Developement) par ce que ca m'aurait fait gagner un temps FOU.
OK JE SUIS DEBILE POURQUOI J'AI PAS FAIT CA PLUS TÔT ???
En fait ce que j'aurais du faire c'est prendre de gros échantillons de toutes les types de windows et j'aurais un parfait framework pour savoir si j'ai amélioré mon OCR ou non.
Voici les exemples que je vais utiliser pour verifier le bon fonctionnement de l'OCR :
Je pense que c'est un set assez correct car j'ai essayé de prendre un peu tous les cas possibles.
Le seul qui m'inquiète un peu c'est celui des pneus mais bon. C'est aussi celui qui m'inquiète le plus en temps normal.
Non mais c'est juste génial les tests en fait... j'avais pas vu que parfois ma detection de GAP TO LEADER comprenait le "+1:34.567" en "61:34.567" car le '+' était interprêté comme un 6. Sans les tests je ne m'en serais pas rendu compte.
Ce qui est génial c'est que ca veut dire que si je veux améliorer mon OCR j'ai juste à mettre plus d'exemples dans le dossier de tests et de run les tests et voir ou il a des soucis. C'est un peu tard mais ca m'aurait fait gagner TELLEMENT de temps c'est absolument ridicule.









































































































