Распознавание алфавита глухонемых с помощью нейронной сети

Сама тема сурдоперевода мне близка, т.к. я сам на нем немного разговариваю. Поэтому темой диплома я выбрал — компьютерное зрение и алфавит глухонемых.

Первоначальная задумка была yolov5 + сверточная сеть. 

Некоторые буквы алфовита динамические, например «б», «д», «з» , «й» и т.д. поэтому на первом этапе пришлось откинуть некоторые буквы, кстати буквы «ё» вообще нет в алфавите. Для упрощения демонстрации были добавлены жесты «spoke» и средний палец. Средний палец уж обязательно кто-нибудь покажет в камеру ;) 

0e9788ff54990ce0f320f683a5682ffd.jpeg

В итоге получился такой алфавит — классы:

    # 00 - А 10 - Л 20 - Х

    # 01 - Б 11 - M 21 - ц

    # 02 - В 12 - Н 22 - ч

    # 03 - Г 13 - О 23 - Ш

    # 04 - Д 14 - П 24 - Ъ

    # 05 - Е 15 - Р 25 - Ы

    # 06 - Ж 16 - С 26 - Ь

    # 07 - З 17 - Т 27 - Э

    # 08 - И 18 - У 28 - Ю

    # 9 - К 19 - Ф 29 - Я

    #31 - spoke

    #32 - FU

