Added to doc

This commit is contained in:
2023-05-15 11:15:12 +02:00
parent 1a42b0e955
commit c94ceabc6b
19 changed files with 310 additions and 2980 deletions
+310 -2
View File
@@ -634,11 +634,313 @@ Maintenant que l'on sait comment simuler et manipuler un navigateur internet, qu
#### Calibration
[AJOUTER EXPLICATION]
Maintenant que l'on a des images de la page Data de la F1TV on pourrait croire que c'est tout bon on peut direct passer à la partie OCR. Mais que nenni !
Le gros soucis de l'OCR c'est que sa précision est grandement réduite dès que l'on augmente la taille de la zone de recherche. Même simplement deux mots sur une image, si on les prends dans images individuelles on a de grandes chances de trouver quelque chose mais si on les mets les deux sur la même et que on tente l'OCR on va avoir de résultats bien moins bons.
Et puis il faut aussi voir que selon les données que je cherche je ne peux pas faire le même traitement.
Par exemple, savoir si le DRS est allumé, savoir quels pneus chausse un pilote et depuis combien de tours et savoir quel est le temps de son dernier tour, ce sont des informations qui demandent des traitements qui n'ont rien à voir.
Il faut donc pouvoir dire au programme d'OCR ou se trouvent les informations et quelle est leur nature pour qu'il puisse les décoder.
Il faut donc faire une calibration qui puisse donner toutes les infos importantes mais qui en même temps soit facile à utiliser car un utilisateur doit être capable de le faire assez facilement.
Voici la liste des informations que l'on doit récupèrer :
- La liste des pilotes présent sur le Grand Prix
- La position de la zone principale
- La position de chaque zone de pilote
- La position de toutes les Window sur chaque zone de pilote
Le but a été de retirer le plus d'étapes possibles à l'utilisateur. Techniquement j'aurais pu faire une version complêtement manuelle mais ca aurait pris trop de temps alors il y a des systèmes qui permettent de rendre cette tâche moins pénible.
##### Liste des pilotes
Pour la liste des pilotes j'ai pensé à utiliser une API externe pour avoir une liste dans laquelle on pourrait selectionner des noms de pilotes sauf que j'ai abandonné l'idée car je trouvais que le projet avait déja bien assez de points qui dépendent de l'exterieur.
Il y a donc une liste de pilotes dans laquelle on peut ajouter ou supprimer des noms de pilotes. L'idéal serait de mettre tous les pilotes de reserve comme ca si un pilote est malade sur une course on a pas besoin de venir changer la liste.
##### Zone principale
Pour la zone principale c'est complêtement manuel, on attend de l'utilisateur deux points x,y sur l'image pour ensuite avoir une idée de ou est sensé se trouver la zone.
!["Exemple de zone principale"](./Images/Screens/MainZoneExample.png)
##### Zones pilotes
C'est la que ca devient intéressant. L'utilisateur n'a pas besoin de faire quoi que ce soit pour que le programme sache ou sont les zones des pilotes.
J'aurais pu le faire manuellement en faisant choisir à l'utilisateur de donner deux points qui correspondent à la première zone et extrapoler pour en avoir 20. Sauf que si l'utilisateur n'est pas précis au pixel près (et même comme ca parfois) le vingtième pilote se retrouve avec une zone complêtement desaxée.
La, le programme va "simplement" effectuer une reconaissance de texte sur toute l'image. Les résultats ne nous intéressent pas vraiment tout ce que l'on veut c'est la position des textes.
Avec les position il est facile de determiner ou sont toutes les zones de pilotes et donc sans que l'utilisateur n'aie à toucher quoi que ce soit, dès qu'il a donné les infos pour la zone principale, les zones de pilotes sont determinées.
!["Exemple zone pilote"](./Images/Screens/DriverZoneExample.png)
Voici un exemple du code utilisé pour trouver ou dessiner des zones de pilotes :
```Csharp
public void AutoCalibrate()
{
List<Rectangle> detectedText = new List<Rectangle>();
List<Zone> zones = new List<Zone>();
TesseractEngine engine = new TesseractEngine(Window.TESS_DATA_FOLDER.FullName, "eng", EngineMode.Default);
Image image = MainZone.ZoneImage;
var tessImage = Pix.LoadFromMemory(Window.ImageToByte(image));
Page page = engine.Process(tessImage);
using (var iter = page.GetIterator())
{
iter.Begin();
do
{
Rect boundingBox;
if (iter.TryGetBoundingBox(PageIteratorLevel.Word, out boundingBox))
{
//var text = iter.GetText(PageIteratorLevel.Word).ToUpper();
//We remove all the rectangles that are definitely too big
if (boundingBox.Height < image.Height / NUMBER_OF_DRIVERS)
{
//Now we add a filter to only get the boxes in the right because they are much more reliable in size
if (boundingBox.X1 > image.Width / 2)
{
//Now we check if an other square box has been found roughly in the same y axis
bool match = false;
//The tolerance is roughly half the size that a window will be
int tolerance = (image.Height / NUMBER_OF_DRIVERS) / 2;
foreach (Rectangle rect in detectedText)
{
if (rect.Y > boundingBox.Y1 - tolerance && rect.Y < boundingBox.Y1 + tolerance)
{
//There already is a rectangle in this line
match = true;
}
}
//if nothing matched we can add it
if (!match)
detectedText.Add(new Rectangle(boundingBox.X1, boundingBox.Y1, boundingBox.Width, boundingBox.Height));
}
}
}
} while (iter.Next(PageIteratorLevel.Word));
}
//DEBUG
int i = 1;
foreach (Rectangle Rectangle in detectedText)
{
Rectangle windowRectangle;
Size windowSize = new Size(image.Width, image.Height / NUMBER_OF_DRIVERS);
Point windowLocation = new Point(0, (Rectangle.Y + Rectangle.Height / 2) - windowSize.Height / 2);
windowRectangle = new Rectangle(windowLocation, windowSize);
//We add the driver zones
Zone driverZone = new Zone(MainZone.ZoneImage, windowRectangle, "DriverZone");
MainZone.AddZone(driverZone);
//driverZone.ZoneImage.Save("Driver" + i+".png");
i++;
}
}
```
##### Windows pilotes
C'est ici que c'est le plus pénible pour l'utilisateur, il doit selectionner manuellement les positions des fenêtres de données. Ensuite dès que l'utilisateur a donnée une position pour chaque window, on applique les positions pour chaque zone de pilote.
Il y a plusieurs types de windows et selon le type le traitement est différent comme je l'ai dit plus tôt. Voici des exemples concrets :
!["Exemple Window de pneus"](./Images/Screens/TyreZoneExemple1.png)
!["Exemple Window temps au tour"](./Images/Screens/DriverLapTimeExample.png)
!["Exemple window Drs"](./Images/Screens/DRSFalse.png)
Il est important que toutes ces zones soient transmises avec le plus de précision possible pour que l'OCR puisse bien faire son boulot.
##### Stockage
Ensuite quand l'utilisateur a finit de configurer son flux, la configuration est stockée pour qu'il puisse ensuite la réutiliser pour tous les autres Grand Prix de l'année.
Le stockage est fait sous format JSON et est fait pour que le programme d'OCR puisse lire dedans toutes les infos nescessaires.
Cela fait des fichiers plutôt gros mais je n'avais pas vraiment le choix. J'ai testé une version avec seulement les infos de la première zone de pilote mais avec l'interpolation, les derniers pilotes se retrouvent avec des zones clairement pas à la bonne taille.
Voici un exemple de ce à quoi ressemble le JSON final :
```Json
{
"Main": {
"x": 36,
"y": 343,
"width": 3780,
"height": 1454,
"DriverZones": [
{
"name": "Driver1",
"x": 0,
"y": 1,
"width": 3780,
"height": 72,
"Windows": [
{
"Position": {
"x": 45,
"y": 3,
"width": 76,
"height": 65
},
"GapToLeader": {
"x": 447,
"y": 1,
"width": 206,
"height": 67
},
"LapTime": {
"x": 863,
"y": 3,
"width": 229,
"height": 65
},
"DRS": {
"x": 1095,
"y": 1,
"width": 174,
"height": 67
},
"Tyres": {
"x": 1274,
"y": 3,
"width": 1448,
"height": 62
},
"Name": {
"x": 2724,
"y": 3,
"width": 361,
"height": 65
},
"Sector1": {
"x": 3088,
"y": 1,
"width": 239,
"height": 65
},
"Sector2": {
"x": 3314,
"y": 4,
"width": 190,
"height": 62
},
"Sector3": {
"x": 3493,
"y": 1,
"width": 198,
"height": 67
}
}
]
},
{
"name": "Driver2",
"x": 0,
"y": 72,
"width": 3780,
"height": 72,
"Windows": [
{
"Position": {
"x": 45,
"y": 3,
"width": 76,
"height": 65
},
"GapToLeader": {
"x": 447,
"y": 1,
"width": 206,
"height": 67
},
"LapTime": {
"x": 863,
"y": 3,
"width": 229,
"height": 65
},
"DRS": {
"x": 1095,
"y": 1,
"width": 174,
"height": 67
},
"Tyres": {
"x": 1274,
"y": 3,
"width": 1448,
"height": 62
},
"Name": {
"x": 2724,
"y": 3,
"width": 361,
"height": 65
},
"Sector1": {
"x": 3088,
"y": 1,
"width": 239,
"height": 65
},
"Sector2": {
"x": 3314,
"y": 4,
"width": 190,
"height": 62
},
"Sector3": {
"x": 3493,
"y": 1,
"width": 198,
"height": 67
}
}
]
}
[Other pilots...]
],
"Drivers": [
"Perez",
"Verstappen",
"Alonso",
"Sainz",
"Russel",
"Gasly",
"Leclerc",
"Ocon",
"Hulkenberg",
"Bottas",
"Hamilton",
"Albon",
"Tsunoda",
"Zhou",
"Stroll",
"De Vries",
"Magnussen",
"Norris",
"Piastri",
"Sargeant"
]
}
}
```
Et avec tout ca. L'OCR peut démarrer dans de bonnes conditions
### OCR
Ici je vais parler de la seconde partie du projet qui parle du processus de reconnaissance de data sur une image du feed DATA de la F1TV.
Maintenant que on a des images qui arrivent automatiquement et que l'on sait ou se trouvent les informations sur ces dites images, je vais parler de la seconde partie du projet qui parle du processus de reconnaissance de data sur une image du feed DATA de la F1TV.
C'est je pense la partie qui a demandé le plus tests et de refactor.
@@ -1125,6 +1427,12 @@ Une variante spécialisée pour la reconnaissance des pneus appelée affectueuse
Il y aussi d'autre methodes comme un filtre Gaussien ou *Highlight countour* que j'ai du développer mais que je n'ai pas utilisé donc je ne vais pas en parler ici.
###### Petit point résolution
Un petit peu embarassant, vers les deux tiers du projet j'ai découvert un moyen de récupèrer des images 4K (ou presque) et du coup la reconnaissance était beaucoup moins demandeuse en filtres et en modifications.
Donc même si toutes ces techniques sont encore utilisées pour certains cas spécifiques. Vers la fin du projet j'ai pu simplifier énormément la reconnaissance pour certaines windows en retirant certains filtres qui étaient redondants.
### Interprétation des données
### Stockage des données