Сама тема сурдоперевода мне близка, т.к. я сам на нем немного разговариваю. Поэтому темой диплома я выбрал – компьютерное зрение и алфавит глухонемых.
Первоначальная задумка была yolov5 + сверточная сеть.
Некоторые буквы алфовита динамические, например «б», «д», «з» , «й» и т.д. поэтому на первом этапе пришлось откинуть некоторые буквы, кстати буквы «ё» вообще нет в алфавите. Для упрощения демонстрации были добавлены жесты «spoke» и средний палец. Средний палец уж обязательно кто-нибудь покажет в камеру ;)
В итоге получился такой алфавит - классы:
# 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, 63) по осям x,у, а потом я решил, что чем больше данных, тем лучше и добавил ось z. Данное решение привело к переписыванию всего датасета.
Достаточно быстро я сделал код который сканировал руку и собирал все координаты. Эксперимент с округлением до целых дал плохие результаты, т.к. выдаваемые координаты были близки к нулю и в результате получалось слишком много нулей. Поэтому я решил округлять до float8, что по моим подсчетам создавало некий ореол вокруг точек и должно было увеличить распознование.
Итак первый датасет был сделан float8 путем вывода в терминал координат, копирования в текстовый файл и дальнейшей обработки csv. Честно говоря, мне это показалось наиболее быстрым и эффективным способом, нежели накопление ~1000 снимков руки в памяти и запись в файл. Позже я решил не сокращать выдваемые координаты до float8 и исходые данные mediapipe приводил в float32 (tf.convert_to_tensor(yTrain, dtype=tf.float32). Нормализацию тоже решил не использовать т.к. вдываемые значения и так близки к нулю.
В итоге был собран датасет (43589, 63), 64 столбец – это название класса. По началу очень пригодилось то, что я записывал каждый класс в отдельный файл и производил конкатенацию после парсинга (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 разобрать его на кадры и подавать на распознавание по кадрам, а потом собрать обратно. Первой фразой была выбрана, естественно «привет Мир».
Получившийся резултат:
Ручное тестирование
#Выбираем номер пример
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-разработчик ;)