Создаем датасет печатных букв с любым шрифтом за 170 строк. python.. python. генерация изображений.. python. генерация изображений. датасет.. python. генерация изображений. датасет. искусственный интеллект.. python. генерация изображений. датасет. искусственный интеллект. Машинное обучение.. python. генерация изображений. датасет. искусственный интеллект. Машинное обучение. Обработка изображений.. python. генерация изображений. датасет. искусственный интеллект. Машинное обучение. Обработка изображений. Программирование.

Всем привет, это моя первая статья на Хабре. В этой статье я хочу рассказать, как сгенерировать датасет печатных букв с помощью .ttf файла и кода на Python в 170 строк.

Зачем?

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

Создание изображения

Начнем с библиотек, нам потребуется следующий инструментарий:

from PIL import Image, ImageDraw, ImageFont, ImageFilter

import os

from glob import glob

import numpy as np

PIL – он же Pillow, позволит нам создавать изображения и работать с ними.
OS – позволит работать с директориями.
glob – для поиска изображений в папках.
numpy – для работы с массивами.

Я работаю в PyCharm, поэтому не буду затрагивать тему установки библиотек. Об этом можно почитать на официальном сайте Python.

Что мы имеем на входе?

На входе нам даны следующие параметры:

FONT_PATH = "GOST2304A.ttf"  # Путь к файлу шрифта (поддерживающему кириллицу)

FONT_SIZE = 36  # Размер шрифта 

IMAGE_SIZE = (28, 28)  # Размер выходного изображения

BACKGROUND_COLOR = (255, 255, 255)  # Белый фон

TEXT_COLOR = (0, 0, 0)  # Черный цвет текста

NUM_IMAGES = 10  # Количество изображений на букву

# Список русских букв

RUSSIAN_LETTERS = [

    'А', 'Б', 'В', 'Г', 'Д', 'Е', 'Ё', 'Ж', 'З', 'И', 'Й',

    'К', 'Л', 'М', 'Н', 'О', 'П', 'Р', 'С', 'Т', 'У', 'Ф',

    'Х', 'Ц', 'Ч', 'Ш', 'Щ', 'Ъ', 'Ы', 'Ь', 'Э', 'Ю', 'Я'

]

Как видно из кода, мы можем регулировать следующие параметры:

  • шрифт

  • размер шрифта

  • размер картинки (в пикселях)

  • цвет фона (в RGB)

  • цвет текста (в RGB)

  • количество экземпляров каждой буквы в нашем будущем датасете.

  • Алфавит для генерации

3. Дальше напишем функцию для генерации картинок, на вход она будет принимать только букву и путь до шрифта:

def create_letter_image(letter, font_path):

    """Создает изображение с наклонной буквой"""

    # Загружаем шрифт

    try:

        base_font = ImageFont.truetype(font_path, FONT_SIZE)

    except OSError as e:

        print(f"Ошибка загрузки шрифта: {font_path}")

        print(e)

        return

    # Создаем временное изображение для измерения текста

    temp_image = Image.new('RGB', IMAGE_SIZE, BACKGROUND_COLOR)

    draw = ImageDraw.Draw(temp_image)

    text_width, text_height = draw.textbbox((0, 0), letter, font=base_font)[2:]

    # Создаем основное изображение с учетом наклона

    image = Image.new('RGB',

                      (int(IMAGE_SIZE[0]), IMAGE_SIZE[1]),

                      BACKGROUND_COLOR)

    draw = ImageDraw.Draw(image)

    # Рисуем текст с наклоном

    x = (image.width - text_width) / 2

    y = (image.height - text_height) / 2

    draw.text((x, y), letter, font=base_font, fill=TEXT_COLOR)

    return image

Сначала проверяем наличие шрифта в нашем проекте.

Создаем новое изображение с помощью Image.new. Задаем цветовой канал, размер изображения и цвет фона.

С помощью метода textbbox создадим поле для написания текста и зададим высоту и ширину поля.

Задаем координаты центра буквы и заполняем текстовое поле взяв букву из нашего алфавита.

Добавляем шумы

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

def add_noise(image):

    """

    Добавляет гауссов шум на изображение в оттенках серого

    (шум одинаков для всех каналов, сохраняя изображение серым)

    """

    # Конвертируем в numpy array и преобразуем в grayscale

    img_array = np.array(image.convert('L'))  # 'L' - режим оттенков серого

    height, width = img_array.shape

    noisy = img_array.copy()

    # Параметры гауссова шума

    mean = 0

    var = np.random.uniform(0.001, 0.02)  # Диапазон дисперсии

    sigma = var ** 0.5

    # Генерируем шум (один канал)

    gauss = np.random.normal(mean, sigma, (height, width))

    # Применяем шум и обрезаем значения

    noisy = np.clip(noisy + gauss * 255, 0, 255).astype(np.uint8)

    # Конвертируем обратно в RGB (но сохраняем оттенки серого)

    return Image.fromarray(noisy).convert('RGB')

Было использовано гауссовское распределение для отрисовки серых пикселей случайной яркости.

Итоговая функция

Итоговая функция будет выглядеть следующим образом:

def main():

    # Загружаем шрифт

    try:

        font = ImageFont.truetype(FONT_PATH, FONT_SIZE)

    except IOError:

        print(f"Ошибка: Шрифт по пути '{FONT_PATH}' не найден")

        return

    # Создаем корневую директорию

    for letter in RUSSIAN_LETTERS:

        # Создаем директорию для буквы

        letter_dir = f"Generated_images/Letter_{letter}"

        os.makedirs(letter_dir, exist_ok=True)

        # Генерируем эталонное изображение

        # Сохраняем NUM_IMAGES копий

        for i in range(NUM_IMAGES):

            img = create_letter_image(letter, FONT_PATH)

            img = add_noise(img)

            file_name = f"Letter_{letter}_{i:04d}.png"

            file_path = os.path.join(letter_dir, file_name)

            img.save(file_path)

        print(f"Сгенерировано {NUM_IMAGES} изображений для буквы '{letter}'")

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

Итог

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

Автор: PoStM0DeRn

Источник

Rambler's Top100