ML.NET часть 3 — генерация датасета для распознавания рукописных букв

В прошлой статье я показал, как можно с помощью ML.NET распознавать рукописные числа. При наличии подготовленного датасета сам процесс проблем не возникает. Но его отсутствие может стать проблемой. В данной статье я покажу как можно подготовить датасет для распознавания букв.

Конечно, для получения хорошего датасета, нужно было бы посадить реальных людей, которые бы рисовали буквы, на основе изображений которых получался бы датасет. Такой возможности у меня, к сожалению, нет. Да и тратить время на подобное не очень целесообразно. А вариант с поиском готового доступного датасета, который можно было бы использовать сейчас рассматривать не будем.

Чтобы подготовить требуемый датасет, будем генерировать изображения, выполнив следующие условия:

  • Буквы изображений будем записывать различными шрифтами. Желательно выбирать шрифты похожие на ваш почерк.
  • Буквы будем записывать разными размерами. Главное, чтобы буквы были различимыми и не выходили за размеры изображения.
  • Будем наклонять буквы на некоторый угол, тем самым эмулируя наклон почерка.

Датасет будет соответствовать тому, что использовался в проекте из предыдущей статьи. Напомню, что для его генерации нужно получить изображения размером 32x32 пикселей, а затем вычислить 64 числа, которые будут составлять строку датасета вместе со значением, которое нужно предсказать.

Код для генерации изображений будет следующий:

var fontsList = new List() { "Arial", "Arial Narrow", "Calibri", "Times New Roman", "Calibri Light" };
var fontStyles = new List() { FontStyle.Bold };
var pathDataset = @"../../../../MulticlassClassificationML.ConsoleApp/dataset.csv";
var drawString = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
var backgroundColor = "ffffffff";
var saveFiles = false;
var filesCatalogName = "images";

var tw = new StreamWriter(pathDataset);
tw.WriteLine("PixelValues,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,Number");

if (saveFiles)
{
    var dn = new DirectoryInfo(filesCatalogName);
    if (!dn.Exists)
    {
        dn.Create();
    }
}

foreach (var c in drawString)
{
    foreach (var fontName in fontsList)
    {
        for (int size = 10; size < 26; size++)
        {
            foreach (var fontStyle in fontStyles)
            {
                for (int angle = -13; angle < 13; angle++)
                {
                    var res = new Bitmap(32, 32);

                    // Create font and brush.
                    Font drawFont = new Font(fontName, size, fontStyle);
                    var drawBrush = new SolidBrush(Color.Black);

                    // Create point for upper-left corner of drawing.
                    float x = 1;
                    float y = 1;

                    // Set format of string.
                    StringFormat drawFormat = new StringFormat();

                    using (var g = Graphics.FromImage(res))
                    {
                        g.Clear(Color.White);
                        g.RotateTransform(angle); // set up rotate
                        g.DrawString(c.ToString(), drawFont, drawBrush, x, y, drawFormat);

                    }
                    if (saveFiles)
                    {
                        var path = $"{filesCatalogName}\\{c}_{Guid.NewGuid()}.png";
                        res.Save(path);
                    }

                    var handwritingRecognition = new HandwritingRecognition();

                    var datasetValue = handwritingRecognition.GetDatasetValues(res, backgroundColor);

                    tw.WriteLine($"{string.Join(",", datasetValue)},{c - 'A'}");

                }

            }
        }
    }
}
tw.Close();

Всё максимально просто, для каждой буквы мы получаем вариации с различными шрифтами, размером букв и их наклоном. Если включить вывод изображений saveFiles = true, то можно будет просмотреть сгенерированные изображения. Выглядеть они будут следующим образом (показаны некоторые изображения для буквы 'A'):

Некоторые изображения сгенерированные для буквы 'A'

С текущими настройками сгенерирует 54080 изображений. Это 2080 изображений на одну букву.

Выполним обучение сети по полученному датасету и проверим что получилось:

Пример работы распознавания рукописных букв

Как можно увидеть, распознавание работает. Конечно, очень желательно писать буквы максимально похожими на те, что были сгенерированы, тогда распознавание будет работать максимально точно.

Генерация датасета не всегда может дать данные максимального качества, но зато их получение занимает минимальное время. Получив данный датасет можно начать работать с вашей программой и уже в процессе повышая качество имеющихся данных. Подобный подход можно использовать не только для чисел или букв, но и для любых объектов, получить которые можно програмно, внося небольшие изменения в генерируемые изображения.

Исходный код проекта: https://github.com/flash2048/Handwriting_Letter_Recognition_ML_NET

Приятного программирования.

Добавить комментарий