- PVSM.RU - https://www.pvsm.ru -
Периодически меня подмывает сделать что-то странное. Очевидно бесполезную вещь, которая не оправдывает себя по объему вложенных средств, и через полгода после создания пылиться на полке. Но зато полностью оправдывает себя по количеству эмоций, полученному опыту и новым рассказам. На Хабре даже есть две моих статьи про такие эксперименты: Алкоорган [1] и умная кормушка [2] для птиц.
Что ж. Пришло время рассказать о новом эксперименте. Как собрал, что из этого вышло и как повторить.
К новому проекту меня подтолкнуло событие, в каком-то смысле, банальное — родился сын. Я заранее устроил себе отпуск на месяц. Но ребёнок оказался тихим — было свободное время. И спящий рядом деть.
Дома много разных [3] embedded-железок для computer vision. В итоге решил сделать видео-няню. Но не такую унылую, которыми завалены все магазины. А что-то поумнее и поинтереснее.
Статья будет написана в повествовательном ключе, чтобы понять как шла разработка игрушки, куда она пришла и куда движется дальше.
У статьи есть несколько дополнений:
Самый банальный функционал видеоняни — посмотреть в любой момент что происходит с ребёнком. Но, к сожалению, это не всегда работает. Вы не будете смотреть трансляцию всё время, это не удобно. Младенца можно вообще положить спать рядом в коконе, зачем видео всё время? В итоге, для начала собралась следующая подборка:
У меня была большая статья на Хабре про сравнение различных платформ. Глобально, для прототипа типа того что я делаю есть несколько вариантов:
Итого — я выбрал Rpi+movidius. Есть на руках, умею работать с ним.
Вычислитель — Raspberry Pi 3B, нейропроцессор — Movidius Myriad X. С этим понятно.
Остальное — поскрёб по сусекам, докупил.
Я проверил три разных, которые у меня были:
А фильтр меняется вот так:
В целом, понятно, что это не продуктовое решение. Но работает.
Если что, то в коде увидите оставшиеся куски для перехода на другие два типа камер. Возможно даже что-то сходу заработает, если 1-2 параметра поменять.
С одной из старых задачек у меня завалялся осветитель.
Подпаял к нему какой-то блок питания. Светит неплохо.
Направляю на потолок — комната освещена.
Для некоторых режимов работы мне понадобился монитор. Остановился на таком [9]. Хотя не уверен что это самое правильное решение. Может стоило взять полноформатный. Но про это позже.
Ребёнок спит в произвольных местах. Так что проще когда система питается от павербанка. Выбрал такой, просто потому что лежит дома для походов:
Пройдём немного по OpenVino. Как я сказал выше — большим преимуществом OpenVino является большой объём предобученных сетей. Что нам может пригодиться.
Детекция лица. Таких сетей в OpenVino много:
Распознавание ключевых точек на лице [13]. Это нужно нам чтобы запускать следующие сети
Распознавание ориентации лица [14]. Активность ребёнка и куда смотрит.
Распознавание направление взгляда [15] — если пробовать взаимодействовать
Анализ глубины [16]? Может быть получится
Анализ скелета [17]
Ну и много других интересных…
Основным минусом этих сетей будет их основное преимущество — их предобученность…
Это можно поправить, но сейчас мы делаем быстрый прототип, наша цель не работа в 100% случаев, а принципиальная работа которая будет приносить хоть какую-то пользу.
Так как мы разрабатываем embedded устройство, то нам надо с ним как-то взаимодействовать. Получать фото/сигналы о тревоге. Так что решил сделать так же как когда делал кормушку [2], через телеграмм. Но довести до ума.
Для первой версии я решил:
Пошло всё более-менее неплохо, не считая кучи багов всего вокруг. Это свойственно для ComputerVision… Я привык к этому.
Вот краткая сводка того на что я натолкнулся:
Но, тут как… Уверен что если бы пошёл через TensorRT, то там бы проблем было как всегда больше.
Итак. Всё сбилжено, сети запущены, получаем что-то такое (запустив стек по голове, ориентации, ключевым точкам):
Видно, что лицо будет часто теряться, когда ребёнок закрывает его руками/поворачивает голову. да и не все показатели стабильны.
Что дальше? Как анализировать засыпание?
Смотрю на те сетки что есть, и первое что приходит в голову — распознавать эмоции. Когда ребёнок спит и тих — на лице нейтральное выражение. Но не всё так просто. Вот тут темно-синий график это нейтральное выражение спящего ребёнка на протяжении часа:
Остальные графики — грусть/злость/радость/удивление. Даже не особо суть того что где по цветам. К сожалению, данные сети — нестабильны, что мы и видим. Нестабильность возникает когда:
В целом, я не удивился. С сетями распознающими эмоции я сталкивался и ранее, и они всегда нестабильны, в том числе из-за нестабильности перехода между эмоциями — нет чёткой границы.
Ок, с помощью эмоций просыпание не распознать. Пока что мне не хотелось обучать что-то самому, так что решил попробовать на базе тех же сетей но с другой стороны. Одна из сетей дает угол поворота головы. Это уже лучше (суммарное отклонение от взгляда в камеру во времени в градусах). Последние 5-10 минут перед просыпанием:
Уже лучше. Но… Сын может начать махать головой во сне. Или наоборот, если поставить большой порог — проснуться и не махать головой после этого. Получать каждый раз уведомление… Уныло:
(здесь где-то час времени сна)
Значит надо всё же делать нормальное распознавание.
Просуммируем всё что мне не понравилось в первой версии.
В целом, схема автозапуска получилась следующей:
В OpenVino нет готовой сети для распознавания глаз.
Хахаха. Сеть уже появилась. Но её запушили, как оказалось, только после того как я начал разрабатывать. А в релизе и документации она появилась уже когда я более-менее всё сделал. Сейчас писал статью и нашёл апдейт [20].
Но, переделывать не буду, так что пишу как делал.
Можно очень просто обучить такую сеть. Выше я уже говорил, что использовал выделение глаз по кадру. Осталось всего ничего: добавить сохранение все встреченных на кадре глаз. Получается такой датасет:
Остаётся его разметить и обучить. Более подробно процесс разметки я описал тут [21] (и видео процесса на 10 минут тут [22]). Для разметки использовалась Толока. На настройку задания ушло~2 часа, на разметку — 5 минут + 300 рублей бюджета.
При обучении думать особо не хотелось, так что взял заведомо быструю сеть, которая имеет достаточное качество для разрешения задачи — mobilenetv2. Весь код, включая загрузку датасета, инициализацию и сохранение занял меньше 100 строк (большей частью взятых из открытых источников, переписал пару десятков строк):
import numpy as np
import torch
from torch import nn
from torch import optim
from torchvision import datasets, transforms, models
data_dir = 'F:/Senya/Dataset'
def load_split_train_test(datadir, valid_size = .1):
train_transforms = transforms.Compose([transforms.Resize(64),
transforms.RandomHorizontalFlip(),
transforms.ToTensor(),
])
test_transforms = transforms.Compose([transforms.Resize(64),
transforms.ToTensor(),
])
train_data = datasets.ImageFolder(datadir,
transform=train_transforms)
test_data = datasets.ImageFolder(datadir,
transform=test_transforms)
num_train = len(train_data)
indices = list(range(num_train))
split = int(np.floor(valid_size * num_train))
np.random.shuffle(indices)
from torch.utils.data.sampler import SubsetRandomSampler
train_idx, test_idx = indices[split:], indices[:split]
train_sampler = SubsetRandomSampler(train_idx)
test_sampler = SubsetRandomSampler(test_idx)
trainloader = torch.utils.data.DataLoader(train_data,
sampler=train_sampler, batch_size=64)
testloader = torch.utils.data.DataLoader(test_data,
sampler=test_sampler, batch_size=64)
return trainloader, testloader
trainloader, testloader = load_split_train_test(data_dir, .1)
print(trainloader.dataset.classes)
device = torch.device("cuda" if torch.cuda.is_available()
else "cpu")
model = models.mobilenet_v2(pretrained=True)
model.classifier = nn.Sequential(nn.Linear(1280, 3),
nn.LogSoftmax(dim=1))
print(model)
criterion = nn.NLLLoss()
optimizer = optim.Adam(model.parameters(), lr=0.003)
model.to(device)
epochs = 5
steps = 0
running_loss = 0
print_every = 10
train_losses, test_losses = [], []
for epoch in range(epochs):
for inputs, labels in trainloader:
steps += 1
inputs, labels = inputs.to(device), labels.to(device)
optimizer.zero_grad()
logps = model.forward(inputs)
loss = criterion(logps, labels)
loss.backward()
optimizer.step()
running_loss += loss.item()
if steps % print_every == 0:
test_loss = 0
accuracy = 0
model.eval()
with torch.no_grad():
for inputs, labels in testloader:
inputs, labels = inputs.to(device), labels.to(device)
logps = model.forward(inputs)
batch_loss = criterion(logps, labels)
test_loss += batch_loss.item()
ps = torch.exp(logps)
top_p, top_class = ps.topk(1, dim=1)
equals = top_class == labels.view(*top_class.shape)
accuracy += torch.mean(equals.type(torch.FloatTensor)).item()
train_losses.append(running_loss / len(trainloader))
test_losses.append(test_loss / len(testloader))
print(f"Epoch {epoch + 1}/{epochs}.. "
f"Train loss: {running_loss / print_every:.3f}.. "
f"Test loss: {test_loss / len(testloader):.3f}.. "
f"Test accuracy: {accuracy / len(testloader):.3f}")
running_loss = 0
model.train()
torch.save(model, 'EyeDetector.pth')
И ещё пара строк на сохранение модели в ONNX:
from torchvision import transforms
import torch
from PIL import Image
use_cuda=1
mobilenet = torch.load("EyeDetector.pth")
mobilenet.classifier = mobilenet.classifier[:-1]
mobilenet.cuda()
img = Image.open('E:/OpenProject/OpenVinoTest/face_detect/EyeDataset/krnwapzu_left.jpg')
mobilenet.eval()
transform = transforms.Compose([transforms.Resize(64),
transforms.ToTensor(),
])
img = transform(img)
img = torch.unsqueeze(img, 0)
if use_cuda:
img = img.cuda()
img = torch.autograd.Variable(img)
list_features = mobilenet(img)
ps = torch.exp(list_features.data.cpu())
top_p, top_class = ps.topk(1, dim=1)
list_features_numpy = []
for feature in list_features:
list_features_numpy.append(feature.data.cpu().numpy())
mobilenet.cpu()
x = torch.randn(1, 3, 64, 64, requires_grad=True)
torch_out = mobilenet(x)
torch.onnx.export(mobilenet, x,"mobilnet.onnx", export_params=True, opset_version=10, do_constant_folding=True,
input_names = ['input'],output_names = ['output'])
print(list_features_numpy)
Сохранение модели в ONNX нужно для дальнейшего вызова модели в Open Vino. Я не запаривался с преобразованиев в int8, оставил модель как была в 32-битном формате.
Анализ точности, метрики качества?.. Зачем это в любительском проекте. Такие штуки оцениваются по-другому. Никакая метрика не скажет вам “система работает”. Работает система или нет — вы поймёте только на практике. Даже 1% ошибок может сделать систему неприятной для использования. Я бывает обратное. Вроде ошибок 20%, но система сконфигурирована так, что они не видны.
Такие вещи проще смотреть на практике, “будет работать или нет”. И уже поняв критерий работы — вводить метрики, если они будут нужны.
Текущая реализация качественно другая, но всё же она имеет ряд проблем:
Я не стал переобучать детект лица. В отличие от распознавания глаз тут сильно больше работы. И со сбором датасета, и с качественным обучением.
Конечно, можно заоверфититься на лицо сына, наверное даже чуть лучше работать будет чем текущая сеть. Но по остальным людям нет. И, возможно, по сыну через 2 месяца — тоже нет.
Собирать нормальный датасет — долго.
Можно было бы пойти по классическому пути распознавания звука, и обучить нейронку. В целом это было бы не очень долго, максимум в несколько раз дольше чем распознавание глаз. Но мне не хотелось возиться со сбором датасета, так что воспользовался более простым способом. Можно использовать готовые инструменты WebRTC [23]. Получается всё элегантно и просто, в пару строк.
Минус, который я нашёл — на разных микрофонах качество работы решения разные. Где-то тригериться со скрипа, а где то только с громкого крика.
В какой-то момент я провёл тест, запустив зацикленное 5-секундное видео себя с супругой:
Был видно, что сын залипает на лица людей в поле зрения (монитор подвесил его минут на 30). И родилась идея: сделать управление выражением лица. Это не просто статичное видео, но и вариант взаимодействия. Получилось как-то так (при смене эмоции сына происходит переключение видеоряда):
“Папа, ты что, долбанулся?!”
Наверное, надо попробовать с большим монитором. Но пока не соберусь.
Может надо подменить проигрываемое видео. Благо это просто — видео проигрывается из отдельных картинок, где смена кадров подстраивается под FPS.
Может надо подождать (на текущем уровне ребёнок мог банально не понять связи своих эмоций и экрана)
Одним и перспективных направлений, мне кажется, попробовать сделать управление какими-то физическими обектами/лампочками/моторчиками через направление взгляда/позу.
Но пока глубоко не продумывал этот вопрос. Скорее пока что буду тестировать управление эмоциями.
Как сейчас всё работает (в начале статьи есть более масштабное видео):
В целом отзывы от себя любимого:
Как я и говорил выше — я попробовал выложить все исходники. Проект большой и разветвлённый, так что может что-то забыл или не дал подробных инструкий. Не стесняйтесь спрашивать и уточнять.
Есть несколько способов всё развернуть:
Основной репозиторий расположен тут — github.com/ZlodeiBaal/BabyFaceAnalizer [6]
Он состоит из двух файлов которые надо запускать:
Кроме того. в Git нет двух вещей:
Можно позволить система создать их автоматически при следующем запуске. Можно использвать следующие, заполнив пустые поля:
К сожалению, просто положить и запустить скрипты на RPi не получиться. Вот что ещё надо для стабильной работы:
Образ выложен тут [7](5 гигов).
Пара моментов:
Первый вариант — после запуска показать QR код с текстом:
WIFI:T:WPA;P:qwerty123456;S:TestNet;;
Где после P — идёт пароль сети, после S — индентификатор сети.
Второй вариант — зайти по SSH на RPi (подключившись по проводу). Либо включить монитор и клавиатуру. И положить файл
c вашими настройками wi-fi в папку "/home/pi/face_detect".
Первый вариант — после запуска показать QR код с текстом:
сгенерировав его через www.the-qrcode-generator.com [28]
Второй вариант — зайти по SSH на RPi (подключившись по проводу). Либо включить монитор и клавиатуру. И положить файл tg_creedential.txt описанный выше в папку "/home/pi/face_detect".
Уже когда собрал первую версию и показывал её своей маме, то получил внезапный ответ:
“-О, а мы в твоём детстве почти так же делали.”
“?!”
“Ну, коляску с тобой на балкон выставляли, через форточку туда выкидывали микрофон, который был включен в усилитель в квартире”.
Вообщем внезапно оказалось, что это наследственное.
“А как супруга отнеслась?”
“А как она позволила тебе эксперименты над сыном ставить?!”
Спрашивали не раз.
Но, я жену испортил хорошо. Вот, она [29] даже на Хабре иногда статьи пишет.
Я не специалист по ИБ. Конечно, я попробовал сделать так, чтобы никаких паролей нигде не светилось, и.т.д., а каждый смог сконфигурировать под себя, указав всю секьюрную информацию после старта.
Но не исключаю что что-то где-то не досмотрел. Если увидите явные ошибки — попробую исправить.
Скорее всего про апдейты по этому проекту буду рассказывать в своём телеграм-канале [30], либо в группе вконтакте [31]. Если накопиться много интересного, то сделаю ещё одну публикацию тут.
Автор: Мальцев Антон
Источник [32]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/raspberry-pi/356429
Ссылки в тексте:
[1] Алкоорган: https://habr.com/ru/post/142139/
[2] умная кормушка: https://habr.com/ru/post/322520/
[3] разных: https://habr.com/ru/company/recognitor/blog/468421/
[4] Видео: https://youtu.be/-FAEaXFcjNI
[5] статья: https://vc.ru/ml/153541-computer-vision-chto-mozhet-poyti-ne-tak
[6] Сорсы: https://github.com/ZlodeiBaal/BabyFaceAnalizer
[7] образ: https://yadi.sk/d/mkt8nhx1w03m3A
[8] СВДС: https://ru.wikipedia.org/wiki/%D0%A1%D0%B8%D0%BD%D0%B4%D1%80%D0%BE%D0%BC_%D0%B2%D0%BD%D0%B5%D0%B7%D0%B0%D0%BF%D0%BD%D0%BE%D0%B9_%D0%B4%D0%B5%D1%82%D1%81%D0%BA%D0%BE%D0%B9_%D1%81%D0%BC%D0%B5%D1%80%D1%82%D0%B8
[9] таком: https://www.waveshare.com/wiki/5inch_HDMI_LCD
[10] 1: https://docs.openvinotoolkit.org/2018_R5/_docs_Transportation_object_detection_face_pruned_mobilenet_reduced_ssd_shared_weights_caffe_desc_face_detection_adas_0001.html
[11] 2: https://docs.openvinotoolkit.org/2018_R5/_docs_Retail_object_detection_face_sqnet10modif_ssd_0004_caffe_desc_face_detection_retail_0004.html
[12] 3: https://docs.openvinotoolkit.org/2018_R5/_docs_Retail_object_detection_face_pedestrian_rmnet_ssssd_2heads_0002_caffe_desc_face_person_detection_retail_0002.html
[13] Распознавание ключевых точек на лице: https://docs.openvinotoolkit.org/2018_R5/_docs_Retail_object_attributes_landmarks_regression_0009_onnx_desc_landmarks_regression_retail_0009.html
[14] Распознавание ориентации лица: https://docs.openvinotoolkit.org/2018_R5/_docs_Transportation_object_attributes_headpose_vanilla_cnn_desc_head_pose_estimation_adas_0001.html
[15] Распознавание направление взгляда: https://docs.openvinotoolkit.org/latest/omz_demos_gaze_estimation_demo_README.html
[16] Анализ глубины: https://docs.openvinotoolkit.org/latest/omz_demos_python_demos_monodepth_demo_README.html
[17] Анализ скелета: https://docs.openvinotoolkit.org/2018_R5/_docs_Transportation_human_pose_estimation_mobilenet_v1_caffe_desc_human_pose_estimation_0001.html
[18] брать старые: https://software.intel.com/en-us/forums/intel-distribution-of-openvino-toolkit/topic/848966
[19] не смог сконвертировать сети в onnx: https://github.com/Daniil-Osokin/lightweight-human-pose-estimation-3d-demo.pytorch/issues/19#issuecomment-620313541
[20] апдейт: https://docs.openvinotoolkit.org/latest/omz_models_public_open_closed_eye_0001_description_open_closed_eye_0001.html
[21] тут: http://cv-blog.ru/?p=350
[22] тут: https://youtu.be/BvQY_PpI7zE
[23] WebRTC: https://github.com/wiseman/py-webrtcvad
[24] описание: https://github.com/ZlodeiBaal/RPi_WiFi_autoconnect
[25] docs.openvinotoolkit.org/latest/openvino_docs_install_guides_installing_openvino_raspbian.html: https://docs.openvinotoolkit.org/latest/openvino_docs_install_guides_installing_openvino_raspbian.html
[26] автозапуск: https://github.com/ZlodeiBaal/RPi_WiFi_autoconnect#work-on-the-startup
[27] www.raspberrypi.org/forums/viewtopic.php?t=260355: https://www.raspberrypi.org/forums/viewtopic.php?t=260355
[28] www.the-qrcode-generator.com: https://www.the-qrcode-generator.com/
[29] она: https://habr.com/ru/users/Ahveska/
[30] телеграм-канале: https://t.me/CVML_team
[31] вконтакте: https://vk.com/cvml_team
[32] Источник: https://habr.com/ru/post/516232/?utm_source=habrahabr&utm_medium=rss&utm_campaign=516232
Нажмите здесь для печати.