В эпоху самоизоляции хорошо заниматься спортом, но проблема в том, что не все с этим согласны, поэтому пришлось приложить некоторые усилия.
Однако работать супервайзером мне не очень хотелось, потому что работать фактически приходилось, а спортивный процесс, оставленный на волю случая, наблюдаемый в лучшем случае одним глазом, имел тенденцию скатываться к халяве.
Профессионально деформированный мозг переживал, что нужно как-то контролировать эти процессы, собирать метрики, и делать это, конечно, не вручную, а так, чтобы он все считал сам.
Было решено начать с приседаний.
Фундаментальные движения, с явными состояниями, большой амплитудой, в общем идеальный выбор.
В техническом плане у меня есть Raspberry Pi и камера, вполне достаточная для прототипа.
Сбор данных
Здесь все просто: включаем камеру через OpenCV и записываем снимки в файлы, соблюдая последовательность действий.
Датчик движения
Вариантов было несколько — например, с помощью сегментации выбрать человека на картинке и что-то с ним сделать.Но сегментация за разумное время на Raspberry — нереальная комбинация слов, к тому же нам пришлось бы сегментировать каждый кадр, упуская из виду тот важный факт, что у нас их целая последовательность.
Поэтому я остановился на выделении движущихся элементов в клипе.
OpenCV имеет Отличные функции удаления фона , из которого с помощью некоторых манипуляций можно получить сегментированный объект. Итак, давайте создадим вычитатель фона:
И начинаем кормить его кадрами:backSub = cv.createBackgroundSubtractorMOG2()
mask = backSub.apply(frame)
Вывод выглядит примерно так:
Затем добавьте белый цвет, чтобы сделать контур более четким.
mask = cv.dilate(mask, None, 3)
Идея состоит в том, чтобы вырезать из каждого кадра похожую маску и классифицировать ее как стойку, приседание или ничего.
Интересный вопрос, как вырезать из этой рамки всю фигуру.
Для начала найдем контуры: cnts, _ = cv.findContours(img, cv.RETR_CCOMP, cv.CHAIN_APPROX_SIMPLE)
С той мыслью, что наибольший контур будет с достаточной точностью соответствовать фигуре.
Увы, это не всегда так.
Однако можно начать с большого наброска, используя детали решаемой проблемы.
Особенность приседания в том, что оно делается в одном месте – а значит, мы можем рассчитывать на то, что фигура сохраняет свое положение и размер от кадра к кадру.
Затем мы можем итеративно построить ограничивающий прямоугольник, увеличивая его, если основной контур оказывается за его пределами.
Например, на этом рисунке самый большой контур — красный, ограничивающий прямоугольник этого контура — синий, ограничивающий прямоугольник всей фигуры — зеленый.
В результате с большой достоверностью можно получить прямоугольник, в котором происходит движение.
Теперь нам нужно понять, что мы получили.
Классификация поз
Вырезаем получившийся прямоугольник, помещаем его в квадрат, доводим до тех же размеров, классифицируем вручную и получаем вот такие маски: Для приседа:Для стойки:
Это уже может кормить нейронную сеть.
Для классификации мы будем использовать Keras + Tensorflow на черно-белых изображениях.
Размер картинок - интересный вопрос, я экспериментировал с двумя вариантами: 64x64 против 128x128. Есть три занятия – стойка, приседание и ничего.
Собираем простейшую сверточную сеть:
model = Sequential([
Convolution2D(8,(5,5), activation='relu', input_shape=input_shape),
MaxPooling2D(),
Flatten(),
Dense(512, activation='relu'),
Dense(3, activation='softmax')
])
model.compile(loss="categorical_crossentropy", optimizer=SGD(lr=0.01), metrics=["accuracy"])
Существует мнение, что минимумом для классификации является Ленет-подобный модель с двумя сверточными слоями, но на практике работает и этот односверточный.
А 128х128 на 8 фильтрах и 10 эпохах получаем 92,66%.
Выглядит многообещающе.
При увеличении времени обучения до 20 эпох точность возрастает до 99,34%.
64x64 на 10 эпохах дает точность только 86%.
В 20 лет мы достигаем 94, а в 30 — 96. Но модель в 4 раза меньше и работает в 4 раза быстрее.
По собранным данным Модель 64 дала аналогичные результаты, поэтому я остановился на ней.
Запуск на Raspberry Pi
OpenCV
Я большой поклонник модуля OpenCV-DNN и надеялся запустить с его помощью модель, не прибегая к использованию тяжелого Tensorflow. Однако после конвертации модели из Keras в TF и запуска теста я получил вот такое печальное сообщение: cv2.error: OpenCV(4.2.0) C:\projects\opencv-python\opencv\modules\dnn\src\dnn.cpp:562: error: (-2:Unspecified error) Can't create layer "flatten_1/Shape" of type "Shape" in function 'cv::dnn::dnn4_v20191202::LayerData::getLayerInstance'
На Переполнение стека Есть ветка полугодовой давности, где советуют:
- обновиться до последней версии
- творите чудеса, заменив Flatten на Reshape
Тензорный поток
Так что других альтернатив, кроме как использовать TF, не остается.Google уже довольно давно официально поддерживает Raspberry, так что на одну головную боль меньше.
TF содержит адаптеры для Keras, поэтому ничего конвертировать не нужно.
Загружаем модель:
with open(MODEL_JSON, 'r') as f:
model_data = f.read()
model = tf.keras.models.model_from_json(model_data)
model.load_weights(MODEL_H5)
graph = tf.get_default_graph()
И скармливаем ей картинки масок из файлов:
img = cv.imread(path + f, cv.IMREAD_GRAYSCALE)
img = np.reshape(img,[1,64,64,1])
with graph.as_default():
c = model.predict_classes(img)
return c[0] if c else None
Классификация (на Raspberry) занимает четверть секунды для изображений размером 128х128 и 60-70 миллисекунд для изображений 64х64, это практически реальное время.
Программа
Собираем из этих кусочков программа для малины .Сервис будет в Flask со следующим интерфейсом:
- ПОЛУЧАТЬ / — страница управления , о ней ниже
- ПОЛУЧИТЬ/статус — получить текущее состояние, количество приседаний, кадров
- ПОСТ/начало - начать упражнение
- ПОСТ/стоп - закончить упражнение
- ПОЛУЧИТЬ/поток — видеопоток с камеры
Это очень плохая идея, особенно на Raspberry — TF будет съедать память и ресурсы, а сервис будет не только медленным с ответами, но и легко выйдет из строя, когда у TF закончатся ресурсы Raspberry (а это обязательно произойдет).
Но для первой версии мне поленилось настраивать межпроцессное взаимодействие, поэтому пусть прототип будет таким.
Для конечного пользователя мы создаем простой веб-приложение (который снова распространяется той же службой колб), который может:
- показать видео с камеры
- начать/закончить упражнение
- показывает счетчик приседаний
По-хорошему, они должны удаляться автоматически, а пока их можно забрать для обучения нейросети.
Как только упражнение начинается, программа начинает обрабатывать картинки в поисках движущегося объекта, вырезает маску этого объекта, передает эту маску классификатору и, если ему удается соблюсти последовательность стойка-приседание-стойка, тогда приседание можно засчитывать.
Маркировочный инструмент это приложение python + opencv + GUI.
Картинка ищет контур, рамку для фигуры и, нажимая кнопки S (Стойка), Q (sQuat), N (Ничего), можно классифицировать картинку, и ее маска автоматически запишется в нужную.
каталог.
После этого каталог с новыми классифицированными масками необходимо перенести в данные для нейронные сети и переобучить ее.
Детектор я запускал на Raspberry, но ничто не мешает мне запустить его на любой машине с python, opencv и камерой — с Raspberry проще, чем таскать с собой ноутбук.
Проблемы
В нынешнем виде это можно признать MVP, но до стабильного решения все равно нужно много времени.
- Улучшите качество при удалении фона.
Тени и блики оставляют неприятные артефакты, которые затем поражают воображение классификатора.
- Соберите больше данных для классификатора
- Улучшите классификатор.
Существующий метод быстрый и простой, но достоверность его результатов сомнительна.
Древний Ленет-5 заметно совершеннее и за ним стоит логика, в которую стоит вникнуть.
Ссылки
- Оригинальная статья Янна Лекуна о сверточных сетях в классификации цифр.
- Статья о сетевых архитектурах
- Удаление фона в OpenCV
- Репо на GitHub
-
Ar-Go: Портативный Компьютер Своими Руками
19 Oct, 24 -
А Когда Ты Спишь, %Username%?
19 Oct, 24 -
О Выпуске Small Basic 0.2
19 Oct, 24 -
Межсайтовая Аутентификация (Sso)
19 Oct, 24