Compare commits
19 Commits
0.1
..
f2761db4c9
| Author | SHA1 | Date | |
|---|---|---|---|
| f2761db4c9 | |||
| 57f19a1438 | |||
| 32b20ff317 | |||
| 7cb27ca69c | |||
| a784cef87f | |||
| fa07045199 | |||
| 274cf48f37 | |||
| 13481445fa | |||
| 997f6407f6 | |||
| 70085721f4 | |||
| 5f8f1f0704 | |||
| dbda3d3788 | |||
| c8359c074c | |||
| bc18274762 | |||
| 0c84d468b2 | |||
| fde3bcaae6 | |||
| 8796fed916 | |||
| e278a1c006 | |||
| 4d03070849 |
@@ -0,0 +1,90 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace OCR_Decode
|
||||||
|
{
|
||||||
|
public class DriverData
|
||||||
|
{
|
||||||
|
public bool DRS; //True = Drs is opened
|
||||||
|
public int GapToLeader; //In ms
|
||||||
|
public int LapTime; //In ms
|
||||||
|
public string Name; //Ex: LECLERC
|
||||||
|
public int Position; //Ex: 1
|
||||||
|
public int Sector1; //in ms
|
||||||
|
public int Sector2; //in ms
|
||||||
|
public int Sector3; //in ms
|
||||||
|
public Tyre CurrentTyre;//Ex Soft 11 laps
|
||||||
|
|
||||||
|
public DriverData(bool dRS, int gapToLeader, int lapTime, string name, int position, int sector1, int sector2, int sector3, Tyre tyre)
|
||||||
|
{
|
||||||
|
DRS = dRS;
|
||||||
|
GapToLeader = gapToLeader;
|
||||||
|
LapTime = lapTime;
|
||||||
|
Name = name;
|
||||||
|
Position = position;
|
||||||
|
Sector1 = sector1;
|
||||||
|
Sector2 = sector2;
|
||||||
|
Sector3 = sector3;
|
||||||
|
CurrentTyre = tyre;
|
||||||
|
}
|
||||||
|
public DriverData()
|
||||||
|
{
|
||||||
|
DRS = false;
|
||||||
|
GapToLeader = -1;
|
||||||
|
LapTime = -1;
|
||||||
|
Name = "Unknown";
|
||||||
|
Position = -1;
|
||||||
|
Sector1 = -1;
|
||||||
|
Sector2 = -1;
|
||||||
|
Sector3 = -1;
|
||||||
|
CurrentTyre = new Tyre(Tyre.Type.Undefined,-1);
|
||||||
|
}
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
string result = "";
|
||||||
|
|
||||||
|
//Position
|
||||||
|
result += "Position : " + Position +Environment.NewLine;
|
||||||
|
//Gap
|
||||||
|
result += "Gap to leader : " + Reader.ConvertMsToTime(GapToLeader) + Environment.NewLine;
|
||||||
|
//LapTime
|
||||||
|
result += "Lap time : " + Reader.ConvertMsToTime(LapTime) + Environment.NewLine;
|
||||||
|
//DRS
|
||||||
|
result += "DRS : " + DRS + Environment.NewLine;
|
||||||
|
//Tyres
|
||||||
|
result += "Uses " + CurrentTyre.Coumpound + " tyre " + CurrentTyre.NumberOfLaps + " laps old" + Environment.NewLine;
|
||||||
|
//Name
|
||||||
|
result += "Driver name : " + Name +Environment.NewLine;
|
||||||
|
//Sector 1
|
||||||
|
result += "Sector 1 : " + Reader.ConvertMsToTime(Sector1) + Environment.NewLine;
|
||||||
|
//Sector 1
|
||||||
|
result += "Sector 2 : " + Reader.ConvertMsToTime(Sector2) + Environment.NewLine;
|
||||||
|
//Sector 1
|
||||||
|
result += "Sector 3 : " + Reader.ConvertMsToTime(Sector3) + Environment.NewLine;
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public struct Tyre
|
||||||
|
{
|
||||||
|
public enum Type
|
||||||
|
{
|
||||||
|
Soft,
|
||||||
|
Medium,
|
||||||
|
Hard,
|
||||||
|
Inter,
|
||||||
|
Wet,
|
||||||
|
Undefined
|
||||||
|
}
|
||||||
|
public Type Coumpound;
|
||||||
|
public int NumberOfLaps;
|
||||||
|
public Tyre(Type type, int laps)
|
||||||
|
{
|
||||||
|
Coumpound = type;
|
||||||
|
NumberOfLaps = laps;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -18,7 +18,7 @@ namespace OCR_Decode
|
|||||||
{
|
{
|
||||||
Name = "DRS";
|
Name = "DRS";
|
||||||
}
|
}
|
||||||
public override object DecodePng()
|
public override async Task<object> DecodePng()
|
||||||
{
|
{
|
||||||
bool result = false;
|
bool result = false;
|
||||||
int greenValue = GetGreenPixels();
|
int greenValue = GetGreenPixels();
|
||||||
@@ -28,7 +28,7 @@ namespace OCR_Decode
|
|||||||
if (greenValue > EmptyDrsGreenValue + EmptyDrsGreenValue / 100 * 30)
|
if (greenValue > EmptyDrsGreenValue + EmptyDrsGreenValue / 100 * 30)
|
||||||
result = true;
|
result = true;
|
||||||
|
|
||||||
WindowImage.Save(Reader.DEBUG_DUMP_FOLDER + "DRS" + result + ".png");
|
//WindowImage.Save(Reader.DEBUG_DUMP_FOLDER + "DRS" + result + ".png");
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
@@ -68,7 +68,6 @@ namespace OCR_Decode
|
|||||||
}
|
}
|
||||||
public Rectangle GetBox()
|
public Rectangle GetBox()
|
||||||
{
|
{
|
||||||
|
|
||||||
var tessImage = Pix.LoadFromMemory(ImageToByte(WindowImage));
|
var tessImage = Pix.LoadFromMemory(ImageToByte(WindowImage));
|
||||||
Engine.SetVariable("tessedit_char_whitelist", "");
|
Engine.SetVariable("tessedit_char_whitelist", "");
|
||||||
Page page = Engine.Process(tessImage);
|
Page page = Engine.Process(tessImage);
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ using System.Drawing;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Tesseract;
|
||||||
|
|
||||||
namespace OCR_Decode
|
namespace OCR_Decode
|
||||||
{
|
{
|
||||||
@@ -13,9 +14,9 @@ namespace OCR_Decode
|
|||||||
{
|
{
|
||||||
Name = "Gap to leader";
|
Name = "Gap to leader";
|
||||||
}
|
}
|
||||||
public override object DecodePng()
|
public override async Task<object> DecodePng()
|
||||||
{
|
{
|
||||||
int result = GetTimeFromPng(WindowImage, OcrImage.WindowType.Gap);
|
int result = await GetTimeFromPng(WindowImage, OcrImage.WindowType.Gap, Engine);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,9 +14,9 @@ namespace OCR_Decode
|
|||||||
{
|
{
|
||||||
Name = "Lap time";
|
Name = "Lap time";
|
||||||
}
|
}
|
||||||
public override object DecodePng()
|
public override async Task<object> DecodePng()
|
||||||
{
|
{
|
||||||
int result = GetTimeFromPng(WindowImage,OcrImage.WindowType.LapTime);
|
int result = await GetTimeFromPng(WindowImage, OcrImage.WindowType.LapTime,Engine);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,10 +15,10 @@ namespace OCR_Decode
|
|||||||
{
|
{
|
||||||
Name = "Name";
|
Name = "Name";
|
||||||
}
|
}
|
||||||
public override object DecodePng(List<string> DriverList)
|
public override async Task<object> DecodePng(List<string> DriverList)
|
||||||
{
|
{
|
||||||
string result = "";
|
string result = "";
|
||||||
result = GetStringFromPng(WindowImage);
|
result = await GetStringFromPng(WindowImage,Engine);
|
||||||
|
|
||||||
if (!IsADriver(DriverList, result))
|
if (!IsADriver(DriverList, result))
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ using System.Drawing;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Tesseract;
|
||||||
|
|
||||||
namespace OCR_Decode
|
namespace OCR_Decode
|
||||||
{
|
{
|
||||||
@@ -13,9 +14,9 @@ namespace OCR_Decode
|
|||||||
{
|
{
|
||||||
Name = "Position";
|
Name = "Position";
|
||||||
}
|
}
|
||||||
public override object DecodePng()
|
public override async Task<object> DecodePng()
|
||||||
{
|
{
|
||||||
string result = GetStringFromPng(WindowImage,"0123456789");
|
string result = await GetStringFromPng(WindowImage,Engine, "0123456789");
|
||||||
|
|
||||||
int position;
|
int position;
|
||||||
try
|
try
|
||||||
@@ -25,7 +26,7 @@ namespace OCR_Decode
|
|||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
position = -1;
|
position = -1;
|
||||||
WindowImage.Save(Reader.DEBUG_DUMP_FOLDER + "FailedPosition"+".png");
|
//WindowImage.Save(Reader.DEBUG_DUMP_FOLDER + "FailedPosition" + ".png");
|
||||||
}
|
}
|
||||||
return position;
|
return position;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,16 @@
|
|||||||
using System;
|
/// Author : Maxime Rohmer
|
||||||
|
/// Date : 25/04/2023
|
||||||
|
/// File : DriverSector1Window.cs
|
||||||
|
/// Brief : Window that contains infos about the first sector of a driver
|
||||||
|
/// Version : 0.1
|
||||||
|
|
||||||
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Drawing;
|
using System.Drawing;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Tesseract;
|
||||||
|
|
||||||
namespace OCR_Decode
|
namespace OCR_Decode
|
||||||
{
|
{
|
||||||
@@ -13,9 +20,13 @@ namespace OCR_Decode
|
|||||||
{
|
{
|
||||||
Name = "Sector 1";
|
Name = "Sector 1";
|
||||||
}
|
}
|
||||||
public override object DecodePng()
|
/// <summary>
|
||||||
|
/// Decodes the sector
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>the sector time in int (ms)</returns>
|
||||||
|
public override async Task<object> DecodePng()
|
||||||
{
|
{
|
||||||
int result = GetTimeFromPng(WindowImage, OcrImage.WindowType.Sector);
|
int result = await GetTimeFromPng(WindowImage, OcrImage.WindowType.Sector, Engine);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,17 @@
|
|||||||
using System;
|
/// Author : Maxime Rohmer
|
||||||
|
/// Date : 25/04/2023
|
||||||
|
/// File : DriverSector2Window.cs
|
||||||
|
/// Brief : Window that contains infos about the second sector of a driver
|
||||||
|
/// Version : 0.1
|
||||||
|
|
||||||
|
|
||||||
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Drawing;
|
using System.Drawing;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Tesseract;
|
||||||
|
|
||||||
namespace OCR_Decode
|
namespace OCR_Decode
|
||||||
{
|
{
|
||||||
@@ -13,9 +21,13 @@ namespace OCR_Decode
|
|||||||
{
|
{
|
||||||
Name = "Sector 2";
|
Name = "Sector 2";
|
||||||
}
|
}
|
||||||
public override object DecodePng()
|
/// <summary>
|
||||||
|
/// Decodes the sector
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>the sector time in int (ms)</returns>
|
||||||
|
public override async Task<object> DecodePng()
|
||||||
{
|
{
|
||||||
int result = GetTimeFromPng(WindowImage, OcrImage.WindowType.Sector);
|
int result = await GetTimeFromPng(WindowImage, OcrImage.WindowType.Sector,Engine);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,17 @@
|
|||||||
using System;
|
/// Author : Maxime Rohmer
|
||||||
|
/// Date : 25/04/2023
|
||||||
|
/// File : DriverSector3Window.cs
|
||||||
|
/// Brief : Window that contains infos about the third sector of a driver
|
||||||
|
/// Version : 0.1
|
||||||
|
|
||||||
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
using System.Drawing;
|
using System.Drawing;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Tesseract;
|
||||||
|
|
||||||
namespace OCR_Decode
|
namespace OCR_Decode
|
||||||
{
|
{
|
||||||
@@ -13,9 +21,13 @@ namespace OCR_Decode
|
|||||||
{
|
{
|
||||||
Name = "Sector 3";
|
Name = "Sector 3";
|
||||||
}
|
}
|
||||||
public override object DecodePng()
|
/// <summary>
|
||||||
|
/// Decodes the sector
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>the sector time in int (ms)</returns>
|
||||||
|
public override async Task<object> DecodePng()
|
||||||
{
|
{
|
||||||
int result = GetTimeFromPng(WindowImage,OcrImage.WindowType.Sector);
|
int result = await GetTimeFromPng(WindowImage, OcrImage.WindowType.Sector,Engine);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,12 @@
|
|||||||
using System;
|
/// Author : Maxime Rohmer
|
||||||
|
/// Date : 25/04/2023
|
||||||
|
/// File : DriverTyresWindow.cs
|
||||||
|
/// Brief : Window that contains infos about tyres and that can decode them
|
||||||
|
/// Version : 0.1
|
||||||
|
|
||||||
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
using System.Drawing;
|
using System.Drawing;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
@@ -11,111 +18,98 @@ namespace OCR_Decode
|
|||||||
internal class DriverTyresWindow : Window
|
internal class DriverTyresWindow : Window
|
||||||
{
|
{
|
||||||
private static Random rnd = new Random();
|
private static Random rnd = new Random();
|
||||||
|
int seed = rnd.Next(0,10000);
|
||||||
|
|
||||||
public static Color SOFT_TYRE_COLOR = Color.FromArgb(0xff,0x00,0x00);
|
//Those are the colors I found but you can change them if they change in the future like in 2019
|
||||||
public static Color MEDIUM_TYRE_COLOR = Color.FromArgb(0xf5,0xbf,0x00);
|
public static Color SOFT_TYRE_COLOR = Color.FromArgb(0xff, 0x00, 0x00);
|
||||||
public static Color HARD_TYRE_COLOR = Color.FromArgb(0xd9,0xd8,0xd4);
|
public static Color MEDIUM_TYRE_COLOR = Color.FromArgb(0xf5, 0xbf, 0x00);
|
||||||
public static Color INTER_TYRE_COLOR = Color.FromArgb(0x00,0xa4,0x2e);
|
public static Color HARD_TYRE_COLOR = Color.FromArgb(0xa4, 0xa5, 0xa8);
|
||||||
public static Color WET_TYRE_COLOR = Color.FromArgb(0x27,0x60,0xa6);
|
public static Color INTER_TYRE_COLOR = Color.FromArgb(0x00, 0xa4, 0x2e);
|
||||||
|
public static Color WET_TYRE_COLOR = Color.FromArgb(0x27, 0x60, 0xa6);
|
||||||
|
public static Color EMPTY_COLOR = Color.FromArgb(0x20,0x20,0x20);
|
||||||
|
|
||||||
public DriverTyresWindow(Bitmap image, Rectangle bounds) : base(image, bounds)
|
public DriverTyresWindow(Bitmap image, Rectangle bounds) : base(image, bounds)
|
||||||
{
|
{
|
||||||
Name = "Tyres";
|
Name = "Tyres";
|
||||||
}
|
}
|
||||||
public override object DecodePng()
|
/// <summary>
|
||||||
|
/// This will decode the content of the image
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>And object containing what was on the image</returns>
|
||||||
|
public override async Task<object> DecodePng()
|
||||||
{
|
{
|
||||||
//WindowImage.Save(Reader.DEBUG_DUMP_FOLDER + "testTyres" + rnd.Next(0, 1000) + ".png");
|
return await GetTyreInfos();
|
||||||
|
|
||||||
return GetTyreInfos();
|
|
||||||
}
|
}
|
||||||
private Tyre GetTyreInfos()
|
/// <summary>
|
||||||
|
/// Method that will decode whats on the image and return the tyre infos it could manage to recover
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>A tyre object containing tyre infos</returns>
|
||||||
|
private async Task<Tyre> GetTyreInfos()
|
||||||
{
|
{
|
||||||
Bitmap tyreZone = GetSmallBitmapFromBigOne(WindowImage, FindTyreZone());
|
Bitmap tyreZone = GetSmallBitmapFromBigOne(WindowImage, FindTyreZone());
|
||||||
|
|
||||||
tyreZone.Save(Reader.DEBUG_DUMP_FOLDER + "ZONE" + ".png");
|
|
||||||
Tyre.Type type = Tyre.Type.Undefined;
|
Tyre.Type type = Tyre.Type.Undefined;
|
||||||
|
type = GetTyreTypeFromColor(OcrImage.GetAvgColorFromBitmap(tyreZone));
|
||||||
int laps = -1;
|
int laps = -1;
|
||||||
|
|
||||||
//MHIWsmhiw so we can detect Soft Medium Hard Inters and Wet tyres
|
string number = await GetStringFromPng(tyreZone, Engine, "0123456789", OcrImage.WindowType.Tyre);
|
||||||
string text = GetStringFromPng(tyreZone, "SMHIW", OcrImage.WindowType.Tyre);
|
try
|
||||||
if (text.Length == 1 && text != "")
|
|
||||||
{
|
{
|
||||||
//We found a tire letter
|
laps = Convert.ToInt32(number);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
//We could not convert the number so its a letter so its 0 laps old
|
||||||
laps = 0;
|
laps = 0;
|
||||||
text = text.ToUpper();
|
|
||||||
switch (text[0])
|
|
||||||
{
|
|
||||||
case 'S':
|
|
||||||
type = Tyre.Type.Soft;
|
|
||||||
break;
|
|
||||||
case 'M':
|
|
||||||
type = Tyre.Type.Medium;
|
|
||||||
break;
|
|
||||||
case 'H':
|
|
||||||
type = Tyre.Type.Hard;
|
|
||||||
break;
|
|
||||||
case 'I':
|
|
||||||
type = Tyre.Type.Inter;
|
|
||||||
break;
|
|
||||||
case 'W':
|
|
||||||
type = Tyre.Type.Wet;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
type = Tyre.Type.Undefined;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else
|
//tyreZone.Save(Reader.DEBUG_DUMP_FOLDER + "Tyre" + type + "Laps" + laps + '#' + rnd.Next(0, 1000) + ".png");
|
||||||
{
|
return new Tyre(type, laps);
|
||||||
string number = GetStringFromPng(tyreZone, "0123456789", OcrImage.WindowType.Tyre);
|
|
||||||
try
|
|
||||||
{
|
|
||||||
laps = Convert.ToInt32(number);
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
laps = -1;
|
|
||||||
}
|
|
||||||
type = GetTyreTypeFromColor(OcrImage.GetAvgColorFromBitmap(OcrImage.Resize(tyreZone)));
|
|
||||||
}
|
|
||||||
|
|
||||||
tyreZone.Save(Reader.DEBUG_DUMP_FOLDER + "Tyre" + type + "Laps" + laps + '#' + rnd.Next(0, 1000) + ".png");
|
|
||||||
return new Tyre(type,laps);
|
|
||||||
}
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// Finds where the important part of the image is
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>A rectangle containing position and dimensions of the important part of the image</returns>
|
||||||
private Rectangle FindTyreZone()
|
private Rectangle FindTyreZone()
|
||||||
{
|
{
|
||||||
Bitmap bmp = WindowImage;
|
Bitmap bmp = WindowImage;
|
||||||
int currentPosition = bmp.Width;
|
int currentPosition = bmp.Width;
|
||||||
int height = bmp.Height / 2;
|
int height = bmp.Height / 2;
|
||||||
Color limitColor = Color.FromArgb(0x50,0x50,0x50);
|
Color limitColor = Color.FromArgb(0x50, 0x50, 0x50);
|
||||||
Color currentColor = Color.FromArgb(0,0,0);
|
Color currentColor = Color.FromArgb(0, 0, 0);
|
||||||
|
|
||||||
Size newWindowSize = new Size(bmp.Height -Convert.ToInt32((float)bmp.Height / 100f * 25f),bmp.Height - Convert.ToInt32((float)bmp.Height / 100f * 35f));
|
Size newWindowSize = new Size(bmp.Height - Convert.ToInt32((float)bmp.Height / 100f * 25f), bmp.Height - Convert.ToInt32((float)bmp.Height / 100f * 35f));
|
||||||
|
|
||||||
while(currentColor.R <= limitColor.R && currentColor.G <= limitColor.G && currentColor.B <= limitColor.B && currentPosition > 0)
|
while (currentColor.R <= limitColor.R && currentColor.G <= limitColor.G && currentColor.B <= limitColor.B && currentPosition > 0)
|
||||||
{
|
{
|
||||||
currentPosition--;
|
currentPosition--;
|
||||||
currentColor = bmp.GetPixel(currentPosition,height);
|
currentColor = bmp.GetPixel(currentPosition, height);
|
||||||
}
|
}
|
||||||
|
|
||||||
//Its here to let the new window include a little bit of the right
|
//Its here to let the new window include a little bit of the right
|
||||||
int CorrectedX = currentPosition - (newWindowSize.Width) + Convert.ToInt32((float)newWindowSize.Width/100f*10f);
|
int CorrectedX = currentPosition - (newWindowSize.Width) + Convert.ToInt32((float)newWindowSize.Width / 100f * 10f);
|
||||||
int CorrectedY = Convert.ToInt32((float)newWindowSize.Height / 100f * 35f);
|
int CorrectedY = Convert.ToInt32((float)newWindowSize.Height / 100f * 35f);
|
||||||
if (CorrectedX <= 0)
|
if (CorrectedX <= 0)
|
||||||
return new Rectangle(0,0,newWindowSize.Width,newWindowSize.Height);
|
return new Rectangle(0, 0, newWindowSize.Width, newWindowSize.Height);
|
||||||
|
|
||||||
return new Rectangle(CorrectedX,CorrectedY,newWindowSize.Width,newWindowSize.Height);
|
return new Rectangle(CorrectedX, CorrectedY, newWindowSize.Width, newWindowSize.Height);
|
||||||
}
|
}
|
||||||
//This method has been created with the help of chatGPT
|
//This method has been created with the help of chatGPT
|
||||||
|
/// <summary>
|
||||||
|
/// Methods that compares a list of colors to see wich is the closest from the input color and decide wich tyre type it is
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="inputColor">The color that you found</param>
|
||||||
|
/// <returns>The tyre type</returns>
|
||||||
public Tyre.Type GetTyreTypeFromColor(Color inputColor)
|
public Tyre.Type GetTyreTypeFromColor(Color inputColor)
|
||||||
{
|
{
|
||||||
Tyre.Type type = Tyre.Type.Undefined;
|
Tyre.Type type = Tyre.Type.Undefined;
|
||||||
List<Color> colors = new List<Color>();
|
List<Color> colors = new List<Color>();
|
||||||
|
//dont forget that if for some reason someday F1 adds a new Tyre type you will need to add it in the constants but also here in the list
|
||||||
|
//You will also need to add it below in the Tyre object's enum and add an if in the end of this method
|
||||||
colors.Add(SOFT_TYRE_COLOR);
|
colors.Add(SOFT_TYRE_COLOR);
|
||||||
colors.Add(MEDIUM_TYRE_COLOR);
|
colors.Add(MEDIUM_TYRE_COLOR);
|
||||||
colors.Add(HARD_TYRE_COLOR);
|
colors.Add(HARD_TYRE_COLOR);
|
||||||
colors.Add(INTER_TYRE_COLOR);
|
colors.Add(INTER_TYRE_COLOR);
|
||||||
colors.Add(WET_TYRE_COLOR);
|
colors.Add(WET_TYRE_COLOR);
|
||||||
|
colors.Add(EMPTY_COLOR);
|
||||||
|
|
||||||
Color closestColor = colors[0];
|
Color closestColor = colors[0];
|
||||||
int closestDistance = int.MaxValue;
|
int closestDistance = int.MaxValue;
|
||||||
@@ -140,27 +134,10 @@ namespace OCR_Decode
|
|||||||
type = Tyre.Type.Inter;
|
type = Tyre.Type.Inter;
|
||||||
if (closestColor == WET_TYRE_COLOR)
|
if (closestColor == WET_TYRE_COLOR)
|
||||||
type = Tyre.Type.Wet;
|
type = Tyre.Type.Wet;
|
||||||
|
if (closestColor == EMPTY_COLOR)
|
||||||
|
return Tyre.Type.Undefined;
|
||||||
|
|
||||||
return type;
|
return type;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
struct Tyre
|
|
||||||
{
|
|
||||||
public enum Type
|
|
||||||
{
|
|
||||||
Soft,
|
|
||||||
Medium,
|
|
||||||
Hard,
|
|
||||||
Inter,
|
|
||||||
Wet,
|
|
||||||
Undefined
|
|
||||||
}
|
|
||||||
public Type Coumpound;
|
|
||||||
public int NumberOfLaps;
|
|
||||||
public Tyre(Type type, int laps)
|
|
||||||
{
|
|
||||||
Coumpound = type;
|
|
||||||
NumberOfLaps = laps;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
+24
-4
@@ -7,6 +7,7 @@ using System.Linq;
|
|||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using System.Windows.Forms;
|
using System.Windows.Forms;
|
||||||
|
using System.Diagnostics;
|
||||||
|
|
||||||
namespace OCR_Decode
|
namespace OCR_Decode
|
||||||
{
|
{
|
||||||
@@ -17,6 +18,8 @@ namespace OCR_Decode
|
|||||||
const string TEMPORARY_IMAGE_FOLDER = @"C:\Users\Moi\Pictures\SeleniumScreens\DataSetHighRes\";
|
const string TEMPORARY_IMAGE_FOLDER = @"C:\Users\Moi\Pictures\SeleniumScreens\DataSetHighRes\";
|
||||||
int pageNmbr = 82;
|
int pageNmbr = 82;
|
||||||
Reader Reader;
|
Reader Reader;
|
||||||
|
Stopwatch stopWatch = new Stopwatch();
|
||||||
|
int LoadMS = 0;
|
||||||
public Form1()
|
public Form1()
|
||||||
{
|
{
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
@@ -24,17 +27,34 @@ namespace OCR_Decode
|
|||||||
|
|
||||||
private void Form1_Load(object sender, EventArgs e)
|
private void Form1_Load(object sender, EventArgs e)
|
||||||
{
|
{
|
||||||
|
stopWatch.Start();
|
||||||
Reader = new Reader(CONFIG_FILE,TEMPORARY_IMAGE_FOLDER);
|
Reader = new Reader(CONFIG_FILE,TEMPORARY_IMAGE_FOLDER);
|
||||||
|
stopWatch.Stop();
|
||||||
|
LoadMS = (int)stopWatch.ElapsedMilliseconds;
|
||||||
|
|
||||||
RefreshUi();
|
RefreshUi();
|
||||||
}
|
}
|
||||||
private void RefreshUi()
|
private async void RefreshUi()
|
||||||
{
|
{
|
||||||
|
|
||||||
tbxResult.Text = "";
|
tbxResult.Text = "";
|
||||||
|
stopWatch.Restart();
|
||||||
pbxImage.Image = Reader.Draw(pageNmbr);
|
pbxImage.Image = Reader.Draw(pageNmbr);
|
||||||
|
stopWatch.Stop();
|
||||||
|
|
||||||
tbxResult.Text = Reader.Decode(pageNmbr);
|
int DrawMS = (int)stopWatch.ElapsedMilliseconds;
|
||||||
|
|
||||||
|
stopWatch.Restart();
|
||||||
|
tbxResult.Text = await Reader.Decode(pageNmbr);
|
||||||
|
stopWatch.Stop();
|
||||||
|
|
||||||
|
int OcrMS = (int)stopWatch.ElapsedMilliseconds;
|
||||||
|
MessageBox.Show(
|
||||||
|
"Loading took : " + Reader.ConvertMsToTime(LoadMS) +
|
||||||
|
" Image splitting took : " + Reader.ConvertMsToTime(DrawMS) + Environment.NewLine +
|
||||||
|
" Reading the infos from the image took : " + Reader.ConvertMsToTime(OcrMS) + Environment.NewLine
|
||||||
|
);
|
||||||
|
GC.Collect();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void btnNext_Click(object sender, EventArgs e)
|
private void btnNext_Click(object sender, EventArgs e)
|
||||||
|
|||||||
@@ -107,6 +107,7 @@
|
|||||||
</Reference>
|
</Reference>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<Compile Include="DriverData.cs" />
|
||||||
<Compile Include="DriverDrsWindow.cs" />
|
<Compile Include="DriverDrsWindow.cs" />
|
||||||
<Compile Include="DriverGapToLeaderWindow.cs" />
|
<Compile Include="DriverGapToLeaderWindow.cs" />
|
||||||
<Compile Include="DriverLapTimeWindow.cs" />
|
<Compile Include="DriverLapTimeWindow.cs" />
|
||||||
|
|||||||
+245
-147
@@ -1,4 +1,10 @@
|
|||||||
using System;
|
/// Author : Maxime Rohmer
|
||||||
|
/// Date : 25/04/2023
|
||||||
|
/// File : OcrImage.cs
|
||||||
|
/// Brief : Class that contains all the post processing methods that can help with OCR
|
||||||
|
/// Version : 0.1
|
||||||
|
|
||||||
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
@@ -14,7 +20,10 @@ namespace OCR_Decode
|
|||||||
//Just so you know
|
//Just so you know
|
||||||
public class OcrImage
|
public class OcrImage
|
||||||
{
|
{
|
||||||
Bitmap InputImage;
|
//this is a hardcoded value based on the colors of the F1TV data channel background you can change it if sometime in the future the color changes
|
||||||
|
//Any color that has any of its R,G or B channel higher than the treshold will be considered as being usefull information
|
||||||
|
public static Color F1TV_BACKGROUND_TRESHOLD = Color.FromArgb(0x50, 0x50, 0x50);
|
||||||
|
Bitmap InputBitmap;
|
||||||
public enum WindowType
|
public enum WindowType
|
||||||
{
|
{
|
||||||
LapTime,
|
LapTime,
|
||||||
@@ -23,62 +32,116 @@ namespace OCR_Decode
|
|||||||
Gap,
|
Gap,
|
||||||
Tyre,
|
Tyre,
|
||||||
}
|
}
|
||||||
public OcrImage(Bitmap inputImage)
|
//For debugging purposes
|
||||||
|
public bool DumpDebugImages;
|
||||||
|
Random rnd = new Random();
|
||||||
|
int salt = 0;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Create a new Ocr image to help enhance the given bitmap for OCR
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="inputBitmap">The image you want to enhance</param>
|
||||||
|
public OcrImage(Bitmap inputBitmap)
|
||||||
{
|
{
|
||||||
InputImage = inputImage;
|
InputBitmap = inputBitmap;
|
||||||
|
//Before turning this on check that the DEBUG_DUMP_FOLDER constant has been changed in the Reader.cs file
|
||||||
|
DumpDebugImages = false;
|
||||||
|
|
||||||
|
//To help debugging
|
||||||
|
salt = rnd.Next(0, 10000);
|
||||||
}
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// Enhances the image depending on wich type of window the image comes from
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="type">The type of the window. Depending on it different enhancing features will be applied</param>
|
||||||
|
/// <returns>The enhanced Bitmap</returns>
|
||||||
public Bitmap Enhance(WindowType type = WindowType.Text)
|
public Bitmap Enhance(WindowType type = WindowType.Text)
|
||||||
{
|
{
|
||||||
Bitmap result = (Bitmap)InputImage.Clone();
|
Bitmap outputBitmap = (Bitmap)InputBitmap.Clone();
|
||||||
|
|
||||||
switch (type)
|
switch (type)
|
||||||
{
|
{
|
||||||
case WindowType.LapTime:
|
case WindowType.LapTime:
|
||||||
result = Grayscale(result);
|
if (DumpDebugImages)
|
||||||
result = InvertColors(result);
|
outputBitmap.Save(Reader.DEBUG_DUMP_FOLDER + "LapTimeBefore_" + salt + ".png");
|
||||||
result = Tresholding(result, 165);
|
|
||||||
result = Resize(result);
|
outputBitmap = Tresholding(outputBitmap, 185);
|
||||||
result = Resize(result);
|
if (DumpDebugImages)
|
||||||
|
outputBitmap.Save(Reader.DEBUG_DUMP_FOLDER + "LapTimeFilter1_" + salt + ".png");
|
||||||
|
|
||||||
|
outputBitmap = Resize(outputBitmap, 2);
|
||||||
|
if (DumpDebugImages)
|
||||||
|
outputBitmap.Save(Reader.DEBUG_DUMP_FOLDER + "LapTimeFilter2_" + salt + ".png");
|
||||||
|
|
||||||
|
outputBitmap = Dilatation(outputBitmap, 1);
|
||||||
|
if (DumpDebugImages)
|
||||||
|
outputBitmap.Save(Reader.DEBUG_DUMP_FOLDER + "LapTimeFilter3_" + salt + ".png");
|
||||||
|
|
||||||
|
outputBitmap = Erode(outputBitmap, 1);
|
||||||
|
if (DumpDebugImages)
|
||||||
|
outputBitmap.Save(Reader.DEBUG_DUMP_FOLDER + "LapTimeAfter_" + salt + ".png");
|
||||||
break;
|
break;
|
||||||
case WindowType.Text:
|
case WindowType.Text:
|
||||||
result = Grayscale(result);
|
if (DumpDebugImages)
|
||||||
result = InvertColors(result);
|
outputBitmap.Save(Reader.DEBUG_DUMP_FOLDER + "TextBefore_" + salt + ".png");
|
||||||
result = Tresholding(result, 165);
|
|
||||||
result = Resize(result);
|
outputBitmap = InvertColors(outputBitmap);
|
||||||
result = Dilatation(result, 1);
|
if (DumpDebugImages)
|
||||||
|
outputBitmap.Save(Reader.DEBUG_DUMP_FOLDER + "TextFilter1_" + salt + ".png");
|
||||||
|
|
||||||
|
outputBitmap = Tresholding(outputBitmap, 165);
|
||||||
|
if (DumpDebugImages)
|
||||||
|
outputBitmap.Save(Reader.DEBUG_DUMP_FOLDER + "TextFilter2_" + salt + ".png");
|
||||||
|
|
||||||
|
outputBitmap = Resize(outputBitmap, 2);
|
||||||
|
if (DumpDebugImages)
|
||||||
|
outputBitmap.Save(Reader.DEBUG_DUMP_FOLDER + "TextFilter3_" + salt + ".png");
|
||||||
|
|
||||||
|
outputBitmap = Dilatation(outputBitmap, 1);
|
||||||
|
if (DumpDebugImages)
|
||||||
|
outputBitmap.Save(Reader.DEBUG_DUMP_FOLDER + "TextAfter_" + salt + ".png");
|
||||||
break;
|
break;
|
||||||
case WindowType.Tyre:
|
case WindowType.Tyre:
|
||||||
//result = RemoveBG(result);
|
if (DumpDebugImages)
|
||||||
result = RemoveUseless(result);
|
outputBitmap.Save(Reader.DEBUG_DUMP_FOLDER + "TyreBefore_" + salt + ".png");
|
||||||
result = Resize(result);
|
|
||||||
result = Resize(result);
|
outputBitmap = RemoveUseless(outputBitmap);
|
||||||
result = Resize(result);
|
if (DumpDebugImages)
|
||||||
result = Dilatation(result, 1);
|
outputBitmap.Save(Reader.DEBUG_DUMP_FOLDER + "TyreFilter01_" + salt + ".png");
|
||||||
|
|
||||||
|
outputBitmap = Resize(outputBitmap, 4);
|
||||||
|
if (DumpDebugImages)
|
||||||
|
outputBitmap.Save(Reader.DEBUG_DUMP_FOLDER + "TyreFilter02_" + salt + ".png");
|
||||||
|
|
||||||
|
outputBitmap = Dilatation(outputBitmap, 1);
|
||||||
|
if (DumpDebugImages)
|
||||||
|
outputBitmap.Save(Reader.DEBUG_DUMP_FOLDER + "TyreAfter_" + salt + ".png");
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
result = Tresholding(result, 165);
|
outputBitmap = Tresholding(outputBitmap, 165);
|
||||||
result = Resize(result);
|
outputBitmap = Resize(outputBitmap, 4);
|
||||||
result = Resize(result);
|
outputBitmap = Erode(outputBitmap, 1);
|
||||||
result = Erode(result, 1);
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
//result = Dilatation(result, 1);
|
return outputBitmap;
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
public static Bitmap Grayscale(Bitmap input)
|
/// <summary>
|
||||||
|
/// Method that convert a colored RGB bitmap into a GrayScale image
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="inputBitmap">The Bitmap you want to convert</param>
|
||||||
|
/// <returns>The bitmap in grayscale</returns>
|
||||||
|
public static Bitmap Grayscale(Bitmap inputBitmap)
|
||||||
{
|
{
|
||||||
Bitmap bmp = input;
|
Rectangle rect = new Rectangle(0, 0, inputBitmap.Width, inputBitmap.Height);
|
||||||
Rectangle rect = new Rectangle(0, 0, bmp.Width, bmp.Height);
|
BitmapData bmpData = inputBitmap.LockBits(rect, ImageLockMode.ReadWrite, inputBitmap.PixelFormat);
|
||||||
BitmapData bmpData = bmp.LockBits(rect, ImageLockMode.ReadWrite, bmp.PixelFormat);
|
int bytesPerPixel = Bitmap.GetPixelFormatSize(inputBitmap.PixelFormat) / 8;
|
||||||
int bytesPerPixel = Bitmap.GetPixelFormatSize(bmp.PixelFormat) / 8;
|
|
||||||
|
|
||||||
unsafe
|
unsafe
|
||||||
{
|
{
|
||||||
byte* ptr = (byte*)bmpData.Scan0.ToPointer();
|
byte* ptr = (byte*)bmpData.Scan0.ToPointer();
|
||||||
for (int y = 0; y < bmp.Height; y++)
|
for (int y = 0; y < inputBitmap.Height; y++)
|
||||||
{
|
{
|
||||||
byte* currentLine = ptr + (y * bmpData.Stride);
|
byte* currentLine = ptr + (y * bmpData.Stride);
|
||||||
for (int x = 0; x < bmp.Width; x++)
|
for (int x = 0; x < inputBitmap.Width; x++)
|
||||||
{
|
{
|
||||||
byte* pixel = currentLine + (x * bytesPerPixel);
|
byte* pixel = currentLine + (x * bytesPerPixel);
|
||||||
|
|
||||||
@@ -86,63 +149,74 @@ namespace OCR_Decode
|
|||||||
byte green = pixel[1];
|
byte green = pixel[1];
|
||||||
byte red = pixel[2];
|
byte red = pixel[2];
|
||||||
|
|
||||||
|
//Those a specific values to correct the weights so its more pleasing to the human eye
|
||||||
int gray = (int)(red * 0.3 + green * 0.59 + blue * 0.11);
|
int gray = (int)(red * 0.3 + green * 0.59 + blue * 0.11);
|
||||||
|
|
||||||
pixel[0] = pixel[1] = pixel[2] = (byte)gray;
|
pixel[0] = pixel[1] = pixel[2] = (byte)gray;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
bmp.UnlockBits(bmpData);
|
inputBitmap.UnlockBits(bmpData);
|
||||||
|
|
||||||
return bmp;
|
return inputBitmap;
|
||||||
}
|
}
|
||||||
public static Bitmap Tresholding(Bitmap input, int threshold)
|
/// <summary>
|
||||||
|
/// Method that binaries the input image up to a certain treshold given
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="inputBitmap">the bitmap you want to convert to binary colors</param>
|
||||||
|
/// <param name="threshold">The floor at wich the color is considered as white or black</param>
|
||||||
|
/// <returns>The binarised bitmap</returns>
|
||||||
|
public static Bitmap Tresholding(Bitmap inputBitmap, int threshold)
|
||||||
{
|
{
|
||||||
Bitmap bmp = input;
|
Rectangle rect = new Rectangle(0, 0, inputBitmap.Width, inputBitmap.Height);
|
||||||
Rectangle rect = new Rectangle(0, 0, bmp.Width, bmp.Height);
|
BitmapData bmpData = inputBitmap.LockBits(rect, ImageLockMode.ReadWrite, inputBitmap.PixelFormat);
|
||||||
BitmapData bmpData = bmp.LockBits(rect, ImageLockMode.ReadWrite, bmp.PixelFormat);
|
int bytesPerPixel = Bitmap.GetPixelFormatSize(inputBitmap.PixelFormat) / 8;
|
||||||
int bytesPerPixel = Bitmap.GetPixelFormatSize(bmp.PixelFormat) / 8;
|
|
||||||
|
|
||||||
unsafe
|
unsafe
|
||||||
{
|
{
|
||||||
byte* ptr = (byte*)bmpData.Scan0.ToPointer();
|
byte* ptr = (byte*)bmpData.Scan0.ToPointer();
|
||||||
for (int y = 0; y < bmp.Height; y++)
|
int bmpHeight = inputBitmap.Height;
|
||||||
|
int bmpWidth = inputBitmap.Width;
|
||||||
|
Parallel.For(0, bmpHeight, y =>
|
||||||
{
|
{
|
||||||
byte* currentLine = ptr + (y * bmpData.Stride);
|
byte* currentLine = ptr + (y * bmpData.Stride);
|
||||||
for (int x = 0; x < bmp.Width; x++)
|
for (int x = 0; x < bmpWidth; x++)
|
||||||
{
|
{
|
||||||
byte* pixel = currentLine + (x * bytesPerPixel);
|
byte* pixel = currentLine + (x * bytesPerPixel);
|
||||||
|
|
||||||
byte blue = pixel[0];
|
byte blue = pixel[0];
|
||||||
byte green = pixel[1];
|
byte green = pixel[1];
|
||||||
byte red = pixel[2];
|
byte red = pixel[2];
|
||||||
|
//Those a specific values to correct the weights so its more pleasing to the human eye
|
||||||
int gray = (int)(red * 0.3 + green * 0.59 + blue * 0.11);
|
int gray = (int)(red * 0.3 + green * 0.59 + blue * 0.11);
|
||||||
int value = gray < threshold ? 0 : 255;
|
int value = gray < threshold ? 0 : 255;
|
||||||
|
|
||||||
pixel[0] = pixel[1] = pixel[2] = (byte)value;
|
pixel[0] = pixel[1] = pixel[2] = (byte)value;
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
}
|
}
|
||||||
bmp.UnlockBits(bmpData);
|
inputBitmap.UnlockBits(bmpData);
|
||||||
|
|
||||||
return bmp;
|
return inputBitmap;
|
||||||
}
|
}
|
||||||
public static Bitmap RemoveBG(Bitmap input)
|
/// <summary>
|
||||||
|
/// Method that removes the pixels that are flagged as background
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="inputBitmap">The bitmap you want to remove the background from</param>
|
||||||
|
/// <returns>The Bitmap without the background</returns>
|
||||||
|
public static Bitmap RemoveBG(Bitmap inputBitmap)
|
||||||
{
|
{
|
||||||
Color limitColor = Color.FromArgb(0x50, 0x50, 0x50);
|
Rectangle rect = new Rectangle(0, 0, inputBitmap.Width, inputBitmap.Height);
|
||||||
Bitmap bmp = input;
|
BitmapData bmpData = inputBitmap.LockBits(rect, ImageLockMode.ReadWrite, inputBitmap.PixelFormat);
|
||||||
Rectangle rect = new Rectangle(0, 0, bmp.Width, bmp.Height);
|
int bytesPerPixel = Bitmap.GetPixelFormatSize(inputBitmap.PixelFormat) / 8;
|
||||||
BitmapData bmpData = bmp.LockBits(rect, ImageLockMode.ReadWrite, bmp.PixelFormat);
|
|
||||||
int bytesPerPixel = Bitmap.GetPixelFormatSize(bmp.PixelFormat) / 8;
|
|
||||||
|
|
||||||
unsafe
|
unsafe
|
||||||
{
|
{
|
||||||
byte* ptr = (byte*)bmpData.Scan0.ToPointer();
|
byte* ptr = (byte*)bmpData.Scan0.ToPointer();
|
||||||
for (int y = 0; y < bmp.Height; y++)
|
for (int y = 0; y < inputBitmap.Height; y++)
|
||||||
{
|
{
|
||||||
byte* currentLine = ptr + (y * bmpData.Stride);
|
byte* currentLine = ptr + (y * bmpData.Stride);
|
||||||
for (int x = 0; x < bmp.Width; x++)
|
for (int x = 0; x < inputBitmap.Width; x++)
|
||||||
{
|
{
|
||||||
byte* pixel = currentLine + (x * bytesPerPixel);
|
byte* pixel = currentLine + (x * bytesPerPixel);
|
||||||
|
|
||||||
@@ -150,25 +224,29 @@ namespace OCR_Decode
|
|||||||
int G = pixel[1];
|
int G = pixel[1];
|
||||||
int R = pixel[2];
|
int R = pixel[2];
|
||||||
|
|
||||||
if (R <= limitColor.R && G <= limitColor.G && B <= limitColor.B)
|
if (R <= F1TV_BACKGROUND_TRESHOLD.R && G <= F1TV_BACKGROUND_TRESHOLD.G && B <= F1TV_BACKGROUND_TRESHOLD.B)
|
||||||
pixel[0] = pixel[1] = pixel[2] = 0;
|
pixel[0] = pixel[1] = pixel[2] = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
bmp.UnlockBits(bmpData);
|
inputBitmap.UnlockBits(bmpData);
|
||||||
|
|
||||||
return bmp;
|
return inputBitmap;
|
||||||
}
|
}
|
||||||
public unsafe static Bitmap RemoveUseless(Bitmap bmp)
|
/// <summary>
|
||||||
|
/// Method that removes all the useless things from the image and returns hopefully only the numbers
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="inputBitmap">The bitmap you want to remove useless things from (Expects a cropped part of the TyreWindow)</param>
|
||||||
|
/// <returns>The bitmap with (hopefully) only the digits</returns>
|
||||||
|
public unsafe static Bitmap RemoveUseless(Bitmap inputBitmap)
|
||||||
{
|
{
|
||||||
bmp = RemoveBG(bmp);
|
//Note you can use something else than a cropped tyre window but I would recommend checking the code first to see if it fits your intended use
|
||||||
Rectangle rect = new Rectangle(0, 0, bmp.Width, bmp.Height);
|
Rectangle rect = new Rectangle(0, 0, inputBitmap.Width, inputBitmap.Height);
|
||||||
BitmapData bmpData = bmp.LockBits(rect, ImageLockMode.ReadWrite, bmp.PixelFormat);
|
BitmapData bmpData = inputBitmap.LockBits(rect, ImageLockMode.ReadWrite, inputBitmap.PixelFormat);
|
||||||
int bytesPerPixel = Bitmap.GetPixelFormatSize(bmp.PixelFormat) / 8;
|
int bytesPerPixel = Bitmap.GetPixelFormatSize(inputBitmap.PixelFormat) / 8;
|
||||||
|
|
||||||
|
|
||||||
byte* ptr = (byte*)bmpData.Scan0.ToPointer();
|
byte* ptr = (byte*)bmpData.Scan0.ToPointer();
|
||||||
for (int y = 0; y < bmp.Height; y++)
|
for (int y = 0; y < inputBitmap.Height; y++)
|
||||||
{
|
{
|
||||||
byte* currentLine = ptr + (y * bmpData.Stride);
|
byte* currentLine = ptr + (y * bmpData.Stride);
|
||||||
|
|
||||||
@@ -176,7 +254,7 @@ namespace OCR_Decode
|
|||||||
|
|
||||||
bool fromBorder = true;
|
bool fromBorder = true;
|
||||||
|
|
||||||
for (int x = 0; x < bmp.Width; x++)
|
for (int x = 0; x < inputBitmap.Width; x++)
|
||||||
{
|
{
|
||||||
byte* pixel = currentLine + (x * bytesPerPixel);
|
byte* pixel = currentLine + (x * bytesPerPixel);
|
||||||
|
|
||||||
@@ -184,17 +262,21 @@ namespace OCR_Decode
|
|||||||
int G = pixel[1];
|
int G = pixel[1];
|
||||||
int R = pixel[2];
|
int R = pixel[2];
|
||||||
|
|
||||||
if (fromBorder && B < 0x10 && G < 0x10 && R < 0x10)
|
if (fromBorder && B < F1TV_BACKGROUND_TRESHOLD.B && G < F1TV_BACKGROUND_TRESHOLD.G && R < F1TV_BACKGROUND_TRESHOLD.R)
|
||||||
{
|
{
|
||||||
pixelsToRemove.Add(x);
|
pixelsToRemove.Add(x);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
fromBorder = false;
|
if (fromBorder)
|
||||||
|
{
|
||||||
|
fromBorder = false;
|
||||||
|
pixelsToRemove.Add(x);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fromBorder = true;
|
fromBorder = true;
|
||||||
for (int x = bmp.Width - 1; x > 0; x--)
|
for (int x = inputBitmap.Width - 1; x > 0; x--)
|
||||||
{
|
{
|
||||||
byte* pixel = currentLine + (x * bytesPerPixel);
|
byte* pixel = currentLine + (x * bytesPerPixel);
|
||||||
|
|
||||||
@@ -202,13 +284,17 @@ namespace OCR_Decode
|
|||||||
int G = pixel[1];
|
int G = pixel[1];
|
||||||
int R = pixel[2];
|
int R = pixel[2];
|
||||||
|
|
||||||
if (fromBorder && B < 0x10 && G < 0x10 && R < 0x10)
|
if (fromBorder && B < F1TV_BACKGROUND_TRESHOLD.B && G < F1TV_BACKGROUND_TRESHOLD.G && R < F1TV_BACKGROUND_TRESHOLD.R)
|
||||||
{
|
{
|
||||||
pixelsToRemove.Add(x);
|
pixelsToRemove.Add(x);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
fromBorder = false;
|
if (fromBorder)
|
||||||
|
{
|
||||||
|
fromBorder = false;
|
||||||
|
pixelsToRemove.Add(x);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -222,13 +308,11 @@ namespace OCR_Decode
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//Removing the color parts
|
||||||
//NOW REMOVING THE COLOR
|
for (int y = 0; y < inputBitmap.Height; y++)
|
||||||
Color limitColor = Color.FromArgb(0x50, 0x50, 0x50);
|
|
||||||
for (int y = 0; y < bmp.Height; y++)
|
|
||||||
{
|
{
|
||||||
byte* currentLine = ptr + (y * bmpData.Stride);
|
byte* currentLine = ptr + (y * bmpData.Stride);
|
||||||
for (int x = 0; x < bmp.Width; x++)
|
for (int x = 0; x < inputBitmap.Width; x++)
|
||||||
{
|
{
|
||||||
byte* pixel = currentLine + (x * bytesPerPixel);
|
byte* pixel = currentLine + (x * bytesPerPixel);
|
||||||
|
|
||||||
@@ -236,9 +320,7 @@ namespace OCR_Decode
|
|||||||
int G = pixel[1];
|
int G = pixel[1];
|
||||||
int R = pixel[2];
|
int R = pixel[2];
|
||||||
|
|
||||||
//We remove the background pixels
|
if (R >= F1TV_BACKGROUND_TRESHOLD.R + 15 || G >= F1TV_BACKGROUND_TRESHOLD.G + 15 || B >= F1TV_BACKGROUND_TRESHOLD.B + 15)
|
||||||
|
|
||||||
if (R >= limitColor.R || G >= limitColor.G || B >= limitColor.B)
|
|
||||||
{
|
{
|
||||||
pixel[0] = 0xFF;
|
pixel[0] = 0xFF;
|
||||||
pixel[1] = 0xFF;
|
pixel[1] = 0xFF;
|
||||||
@@ -247,18 +329,19 @@ namespace OCR_Decode
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inputBitmap.UnlockBits(bmpData);
|
||||||
|
return inputBitmap;
|
||||||
bmp.UnlockBits(bmpData);
|
|
||||||
|
|
||||||
return bmp;
|
|
||||||
}
|
}
|
||||||
public static Color GetAvgColorFromBitmap(Bitmap bmp)
|
/// <summary>
|
||||||
|
/// Recovers the average colors from the Image. NOTE : It wont take in account colors that are lower than the background
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="inputBitmap">The bitmap you want to get the average color from</param>
|
||||||
|
/// <returns>The average color of the bitmap</returns>
|
||||||
|
public static Color GetAvgColorFromBitmap(Bitmap inputBitmap)
|
||||||
{
|
{
|
||||||
Color limitColor = Color.FromArgb(0x50, 0x50, 0x50);
|
Rectangle rect = new Rectangle(0, 0, inputBitmap.Width, inputBitmap.Height);
|
||||||
Rectangle rect = new Rectangle(0, 0, bmp.Width, bmp.Height);
|
BitmapData bmpData = inputBitmap.LockBits(rect, ImageLockMode.ReadWrite, inputBitmap.PixelFormat);
|
||||||
BitmapData bmpData = bmp.LockBits(rect, ImageLockMode.ReadWrite, bmp.PixelFormat);
|
int bytesPerPixel = Bitmap.GetPixelFormatSize(inputBitmap.PixelFormat) / 8;
|
||||||
int bytesPerPixel = Bitmap.GetPixelFormatSize(bmp.PixelFormat) / 8;
|
|
||||||
|
|
||||||
int totR = 0;
|
int totR = 0;
|
||||||
int totG = 0;
|
int totG = 0;
|
||||||
@@ -269,10 +352,12 @@ namespace OCR_Decode
|
|||||||
unsafe
|
unsafe
|
||||||
{
|
{
|
||||||
byte* ptr = (byte*)bmpData.Scan0.ToPointer();
|
byte* ptr = (byte*)bmpData.Scan0.ToPointer();
|
||||||
for (int y = 0; y < bmp.Height; y++)
|
int bmpHeight = inputBitmap.Height;
|
||||||
|
int bmpWidth = inputBitmap.Width;
|
||||||
|
Parallel.For(0, bmpHeight, y =>
|
||||||
{
|
{
|
||||||
byte* currentLine = ptr + (y * bmpData.Stride);
|
byte* currentLine = ptr + (y * bmpData.Stride);
|
||||||
for (int x = 0; x < bmp.Width; x++)
|
for (int x = 0; x < bmpWidth; x++)
|
||||||
{
|
{
|
||||||
byte* pixel = currentLine + (x * bytesPerPixel);
|
byte* pixel = currentLine + (x * bytesPerPixel);
|
||||||
|
|
||||||
@@ -280,9 +365,7 @@ namespace OCR_Decode
|
|||||||
int G = pixel[1];
|
int G = pixel[1];
|
||||||
int R = pixel[2];
|
int R = pixel[2];
|
||||||
|
|
||||||
//We remove the background pixels
|
if (R >= F1TV_BACKGROUND_TRESHOLD.R || G >= F1TV_BACKGROUND_TRESHOLD.G || B >= F1TV_BACKGROUND_TRESHOLD.B)
|
||||||
|
|
||||||
if (R >= limitColor.R || G >= limitColor.G || B >= limitColor.B)
|
|
||||||
{
|
{
|
||||||
totPixels++;
|
totPixels++;
|
||||||
totB += pixel[0];
|
totB += pixel[0];
|
||||||
@@ -290,26 +373,30 @@ namespace OCR_Decode
|
|||||||
totR += pixel[2];
|
totR += pixel[2];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
}
|
}
|
||||||
bmp.UnlockBits(bmpData);
|
inputBitmap.UnlockBits(bmpData);
|
||||||
|
|
||||||
return Color.FromArgb(255, Convert.ToInt32((float)totR / (float)totPixels), Convert.ToInt32((float)totG / (float)totPixels), Convert.ToInt32((float)totB / (float)totPixels));
|
return Color.FromArgb(255, Convert.ToInt32((float)totR / (float)totPixels), Convert.ToInt32((float)totG / (float)totPixels), Convert.ToInt32((float)totB / (float)totPixels));
|
||||||
}
|
}
|
||||||
public static Bitmap InvertColors(Bitmap input)
|
/// <summary>
|
||||||
|
/// This method simply inverts all the colors in a Bitmap
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="inputBitmap">the bitmap you want to invert the colors from</param>
|
||||||
|
/// <returns>The bitmap with inverted colors</returns>
|
||||||
|
public static Bitmap InvertColors(Bitmap inputBitmap)
|
||||||
{
|
{
|
||||||
Bitmap bmp = input;
|
Rectangle rect = new Rectangle(0, 0, inputBitmap.Width, inputBitmap.Height);
|
||||||
Rectangle rect = new Rectangle(0, 0, bmp.Width, bmp.Height);
|
BitmapData bmpData = inputBitmap.LockBits(rect, ImageLockMode.ReadWrite, inputBitmap.PixelFormat);
|
||||||
BitmapData bmpData = bmp.LockBits(rect, ImageLockMode.ReadWrite, bmp.PixelFormat);
|
int bytesPerPixel = Bitmap.GetPixelFormatSize(inputBitmap.PixelFormat) / 8;
|
||||||
int bytesPerPixel = Bitmap.GetPixelFormatSize(bmp.PixelFormat) / 8;
|
|
||||||
|
|
||||||
unsafe
|
unsafe
|
||||||
{
|
{
|
||||||
byte* ptr = (byte*)bmpData.Scan0.ToPointer();
|
byte* ptr = (byte*)bmpData.Scan0.ToPointer();
|
||||||
for (int y = 0; y < bmp.Height; y++)
|
for (int y = 0; y < inputBitmap.Height; y++)
|
||||||
{
|
{
|
||||||
byte* currentLine = ptr + (y * bmpData.Stride);
|
byte* currentLine = ptr + (y * bmpData.Stride);
|
||||||
for (int x = 0; x < bmp.Width; x++)
|
for (int x = 0; x < inputBitmap.Width; x++)
|
||||||
{
|
{
|
||||||
byte* pixel = currentLine + (x * bytesPerPixel);
|
byte* pixel = currentLine + (x * bytesPerPixel);
|
||||||
|
|
||||||
@@ -319,47 +406,47 @@ namespace OCR_Decode
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
bmp.UnlockBits(bmpData);
|
inputBitmap.UnlockBits(bmpData);
|
||||||
|
|
||||||
return bmp;
|
return inputBitmap;
|
||||||
}
|
}
|
||||||
public static Bitmap ApplyGaussianBlur(Bitmap input, int radius)
|
/// <summary>
|
||||||
|
/// Methods that applies Bicubic interpolation to increase the size and resolution of an image
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="inputBitmap">The bitmap you want to resize</param>
|
||||||
|
/// <param name="resizeFactor">The factor of resizing you want to use. I recommend using even numbers</param>
|
||||||
|
/// <returns>The bitmap witht the new size</returns>
|
||||||
|
public static Bitmap Resize(Bitmap inputBitmap, int resizeFactor)
|
||||||
{
|
{
|
||||||
GaussianBlur filter = new GaussianBlur(radius);
|
var resultBitmap = new Bitmap(inputBitmap.Width * resizeFactor, inputBitmap.Height * resizeFactor);
|
||||||
Bitmap output = filter.Apply(input);
|
|
||||||
return output;
|
|
||||||
}
|
|
||||||
public static Bitmap Resize(Bitmap sourceBitmap)
|
|
||||||
{
|
|
||||||
// Create a new Bitmap object to hold the resized image
|
|
||||||
var resultBitmap = new Bitmap(sourceBitmap.Width * 2, sourceBitmap.Height * 2);
|
|
||||||
|
|
||||||
// Create a Graphics object to draw the resized image
|
|
||||||
using (var graphics = Graphics.FromImage(resultBitmap))
|
using (var graphics = Graphics.FromImage(resultBitmap))
|
||||||
{
|
{
|
||||||
// Set the interpolation mode to high-quality bicubic
|
|
||||||
graphics.InterpolationMode = InterpolationMode.HighQualityBicubic;
|
graphics.InterpolationMode = InterpolationMode.HighQualityBicubic;
|
||||||
|
graphics.DrawImage(inputBitmap, new Rectangle(0, 0, resultBitmap.Width, resultBitmap.Height));
|
||||||
// Draw the resized image using the Graphics object
|
|
||||||
graphics.DrawImage(sourceBitmap, new Rectangle(0, 0, resultBitmap.Width, resultBitmap.Height));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return resultBitmap;
|
return resultBitmap;
|
||||||
}
|
}
|
||||||
public static Bitmap HighlightContours(Bitmap input)
|
/// <summary>
|
||||||
|
/// method that Highlights the countours of a Bitmap
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="inputBitmap">The bitmap you want to highlight the countours of</param>
|
||||||
|
/// <returns>The bitmap with countours highlighted</returns>
|
||||||
|
public static Bitmap HighlightContours(Bitmap inputBitmap)
|
||||||
{
|
{
|
||||||
Bitmap output = new Bitmap(input.Width, input.Height);
|
Bitmap outputBitmap = new Bitmap(inputBitmap.Width, inputBitmap.Height);
|
||||||
|
|
||||||
Bitmap grayscale = Grayscale(input);
|
Bitmap grayscale = Grayscale(inputBitmap);
|
||||||
Bitmap thresholded = Tresholding(grayscale, 128);
|
Bitmap thresholded = Tresholding(grayscale, 128);
|
||||||
Bitmap dilated = Dilatation(thresholded, 3);
|
Bitmap dilated = Dilatation(thresholded, 3);
|
||||||
Bitmap eroded = Erode(dilated, 3);
|
Bitmap eroded = Erode(dilated, 3);
|
||||||
|
|
||||||
for (int y = 0; y < input.Height; y++)
|
for (int y = 0; y < inputBitmap.Height; y++)
|
||||||
{
|
{
|
||||||
for (int x = 0; x < input.Width; x++)
|
for (int x = 0; x < inputBitmap.Width; x++)
|
||||||
{
|
{
|
||||||
Color pixel = input.GetPixel(x, y);
|
Color pixel = inputBitmap.GetPixel(x, y);
|
||||||
Color dilatedPixel = dilated.GetPixel(x, y);
|
Color dilatedPixel = dilated.GetPixel(x, y);
|
||||||
Color erodedPixel = eroded.GetPixel(x, y);
|
Color erodedPixel = eroded.GetPixel(x, y);
|
||||||
|
|
||||||
@@ -368,24 +455,30 @@ namespace OCR_Decode
|
|||||||
|
|
||||||
if (gray > threshold)
|
if (gray > threshold)
|
||||||
{
|
{
|
||||||
output.SetPixel(x, y, Color.FromArgb(255, 255, 255));
|
outputBitmap.SetPixel(x, y, Color.FromArgb(255, 255, 255));
|
||||||
}
|
}
|
||||||
else if (gray <= threshold && erodedPixel.R == 0)
|
else if (gray <= threshold && erodedPixel.R == 0)
|
||||||
{
|
{
|
||||||
output.SetPixel(x, y, Color.FromArgb(255, 0, 0));
|
outputBitmap.SetPixel(x, y, Color.FromArgb(255, 0, 0));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
output.SetPixel(x, y, Color.FromArgb(0, 0, 0));
|
outputBitmap.SetPixel(x, y, Color.FromArgb(0, 0, 0));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return output;
|
return outputBitmap;
|
||||||
}
|
}
|
||||||
public static Bitmap Erode(Bitmap input, int kernelSize)
|
/// <summary>
|
||||||
|
/// Method that that erodes the morphology of a bitmap
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="inputBitmap">The bitmap you want to erode</param>
|
||||||
|
/// <param name="kernelSize">The amount of Erosion you want (be carefull its expensive on ressources)</param>
|
||||||
|
/// <returns>The Bitmap with the eroded contents</returns>
|
||||||
|
public static Bitmap Erode(Bitmap inputBitmap, int kernelSize)
|
||||||
{
|
{
|
||||||
Bitmap output = new Bitmap(input.Width, input.Height);
|
Bitmap outputBitmap = new Bitmap(inputBitmap.Width, inputBitmap.Height);
|
||||||
|
|
||||||
int[,] kernel = new int[kernelSize, kernelSize];
|
int[,] kernel = new int[kernelSize, kernelSize];
|
||||||
|
|
||||||
@@ -397,9 +490,9 @@ namespace OCR_Decode
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int y = kernelSize / 2; y < input.Height - kernelSize / 2; y++)
|
for (int y = kernelSize / 2; y < inputBitmap.Height - kernelSize / 2; y++)
|
||||||
{
|
{
|
||||||
for (int x = kernelSize / 2; x < input.Width - kernelSize / 2; x++)
|
for (int x = kernelSize / 2; x < inputBitmap.Width - kernelSize / 2; x++)
|
||||||
{
|
{
|
||||||
bool flag = true;
|
bool flag = true;
|
||||||
|
|
||||||
@@ -407,7 +500,7 @@ namespace OCR_Decode
|
|||||||
{
|
{
|
||||||
for (int j = -kernelSize / 2; j <= kernelSize / 2; j++)
|
for (int j = -kernelSize / 2; j <= kernelSize / 2; j++)
|
||||||
{
|
{
|
||||||
Color pixel = input.GetPixel(x + i, y + j);
|
Color pixel = inputBitmap.GetPixel(x + i, y + j);
|
||||||
int gray = (int)(pixel.R * 0.3 + pixel.G * 0.59 + pixel.B * 0.11);
|
int gray = (int)(pixel.R * 0.3 + pixel.G * 0.59 + pixel.B * 0.11);
|
||||||
|
|
||||||
if (gray >= 128 && kernel[i + kernelSize / 2, j + kernelSize / 2] == 1)
|
if (gray >= 128 && kernel[i + kernelSize / 2, j + kernelSize / 2] == 1)
|
||||||
@@ -425,21 +518,26 @@ namespace OCR_Decode
|
|||||||
|
|
||||||
if (flag)
|
if (flag)
|
||||||
{
|
{
|
||||||
output.SetPixel(x, y, Color.FromArgb(255, 255, 255));
|
outputBitmap.SetPixel(x, y, Color.FromArgb(255, 255, 255));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
output.SetPixel(x, y, Color.FromArgb(0, 0, 0));
|
outputBitmap.SetPixel(x, y, Color.FromArgb(0, 0, 0));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return output;
|
return outputBitmap;
|
||||||
}
|
}
|
||||||
|
/// <summary>
|
||||||
public static Bitmap Dilatation(Bitmap input, int kernelSize)
|
/// Method that that use dilatation of the morphology of a bitmap
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="inputBitmap">The bitmap you want to use dilatation on</param>
|
||||||
|
/// <param name="kernelSize">The amount of dilatation you want (be carefull its expensive on ressources)</param>
|
||||||
|
/// <returns>The Bitmap after Dilatation</returns>
|
||||||
|
public static Bitmap Dilatation(Bitmap inputBitmap, int kernelSize)
|
||||||
{
|
{
|
||||||
Bitmap output = new Bitmap(input.Width, input.Height);
|
Bitmap outputBitmap = new Bitmap(inputBitmap.Width, inputBitmap.Height);
|
||||||
|
|
||||||
int[,] kernel = new int[kernelSize, kernelSize];
|
int[,] kernel = new int[kernelSize, kernelSize];
|
||||||
|
|
||||||
@@ -451,9 +549,9 @@ namespace OCR_Decode
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int y = kernelSize / 2; y < input.Height - kernelSize / 2; y++)
|
for (int y = kernelSize / 2; y < inputBitmap.Height - kernelSize / 2; y++)
|
||||||
{
|
{
|
||||||
for (int x = kernelSize / 2; x < input.Width - kernelSize / 2; x++)
|
for (int x = kernelSize / 2; x < inputBitmap.Width - kernelSize / 2; x++)
|
||||||
{
|
{
|
||||||
bool flag = false;
|
bool flag = false;
|
||||||
|
|
||||||
@@ -461,7 +559,7 @@ namespace OCR_Decode
|
|||||||
{
|
{
|
||||||
for (int j = -kernelSize / 2; j <= kernelSize / 2; j++)
|
for (int j = -kernelSize / 2; j <= kernelSize / 2; j++)
|
||||||
{
|
{
|
||||||
Color pixel = input.GetPixel(x + i, y + j);
|
Color pixel = inputBitmap.GetPixel(x + i, y + j);
|
||||||
int gray = (int)(pixel.R * 0.3 + pixel.G * 0.59 + pixel.B * 0.11);
|
int gray = (int)(pixel.R * 0.3 + pixel.G * 0.59 + pixel.B * 0.11);
|
||||||
|
|
||||||
if (gray < 128 && kernel[i + kernelSize / 2, j + kernelSize / 2] == 1)
|
if (gray < 128 && kernel[i + kernelSize / 2, j + kernelSize / 2] == 1)
|
||||||
@@ -479,16 +577,16 @@ namespace OCR_Decode
|
|||||||
|
|
||||||
if (flag)
|
if (flag)
|
||||||
{
|
{
|
||||||
output.SetPixel(x, y, Color.FromArgb(0, 0, 0));
|
outputBitmap.SetPixel(x, y, Color.FromArgb(0, 0, 0));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
output.SetPixel(x, y, Color.FromArgb(255, 255, 255));
|
outputBitmap.SetPixel(x, y, Color.FromArgb(255, 255, 255));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return output;
|
return outputBitmap;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+67
-76
@@ -1,4 +1,10 @@
|
|||||||
using System;
|
/// Author : Maxime Rohmer
|
||||||
|
/// Date : 25/04/2023
|
||||||
|
/// File : Reader.cs
|
||||||
|
/// Brief : Model that contains all the classes and methods to manage the OCR
|
||||||
|
/// Version : 0.1
|
||||||
|
|
||||||
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
@@ -7,6 +13,7 @@ using System.Drawing;
|
|||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
using System.Windows.Forms;
|
using System.Windows.Forms;
|
||||||
|
using System.Diagnostics;
|
||||||
|
|
||||||
namespace OCR_Decode
|
namespace OCR_Decode
|
||||||
{
|
{
|
||||||
@@ -22,7 +29,9 @@ namespace OCR_Decode
|
|||||||
public List<string> Drivers { get => _drivers; private set => _drivers = value; }
|
public List<string> Drivers { get => _drivers; private set => _drivers = value; }
|
||||||
public List<Zone> MainZones { get => _mainZones; set => _mainZones = value; }
|
public List<Zone> MainZones { get => _mainZones; set => _mainZones = value; }
|
||||||
|
|
||||||
|
//All the image infos will be deleted in not too much time when the merge with the program that recovers the images
|
||||||
const string DEFAULT_IMAGE_NAME = "screen_";
|
const string DEFAULT_IMAGE_NAME = "screen_";
|
||||||
|
// You will defenitely have to change this if you want to be able to see debug images
|
||||||
public const string DEBUG_DUMP_FOLDER = @"C:\Users\Moi\Desktop\imgDump\Decode\";
|
public const string DEBUG_DUMP_FOLDER = @"C:\Users\Moi\Desktop\imgDump\Decode\";
|
||||||
const int NUMBER_OF_DRIVERS = 20;
|
const int NUMBER_OF_DRIVERS = 20;
|
||||||
|
|
||||||
@@ -32,6 +41,10 @@ namespace OCR_Decode
|
|||||||
ImagesFolder = imageFolder;
|
ImagesFolder = imageFolder;
|
||||||
Load(82);
|
Load(82);
|
||||||
}
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// Method that reads the JSON config file and create all the Zones and Windows
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="imageNumber">The image #id on wich you want to create the zones on</param>
|
||||||
private void Load(int imageNumber)
|
private void Load(int imageNumber)
|
||||||
{
|
{
|
||||||
MainZones = new List<Zone>();
|
MainZones = new List<Zone>();
|
||||||
@@ -115,25 +128,36 @@ namespace OCR_Decode
|
|||||||
Point driverSector3Position = new Point(driverSector3.GetProperty("x").GetInt32(), driverSector3.GetProperty("y").GetInt32());
|
Point driverSector3Position = new Point(driverSector3.GetProperty("x").GetInt32(), driverSector3.GetProperty("y").GetInt32());
|
||||||
|
|
||||||
float offset = (((float)MainZone.ZoneImage.Height - (float)(Drivers.Count * FirstZoneSize.Height)) / (float)Drivers.Count);
|
float offset = (((float)MainZone.ZoneImage.Height - (float)(Drivers.Count * FirstZoneSize.Height)) / (float)Drivers.Count);
|
||||||
|
Bitmap MainZoneImage = MainZone.ZoneImage;
|
||||||
|
List<Zone> zonesToAdd = new List<Zone>();
|
||||||
|
List<Bitmap> zonesImages = new List<Bitmap>();
|
||||||
|
|
||||||
for (int i = 0; i < NUMBER_OF_DRIVERS; i++)
|
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))*/);
|
Point tmpPos = new Point(0, FirstZonePosition.Y + i * FirstZoneSize.Height - Convert.ToInt32(i * offset));
|
||||||
Zone newDriverZone = new Zone(MainZone.ZoneImage, new Rectangle(tmpPos, FirstZoneSize));
|
Zone newDriverZone = new Zone(MainZoneImage, new Rectangle(tmpPos, FirstZoneSize));
|
||||||
|
zonesToAdd.Add(newDriverZone);
|
||||||
newDriverZone.AddWindow(new DriverPositionWindow(newDriverZone.ZoneImage, new Rectangle(driverPositionPosition, driverPositionArea)));
|
zonesImages.Add(newDriverZone.ZoneImage);
|
||||||
newDriverZone.AddWindow(new DriverGapToLeaderWindow(newDriverZone.ZoneImage, new Rectangle(driverGapToLeaderPosition, driverGapToLeaderArea)));
|
|
||||||
newDriverZone.AddWindow(new DriverLapTimeWindow(newDriverZone.ZoneImage, new Rectangle(driverLapTimePosition, driverLapTimeArea)));
|
|
||||||
newDriverZone.AddWindow(new DriverDrsWindow(newDriverZone.ZoneImage, new Rectangle(driverDrsPosition, driverDrsArea)));
|
|
||||||
newDriverZone.AddWindow(new DriverTyresWindow(newDriverZone.ZoneImage, new Rectangle(driverTyresPosition, driverTyresArea)));
|
|
||||||
newDriverZone.AddWindow(new DriverNameWindow(newDriverZone.ZoneImage, new Rectangle(driverNamePosition, driverNameArea)));
|
|
||||||
newDriverZone.AddWindow(new DriverSector1Window(newDriverZone.ZoneImage, new Rectangle(driverSector1Position, driverSector1Area)));
|
|
||||||
newDriverZone.AddWindow(new DriverSector2Window(newDriverZone.ZoneImage, new Rectangle(driverSector2Position, driverSector2Area)));
|
|
||||||
newDriverZone.AddWindow(new DriverSector3Window(newDriverZone.ZoneImage, new Rectangle(driverSector3Position, driverSector3Area)));
|
|
||||||
|
|
||||||
MainZone.AddZone(newDriverZone);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//Parallel.For(0, NUMBER_OF_DRIVERS, i =>
|
||||||
|
for (int i = 0; i < NUMBER_OF_DRIVERS; i++)
|
||||||
|
{
|
||||||
|
Zone newDriverZone = zonesToAdd[(int)i];
|
||||||
|
Bitmap zoneImg = zonesImages[(int)i];
|
||||||
|
|
||||||
|
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);
|
||||||
|
}//);
|
||||||
//MessageBox.Show("We have a main zone with " + MainZone.Zones.Count() + " Driver zones with " + MainZone.Zones[4].Windows.Count() + " windows each and we have " + Drivers.Count + " drivers");
|
//MessageBox.Show("We have a main zone with " + MainZone.Zones.Count() + " Driver zones with " + MainZone.Zones[4].Windows.Count() + " windows each and we have " + Drivers.Count + " drivers");
|
||||||
MainZones.Add(MainZone);
|
MainZones.Add(MainZone);
|
||||||
}
|
}
|
||||||
@@ -147,6 +171,10 @@ namespace OCR_Decode
|
|||||||
MessageBox.Show("Invalid JSON format: " + ex.Message);
|
MessageBox.Show("Invalid JSON format: " + ex.Message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// Changes the zones and windows to the new image
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="imageNumber">The #id of the new image on wich to do OCR</param>
|
||||||
public void ChangeImage(int imageNumber)
|
public void ChangeImage(int imageNumber)
|
||||||
{
|
{
|
||||||
Bitmap img = null;
|
Bitmap img = null;
|
||||||
@@ -168,14 +196,19 @@ namespace OCR_Decode
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
public string Decode(int idImage)
|
/// <summary>
|
||||||
|
/// Method that calls all the zones and windows to get the content they can find on the image to display them
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="idImage">The id of the image we are working with</param>
|
||||||
|
/// <returns>a string representation of all the returns</returns>
|
||||||
|
public async Task<string> Decode(int idImage)
|
||||||
{
|
{
|
||||||
string result = "";
|
string result = "";
|
||||||
ChangeImage(idImage);
|
ChangeImage(idImage);
|
||||||
List<List<Object>> mainResults = new List<List<Object>>();
|
List<DriverData> mainResults = new List<DriverData>();
|
||||||
|
|
||||||
//Decode
|
//Decode
|
||||||
for (int mainZoneId = 0; mainZoneId < MainZones.Count;mainZoneId ++)
|
for (int mainZoneId = 0; mainZoneId < MainZones.Count; mainZoneId++)
|
||||||
{
|
{
|
||||||
switch (mainZoneId)
|
switch (mainZoneId)
|
||||||
{
|
{
|
||||||
@@ -183,77 +216,41 @@ namespace OCR_Decode
|
|||||||
//Main Zone
|
//Main Zone
|
||||||
foreach (Zone z in MainZones[mainZoneId].Zones)
|
foreach (Zone z in MainZones[mainZoneId].Zones)
|
||||||
{
|
{
|
||||||
mainResults.Add(z.Decode(Drivers));
|
mainResults.Add(await z.Decode(Drivers));
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
//Next there will be the title and laps added
|
//Next there could be a Title Zone and TrackInfoZone
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//Display
|
//Display
|
||||||
foreach (List<Object> objects in mainResults)
|
foreach (DriverData driver in mainResults)
|
||||||
{
|
{
|
||||||
for (int objId = 0; objId < objects.Count; objId++)
|
result += driver.ToString();
|
||||||
{
|
result += Environment.NewLine;
|
||||||
switch (objId)
|
|
||||||
{
|
|
||||||
case 0:
|
|
||||||
//Position
|
|
||||||
result += "Position : " + (int)objects[objId] + " ";
|
|
||||||
break;
|
|
||||||
case 1:
|
|
||||||
//Gap
|
|
||||||
result += "Gap to leader : " + ConvertMsToTime((int)objects[objId]) + " ";
|
|
||||||
break;
|
|
||||||
case 2:
|
|
||||||
//LapTime
|
|
||||||
result += "Lap time : " + ConvertMsToTime((int)objects[objId]) + " ";
|
|
||||||
break;
|
|
||||||
case 3:
|
|
||||||
//DRS
|
|
||||||
result += "DRS : " + (bool)objects[objId] + "";
|
|
||||||
break;
|
|
||||||
case 4:
|
|
||||||
//Tyres
|
|
||||||
Tyre tyre = (Tyre)objects[objId];
|
|
||||||
result += "Uses " + tyre.Coumpound + " tyre for " + tyre.NumberOfLaps + " laps";
|
|
||||||
break;
|
|
||||||
case 5:
|
|
||||||
//Name
|
|
||||||
result += "Driver name : " + (string)objects[objId] + " ";
|
|
||||||
break;
|
|
||||||
case 6:
|
|
||||||
//Sector 1
|
|
||||||
result += "Sector 1 : " + ConvertMsToTime((int)objects[objId]) + " ";
|
|
||||||
break;
|
|
||||||
case 7:
|
|
||||||
//Sector 1
|
|
||||||
result += "Sector 2 : " + ConvertMsToTime((int)objects[objId]) + " ";
|
|
||||||
break;
|
|
||||||
case 8:
|
|
||||||
//Sector 1
|
|
||||||
result += "Sector 3 : " + ConvertMsToTime((int)objects[objId]) + " ";
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
result += "Unknown source : " + objects[objId].ToString();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
result += Environment.NewLine;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// Method that can be used to convert an amount of miliseconds into a more readable human form
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="amountOfMs">The given amount of miliseconds ton convert</param>
|
||||||
|
/// <returns>A human readable string that represents the ms</returns>
|
||||||
public static string ConvertMsToTime(int amountOfMs)
|
public static string ConvertMsToTime(int amountOfMs)
|
||||||
{
|
{
|
||||||
//Convert.ToInt32 would round upand I dont want that
|
//Convert.ToInt32 would round upand I dont want that
|
||||||
int minuts = (int)((float)amountOfMs / (1000f * 60f));
|
int minuts = (int)((float)amountOfMs / (1000f * 60f));
|
||||||
int seconds = (int)((amountOfMs - (minuts*60f*1000f))/1000);
|
int seconds = (int)((amountOfMs - (minuts * 60f * 1000f)) / 1000);
|
||||||
int ms = amountOfMs - ((minuts * 60 * 1000) + (seconds * 1000));
|
int ms = amountOfMs - ((minuts * 60 * 1000) + (seconds * 1000));
|
||||||
|
|
||||||
return minuts + ":" + seconds.ToString("00") + ":" + ms.ToString("000");
|
return minuts + ":" + seconds.ToString("00") + ":" + ms.ToString("000");
|
||||||
}
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// Old method that can draw on an image where the windows and zones are created. mostly used for debugging
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="idImage">the #id of the image we are working with</param>
|
||||||
|
/// <returns>the drawed bitmap</returns>
|
||||||
public Bitmap Draw(int idImage)
|
public Bitmap Draw(int idImage)
|
||||||
{
|
{
|
||||||
Bitmap result;
|
Bitmap result;
|
||||||
@@ -274,12 +271,6 @@ namespace OCR_Decode
|
|||||||
int count = 0;
|
int count = 0;
|
||||||
foreach (Zone zz in z.Zones)
|
foreach (Zone zz in z.Zones)
|
||||||
{
|
{
|
||||||
string driverFolder = DEBUG_DUMP_FOLDER + "driver" + count + "\\";
|
|
||||||
if (!Directory.Exists(driverFolder))
|
|
||||||
Directory.CreateDirectory(driverFolder);
|
|
||||||
|
|
||||||
zz.ZoneImage.Save(driverFolder + "FullImage.png");
|
|
||||||
|
|
||||||
g.DrawRectangle(Pens.Red, z.Bounds);
|
g.DrawRectangle(Pens.Red, z.Bounds);
|
||||||
foreach (Window w in zz.Windows)
|
foreach (Window w in zz.Windows)
|
||||||
{
|
{
|
||||||
|
|||||||
+101
-45
@@ -1,4 +1,10 @@
|
|||||||
using System;
|
/// Author : Maxime Rohmer
|
||||||
|
/// Date : 25/04/2023
|
||||||
|
/// File : Window.cs
|
||||||
|
/// Brief : Parent for futur windows and framework for the childrens
|
||||||
|
/// Version : 0.1
|
||||||
|
|
||||||
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
@@ -16,17 +22,18 @@ namespace OCR_Decode
|
|||||||
private Rectangle _bounds;
|
private Rectangle _bounds;
|
||||||
private Bitmap _image;
|
private Bitmap _image;
|
||||||
private string _name;
|
private string _name;
|
||||||
protected static TesseractEngine Engine;
|
protected TesseractEngine Engine;
|
||||||
public Rectangle Bounds { get => _bounds; private set => _bounds = value; }
|
public Rectangle Bounds { get => _bounds; private set => _bounds = value; }
|
||||||
public Bitmap Image { get => _image; set => _image = value; }
|
public Bitmap Image { get => _image; set => _image = value; }
|
||||||
public string Name { get => _name; protected set => _name = value; }
|
public string Name { get => _name; protected set => _name = value; }
|
||||||
|
//This will have to be changed if you want to make it run on your machine
|
||||||
public static DirectoryInfo TESS_DATA_FOLDER = new DirectoryInfo(@"C:\Users\Moi\Pictures\SeleniumScreens\TessData");
|
public static DirectoryInfo TESS_DATA_FOLDER = new DirectoryInfo(@"C:\Users\Moi\Pictures\SeleniumScreens\TessData");
|
||||||
|
|
||||||
public Bitmap WindowImage
|
public Bitmap WindowImage
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
|
//This little trickery lets you have the image that the window sees
|
||||||
Bitmap sample = new Bitmap(Bounds.Width, Bounds.Height);
|
Bitmap sample = new Bitmap(Bounds.Width, Bounds.Height);
|
||||||
Graphics g = Graphics.FromImage(sample);
|
Graphics g = Graphics.FromImage(sample);
|
||||||
g.DrawImage(Image, new Rectangle(0, 0, sample.Width, sample.Height), Bounds, GraphicsUnit.Pixel);
|
g.DrawImage(Image, new Rectangle(0, 0, sample.Width, sample.Height), Bounds, GraphicsUnit.Pixel);
|
||||||
@@ -37,40 +44,64 @@ namespace OCR_Decode
|
|||||||
{
|
{
|
||||||
Image = image;
|
Image = image;
|
||||||
Bounds = bounds;
|
Bounds = bounds;
|
||||||
|
|
||||||
Engine = new TesseractEngine(TESS_DATA_FOLDER.FullName, "eng", EngineMode.Default);
|
Engine = new TesseractEngine(TESS_DATA_FOLDER.FullName, "eng", EngineMode.Default);
|
||||||
Engine.DefaultPageSegMode = PageSegMode.SingleLine;
|
Engine.DefaultPageSegMode = PageSegMode.SingleLine;
|
||||||
}
|
}
|
||||||
public virtual Object DecodePng()
|
/// <summary>
|
||||||
|
/// Method that will have to be used by the childrens to let the model make them decode the images they have
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>Returns an object because we dont know what kind of return it will be</returns>
|
||||||
|
public virtual async Task<Object> DecodePng()
|
||||||
{
|
{
|
||||||
return "NaN";
|
return "NaN";
|
||||||
}
|
}
|
||||||
public virtual Object DecodePng(List<string> drivers)
|
/// <summary>
|
||||||
|
/// Method that will have to be used by the childrens to let the model make them decode the images they have
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="driverList">This is a list of the different possible drivers in the race. It should not be too big but NEVER be too short</param>
|
||||||
|
/// <returns>Returns an object because we dont know what kind of return it will be</returns>
|
||||||
|
public virtual async Task<Object> DecodePng(List<string> driverList)
|
||||||
{
|
{
|
||||||
return "NaN";
|
return "NaN";
|
||||||
}
|
}
|
||||||
public static byte[] ImageToByte(Image img)
|
/// <summary>
|
||||||
|
/// This converts an image into a byte[]. It can be usefull when doing unsafe stuff. Use at your own risks
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="inputImage">The image you want to convert</param>
|
||||||
|
/// <returns>A byte array containing the image informations</returns>
|
||||||
|
public static byte[] ImageToByte(Image inputImage)
|
||||||
{
|
{
|
||||||
using (var stream = new MemoryStream())
|
using (var stream = new MemoryStream())
|
||||||
{
|
{
|
||||||
img.Save(stream, System.Drawing.Imaging.ImageFormat.Png);
|
inputImage.Save(stream, System.Drawing.Imaging.ImageFormat.Png);
|
||||||
return stream.ToArray();
|
return stream.ToArray();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
public static int GetTimeFromPng(Bitmap wImage,OcrImage.WindowType type)
|
/// <summary>
|
||||||
|
/// This method is used to recover a time from a PNG using Tesseract OCR
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="windowImage">The image where the text is</param>
|
||||||
|
/// <param name="windowType">The type of window it is</param>
|
||||||
|
/// <param name="Engine">The Tesseract Engine</param>
|
||||||
|
/// <returns>The time in milliseconds</returns>
|
||||||
|
public static async Task<int> GetTimeFromPng(Bitmap windowImage,OcrImage.WindowType windowType, TesseractEngine Engine)
|
||||||
{
|
{
|
||||||
|
//Kind of a big method but it has a lot of error handling and has to work with three special cases
|
||||||
string rawResult = "";
|
string rawResult = "";
|
||||||
int result = 0;
|
int result = 0;
|
||||||
|
|
||||||
switch (type)
|
switch (windowType)
|
||||||
{
|
{
|
||||||
case OcrImage.WindowType.Sector:
|
case OcrImage.WindowType.Sector:
|
||||||
|
//The usual sector is in this form : 33.456
|
||||||
Engine.SetVariable("tessedit_char_whitelist", "0123456789.");
|
Engine.SetVariable("tessedit_char_whitelist", "0123456789.");
|
||||||
break;
|
break;
|
||||||
case OcrImage.WindowType.LapTime:
|
case OcrImage.WindowType.LapTime:
|
||||||
|
//The usual Lap time is in this form : 1:45:345
|
||||||
Engine.SetVariable("tessedit_char_whitelist", "0123456789.:");
|
Engine.SetVariable("tessedit_char_whitelist", "0123456789.:");
|
||||||
break;
|
break;
|
||||||
case OcrImage.WindowType.Gap:
|
case OcrImage.WindowType.Gap:
|
||||||
|
//The usual Gap is in this form : + 34.567
|
||||||
Engine.SetVariable("tessedit_char_whitelist", "0123456789.+");
|
Engine.SetVariable("tessedit_char_whitelist", "0123456789.+");
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
@@ -79,7 +110,7 @@ namespace OCR_Decode
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
Bitmap enhancedImage = new OcrImage(wImage).Enhance(type);
|
Bitmap enhancedImage = new OcrImage(windowImage).Enhance(windowType);
|
||||||
|
|
||||||
var tessImage = Pix.LoadFromMemory(ImageToByte(enhancedImage));
|
var tessImage = Pix.LoadFromMemory(ImageToByte(enhancedImage));
|
||||||
|
|
||||||
@@ -104,12 +135,10 @@ namespace OCR_Decode
|
|||||||
} while (iter.Next(PageIteratorLevel.Word));
|
} while (iter.Next(PageIteratorLevel.Word));
|
||||||
}
|
}
|
||||||
|
|
||||||
enhancedImage.Save(Reader.DEBUG_DUMP_FOLDER + Regex.Replace(rawResult, "[^0-9A-Za-z]", "") + ".png");
|
|
||||||
|
|
||||||
List<string> rawNumbers;
|
List<string> rawNumbers;
|
||||||
|
|
||||||
//In the gaps we can find '+' but we dont care about it its redondant
|
//In the gaps we can find '+' but we dont care about it its redondant a driver will never be - something
|
||||||
if(type == OcrImage.WindowType.Gap)
|
if(windowType == OcrImage.WindowType.Gap)
|
||||||
rawResult = Regex.Replace(rawResult, "[^0-9.:]", "");
|
rawResult = Regex.Replace(rawResult, "[^0-9.:]", "");
|
||||||
|
|
||||||
//Splits into minuts seconds miliseconds
|
//Splits into minuts seconds miliseconds
|
||||||
@@ -128,6 +157,17 @@ namespace OCR_Decode
|
|||||||
{
|
{
|
||||||
//ss:ms
|
//ss:ms
|
||||||
result = (Convert.ToInt32(rawNumbers[0]) * 1000) + Convert.ToInt32(rawNumbers[1]);
|
result = (Convert.ToInt32(rawNumbers[0]) * 1000) + Convert.ToInt32(rawNumbers[1]);
|
||||||
|
|
||||||
|
if (result > 999999)
|
||||||
|
{
|
||||||
|
//We know that we have way too much seconds to make a minut
|
||||||
|
//Its usually because the ":" have been interpreted as a number
|
||||||
|
int minuts = (int)(rawNumbers[0][0] - '0');
|
||||||
|
// rawNumbers[0][1] should contain the : that has been mistaken
|
||||||
|
int seconds = Convert.ToInt32(rawNumbers[0][2].ToString() + rawNumbers[0][3].ToString());
|
||||||
|
int ms = Convert.ToInt32(rawNumbers[1]);
|
||||||
|
result = (Convert.ToInt32(minuts) * 1000 * 60) + (Convert.ToInt32(seconds) * 1000) + Convert.ToInt32(ms);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -153,13 +193,21 @@ namespace OCR_Decode
|
|||||||
page.Dispose();
|
page.Dispose();
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
public static string GetStringFromPng(Bitmap image,string allowedChars = "",OcrImage.WindowType windowType = OcrImage.WindowType.Text)
|
/// <summary>
|
||||||
|
/// Method that recovers strings from an image using Tesseract OCR
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="WindowImage">The image of the window that contains text</param>
|
||||||
|
/// <param name="Engine">The Tesseract engine</param>
|
||||||
|
/// <param name="allowedChars">The list of allowed chars</param>
|
||||||
|
/// <param name="windowType">The type of window the text is on. Depending on the context the OCR will behave differently</param>
|
||||||
|
/// <returns>the string it found</returns>
|
||||||
|
public static async Task<string> GetStringFromPng(Bitmap WindowImage, TesseractEngine Engine, string allowedChars = "",OcrImage.WindowType windowType = OcrImage.WindowType.Text)
|
||||||
{
|
{
|
||||||
string result = "";
|
string result = "";
|
||||||
|
|
||||||
Engine.SetVariable("tessedit_char_whitelist", allowedChars);
|
Engine.SetVariable("tessedit_char_whitelist", allowedChars);
|
||||||
|
|
||||||
Bitmap rawData = image;
|
Bitmap rawData = WindowImage;
|
||||||
Bitmap enhancedImage = new OcrImage(rawData).Enhance(windowType);
|
Bitmap enhancedImage = new OcrImage(rawData).Enhance(windowType);
|
||||||
|
|
||||||
Page page = Engine.Process(enhancedImage);
|
Page page = Engine.Process(enhancedImage);
|
||||||
@@ -171,34 +219,36 @@ namespace OCR_Decode
|
|||||||
result += iter.GetText(PageIteratorLevel.Word);
|
result += iter.GetText(PageIteratorLevel.Word);
|
||||||
} while (iter.Next(PageIteratorLevel.Word));
|
} while (iter.Next(PageIteratorLevel.Word));
|
||||||
}
|
}
|
||||||
if (allowedChars.Contains("S"))
|
|
||||||
{
|
|
||||||
enhancedImage.Save(Reader.DEBUG_DUMP_FOLDER + "Tyre" + result +".png");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
enhancedImage.Save(Reader.DEBUG_DUMP_FOLDER + result +".png");
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
page.Dispose();
|
page.Dispose();
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
protected Bitmap GetSmallBitmapFromBigOne(Bitmap bmp, Rectangle rectangle)
|
/// <summary>
|
||||||
|
/// Get a smaller image from a bigger one
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="inputBitmap">The big bitmap you want to get a part of</param>
|
||||||
|
/// <param name="newBitmapDimensions">The dimensions of the new bitmap</param>
|
||||||
|
/// <returns>The little bitmap</returns>
|
||||||
|
protected Bitmap GetSmallBitmapFromBigOne(Bitmap inputBitmap, Rectangle newBitmapDimensions)
|
||||||
{
|
{
|
||||||
Bitmap sample = new Bitmap(rectangle.Width, rectangle.Height);
|
Bitmap sample = new Bitmap(newBitmapDimensions.Width, newBitmapDimensions.Height);
|
||||||
Graphics g = Graphics.FromImage(sample);
|
Graphics g = Graphics.FromImage(sample);
|
||||||
g.DrawImage(bmp, new Rectangle(0, 0, sample.Width, sample.Height), rectangle, GraphicsUnit.Pixel);
|
g.DrawImage(inputBitmap, new Rectangle(0, 0, sample.Width, sample.Height), newBitmapDimensions, GraphicsUnit.Pixel);
|
||||||
return sample;
|
return sample;
|
||||||
}
|
}
|
||||||
protected static string FindClosestMatch(List<string> array, string target)
|
/// <summary>
|
||||||
|
/// Returns the closest string from a list of options
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="options">an array of all the possibilities</param>
|
||||||
|
/// <param name="testString">the string you want to compare</param>
|
||||||
|
/// <returns>The closest option</returns>
|
||||||
|
protected static string FindClosestMatch(List<string> options, string testString)
|
||||||
{
|
{
|
||||||
var closestMatch = "";
|
var closestMatch = "";
|
||||||
var closestDistance = int.MaxValue;
|
var closestDistance = int.MaxValue;
|
||||||
|
|
||||||
foreach (var item in array)
|
foreach (var item in options)
|
||||||
{
|
{
|
||||||
var distance = LevenshteinDistance(item, target);
|
var distance = LevenshteinDistance(item, testString);
|
||||||
if (distance < closestDistance)
|
if (distance < closestDistance)
|
||||||
{
|
{
|
||||||
closestMatch = item;
|
closestMatch = item;
|
||||||
@@ -208,39 +258,45 @@ namespace OCR_Decode
|
|||||||
return closestMatch;
|
return closestMatch;
|
||||||
}
|
}
|
||||||
//This method has been generated with the help of ChatGPT
|
//This method has been generated with the help of ChatGPT
|
||||||
protected static int LevenshteinDistance(string s1, string s2)
|
/// <summary>
|
||||||
|
/// Method that computes a score of distance between two strings
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="string1">The first string (order irrelevant)</param>
|
||||||
|
/// <param name="string2">The second string (order irrelevant)</param>
|
||||||
|
/// <returns>The levenshtein distance</returns>
|
||||||
|
protected static int LevenshteinDistance(string string1, string string2)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(s1))
|
if (string.IsNullOrEmpty(string1))
|
||||||
{
|
{
|
||||||
return string.IsNullOrEmpty(s2) ? 0 : s2.Length;
|
return string.IsNullOrEmpty(string2) ? 0 : string2.Length;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(s2))
|
if (string.IsNullOrEmpty(string2))
|
||||||
{
|
{
|
||||||
return string.IsNullOrEmpty(s1) ? 0 : s1.Length;
|
return string.IsNullOrEmpty(string1) ? 0 : string1.Length;
|
||||||
}
|
}
|
||||||
|
|
||||||
var d = new int[s1.Length + 1, s2.Length + 1];
|
var d = new int[string1.Length + 1, string2.Length + 1];
|
||||||
for (var i = 0; i <= s1.Length; i++)
|
for (var i = 0; i <= string1.Length; i++)
|
||||||
{
|
{
|
||||||
d[i, 0] = i;
|
d[i, 0] = i;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (var j = 0; j <= s2.Length; j++)
|
for (var j = 0; j <= string2.Length; j++)
|
||||||
{
|
{
|
||||||
d[0, j] = j;
|
d[0, j] = j;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (var i = 1; i <= s1.Length; i++)
|
for (var i = 1; i <= string1.Length; i++)
|
||||||
{
|
{
|
||||||
for (var j = 1; j <= s2.Length; j++)
|
for (var j = 1; j <= string2.Length; j++)
|
||||||
{
|
{
|
||||||
var cost = (s1[i - 1] == s2[j - 1]) ? 0 : 1;
|
var cost = (string1[i - 1] == string2[j - 1]) ? 0 : 1;
|
||||||
d[i, j] = Math.Min(Math.Min(d[i - 1, j] + 1, d[i, j - 1] + 1), d[i - 1, j - 1] + cost);
|
d[i, j] = Math.Min(Math.Min(d[i - 1, j] + 1, d[i, j - 1] + 1), d[i - 1, j - 1] + cost);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return d[s1.Length, s2.Length];
|
return d[string1.Length, string2.Length];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+44
-12
@@ -1,4 +1,10 @@
|
|||||||
using System;
|
/// Author : Maxime Rohmer
|
||||||
|
/// Date : 25/04/2023
|
||||||
|
/// File : Zone.cs
|
||||||
|
/// Brief : Parent for futur zones and the that contains all the usefull methods for any zone
|
||||||
|
/// Version : 0.1
|
||||||
|
|
||||||
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
@@ -18,6 +24,7 @@ namespace OCR_Decode
|
|||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
|
//This little trickery lets you have the image that the zone sees
|
||||||
Bitmap sample = new Bitmap(Bounds.Width, Bounds.Height);
|
Bitmap sample = new Bitmap(Bounds.Width, Bounds.Height);
|
||||||
Graphics g = Graphics.FromImage(sample);
|
Graphics g = Graphics.FromImage(sample);
|
||||||
g.DrawImage(Image, new Rectangle(0, 0, sample.Width, sample.Height), Bounds, GraphicsUnit.Pixel);
|
g.DrawImage(Image, new Rectangle(0, 0, sample.Width, sample.Height), Bounds, GraphicsUnit.Pixel);
|
||||||
@@ -29,6 +36,7 @@ namespace OCR_Decode
|
|||||||
get { return _image; }
|
get { return _image; }
|
||||||
set
|
set
|
||||||
{
|
{
|
||||||
|
//It automatically sets the image for the contained windows and zones
|
||||||
_image = Image;
|
_image = Image;
|
||||||
foreach (Window w in Windows)
|
foreach (Window w in Windows)
|
||||||
{
|
{
|
||||||
@@ -54,28 +62,52 @@ namespace OCR_Decode
|
|||||||
_image = image;
|
_image = image;
|
||||||
Bounds = bounds;
|
Bounds = bounds;
|
||||||
}
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// Adds a zone to the list of zones
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="zone">The zone you want to add</param>
|
||||||
public virtual void AddZone(Zone zone)
|
public virtual void AddZone(Zone zone)
|
||||||
{
|
{
|
||||||
Zones.Add(zone);
|
Zones.Add(zone);
|
||||||
}
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// Add a window to the list of windows
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="window">the window you want to add</param>
|
||||||
public virtual void AddWindow(Window window)
|
public virtual void AddWindow(Window window)
|
||||||
{
|
{
|
||||||
Windows.Add(window);
|
Windows.Add(window);
|
||||||
}
|
}
|
||||||
public virtual List<Object> Decode(List<string> drivers)
|
/// <summary>
|
||||||
|
/// Calls all the windows to do OCR and to give back the results so we can send them to the model
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="driverList">A list of all the driver in the race to help with text recognition</param>
|
||||||
|
/// <returns>A driver data object that contains all the infos about a driver</returns>
|
||||||
|
public virtual async Task<DriverData> Decode(List<string> driverList)
|
||||||
{
|
{
|
||||||
List<Object> result = new List<Object>();
|
DriverData result = new DriverData();
|
||||||
foreach (Window w in Windows)
|
Parallel.ForEach(Windows,async w =>
|
||||||
{
|
{
|
||||||
|
// A switch would be prettier but I dont think its supported in this C# version
|
||||||
if (w is DriverNameWindow)
|
if (w is DriverNameWindow)
|
||||||
{
|
result.Name = (string)await (w as DriverNameWindow).DecodePng(driverList);
|
||||||
result.Add((w as DriverNameWindow).DecodePng(drivers));
|
if (w is DriverDrsWindow)
|
||||||
}
|
result.DRS = (bool)await (w as DriverDrsWindow).DecodePng();
|
||||||
else
|
if (w is DriverGapToLeaderWindow)
|
||||||
{
|
result.GapToLeader = (int)await (w as DriverGapToLeaderWindow).DecodePng();
|
||||||
result.Add(w.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 DriverSector1Window)
|
||||||
|
result.Sector1 = (int)await (w as DriverSector1Window).DecodePng();
|
||||||
|
if (w is DriverSector2Window)
|
||||||
|
result.Sector2 = (int)await (w as DriverSector2Window).DecodePng();
|
||||||
|
if (w is DriverSector3Window)
|
||||||
|
result.Sector3 = (int)await (w as DriverSector3Window).DecodePng();
|
||||||
|
if (w is DriverTyresWindow)
|
||||||
|
result.CurrentTyre = (Tyre)await (w as DriverTyresWindow).DecodePng();
|
||||||
|
});
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,9 @@
|
|||||||
# OCR decode
|
# OCR decode
|
||||||
|
|
||||||
Project that takes config files in the JSON format and decodes screenshots from the F1TV data channel.
|
Project that takes config files in the JSON format and decodes screenshots from the F1TV data channel.
|
||||||
|
|
||||||
|
If you want to use it go change thoses variables :
|
||||||
|
|
||||||
|
Reader.cs "DEBUG_DUMP_FOLDER"
|
||||||
|
Window.cs "TESS_DATA_FOLDER"
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user