Для этой цели был создан датасет из нескольких сотен фотографий. Roboflow позволил аугментировать датасет до нескольких тысяч (максимальный размер был >14 000). Однако, первые эксперименты yolo+conv2d показали низкую производительность… Поскольку тестировать камеру можно было только локально ;(

Буква

Буква «Ю»

Поэтому я начал искать пути ускорения. Одним из найденных вариантов стала mediapipe — это модель которая сканирует человека, в том числе и руки. При этом в части распознования кисти руки он выдает 21 координату в осях x, y, z. Таким образом, кисть человека в момент сканирования имеет 63 точки. Эврика — подумал я и принялся за работу. В итоге был собран собственный датасет (39230, 32) по осям x, у, а потом я решил, что чем больше данных, тем лучше и добавил ось z. Данное решение привело к переписыванию всего датасета.

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

Итак первый датасет был сделан float8 путем вывода в терминал координат, копирования в текстовый файл и дальнейшей обработки csv. Честно говоря, мне это показалось наиболее быстрым и эффективным способом, нежели накопление  ~1000 снимков руки в памяти и запись в файл. Позже я решил не сокращать выдваемые координаты до float8 и исходые данные mediapipe приводил  в float32 (tf.convert_to_tensor (yTrain,  dtype=tf.float32). Нормализацию тоже решил не использовать т.к. вдываемые значения и так близки к нулю.

В итоге был собран датасет (43589, 63), 63 столбец — это название класса. По началу очень пригодилось то, что я записывал каждый класс в отдельный файл и производил конкатенацию после парсинга (pandas). Это позволяло отключать проблемный класс и заниматься только им для парсинга.

Была сделана простая сетка из трех полносвязных слоев:

  model = Sequential()

  # Добавляем слои

  model.add(Dense(60, input_dim = x_train.shape[1], activation='relu'))

  model.add(Dense(512, activation='relu')) #relu

  model.add(Dropout(0.2))

  model.add(Dense(classes, activation='softmax'))

Примечательно, что chatGPT предложил схожую модель:

model = Sequential()

 model.add(Dense(128, activation='relu', input_dim = x_train.shape[1]))

 model.add(Dropout(0.2))

 model.add(Dense(64, activation='relu'))

 model.add(Dropout(0.2))

 model.add(Dense(32, activation='softmax'))

Итак, у меня был датасет и две архитектуры сети. Естественно, был произведен запуск обучения. Первые тесты показали достаточно высокую степень обучаемости — val_accuracy: 0.9986. Процесс обучения шел достаточно быстро и можно было экспериментировать с высокой скоростью получения результатов.

Однако,  colab не дает работать с камерой, поэтому было решение записать видео и на colab разобрать его на кадры и подавать на распознавание по кадрам, а потом собрать обратно. Первой фразой была выбрана, естественно «привет Мир».

Получившийся резултат:  

https://youtu.be/y73Y696PSyE

Ручное тестирование 

#Выбираем номер пример

n = np.random.randint(xTest.shape[0])

#Получаем выход сети на этом примере

prediction = model.predict(xTest)

буква Е

буква Е

#Выводим на экран результаты

print("Вход сети:",xTest[n])

print("Выход сети: ", prediction[n])

print("Распознанная буква: ", np.argmax(prediction[n]))

print("Уверенность: ", max(prediction[n]))

#print("Верный ответ: ", np.argmax(yTest[n]))

print("Верный ответ: ", yTest[n])

print("Буква ответа: ", alphabet[np.argmax(prediction[n])])

Выдача:  

92/92 [==============================] - 0s 1ms/step

Вход сети: tf.Tensor(

[ 2.5973994e-01  8.9324623e-01  1.1528828e-07  3.5665974e-01

  7.9688764e-01  2.2596706e-02  4.1823992e-01  6.9790035e-01

  1.9687276e-02  4.6605426e-01  6.4053899e-01  6.4189350e-03

  5.0649643e-01  6.0523713e-01 -6.8526058e-03  3.2173285e-01

  4.7568220e-01  1.6309600e-02  4.1397539e-01  4.3379956e-01

 -8.6668450e-03  4.7078431e-01  4.7556013e-01 -3.0949520e-02

  5.0567663e-01  5.2223492e-01 -4.5587722e-02  3.0026716e-01

  4.8494902e-01 -1.4753007e-02  4.1404176e-01  4.3192881e-01

 -4.2532265e-02  4.7451130e-01  4.8612747e-01 -6.2371090e-02

  5.0687182e-01  5.4977232e-01 -7.2590657e-02  2.9144514e-01

  5.2899569e-01 -4.5826677e-02  3.9391020e-01  4.8035461e-01

 -7.2446138e-02  4.5888489e-01  5.2523857e-01 -8.0848157e-02

  4.9609613e-01  5.7446223e-01 -8.1688248e-02  2.9623568e-01

  6.0039777e-01 -7.5467579e-02  3.7127769e-01  5.3574538e-01

 -9.5378488e-02  4.2346746e-01  5.4334986e-01 -9.6289836e-02

  4.6146590e-01  5.6746942e-01 -9.3081534e-02], shape=(63,), dtype=float32)

Выход сети:  [3.0630357e-12 5.2009356e-05 1.5157085e-23 1.1649300e-16 1.1814524e-09

 9.9961591e-01 2.5355426e-04 4.7962585e-11 2.5858966e-19 1.8022822e-18

 1.3008083e-16 6.6251182e-08 1.5936638e-10 8.1857099e-18 2.0038848e-13

 3.5445815e-15 3.4814121e-07 6.9532899e-13 3.3457822e-14 1.4862863e-09

 2.0082558e-08 1.3701579e-14 3.8771297e-13 4.9786916e-14 2.8096974e-07

 2.5667532e-05 5.4011314e-15 4.9024944e-05 6.2479200e-10 3.0690189e-06

 3.1294741e-18 7.7316303e-15]

Распознанная буква:  5

Уверенность:  0.9996159

Верный ответ:  tf.Tensor(5.0, shape=(), dtype=float32)

Буква ответа:  Е

Работа с видео показала, что нейрона в любом имеет уверенность в предсказании,  softmax распределяет уверенность по всем классам и класс с большей уверенностью  и есть предсказание, что бы избежать «слабой» уверенности, когда предсказание распределяется между классами примерно равнозначно, был поставлен порог в 80%, буква рисуется при уверенности >80%. Видео показало еще и недостатки — буквы Т и М, В и жест Spoke плохо различаются, думаю увеличение dataseta в 2–3 раза должно решить эту проблему.

В итоге разработанную нейронку у меня приняли в качестве дипломной работы и теперь я Junior python программист и ML-разработчик;)

© Habrahabr.ru