Добрый день, добрые друзья! Я решил научить нейронную сеть различать рукописные русские буквы, как говорится - на коленке.

Первым делом, стоит найти или сделать(сделать - громко сказано, с такими техническими возможностями). Я не нашел дата сет с русскими буквами, но я решил его сделать.

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

Но где? Я решил это делать на телефоне в приложении "рисовалке" - PaperDraw(не реклама). Другие приложения показались мне слишком сложными.

Так я делаю изображения:

Так я сделал по 9 картинок для каждой буквы.

Вот так выглядит папка с фото буквы - а.
Вот так выглядит папка с фото буквы - а.

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

# rotate_image функция что-бы брать изображение и переворацивть его + закрашивать область определённым цветом
from scipy.ndimage import rotate as rotate_image

# Что-бы открывать и сохранять изображения.
import cv2

# открываем изображение 
image = cv2.imread('a.jpg', cv2.IMREAD_GRAYSCALE)

# переменная итерации для цикла
i = 0

# переменная итерации градуса наклона изображения
g = 0 

# и сам цикл, будет 
while(i < 19):
    
    # берём изображени image, поворациваем изображение на -45 + g(из изначального наклона в левую сторуну плавно переходим в правую сторону), cval - это цвет от 0 до 255
    rotated_img1 = rotate_image(image,-45 + g, cval=243)

    #и сохраняем изображение
    cv2.imwrite('page/'+str(i)+'.jpg',rotated_img1)

    i = i + 1
    g = g + 5

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

import os
import cv2
files = os.listdir('writeimg')


def isTrue(l):
    i = 0
    while (i < len(l)):
        if (l[i] != 243):
            
            return True
        i = i + 1
    return False

print(len(files))

i = 0 

while(i < 0):
    img = cv2.imread('pullImg/'+files[i], cv2.IMREAD_GRAYSCALE)
    height, width = img.shape
    w = 0 
    h = 0
    WR = 0 
    WL = 0
    HU = 0
    HD = 0
    while(w < width):
        lineW = img[0:height, w:w+1]
        if(isTrue(lineW)):
            WL=w
            break
        w = w + 1
    w = 0 
    
    while(w < width):
        lineW = img[0:height, width-w-1:width-w]
        if(isTrue(lineW)):
            WR = (width-w-1)
            break
        w = w + 1

    while(h < height):
        lineH = img[h:h+1, 0:width]
        if(isTrue(lineH[0])):
            HU = h
            break
        h = h + 1
    h = 0

    while(h < height):
        lineH = img[height-h-1:height-h, 0:width]
        if(isTrue(lineH[0])):
            HD = height-h-1
            break
        h = h + 1
    
    cv2.imwrite('writeimg/'+files[i], img[HU:HD, WL:WR])
    i = i + 1

Вот результат:

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

Вот код:

import cv2, os
p = os.listdir('./writeimg')
import cv2
import numpy as np
i = 0

while(i < len(p)):
    image = np.zeros((1100, 1100, 3), dtype=np.uint8)

    image.fill(243)

    image_height, image_width, _ = image.shape


    # Загрузка изображения, которое нужно вставить
    insert_image = cv2.imread('./writeimg/'+p[i])

    # print(insert_image)
    
    # Получение размеров изображения, которое нужно вставить
    insert_height, insert_width, _ = insert_image.shape

    # Определение координат для вставки изображения посередине
    start_x = int((image_width - insert_width) / 2)
    start_y = int((image_height - insert_height) / 2)
    end_x = start_x + insert_width
    end_y = start_y + insert_height

    # Вставка изображения посередине
    image[start_y:end_y, start_x:end_x] = insert_image

    cv2.imwrite(p[i], image)
    i = i + 1

Так же, мы вставили картинки в середину большого "холста".

Теперь, мы просто их делаем черно-белыми, код:

import cv2, os
l = './q/'
p = os.listdir(l)


p = os.listdir('./q/')
o = 0

while(o < len(p)):
    i = 0
    l = cv2.cvtColor(cv2.imread('./q/'+p[o]), cv2.COLOR_BGR2GRAY).reshape(1100*1100)

    while(i < len(l)):
        if(l[i] == 243):
            l[i] = 255
        else:
            l[i] = 0
        i = i + 1

    cv2.imwrite('./grey/'+p[o],l.reshape(1100,1100))
    o = o + 1

И самое простое, если не вдаваться в подробности - создание нейронной сети. Так-как мы просто можем взять пример из примера руководства keras из классификации цифр - мы это и сделаем.

# вот тут просто ипортируем билиотеки 
import tensorflow 
import os
import keras
import numpy as np
import cv2
import keras 
from keras.layers import Dense, Dropout , Conv2D , Flatten, MaxPooling2D
from tensorflow import keras
# 

# переменная с формой входных данных
input_shape = (275, 275, 1)

# это будет x_tain, именно входящие изображения.
data = []

# это метки классов
data1 = []
j = 0
# Дальше запускаем цикл добавления в массив изображений, которые мы получили. И к этому просто добавляею значение(число буквы по счету) в массив.
while(j <= 160):
    o = 1
    h = 0
    while(o<=33):
        data.append( 
            np.array(cv2.imread('./t/'+str(o)+'_'+str(j)+'.jpg', cv2.IMREAD_GRAYSCALE))
        )
        data1.append(h)
        o = o + 1
        h = h + 1
    j = j + 1

# колличество классов
num_classes = 33

# меняем форму массива, из обычного в формат np.
data = np.array(data)
data1 = np.array(data1)

# Важный нюанс, нам нужно изменить размерность массива. 
data = np.expand_dims(data, -1)

# Вот тут просто меняем вид ответа в нейронке, [ [3] ] -> [ [0,0,0,1] ]
y_test = keras.utils.to_categorical(data1, num_classes)

# нейронная сеть из примера классификации цифр.
model = tensorflow.keras.Sequential(
    [
        keras.Input(shape=input_shape),
        Conv2D(32, kernel_size=(3, 3), activation="relu"),
        MaxPooling2D(pool_size=(2, 2)),
        Conv2D(64, kernel_size=(3, 3), activation="relu"),
        MaxPooling2D(pool_size=(2, 2)),
        Flatten(),
        Dropout(0.5),
        Dense(num_classes, activation="softmax")
    ]
)

model.summary()

model.compile(loss="categorical_crossentropy", optimizer="adam", metrics=["accuracy"])

model.fit(data, y_test, batch_size=16, epochs=10,)

# проверяем будет ли правильный ответ.
m = model.predict(np.array([cv2.imread('./t/1_161.jpg',cv2.IMREAD_GRAYSCALE)]))

# да, ответ верный.
print(
    np.argmax(m) + 1
)

Конечный результат:

Т
Т

Точность 97%.

Надеюсь вам было интересно, все доброго.

Комментарии (2)


  1. berng
    10.09.2023 02:55
    +2

    Это у вас не точность, это у вас переобучение


  1. theurus
    10.09.2023 02:55

    рисовать?? а что если искать и выделять буквы с реальных сканов?