Русский
Русский
English
Статистика
Реклама

Openvino

Сим-сим откройся как я научил дверь своего подъезда узнавать меня в лицо

07.06.2021 18:08:54 | Автор: admin

Пятничный рабочий день на удалёнке уже подходил к концу, как в дверь постучали, чтобы сообщить об установке нового домофона. Узнав, что новый домофон имеет мобильное приложение, позволяющее отвечать на звонки не находясь дома, я заинтересовался и сразу же загрузил его на свой телефон. Залогинившись, я обнаружил интересную особенность этого приложения даже без активного вызова в мою квартиру я мог смотреть в камеру домофона и открывать дверь в произвольный момент времени. "Да это же онлайн АРI к двери подъезда!" щёлкнуло в голове. Судьба предстоящих выходных была предрешена.

Видеодемонстрация в конце статьи.

Кадр из фильма Пятый ЭлементКадр из фильма Пятый Элемент

Дисклеймер

Используемая технология аутентификации на основании распознавания лиц не учитывает множества сценариев атак и недостаточна для обеспечения безопасности. Описанная система анализа кадров была активна только в моменты, когда кому-то из моей семьи было необходимо попасть в подъезд она не совершала автоматическую обработку лиц других людей и не могла привести к допуску в подъезд третьих лиц.

Получение API

Чтобы автоматизировать управление дверью, необходимо понять, куда и в каком формате отправляет запросы само приложение. Реверс-инжиниринг дело неоднозначное, поэтому я попытался обойтись без него и вместо этого просто перехватить свой собственный трафик. Для этой задачи я взял НТТР Тооlkit комплекс программ, который позволяет наладить прослушивание http(s) запросов собственного Android устройства.

Первая попытка оказывается провальной после установки на телефон Android-части инструментария и сгенерированного Certificate authority оказалось, что мобильное приложение домофона не доверяет пользовательским СА. Согласно документации, начиная с Android 7 манифест приложения должен явно изъявлять такое желание.

Так как мой телефон не поддерживает root доступ для модификации списка системных СА, я воспользовался официальным эмулятором Android, идущим в комплекте с Android Studio. После запуска эмулятора и перехвата с помощью ADB меня встретило радостное сообщение о том, что трафик от всех приложений без Certificate pinning будет успешно расшифрован.

Успешное соединение с HTTP ToolkitУспешное соединение с HTTP Toolkit

К счастью, приложение оказалось как раз из таких немного побродив по приложению и открыв дверь, можно переходить к анализу логов.

Запрос открытия двериЗапрос открытия двери

Всего интересными показалось три запроса:

  1. Запрос на открытие двери подъезда: POST по адресу /rest/v1/places/{place_id}/accesscontrols/{control_id}/actions с JSON-телом {"name": "accessControlOpen"}

  2. Получение снимка (превью) с камеры: GET по адресу /rest/v1/places/{place_id}/accesscontrols/{control_id}/videosnapshots

  3. Получение ссылки на видеопоток с аудио: GET по адресу /rest/v1/forpost/cameras/{camera_id}/video?LightStream=0

HTTP заголовки всех трёх запросов содержат ключ Authorization судя по всему, именно по нему происходит авторизация при выполнении запросов. Сделав пару запросов руками через Advanced REST Client, чтобы убедиться, что заголовка Authorization достаточно и в самостоятельной работе с API не осталось подводных камней, я понял, что можно откладывать эмулятор и переходить к написанию кода.

Вооружившись Python и requests, я написал необходимые для следующих этапов функции исполнения каждого из трёх действий:

HEADERS = {"Authorization": "Bearer ###"}ACTION_URL = "https://###.ru/rest/v1/places/###/accesscontrols/###/"VIDEO_URL = "https://###.ru/rest/v1/forpost/cameras/###/video?LightStream=0"def get_image():    result = requests.get(f'{ACTION_URL}/videosnapshots', headers=HEADERS)    if result.status_code != 200:        logging.error(f"Failed to get an image with status code {result.status_code}")        return None    logging.warning(f"Image received successfully in {result.elapsed.total_seconds()}sec")    return result.contentdef open_door():    result = requests.post(        f'{ACTION_URL}/actions', headers=HEADERS, json={"name": "accessControlOpen"})    if result.status_code != 200:        logging.error(f"Failed to open the door with status code {result.status_code}")        return False    logging.warning(f"Door opened successfully in {result.elapsed.total_seconds()}sec")    return Truedef get_videostream_link():    result = requests.get(VIDEO_URL, headers=HEADERS)    if result.status_code != 200:        logging.error(f"Failed to get stream link with status code {result.status_code}")        return False    logging.warning(f"Stream link received successfully in {result.elapsed.total_seconds()}sec")    return result.json()['data']['URL']

Поиск знакомых лиц в кадре

Прежде чем начать этот раздел, нужно рассказать пару слов об уже имеющихся на тот момент у меня в распоряжении серверных мощностях это недорогая виртуальная машина с доступом ко всего одному потоку Intel(R) Xeon(R) CPU E5-2650L v3 @ 1.80GHz, 1GB оперативной памяти и 0 GPU. Тратиться на более дорогую конфигурацию не хотелось, а значит, нужно было попробовать выжать максимум из имеющихся ресурсов.

На данном этапе проектом заинтересовалась моя жена, хорошо разбирающаяся в машинном обучении и в итоге взявшая на себя эту задачу. Для этой работы был выбран OpenVINO Toolkit инструментарий от Intel, в том числе заточенный как раз на запуск моделей машинного обучения на CPU.

Непродолжительный поиск существующих решений привёл на страницу Interactive Face Recognition Demo официального демо, показывающего ровно необходимый функционал сравнения видимых в кадре лиц с базой заранее сохранённых. Единственная проблема состояла в том, что данный пример по каким-то причинам исчез после релиза 2020.3, а удобная установка пакета через pip у проекта появилась только с 2021.1. Было решено установить последнюю версию OpenVINO и адаптировать код под неё.

К счастью, гит помнит всё и получить из репозитория проекта код демо и нужные модели не составило труда. После удаления всей работы с командной строкой (взяв для всего значения по умолчанию), визуализацией и видеопотоком вебкамеры, был получен класс, способный распознавать лица в конкретном кадре:

class ImageProcessor:    def __init__(self):        self.frame_processor = FrameProcessor()    def process(self, image):        detections = self.frame_processor.process(image)        labels = []        for roi, landmarks, identity in zip(*detections):            label = self.frame_processor.face_identifier.get_identity_label(                identity.id)            labels.append(label)        return labels

После создания базы лиц из десятка селфи можно было переходить к тестированию фактической производительности полученного решения на имеющемся железе. В качестве подопытных картинок я взял фотографию себя и фото пустой улицы, сделанные на домофонную камеру функцией get_image() выше.

100 runs on an image with known face:Total time: 7.356sTime per frame: 0.007sFPS: 135.944100 runs on an image without faces:Total time: 2.985sTime per frame: 0.003sFPS: 334.962

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

1 FPS: Работа со снимками с камеры

Итак, на этом этапе у меня уже был класс для поиска заданных лиц в кадре, а также функции получения снимка с камеры и ссылки на видеопоток. С видео на тот момент я никогда не работал, потому для MVP решил переложить работу по выделению кадров на сервер и снова воспользоваться get_image().

class ImageProcessor:# <...>    def process_single_image(self, image):        nparr = np.fromstring(image, np.uint8)        img_np = cv2.imdecode(nparr, cv2.IMREAD_COLOR)        labels = self.process(img_np)        return labelsdef snapshot_based_intercom_id():    processor = ImageProcessor()    last_open_door_time = time.time()    while True:        start_time = time.time()        image = get_image()        result = processor.process_single_image(image)        logging.info(f'{result} in {time.time() - start_time}s')        # Successfull detections are "face{N}"        if any(['face' in res for res in result]):            if start_time - last_open_door_time > 5:                open_door()                with open(f'images/{start_time}_OK.jfif', 'wb') as f:                    f.write(image)                last_open_door_time = start_time

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

Момент успешного распознавания, версия с обработкой отдельных снимковМомент успешного распознавания, версия с обработкой отдельных снимков

Заработало! Полностью довольный первым запуском, я вернулся в квартиру. Единственное, что портило впечатление от системы распознавания время реакции на появление лица в кадре, т.к. время отклика API оставляло желать лучшего. Низкая частота поступления данных, 0.7с на получение картинки и 0.6с на открытие двери, давали видимый невооружённым взглядом лаг.

До 30 FPS: Обработка видеопотока

Получить кадры из видео оказалось на удивление просто:

vcap = cv2.VideoCapture(link)success, frame = vcap.read()

Замеры показали, что камера домофона способна выдавать стабильные 30 FPS. Проблемой оказалось поддержание меньшей частоты кадров: метод read() возвращает последний необработанный кадр из внутренней очереди кадров. Если эта очередь наполняется видеопотоком быстрее, чем вычитывается, то обрабатываемые кадры начинают отставать от реального времени, что приводит к нежелательной задержке. Дополнительно, с практической точки зрения, распознавать лица 30 раз в секунду пустая трата вычислительных ресурсов, так как обычно люди подходят к двери подъезда с небольшой скоростью.

Первым потенциальным решением было установить размер внутренней очереди: vcap.set(CV_CAP_PROP_BUFFERSIZE, 0);. Согласно найденной информации, такой трюк должен был хорошо работать с любой конфигурацией системы для версий OpenCV выше 3.4, но по какой-то причине, так и не оказал никакого влияния в моём случае. Единственной рабочей альтернативой стал подход, описанный в этом ответе со StackOverflow завести отдельный поток, читающий кадры из камеры на максимально возможной скорости и сохраняющий последний в поле класса для дальнейшего доступа (впоследствии оказалось, что именно этот цикл ответственен за большую часть потребления процессора).

Получилась модификация ImageProcessor для обработки видеопотока с частотой 3 кадра в секунду:

class CameraBufferCleanerThread(threading.Thread):    def __init__(self, camera, name='camera-buffer-cleaner-thread'):        self.camera = camera        self.last_frame = None        self.finished = False        super(CameraBufferCleanerThread, self).__init__(name=name)        self.start()    def run(self):        while not self.finished:            ret, self.last_frame = self.camera.read()    def __enter__(self): return self    def __exit__(self, type, value, traceback):        self.finished = True        self.join()class ImageProcessor:# <...>    def process_stream(self, link):        vcap = cv2.VideoCapture(link)        interval = 0.3 # ~3 FPS        with CameraBufferCleanerThread(vcap) as cam_cleaner:            while True:                frame = cam_cleaner.last_frame                if frame is not None:                    yield (self.process(frame), frame)                else:                    yield (None, None)                time.sleep(interval)

И соответствующая модификация snapshot_based_intercom_id:

def stream_based_intercom_id():    processor = ImageProcessor()    link = get_videostream_link()    # To notify about delays    last_time = time.time()    last_open_door_time = time.time()    for result, np_image in processor.process_stream(link):        current_time = time.time()        delta_time = current_time - last_time        if delta_time < 1:            logging.info(f'{result} in {delta_time}')        else:            logging.warning(f'{result} in {delta_time}')        last_time = current_time        if result is None:            continue        if any(['face' in res for res in result]):            if current_time - last_open_door_time > 5:                logging.warning(                  f'Hey, I know you - {result[0]}! Opening the door...')                last_open_door_time = current_time                open_door()                cv2.imwrite(f'images/{current_time}_OK.jpg', np_image)

Тест в реальных условиях показал ощутимое падение времени отклика при хорошем освещении подъездная дверь оказывалась открытой ещё до того, как я успевал подойти к ней вплотную.

Момент успешного распознавания, версия с обработкой видеопотокаМомент успешного распознавания, версия с обработкой видеопотока

Управление с помощью Telegram бота

Сама система доказала свою работоспособность и теперь хотелось создать для неё удобный интерфейс для включения/выключения. Телеграм бот показался отличным вариантом решения для этой задачи.

С помощью пакета python-telegram-bot была написана простая оболочка, принимающая в себя callback включения/выключения системы и список доверенных никнеймов.

class TelegramInterface:    def __init__(self, login_whitelist, state_callback):        self.state_callback = state_callback        self.login_whitelist = login_whitelist        self.updater = Updater(            token = "###", use_context = True)        self.run()    def run(self):        dispatcher = self.updater.dispatcher        dispatcher.add_handler(CommandHandler("start", self.start))        dispatcher.add_handler(CommandHandler("run", self.run_intercom))        dispatcher.add_handler(CommandHandler("stop", self.stop_intercom))        self.updater.start_polling()    def run_intercom(self, update: Update, context: CallbackContext):        user = update.message.from_user        update.message.reply_text(            self.state_callback(True) if user.username in self.login_whitelist else 'not allowed',            reply_to_message_id=update.message.message_id)    def stop_intercom(self, update: Update, context: CallbackContext):        user = update.message.from_user        update.message.reply_text(            self.state_callback(False) if user.username in self.login_whitelist else 'not allowed',            reply_to_message_id=update.message.message_id)    def start(self, update: Update, context: CallbackContext) -> None:        update.message.reply_text('Hi!')                class TelegramBotThreadWrapper(threading.Thread):    def __init__(self, state_callback, name='telegram-bot-wrapper'):        self.whitelist = ["###", "###"]        self.state_callback = state_callback        super(TelegramBotThreadWrapper, self).__init__(name=name)        self.start()    def run(self):        self.bot = TelegramInterface(self.whitelist, self.state_callback)

И следующая ступень эволюции функции intercom_id, обрабатывающая синхронизацию между потоками обработки данных и получения команд от бота:

def stream_based_intercom_id_with_telegram():    processor = ImageProcessor()    loop_state_lock = threading.Lock()    loop_should_run = False    loop_should_change_state_cv = threading.Condition(loop_state_lock)    is_loop_finished = True    loop_changed_state_cv = threading.Condition(loop_state_lock)    def stream_processing_loop():        nonlocal loop_should_run        nonlocal loop_should_change_state_cv        nonlocal is_loop_finished        nonlocal loop_changed_state_cv        while True:            with loop_should_change_state_cv:                loop_should_change_state_cv.wait_for(lambda: loop_should_run)                is_loop_finished = False                loop_changed_state_cv.notify_all()                logging.warning(f'Loop is started')            link = get_videostream_link()            last_time = time.time()            last_open_door_time = time.time()            for result, np_image in processor.process_stream(link):                with loop_should_change_state_cv:                    if not loop_should_run:                        is_loop_finished = True                        loop_changed_state_cv.notify_all()                        logging.warning(f'Loop is stopped')                        break                current_time = time.time()                delta_time = current_time - last_time                if delta_time < 1:                    logging.info(f'{result} in {delta_time}')                else:                    logging.warning(f'{result} in {delta_time}')                last_time = current_time                if result is None:                    continue                if any(['face' in res for res in result]):                    if current_time - last_open_door_time > 5:                        logging.warning(f'Hey, I know you - {result[0]}! Opening the door...')                        last_open_door_time = current_time                        open_door()                        cv2.imwrite(f'images/{current_time}_OK.jpg', np_image)    def state_callback(is_running):        nonlocal loop_should_run        nonlocal loop_should_change_state_cv        nonlocal is_loop_finished        nonlocal loop_changed_state_cv        with loop_should_change_state_cv:            if is_running == loop_should_run:                return "Intercom service state is not changed"            loop_should_run = is_running            if loop_should_run:                loop_should_change_state_cv.notify_all()                loop_changed_state_cv.wait_for(lambda: not is_loop_finished)                return "Intercom service is up"            else:                loop_changed_state_cv.wait_for(lambda: is_loop_finished)                return "Intercom service is down"    telegram_bot = TelegramBotThreadWrapper(state_callback)    logging.warning("Bot is ready")    stream_processing_loop()

Результат

Видео:

Послесловие

Несмотря на возможности, которые технология умных домофонов несёт жильцам, объединённые в единую сеть сотни (тысячи?) подъездных дверей с камерами и микрофонами (да, в произвольно получаемом видеопотоке есть и аудио!), ведущими непрерывную запись как по мне, скорее пугающее явление, открывающее новые возможности для нарушения приватности.

Я бы предпочёл, чтобы доступ к видеопотоку предоставлялся только в момент звонка в квартиру и ведущаяся трёхдневная запись, позиционирующаяся как средство раскрытия правонарушений, хранилась не на серверах компании, а непосредственно в домофоне, с возможностью доступа к ней по запросу. Или не велась вовсе.

Подробнее..

RPi-няня

28.08.2020 10:04:17 | Автор: admin
Периодически меня подмывает сделать что-то странное. Очевидно бесполезную вещь, которая не оправдывает себя по объему вложенных средств, и через полгода после создания пылиться на полке. Но зато полностью оправдывает себя по количеству эмоций, полученному опыту и новым рассказам. На Хабре даже есть две моих статьи про такие эксперименты: Алкоорган и умная кормушка для птиц.
Что ж. Пришло время рассказать о новом эксперименте. Как собрал, что из этого вышло и как повторить.

К новому проекту меня подтолкнуло событие, в каком-то смысле, банальное родился сын. Я заранее устроил себе отпуск на месяц. Но ребёнок оказался тихим было свободное время. И спящий рядом деть.
Дома много разных embedded-железок для computer vision. В итоге решил сделать видео-няню. Но не такую унылую, которыми завалены все магазины. А что-то поумнее и поинтереснее.


Статья будет написана в повествовательном ключе, чтобы понять как шла разработка игрушки, куда она пришла и куда движется дальше.
У статьи есть несколько дополнений:
1) Видео где я показываю и рассказываю как всё работает.
2) Небольшая статья на VC где я рассказываю почему такие штуки скорее всего не придут в нормальный продакшн, и про ограничения ML систем такого плана.
3) Сорсы всего на гитхабе + готовый образ для RPi. В конце статьи описание как пользоваться.

Выбор идеи


Самый банальный функционал видеоняни посмотреть в любой момент что происходит с ребёнком. Но, к сожалению, это не всегда работает. Вы не будете смотреть трансляцию всё время, это не удобно. Младенца можно вообще положить спать рядом в коконе, зачем видео всё время? В итоге, для начала собралась следующая подборка:
  1. Система должна давать возможность посмотреть видео или фото в любой момент с телефона
  2. Система должна реагировать на просыпание ребёнка, и извещать об этом
  3. Система должна детектировать пропадание лица, для предотвращения СВДС


Выбор платформы


У меня была большая статья на Хабре про сравнение различных платформ. Глобально, для прототипа типа того что я делаю есть несколько вариантов:
  1. Jetson Nano. Есть на руках + я много с ним работал (не только с Nano), но меня немного смущает, что он более производственный. Из коробки запустятся только самые простые модели. Чтобы ускорить и оптимизировать память надо переносить на TensorRT. Это требует времени. Но все обученные сети надо искать, тестировать, не факт что запуститься их коробки, не факт что из коробки TensorRT пойдёт.
  2. VIM3. Никогда не использовал, попробовать было бы интересно. Но ради проекта для которого есть уже пять разных вариантов исполнения не хотелось тратить денег.
  3. Raspberry PI + Movidius. Есть большая пачка предобученных сетей. Производительности хватит, удобно работает, более менее стабилен.
    1. Абсолютно непроизводственное решение, но для игрушки сойдёт.
    2. Нереально переобучить выложенные сети. Не все сети обученные с нуля будут работать.
  4. Raspberry PI 4 при работе через OpenCV будет хорошо утилизировать открытые сети, чего должно хватить. Но, было подозрение, что не хватит производительности.
  5. Coral у меня он есть на руках, по производительности прошло бы, но в другой моей статье написано почему я его не люблю:)

Итого я выбрал Rpi+movidius. Есть на руках, умею работать с ним.

Железо


Вычислитель Raspberry Pi 3B, нейропроцессор Movidius Myriad X. С этим понятно.
Остальное поскрёб по сусекам, докупил.



Камера


Я проверил три разных, которые у меня были:
  • Камера от RaspberryPI. Шумная, неудобный кабель, нет удобного крепления. Забил.
  • Какая-то IP камера. Очень удобно потому что не надо включать в RPI. Камера разнесена с вычислителем. Моя камера имела даже два режима, дневной и ночной. Но та что была у меня не давала достаточного качества лица.
  • Веб камера от Genius. Я её использовал уже лет 5. Но что-то в последнее время стала работать нестабильно.А для RPI в самый раз. Более того, оказалось что её можно тривиально разобрать и достать оттуда IR фильтр. Плюс, как потом выяснилось, что это был единственный вариант с микрофоном.


А фильтр меняется вот так:



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

Освещение


С одной из старых задачек у меня завалялся осветитель:
Подпаял к нему какой-то блок питания. Светит неплохо.

Направляю на потолок комната освещена.


Экран


Для некоторых режимов работы мне понадобился монитор. Остановился на таком. Хотя не уверен что это самое правильное решение. Может стоило взять полноформатный. Но про это позже.


Питание


Ребёнок спит в произвольных местах. Так что проще когда система питается от павербанка. Выбрал такой, просто потому что лежит дома для походов:


OpenVino


Пройдём немного по OpenVino. Как я сказал выше большим преимуществом OpenVino является большой объём предобученных сетей. Что нам может пригодиться:
Детекция лица. Таких сетей в OpenVino много:
  1. 1
  2. 2
  3. 3

Распознавание ключевых точек на лице. Это нужно нам чтобы запускать следующие сети
Распознавание ориентации лица. Активность ребёнка и куда смотрит.
Распознавание направление взгляда если пробовать взаимодействовать
Анализ глубины? Может быть получится
Анализ скелета
Ну и много других интересных
Основным минусом этих сетей будет их основное преимущество их предобученность
Это можно поправить, но сейчас мы делаем быстрый прототип, наша цель не работа в 100% случаев, а принципиальная работа которая будет приносить хоть какую-то пользу.

Поехали. Общая логика версии 1


Так как мы разрабатываем embedded устройство, то нам надо с ним как-то взаимодействовать. Получать фото/сигналы о тревоге. Так что решил сделать так же как когда делал кормушку, через телеграмм. Но довести до ума.
Для первой версии я решил:
  • Запустить на RPi обозначенные сети (хотелось бы сразу все, вдруг производительность позволит). Это позволит посмотреть больше вариантов решения задачи/вероятных путей развития
  • Написать общий шаблон программы.
  • Придумать алгоритм распознающий просыпание.
  • Сделать алгоритм присылающий уведомление при потере лица

Пошло всё более-менее неплохо, не считая кучи багов всего вокруг. Это свойственно для ComputerVision Я привык к этому:)
Вот краткая сводка того на что я натолкнулся:
  1. OpenVino под RPi не запускается в последней версии (на май 2020) из-за того что не срабатывает from openvino.inference_engine import IECore. Есть способы иначе использовать OpenVino (через OpenCV например), но там логика программы изменится, не хотелось.
  2. OpenVino старой версии не работает на новых сконверченных нейронных сетях, надо конвертить с -generate_deprecated_IR_V7 или брать старые
  3. OpenVino в последней версии (опять же, на май) с Movidius под виндой не может инферить сетки с int 8 из официального репозитория. В int32 может. Под RPi в int8 может. Ничего критичного, но сложнее дебажить.
  4. OpenVino не устанавливается под виндой в нестандартную папку. Точнее ставится, но все дальнейшие проблемы не решились и OpenVino не запустился. Про это много ругани, но судя по тому что у меня то же самое произошло так и не починили.
  5. OpenVino не работает на старых но мощных процах Intel (не везде дебажить удобно, но не критично).
  6. PyTorch в версии 1.5 не смог сконвертировать сети в onnx, пришлось конвертировать из 1.4


Но, тут как Уверен что если бы пошёл через TensorRT, то там бы проблем было как всегда больше.

Итак. Всё сбилжено, сети запущены, получаем что-то такое (запустив стек по голове, ориентации, ключевым точкам):

Видно, что лицо будет часто теряться когда ребёнок закрывает его руками/поворачивает голову. да и не все показатели стабильны.

Что дальше? Как анализировать засыпание?
Смотрю на те сетки что есть, и первое что приходит в голову распознавать эмоции. Когда ребёнок спит и тих на лице нейтральное выражение. Но не всё так просто. Вот тут темно-синий график это нейтральное выражение спящего ребёнка на протяжении часа:

Остальные графики грусть/злость/радость/удивление. Даже не особо суть того что где по цветам. К сожалению, данные сети нестабильны, что мы и видим. Нестабильность возникает когда:
  • Лишняя тень на лице (что ночью не редкость)
  • Лиц ребёнка не было в обучающей выборке OpenVino => произвольные переключения на другие эмоции
  • Ребёнок реально корчит рожи, в том числе во сне

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

Ок, с помощью эмоций просыпание не распознать. Пока что мне не хотелось обучать что-то самому, так что решил попробовать на базе тех же сетей но с другой стороны. Одна из сетей дает угол поворота головы. Это уже лучше (суммарное отклонение от взгляда в камеру во времени в градусах). Последние 5-10 минут перед просыпанием:

Уже лучше. Но Сын может начать махать головой во сне. Или наоборот, если поставить большой порог проснуться и не махать головой после этого. Получать каждый раз уведомление Уныло:

(здесь где-то час времени сна)
Значит надо всё же делать нормальное распознавание

Возникшие проблемы версии 1


Просуммируем всё что мне не понравилось в первой версии.
  1. Автозапуск. Не удобно запускать эту игрушку каждый раз заново, подключаться по SSH, запускать скрипт наблюдения. При этом скрипт должен:
    • Проверять состояние камеры. Бывает что камера выключена/не воткнута. Система должна ждать пока пользователь включит камеру.
    • Проверка состояния ускорителя. То же самое что с камерой.
    • Проверка сети. Штуку я хочу использовать и дома и на даче. А может где-то ещё. И опять же, заходить по ssh не хочу => надо сделать алгоритм подключения к wiFi если инета нет.
  2. Просыпание, обучение сети. Простые подходы не зашли, значит надо обучать нейронку на распознавание открытых глаз.

Автозапуск


В целом, схема автозапуска получилась следующей:
  • Запускаю на старте программу свою. Как я это делаю написал отдельную статью, не сказать что это на RPi сделать тривиально. Если кратко:
    • Создаю сервис который инициализирует OpenVino окружение
    • Сервис на старте запускает сначала скрипт проверки окружения, а потом основной рабочий скрипт
  • Проверяю наличие камеры
  • Проверяю наличие Movidius-модуля
  • Проверяю наличие интернета
    • Если нет запускаю камеру и жду QR-кода локальной wifi сети
  • Проверяю наличие информации о канале telegram через который будет управление. Если нет жду QR-код с данными на управление


Обучение сети для распознавания глаз


В OpenVino нет готовой сети для распознавания глаз.
Хахаха. Сеть уже появилась. Но её запушили, как оказалось, только после того как я начал разрабатывать. А в релизе и документации она появилась уже когда я более-менее всё сделал. Сейчас писал статью и нашёл апдейт.
Но, переделывать не буду, так что пишу как делал.

Можно очень просто обучить такую сеть. Выше я уже говорил, что использовал выделение глаз по кадру. Осталось всего ничего: добавить сохранение все встреченных на кадре глаз. Получается такой датасет:

Остаётся его разметить и обучить. Более подробно процесс разметки я описал тут (и видео процесса на 10 минут тут). Для разметки использовалась Толока. На настройку задания ушло~2 часа, на разметку 5 минут + 300 рублей бюджета.
При обучении думать особо не хотелось, так что взял заведомо быструю сеть, которая имеет достаточное качество для разрешения задачи mobilenetv2. Весь код, включая загрузку датасета, инициализацию и сохранение занял меньше 100 строк (большей частью взятых из открытых источников, переписал пару десятков строк):
Скрытый текст
import numpy as npimport torchfrom torch import nnfrom torch import optimfrom torchvision import datasets, transforms, modelsdata_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, testloadertrainloader, 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 = 5steps = 0running_loss = 0print_every = 10train_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 transformsimport torchfrom PIL import Imageuse_cuda=1mobilenet = 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


Текущая реализация качественно другая, но всё же она имеет ряд проблем:
  • Лицо детектируется не всегда. Это особенность вызвана тем, что сеть обучалась для других условий:
    • Целевым объектом сети были явно не младенцы
    • Сеть не обучалась в ИК
    • Сеть явно отдаёт преимущество вертикальным лицам
    • Сеть явно предпочитает лица размером где-то в кадра.

  • Иногда ребёнок может проснуться не открывая глаз. Скорее всего при этом он будет махать головой. Но, как мы показали выше, махание головой не есть однозначное просыпание. Так что хотелось бы детектировать звук, чтобы понять проснулся или нет.
  • Мы получили неплохую платформу детектирования просыпания. А что ещё можно сделать на её базе?


Переобучить детект лица?


Я не стал переобучать детект лица. В отличие от распознавания глаз тут сильно больше работы. И со сбором датасета, и с качественным обучением.
Конечно, можно заоверфититься на лицо сына, наверное даже чуть лучше работать будет чем текущая сеть. Но по остальным людям нет. И, возможно, по сыну через 2 месяца тоже нет.
Собирать нормальный датасет долго.

Звук


Можно было бы пойти по классическому пути распознавания звука, и обучить нейронку. В целом это было бы не очень долго, максимум в несколько раз дольше чем распознавание глаз. Но мне не хотелось возиться со сбором датасета, так что воспользовался более простым способом. Можно использовать готовые инструменты WebRTC. Получается всё элегантно и просто, в пару строк.
Минус, который я нашёл на разных микрофонах качество работы решения разные. Где-то тригериться со скрипа, а где то только с громкого крика.

Идём дальше, что ещё


В какой-то момент я провёл тест, запустив зацикленное 5-секундное видео себя с супругой:



Был видно, что сын залипает на лица людей в поле зрения (монитор подвесил его минут на 30). И родилась идея: сделать управление выражением лица. Это не просто статичное видео, но и вариант взаимодействия. Получилось как-то так (при смене эмоции сына происходит переключение видеоряда):

Папа, ты что, долбанулся?!

Наверное, надо попробовать с большим монитором. Но пока не соберусь.
Может надо подменить проигрываемое видео. Благо это просто видео проигрывается из отдельных картинок, где смена кадров подстраивается под FPS.
Может надо подождать (на текущем уровне ребёнок мог банально не понять связи своих эмоций и экрана)

А потом?


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

Как всё в итоге выглядит, описание, мысли


Как сейчас всё работает (в начале статьи есть более масштабное видео):
  • Всё управление идёт через Telegramm + через камеру.
  • Если не нужно управление видео эмоциями, то вся девайсина выглядит так:

  • Запускается включением питания на павербанке.
  • Если есть подключенная сеть то устройство уже готово к работе
  • Если нет сети то надо показать QR-код с сетью, система автоматически запуститься
  • Через Telegramm можно выбрать набор событий за которыми следить:

  • Каждый раз когда происходит событие которое интересно, присылается уведомление:

  • В любой момент можно запросить фотографию с устройства чтобы посмотреть что твориться


В целом отзывы от себя любимого:
  1. Детектор лица работает не очень. Это реально особенность любых детекторов нефайнтьюненых на детей. Обычно это не мешает детектировать просыпание (хоть парочка нормальных фото с открытыми глазами да придёт). Сейчас нет планов переобучать.
  2. Без экрана немного непрозрачный запуск (считался QR код или нет). А с экраном много проводов. Я думаю, самый правильный вариант будет посадить диодов на GPIO. И зажигать их в зависимости от статуса (есть коннект, не работает камера, не работает Movidius, нет коннекта к telegram'у, и.т.д.). Но пока не сделал
  3. Иногда сложно закрепить камеру. Так как у меня есть пара штативов как то справляюсь. А без них, пожалуй, ничего бы не заработало.
  4. Реально позволяет освободить сколько-то времени и дать свободы перемещения. Больше это чем у нормальной радионяни/видеоняни со стримингом? Не знаю. Может чуть-чуть проще.
  5. Прикольная штука для экспериментов.


Как запускать


Как я и говорил выше я попробовал выложить все исходники. Проект большой и разветвлённый, так что может что-то забыл или не дал подробных инструкий. Не стесняйтесь спрашивать и уточнять.
Есть несколько способов всё развернуть:
  1. Сорсы с гитхаба. Это более сложный способ, придётся долго настраивать RPi, может что-то забыл. Зато вы полностью держите процесс под контролем (включая настройки RPi).
  2. Использовать готовый образ. Тут можно сказать что безблагодатно и несекьюрно. Зато на порядок проще.


GitHub


Основной репозиторий расположен тут github.com/ZlodeiBaal/BabyFaceAnalizer
Он состоит из двух файлов которые надо запускать:
  1. Скрипт инициализации/проверки состсояния/настройки сети QRCode.py (для этого скрипта, напомню, есть более подробное описание). Он подключает WiFi и проверяет что имеется настройки для бота в Telegram.
  2. Основной рабочий скрипт face.py

Кроме того. в Git нет двух вещей:
  1. Файла с креденшоналами WiFi wpa_supplicant_auto.conf
  2. Файла с креденшоналами Telegram бота tg_creedential.txt

Можно позволить система создать их автоматически при следующем запуске. Можно использвать следующие, заполнив пустые поля:
tg_creedential.txt
token to access the HTTP API чтобы его получить, отправьте @BotFather в telegram команду "/newbot"
socks5:// по умолчанию не используется, но должна быть пустая строчка хотя бы
логин для socks5 по умолчанию не используется, но должна быть пустая строчка хотя бы
пароль для socks5 по умолчанию не используется, но должна быть пустая строчка хотя бы


wpa_supplicant_auto.conf
network={
ssid="******"
psk="*******"
proto=RSN
key_mgmt=WPA-PSK
pairwise=CCMP
auth_alg=OPEN
}


Свистелки и перделки по настраиванию RPi


К сожалению, просто положить и запустить скрипты на RPi не получиться. Вот что ещё надо для стабильной работы:
  1. Установить l_openvino_toolkit_runtime_raspbian_p_2020.1.023.tgz по инструкции docs.openvinotoolkit.org/latest/openvino_docs_install_guides_installing_openvino_raspbian.html
  2. Установить автозапуск
  3. Удалить мессадж о дефолтном пароле (может не надо, но мне мешало) sudo apt purge libpam-chksshpwd
  4. выключить скринсейвер www.raspberrypi.org/forums/viewtopic.php?t=260355
  5. Для детекции аудио:
    • pip3 install webrtcvad
    • sudo apt-get install python-dev
    • sudo apt-get install portaudio19-dev
    • sudo pip3 install pyaudio
  6. Скачать модели из репозитория OpenVino используя скрипт Get_models.py в папке Models


Образ


Образ выложен тут(5 гигов).
Пара моментов:
  1. Используется стандартный логин-пароль (pi, raspberry)
  2. Включен доступ по SSH
  3. По умолчанию не подключен WiFi и не настроен адрес бота в телеге которого система будет использовать для контроля.


Как настроить WiFi в образе


Первый вариант после запуска показать QR код с текстом:
WIFI:T:WPA;P:qwerty123456;S:TestNet;;

Где после P идёт пароль сети, после S индентификатор сети.
  1. Если у вас есть телефон с Android 10 то там такой QR код генерируется автоматически при нажатии поделиться сетью
  2. Если нет то можно сгенерировать на www.the-qrcode-generator.com


Второй вариант зайти по SSH на RPi (подключившись по проводу). Либо включить монитор и клавиатуру. И положить файл
wpa_supplicant_auto.conf
network={
ssid="*********"
psk="*******"
proto=RSN
key_mgmt=WPA-PSK
pairwise=CCMP
auth_alg=OPEN
}

c вашими настройками wi-fi в папку "/home/pi/face_detect".

Как настроить телеграм-бота в образе


Первый вариант после запуска показать QR код с текстом:
tg_creedential.txt
token to access the HTTP API чтобы его получить, отправьте @BotFather в telegram команду "/newbot"
socks5:// по умолчанию не используется, но должна быть пустая строчка хотя бы
логин для socks5 по умолчанию не используется, но должна быть пустая строчка хотя бы
пароль для socks5 по умолчанию не используется, но должна быть пустая строчка хотя бы

сгенерировав его через www.the-qrcode-generator.com
Второй вариант зайти по SSH на RPi (подключившись по проводу). Либо включить монитор и клавиатуру. И положить файл tg_creedential.txt описанный выше в папку "/home/pi/face_detect".

Ремарка про детство


Уже когда собрал первую версию и показывал её своей маме, то получил внезапный ответ:
-О, а мы в твоём детстве почти так же делали.
?!
Ну, коляску с тобой на балкон выставляли, через форточку туда выкидывали микрофон, который был включен в усилитель в квартире.

Вообщем внезапно оказалось что это наследственное.

Ремарка про супругу


А как супруга отнеслась?
А как она позволила тебе эксперименты над сыном ставить?!
Спрашивали не раз.
Но, я жену испортил хорошо. Вот, она даже на Хабре иногда статьи пишет.

P.S.1


Я не специалист по ИБ. Конечно, я попробовал сделать так, чтобы никаких паролей нигде не светилось, и.т.д., а каждый смог сконфигурировать под себя, указав всю секьюрную информацию после старта.
Но не исключаю что что-то где-то не досмотрел. Если увидите явные ошибки попробую исправить.

P.S.2


Скорее всего про апдейты по этому проекту буду рассказывать в своём телеграм-канале, либо в группе вконтакте. Если накопиться много интересного, то сделаю ещё одну публикацию тут.
Подробнее..

Edge платы для домашнего Computer Vision

13.04.2021 06:09:41 | Автор: admin

Я люблю делать всякие странные штуки с Computer Vision. Из того, что я выкладывал на Хабре - умная кормушку для птиц и камера для слежения за ребенком. По работе примерно тем же занимаюсь. Так что слежу за актуальным рынком embedded устройств для ComputerVision. Прошлый обзор я делал полтора года назад. Для Embedded это долго. В этом я сосредоточусь на устройствах которые вышли недавно + некоторый анализ что из этих устройств можно использовать дома/для хобби.

Рассказ будет построен следующим образом:

  • Продуктовые железки которые стали классикой продакшна / железки которые почти доросли до таких.Их можно взять и запустить из коробки. Большие OpenSource комьюнити/персональные фреймворки. Время развертывания обученной сети на такой железке в 1-2 дня.

  • Продуктовые железки которые почти стали классикой, но по которым слишком мало открытых проектов/документации/где часть проекта закрыта. В таких проектах сложно ожидать большой подставы, и скорее всего все будет более-менее будет работать. Но вот что нужно будет сделать для работы сети, и как её сконвертировать, как перенести - нельзя будет оценить заранее.

  • Железки которые выглядят интересно, но нет ни гайдов по ним, ни историй успехи, ни подробной документации.

  • Железка есть информации почти нет/нельзя получить без запросов. На рынке нет истории использования/успеха.

Сразу несколько дисклеймеров, чтобы не возвращаться к ним:

  • Далеко не все из перечисленного я лично использовал/тестировал

  • Далеко не все перечислено. Я уверен что забыл/не знаю многое. И очень надеюсь что в комментарии накидаете чего-нибудь интересного

  • Я фокусируюсь на устройствах где есть GPU/NPU или прочие ускорители инференса. Безусловно, есть всякие FPGA, и прочее, но я не считаю их применимыми для хоббийных проектов. (что такое NPU GPU TPU и другие аббревиатуры - можно прочитать в этой замечательной статье)

Часть 1. Ближе всего к продукту

С одной стороны, весь этот раздел можно было бы свести к простому перечислению:

  • Jetson

  • Intel

  • Coral

  • Android телефоны

  • Прочие Embedded, устройства с хорошим процессором, без NPU/GPU

И в этом списке за последние 2 года появился бы лишь Coral. Но, в реальности, все сильно интереснее. Появились не только и не столько новые архитектуры, как имплементации/доработки старых. О чем мы и поговорим.

В мире Jetsonов новинок нет. Сейчас в продаже:

  • jetson nano

  • jetson xavier nx

  • jetson agx

  • jetson tx2

12ого началась конференция GTC от NVIDIA, но ничего нового на ней не объявили, так что, скорее всего, на следующий год ничего нового не будет.

Встречаются имплементации Jetson'а, под другие экосистемы. Самое известное - AWS Panorama. Jetson находящийся внутри экосистемы Амазона.

PanoramaPanorama

Jetson, безусловно, одна из самых удобных плат для хобби. Есть разводка GPIO, много кода который работает из коробки. Нейронные сети можно никуда не конвертировать, используя в оригинальном фреймворке.
Cтоит понимать, что из всех четырех Jetson'ов для хобби лучше всего подходит Nano. Он стоит лишь 100$, что значительно меньше следующего в серии NX, стоящего 400$. В теории, TX2в середине, но его почти нет в продаже + менее удобная плата. Проектов на Jetson очень много. Например из того что было в медийном пространстве - 1, 2. Вот тут есть неплохая подборка.
Лично я участвовал где-то в 5-7 проектах где Jetson был основной платформой. 2-3 из них переросли в полноценные продукты. Но, вынужден сказать, что для хобби его не использовал ни разу. Почему? Какая-то совокупность факторов всегда. Nano у меня был первой серии, там были баги по питанию. Иногда была не нужна производительность дополнительная. Иногда хотелось опробовать чего-то нового.

В отличие от Jetson, на базе Movidius появляются интересные штуки. В первую очередь это M.2 и mPCIe карты. Какие-то даже уже были, когда я писал прошлый обзор: 1, 2, 3.
Сейчас их очень много от разных производителей.

Удобны ли ли они для каких-нибудь прототипов и хобийных проектов? Мне кажется, что ниша есть, но очень узкая:

  • Когда надо много производительности (есть сборки где есть несколько мовидиусов)

  • Когда USB соединение слишком нестабильно, но M.2/PCIe хватит (переносные устройства)

Во вторую очередь - это ряд устройств от luxonis. Они устраивали большую компанию на кикстартере про OAK и OAK-D. OAK это платы где movidius воткнут не на материнскую плату, а на плату с камерой. Кроме того, у них есть несколько устройств с movidius когда он стоит на плате 1, 2. Я не буду вдаваться подробнее тут, кому интересно - про них я делал более подробный обзор у себя в блоге/на Youtube:

Кому лень читать/смотреть - вот краткая выдержка:

  • + Минус одно USB соединение + к стабильности - для части проектов полезно

  • - От USB все равно не уйти - до прода скорее всего не дойдет

  • + Неплохой дизайн, неплохой корпус

  • - Заменили хороший OpenVino на какой-то мутный DepthAI

  • - Дорого. Дороже чем собрать такое с оригинальным Movidius'ом, дороже чем с Jetson Nano

  • - В камере хорошее разрешение, но провален куда более полезный функционал для машинного зрения (ИК диапазон, нормальное управление выдержкой/частотой, синхронизация с вспышкой, и.т.д.)

Мне куда больше понравились эти две платы 1, 2. Но тут сразу понятно, что на первой оочень слабый процессор, а вторая дорогая и там есть лишнее соединение.

Кроме luxonis до movidius'а в камере догадался FLIR, достаточно крупный производитель камер. Они выпустили FireFly DL, который явно на порядки более продуктовый чем OAK, а стоит только на 100$ дороже (+объектив).

Чтобы закончить с Movidius окончательно. По простоте использования/наличию документации/комментариям и поддержке - на мой взгляд Movidius - это один из самых разумных ускорителей для своих проектов на нейронных сетях. Его минус - сложный переход с продакшну, и необходимость конвертации сети. То что вышло за последние годы - расширяет возможности использования. Но все же мне не хватает плат сравнимых с RPi3 где мовидиус бы стоял напрямую на плате.

Google Coral. Вот тут много нового. В своем прошлом обзоре я был крайне недоволен им - очень ограниченные платы, очень бажные. Но прогресс. Баги пофикшены, выпущена новая линейка. Есть почти все то же самое что и в movidius, только напрямую от производителя - отдельные платы, стики, M.2, pci-e, чистые чипы, и.т.д..

В качестве основного фреймворка - специальный tflite, где уже сильно меньше потерь на конвертации. Но, как видно, слоев все равно сильно меньше чем в том же ONNX.
Из плюсов, про которые не сказал - на базе Coral уже сторонние производители создают свои решения. Например Asus. Из минусов - считанные разы слышал что его использовали в продакшене. И, обычно, для каких-то простых задач.
Для себя я понимаю почему я избегаю Coral - при прочих равных не хочу трогать TensorFlow. А по текущим характеристикам он нигде не превосходит вариантов выше.

Телефоны. Так же, стоит отметить, многие телефоны получили поддержку из плат сопроцессоров для нейронных сетей. Есть несколько фреймфорков для инференса моделей на них. Например тут и тут описывал. Зачастую телефон стал удобнее чем embedded для пилота и хобби. Основной минус - отсутствие периферии. Второй серьезный минус для меня - внутренняя инфраструктура приложений. Конечно, Unity и Flutter упрощают логику использования. Но все же, лично для меня, телефоны сильно сложнее чем Linux-системы.
С другой стороны, в телефоне уже есть и камера и акселерометр, и интернет.

Прочее. Под "прочим" я в первую очередь подразумеваю системы где заход в нейронные сети идет со стороны процессора. Например в процессорах Intel за счет OpenVino можно сильно оптимизировать сети. На некоторых процессорах, например на RaspberryPI есть оптимизация под инструкции Neon. RPi4 вполне может справляться с какими-то задачами детекции и трекинга в реальном времени. Так что если вам нужно с помощью машинного зрения раз в день проверять рассаду - думаю подойдет.

Часть 2. Работает, но мало информации

Есть такая забавная штука. В ML сейчас 90% знаний открыто. Есть статьи, большая часть публикаций с OpenSource. Подробнейшие фреймворки на Nvidia и Intel. Но рынок аппаратных платформ был исторически не такой. Хотите подключить камеру по csi-mpi к своей платформе? Будьте добры купите дорогущий мануал по протоколу. Хотите разрабатывать на нашей платформе? Для этого вам нужно специальное программное обеспечение которое просто так вы не скачаете. И много фирм по производству железа по-другому и не мыслят. Как результат мы имеем полтора гайда на платформу до её покупки. Невозможность протестировать до покупки. Отсутствие форумов по теме. И проблему с каждой функцией где что-то пошло не так.

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

  • RockChip

  • Gyrfalcon

Обе платформы я видел в проде. Даже немного экспериментировал. Но у обоих платформ крайне неудобный фреймворк переноса сетей/использования.

RochChip. Основная платформа на которой все делается - Rockchip 3399Pro. Самая популярная реализация, наверное - Firefly, RockPiN10 или Toybrick.

Что забавно, у того же ASUS есть версия не только на базе Google Coral, но и на базе RockChip.
Сейчас разрабатывается новая версия - 1, 2.
В целом, плюс RockChip'а - это плата которую любят все разработчики железа. Есть референсный дизайн, комплектующие, и.т.д. Собрать продукт проще и быстрее чем на Jetson.
Но перенос сети весьма непредсказуем. Документация куцая и полукитайская. Как поддерживается - не понятно. Я видел несколько проектов где сети все же перенесли. Но, гарантировать что это всегда можно, нельзя.

Gyrfalcon. Вторым примером закрытой архитектуры, но на базе которой я видел проекты - является Gyrfalcon

Забавно, что платы на его базе в продаже почти отсутствуют. Что-то из того что есть: 1, 2, 3 .

Если я правильно понимаю, Gyrfalcon ориентирован на корпоративный рынок. Отсюда и закрытость инфраструктуры и мало число девайсов в продаже. Глобально, с прошлой моей статьи ничего особо не поменялось.

Делать ли свои проекты на базе этих платформ? Подходят ли они для хобби? В целом, такое мне кажется возможным. Главное чтобы были простые сетки. Классификация/базовая детекция, и.т.д.
По цене такие платформы достаточно дешевы и сравнимы с OpenVino|Jetson вариантами.
Но надо серьезно понимать зачем так делать. Это может быть:

  • желание сделать продукт из своей разработки

  • желание распаять свою систему

  • нехватка в Jetson|RPi каких-то возможностей

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

Часть 3. Внешне все выглядит неплохо, есть какая-то документация, но примеров нет

Пожалуй, сюда я отнесу только одну плату Khadas VIM3 . Много кто о ней знает, но не видел ни одного человека который бы что-то на ней сделал. Есть в открытой продаже, просто купить. Заявлены неплохие параметры по скорости.

Судя по документации перенос моделей достаточно простой. Но, так как никто не пробовал/не тестировал, - не понятны ограничения. Сама плата собрана на базе процессора Amlogic A311D, который содержит NPU модуль. Amlogic многие позиционируют как конкурент RockChip, сравнивая их. Но сравнений именно NPU модулей - нет.

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

Часть 4. Железки есть, информации нет

Большая часть плат тут имеет очень слабую документацию. Часть плат - это отладочные платы для мобильных платформ. Другая часть - платы специально под ML, но по которым очень мало информации и данных в интернете.

BeagleV. Плата которая пока не вышла, но выглядит неплохо. Разработана на базе процессора U74 от SiFive. Используется RISC-V архитектура.

BeagleBoard - странная плата с комьюнити вокруг. Именно вокруг этого комьюнити частично построена плата BeagleV. Плата сделана на базе NPU модуля от Texas Instruments. Вроде даже какие-то репозитории есть:

  1. Фреймворк от TI для обучения нейронных сетей. Аж 55 звезд на гитхабе.

  2. Репозиторий платы. Аж 88 звезд.

Считать ли это "популярным" решением - не уверен. Но, возможно, какие-то базовые сети запустить будет можно.

Пример настоящего Edge (минимальное использование ЦПУ и энергоэффективности) это - Sipeed Maixduino и Grove AI Hat. Но, разработка на них, судя по отзывам, которые я слышал, ужасна. Сети плохо поддерживаются, мало производительности. Вот тут пример использования. Ну, по сути все проблемы обычного Arduino.
Я видел людей которые делали и адекватные проекты на их базе, и хоббийные. Но я не готов:)

Глобально, Qualcomm - это, конечно, производитель процессоров для мобильных. Но, на их же базе, есть какое-то количество именно embedded платформ. При этом, Qualcomm - имеет свой SDK, и свою платформу для исполнения нейронных сетей. Года 2.5 назад я сталкивался с ней. И тогда там была жесть. Простейший слой сложения не поддерживался. Что там говорить про сплиты или объединения. По сути работал лишь VGG на трехканальный вход.
Сейчас, судя по слухам все лучше. К тому же, должен нормально работать Tensorflow lite.
Но вот с платами под эмбеддед у Qualcomm плохо. Есть вот такое (но стоит почти 500 баксов). Или вот такое (мало информации, но выглядит прикольно, за 300 баксов камера + корпус + ускоритель = неплохо).

Примерно так же себя ведет Huawei. Есть фреймворк. Не пользовался, и не знаю. Есть какое-то количество плат. Должен работать Tensorflow lite.
Но, мне сложно придумать где такая плата будет иметь смысл на использование.

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

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

P.S.

Про девайсы которые попадают мне в руки/про которые я читаю - иногда пишу у себя в блоге (telegramm, vk). Наверное, через год-два проапдейчу тут что накопится.
Прошлый апдейт делал на ютубе.

Подробнее..

Deep Learning Inference Benchmark измеряем скорость работы моделей глубокого обучения

22.10.2020 10:15:06 | Автор: admin


Перед разработчиками встает задача определения производительности железа в задаче исполнения глубоких моделей. Например, хочется решить проблему анализа пола-возраста покупателей, которые заходят в магазин, чтобы в зависимости от этого менять оформление магазина или наполнение товаром. Вы уже знаете какие модели хотите использовать в вашем ПО, но до конца не понятно как выбрать железо. Можно выбрать самый топ и переплачивать как за простаивающие мощности, так и за электроэнергию. Можно взять самый дешевый i3 и потом вдруг окажется, что он может вывезти каскад из нескольких глубоких моделей на 8 камерах. А может быть камера всего одна, и для решения задачи достаточно Raspberry Pi с Movidius Neural Compute Stick? Поэтому хочется иметь инструмент для оценки скорости работы вашего инференса на разном железе, причем еще до начала обучения.


В целом можно выделить три направления работы с глубокими моделями:


  1. Обучение. На обучение глубоких моделей уходит требуется много, очень много, а иногда и очень-очень много вычислительной мощности. Начиная с первой ласточки AlexNet (61 миллион параметров), и заканчивая GPT-3 с 175 миллиардами параметров.
  2. Transfer Learning. Берем существующую модель (как правило решающую задачу классификации), и используем ее в качестве основы для собственной модели. Количество ресурсов для дообучения или переобучения можно значительно уменьшить по сравнению с обучением с нуля.
  3. Использование моделей глубокого обучения в работе (Inference). Для использования глубоких моделей в прикладных задачах требуется минимизировать использование ресурсов. Решается это различными способами: использование оптимизированных библиотек, которые максимально плотно заполняют конвейер процессора вычислениями; конвертация параметров модели из fp32 в fp16, int8 или даже int1; использование специализированных ускорителей (GPU, TPU, FPGA, нейронные сопроцессоры).

Общую схему решения задач с использованием глубокого обучения можно представить следующим образом:



Как правило, требования по скорости заложены еще на этапе постановки задачи, и хочется еще до начала обучения или дообучения узнать "уложится" или нет предполагаемая модель в доступные для инференса ресурсы. И для решения этой задачи был создан DLI benchmark.


DLI benchmark


В рамках лаборатории ITLab студентами ННГУ им. Н.И.Лобачевского был разработан открытый фреймворк DLI (https://github.com/itlab-vision/dl-benchmark), который позволяет запустить измерение производительности большого количества разных моделей на доступном железе.


Основные требования, которые были заложены на этапе разработки:


  1. Измерять время работы того, как модель будет работать в реальном Python приложении.
    Мы включили в подсчет времени собственно исполнение модели и копирование обрабатываемых данных из python в библиотеку инференса (это может показать влияние на маленьких моделях).
  2. Обладать модульной архитектурой, чтобы иметь возможность включать в тестирование новые фреймворки, новые модели, новое железо.

Архитектура фреймворка


  1. Target Hardware устройства для выполнения замеров. Предполагается, что на этом компьютере установлено необходимое программное обеспечение (сейчас это делается в Docker) и подготовлен набор моделей для тестирования.
  2. FTP-сервер хранит конфигурации тестов для каждого тестируемого устройства и результаты измерений.
  3. Integrator это компонент, предназначенный для сбора результатов производительности.


Алгоритм работы DLI-benchmark:


  1. Интегратор создает структуру каталогов на FTP-сервере и генерирует файлы конфигурации тестов для инференса на доступных машинах
  2. Интегратор запускает анализ производительности на каждом устройстве. Анализ производительности на конкретном устройстве включает получение файла конфигурации тестов и последовательное выполнение тестов для каждой модели в отдельном процессе. Измерения производительности собираются во время тестов.
  3. По завершении тестов результаты производительности копируются из FTP-сервер.
  4. После того, как бенчмарк завершит все тесты на всём оборудовании, полученные результаты сохраняются в виде csv-таблицы, которая автоматически конвертируется в html-страницу для сайта проекта.

Типы экспериментов


В целом разработчикам привычен синхронный вариант работы программы, когда сначала читается картинка из файла, масштабируется под размер входа сети, вызывается функция run или forward, и цикл начинается сначала.


При обработке картинок по одной вычислительное устройство очень часто недогружено. Можно подать два изображения одной пачкой (batch=2), тогда они обработаются быстрее чем два изображения по отдельности. При увеличении размера пачки производительность сначала начнет расти, а потом падать, и определить лучший размер пачки весьма полезно.


В современных фреймворках поддерживается асинхронный режим работы, когда не нужно дожидаться конца обработки предыдущего изображения, можно новую картинку начать обрабатывать сразу после поступления. При использовании асинхронного API в OpenVINO можно создавать реквесты по 1 картинке независимо и при этом получать по прежнему высокую производительность и при этом низкие задержки. Возможность получать высокую производительность, сохраняя при этом низкие задержки, открывает новые возможности для инференса глубоких моделей на многосокетных высокопроизводительных системах.


Метрики производительности


  • Латентность или задержка (Latency) медианное время обработки одной пачки картинок.
  • Среднее время одного прохода (Average time of a single pass) отношение общего времени обработки всех пачек к числу итераций цикла тестирования.
  • Среднее количество кадров, обрабатываемых за секунду (Frames per Second, FPS) отношение размера пачки изображений к латентности.

В случае когда мы можем подавать несколько картинок независимо друг от друга (асинхронный запуск на обработку), мы используем другие метрики производительности.


  • Среднее время одного прохода (Average time of a single pass) отношение времени обработки всех пачек к количеству итераций цикла тестирования. Представляет собой время выполнения одновременно созданных пачек на устройстве.
  • Среднее количество кадров, обрабатываемых за секунду (Frames per Second, FPS) отношение произведения размера пачки изображений и числа итераций ко времени обработки всех пачек.

Анализ результатов


В ходе экспериментов мы попробовали бенчмарк на различном оборудовании: CPU i7-7700K, i5-8600K, i3-7100, i7-8700, i3-8100, GPU Intel HD Graphics 630, Intel Neural Compute Stick 2.


Пока опубликовали результаты только для фреймворка OpenVINO. Докеры бенчмарка с Caffe, TensofFlow и PyTorch в разработке.


Для демонстрации примеров работы возьмем модель ResNet-152 это очень объемная модель, которая решает задачу классификации ImageNet.


На графике ниже представлена время обработки одной пачки картинок в различных режимах. На данном графике по оси X отложено количество потоков процессора, которые были использованы для обработки изображений, а по оси Y среднее время обработки (или задержка, latency) одной пачки изображений. На этом графике мы видим, что при удвоении использованных потоков скорость обработки увеличивается примерно в два раза это означает отличную параллеллизацию в обработке изображений. Что важно, параллелизация показывает хорошие результаты и для большой пачки картинок, и для одиночной картинки.



Чтобы посчитать FPS (сколько в среднем картинок в секунду обработает процессор в каждом режиме работы) нужно количество картинок в пачке разделить на время обработки одной пачки. Эти данные мы приведем в таблице ниже.



Взглянув на эти цифры, мы видим, что если мы будем использовать все 6 ядер процессора, что при обработке картинок пачками по 8 штук производительность возрастает на 20% из-за более плотной упаковки вычислений. Но в прикладном ПО не слишком хочется писать дополнительные функции, чтобы собирать картинки в пачки, да и задержки тогда возрастают


В этом случае нам на помощь приходит асинхронный запуск нескольких картинок на обработку! OpenVINO поддерживает возможность не собирать из картинок большую пачку, а подавать картинки независимо друг от друга не дожидаясь завершения обработки предыдущей. В данном режиме вы задаете в программе количество "стримов" (это не поток процессора; это сущность, которая захватывает один или несколько потоков процессора и обрабатывает в себе картинку), и можете независимо запускать на обработку картинки по мере поступления.



С использованием OpenVINO в асинхронном режиме с размером пачки в 1 картинку мы почти достигаем производительности, полученной в синхронном режиме с размером пачки 8, при этом почти не меняя код запуска моделей.


Результаты производительности 63 моделей на CPU, Intel GPU и Intel Neural Compute Stick 2 собраны в HTML-таблиц, посмотреть их можно здесь:
http://hpc-education.unn.ru/en/dli
Пока мы измеряли только производительность моделей, сконвертированных в OpenVINO. Уже пишется код для тестирования моделей Caffe, и в разработке другие библиотеки глубокого обучения.

Подробнее..

Под капотом OpenVINO (Intel Neural Stick)

14.01.2021 20:16:39 | Автор: admin

Введение

Привет, Habr! Сегодня я хочу рассказать немного об аппаратном ускорителе Neural Compute Stick. Расскажу с точки зрения hardware-разработчика.

Что внутри OpenVINO

Известно, что OpenVINO взаимодействует с Intel Neural Compute Stick, а сердцем самого стика является чип Movidius Myriad X. Некоторые характеристики данного чипа можно найти в блоге компании Intel. В частности из статьи мы узнаем следующие характеристики Myriad X:

  • Векторные процессоры: 16

  • Основные интерфейсы: 16x MIPI lanes, USB 3.1, PCIe 3.0

  • 20+ ускорителей обработки медиа

Я попытался найти более подробную информацию о чипе в открытых источниках.

В частности, ресурс wikichip.org повествует о том, что у старых версий чипа есть еще и такие интерфейсы: SDIO, SPI, I2C, I2S, UART, JTAG и датчик температуры. В репозитории OpenVINO, в файле ie_plugin_config.hpp находим крохотное подтверждение информации с wikichip.org, что датчик температуры действительно есть: Metric to get a float of device thermal. String value is "DEVICE_THERMAL". Кроме того, SHAVE (Streaming Hybrid Architecture Vector Engine) имеют возможность отключать тактирование, что в целом интересно для LowPower устройств. Это говорит о том, что возможности чипа куда шире, чем просто ускоритель Deep Neural Network (DNN).

В качестве протокола коммуникации выступает фирменный XLink, который кроме всего прочего имеет интересную функцию XLinkBoot(deviceDesc_t deviceDesc, const char* binaryPath)*: Boots specified firmware binary to the remote device, binaryPath - path to the *.mvcmd file.

*.mvcmd - по сути является модифицированным elf-файлом, содержащим дополнительные инструкции для внутреннего загрузчика Myriad.

Пожалуй, на этом информация, которая имеет хоть какое-то отношение к hardware, заканчивается.

Интерфейсы

MIPI-CSI Как указано выше, Myriad X действительно имеет 16 MIPI-CSI lanes, если быть точнее, то 8 MIPI-CSI интерфейсов, которые работают в 2-lane режиме. Так же они могут работать в паре, в 4-lane режиме. Работа с несколькими сенсорами позволяет реализовать стерео зрение. Для его реализации на чипе присутствуют аппаратные ускорители.

MIPI-DSI Кроме того, чип имеет MIPI-DSI интерфейсы, что позволяет использовать Myriad X в проектировании автономных интерактивных устройств с дисплеем. Как пример, первое что приходит в голову: ATM, фотобудка (с поиском лица на фото и т.п.).

I2C Было бы странно, если бы в чипе была реализация MIPI, но не было бы I2C. С этим всё понятно.

I2S Действительно, Myriad X имеет возможность подключения аудио-устройств, что в симбиозе с DNN ускорителем делает его весьма интересным для применения в умных колонках, как например "Яндекс.Станция"

Пример применения (капля пиара)

Мы использовали аппаратные возможности Intel Myriad X в проекте камеры аналитики для ритейла под кодовым названием Jachin.

Камера представляет из себя аппаратную реализацию на базе чипа MA2085 с двумя цветными сенсорами и Ethernet. По своей задумке, камера вешается над входом в магазин или какой-либо другой интересующий нас объект. Один из сенсоров смотрит вниз и один в торговый зал.

С помощью сенсора, направленного на вход, и алгоритма обработки данных на выходе нейронной сети производится вычисление следующих метрик:

  1. Сколько человек вошло/вышло из магазина

  2. Сколько времени провел человек в магазине

По косвенным данным производится вывод о посещаемости магазина в зависимости от

  1. Времени суток

  2. Погоды

  3. Праздничный или будний день

  4. и т.д

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

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

Мы не стали использовать H.264/H.265 только по той причине, что видеонаблюдение не является основным функционалом камеры.

Это краткое описание функционала, которое я могу рассказать как firmware разработчик. Бизнес, бэкэнд, фронтэнд и прочие составные части проекта в данной статье рассматриваться не будут. Если вам интересно взглянуть на описанную камеру, то вы можете увидеть её в разборе на анимации главной страницы https://smass.tech

Вернемся к процессу разработки на Myriad X.

Firmware

Очевидно, что поддержка работы периферии такого кристалла требует ОС. Когда OpenVINO загружает mvcmd файл внутрь Neural Computer Stick, на этом стике запускается RTEMS. Эта информация подтверждается и в открытых источниках от пользователя walts.

Философия разработки автономных решений на базе Myriad X практически наследует уже знакомую пользователям OpenVINO идею Pipeline'ов. Каждый элемент Pipeline работает в отдельном потоке, обрабатывает информацию, преобразует ее и передает следующему звену через очередь сообщений операционной системы.

Для реализации своих идей в распоряжении разработчика есть стандартные библиотеки C:

  • limits.h

  • math.h

  • stdarg.h

  • stdbool.h

  • stddef.h

  • stdint.h

  • stdio.h

  • stdlib.h

  • string.h

  • types.h

  • assert.h

  • ctype.h

И стандартные библиотеки С++:
  • algorithm

  • array

  • bitset

  • cassert

  • cctype

  • cerrno

  • cfenv

  • cfloat

  • ciso646

  • climits

  • cmath

  • cstdarg

  • cstdbool

  • cstddef

  • cstdint

  • cstdio

  • cstdlib

  • cstring

  • ctime

  • cwchar

  • cwctype

  • deque

  • exception

  • forward_list

  • functional

  • initializer_list

  • iterator

  • limits

  • list

  • map

  • numeric

  • queue

  • ratio

  • stack

  • stdexcept

  • string

  • tuple

  • type_traits

  • typeindex

  • typeinfo

  • unordered_map

  • unordered_set

  • utility

  • valarray

  • vector

  • ext/hash_map

  • ext/hash_set

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

Из информации на официальном сайте Intel, SDK также включает специализированную инфраструктуру FLIC в разработке конвейеров обработки изображений. FLIC (Frame Level Isp Cv) представляет из себя плагин с конкретным функциональным назначением, который работает в отдельном потоке операционной системы.

Плагины включают в себя порты коммуникации между собой.

Передатчик (Sender) - шаблонный компонент, передающий указатель на объект. Приемник (Receiver) - очевидно, шаблонный компонент, принимающий указатель на тип данных, указанный в качестве параметра шаблона. По сути, внутри себя оба компонента реализуют функционал через mqsend, mqreceive.

Оба компонента имеют механизмы связывания между собой.

Камера на Myriad X

Рассмотрим упрощенный пример того, как сделать USB камеру на Myriad X.

  1. У нас есть сенсор, подключенный по MIPI к Myriad X. Для того, чтобы сохранить кадр с сенсора нам нужно сначала выделить для него память. Для этого используется FLIC плагин, который выделяет область памяти фиксированного размера из статического массива.

  2. Подключаем плагин, реализующий работу с MIPI. Упрощенно, у него используется два основных порта. Первый, receiver, связывается с sender плагина из п.1, чтобы получить пустой фрейм. Полученные по MIPI данные заполняют пустой фрейм. Когда будет получен сигнал End Of Frame, во второй порт, sender, будет передан указатель на область памяти, где уже содержатся полезные данные в формате RGRB.

  3. Подключаем третий плагин, в котором производится дебаеризация данных. Receiver плагина подключается к sender из п.2. Полученный фрейм из п.2 переводится в формат YUV и передается в sender.

На практике, нам необходим еще один плагин, который выделит память под дебаеризованный фрейм (тоже самое, что в п.1). По большому счету, всегда, когда происходит преобразование данных, нам необходимо выделять новые пустые фреймы: вычитываем из области памяти данные в формате RGRB, преобразуем, записываем в другую область памяти результат в YUV формате. Но для упрощения статьи такие детали далее будут опускаться.

  1. Подключаем плагин H.264 энкодера. Он принимает данные в формате YUV от sender в плагине п.3, производит кодирование и отправляет указатель на закодированные данные в sender.

  2. Подключаем плагин вывода данных по USB. Как уже говорилось в начале статьи, в SDK реализован протокол коммуникации XLink, что используется в OpenVINO. Хорошо, берем плагин XLink. Он не имеет порта sender, но имеет receiver. Его мы подключаем к sender плагина H.264 энкодера. Как только от энкодера придут данные в плагин XLink, они будут переданы по USB хосту.

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

MIPI генерирует фреймы с фиксированным FPS, пусть для примера он будет равен 20, то есть период следования фреймов 50мс. За 50мс программа должна успеть принять данные, передать их в энкодер и далее по USB. Предположим, возникли проблемы с USB, и весь цикл обработки одного фрейма занял 62мс. В такой ситуации фрейм, выделенный в п.1, не успеет вовремя вернуться обратно в пул, и тогда новый кадр с сенсора будет пропущен. Как правило, плагин из п.1 использует статический массив, в котором умещается сразу несколько пустых фреймов.

DEBUG

Стоит отметить, что отладка кода представляет из себя не менее увлекательный процесс, чем его написание. К счастью или нет, но мне очень нравится среда разработки Visual Studio, ну вот нравится и всё, что бы мне не говорили про VIM, Sublime, gedit и прочее. Соответственно, после Visual Studio, со всеми ее брейкпоинтами и пошаговой отладкой было довольно непривычно отлаживать код через CLI интерфейс:

  • Запуск отладки через консольную команду для утилиты, работающей через JTAG (тут ничего удивительного)

  • Установка breakpoint через консольную команду для утилиты, работающей через JTAG. В качестве аргумента указывается номер строки конкретного файла проекта.

  • Пошаговая отладка тоже через консоль

В целом, здесь всё делается через консоль. Хочешь посмотреть состояние аппаратного блока - вводи команду. Проверить стек вызовов - вводим команду. Наверное, это единственное, что может слегка расстроить с непривычки: вместо пары кликов в IDE нужно сделать 15-30 кликов по клавиатуре.

Заключение

Я не планировал в рамках данной статьи давать исчерпывающие ответы на аппаратные и программные возможности Myriad X, а лишь хотел рассказать разработчикам, что Intel Myriad X может найти более широкое применение, нежели просто USB ускоритель DNN.

Подводя промежуточные итоги, хотел бы сказать, что Myriad X довольно интересный чип при своем энергопотреблении. Огромное количество документации поражает воображение. Но, если изучить эту документацию, то с Myriad X перед разработчиками открываются довольно мощные возможности в проектировании AI устройств: от интеллектуальных беспилотников на аккумуляторном питании, до банкоматов с распознаванием лиц.

P.S. В данной статье изложена малая часть информации по чипу. Сделано это для знакомства читателя с ним. Если вдруг статья вызовет интерес по каким-то конкретным вопросам, то я с радостью отвечу на них в комментариях, либо в виде отдельной статьи. В рамках определённых договоренностей, некоторые детали работы с чипом разглашению не подлежат. По этой причине на некоторые вопросы я не смогу ответить или ответы могут задерживаться из-за согласования с руководством.

Подробнее..

Разбираемся, как подавить шум в речи с помощью глубокого обучения и OpenVINO

31.05.2021 12:16:05 | Автор: admin

Данная статья будет полезна студентам и тем, кто хочет разобраться с тем, как происходит шумоподавление речи (Speech Denoising) с помощью глубокого обучения. На Хабре уже были статьи по данной тематике несколько лет назад (раз, два), но нашей целью является желание дать несколько более глубокое понимание процесса работы со звуком.

Картинка со звукомКартинка со звуком

Задача шумоподавления с помощью глубокого обучения и OpenVINO попала в руки к студентам ITlab учебно-исследовательской лаборатории Университета Лобачевского при поддержке компании Intel. Студенты, начиная со 2 курса, под руководством преподавателей работают над интересными инженерными и научными проектами. Создание высокопроизводительного программного обеспечения требует применения специальных инструментов разработчика и технологий параллельного исполнения кода, и в рамках проектов лаборатории студенты с ними знакомятся. Данная статья является результатом работы студентов Вихрева Ивана, Рустамова Азера, Зайцевой Ксении, Кима Никиты, Бурдукова Михаила, Филатова Андрея.

Что есть звук в компьютере

Подавление фонового шума речи беспокоит людей уже очень давно, а с приходом дистанционной работы и обучения значимость проблемы возникла многократно. Разговаривать через интернет приходится все больше, и аудиосообщения встроены во все популярные мессенджеры. Как бы банально не звучало, но хороший, чистый звук человек воспринимает лучше. Однако хороший микрофон стоит денег, да и звукоизоляция в среднестатистической квартире оставляет желать лучшего, и мы слышим всё - от работы кулеров в ноутбуке и заканчивая дрелью надоедливого соседа (шум дрели у преподавателя особо мешает восприятию материала).

Как получить чистый звук, не покупая студийный микрофон и не обивая всю квартиру звукоизолирующими материалами? Над этим вопросом люди думают ещё с конца прошлого века. Решением стали программы, оцифровывающие и редактирующие входящий из микрофона звук.

Lizemijn Libgott / Vice.comLizemijn Libgott / Vice.com

Записанный звук состоит из множества звуковых волн, одновременно попадающих на датчик микрофона в некоторый промежуток времени, в результате чего мы получаем длинный вектор из чисел - это амплитуды (громкость) сигнала в течение небольшого времени. Частота сигнала проводного телефона 8kHz, это значит что мы за секунду 8000 раз измеряем амплитуду (громкость) суммарного сигнала, звуковые карты как правило используют частоту 44.1 или 48kHz.

На этой картинке 3 секунды звука или график на 120 тысяч значений. При таком сильном сжатии по оси X кажется, что мы закрасили площадь под графиком. На этой картинке 3 секунды звука или график на 120 тысяч значений. При таком сильном сжатии по оси X кажется, что мы закрасили площадь под графиком.

Воспроизвести аудиофайл в Python можно с помощью библиотек soundfile и sounddevice.

import sounddevice as sdimport soundfile as sfpath_wav = 'test_wav.wav'data, fs = sf.read(path_wav)sd.play(data, fs)status = sd.wait()

Запись данных с микрофона тоже происходит очень просто - посмотрите и запустите record.py.

Кстати, важная для нас информация. Наша последовательность является суммой множества звуковых волн, и мы можем вычислить какие волны приняли участи в нашей сумме. Теоретически, любой сложный звук может быть разложен на последовательность простейших гармонических сигналов разных частот, каждый из которых представляет собой правильную синусоиду и может быть описан числовыми параметрами (а вы говорили, что матан не нужен). Исследования по данной тематике входят в область цифровой обработки сигналов, на хабре есть информация об открытом курсе Основы цифровой обработки сигналов.

Справа мы видим спектр - вклад каждой из волн в частотное разложение. Схема с сайта nuancesprog.ruСправа мы видим спектр - вклад каждой из волн в частотное разложение. Схема с сайта nuancesprog.ru

Чтобы делать с аудио сложные вещи, такие как распознавание человека по голосу, перевод речи речи в текст или удаление шума с помощью глубокого обучения, нам понадобится вычислить вклад различных частот в аудиопоследовательность спектр. Спектр можно представить в виде спектрограммы изображения, показывающего зависимость амплитуды сигнала во времени на различных частотах. Один столбец в спектрограмме соответствует спектру короткого участка исходного сигнала, более тёплые тона означают большее значение.

Пример спектрограммы, полученной из звукового файла, при помощи библиотеки Numpy.Пример спектрограммы, полученной из звукового файла, при помощи библиотеки Numpy.

Спектр для спектрограммы можно вычислить с помощью дискретного преобразования Фурье, реализованного в библиотеке Numpy.Рассмотрим пример создания спектрограммы, описанный в сэмпле. Для этого используются две функции из файла features.py:

def calcSpec(y, params, channel=None):     """compute complex spectrum from audio file"""     fs = int(params["fs"]) # Константа, обозначающая частоту дискредитации     # В нашем случае равна 16000 - наш wav файл записан с частотой 16 кГц    if channel is not None and (len(y.shape)>1):     # Если аудио содержит два канала (стерео) - берем только один канал         sig = sig[:,channel]     # STFT parameters     N_win = int(float(params["winlen"])*fs) # Расчёт размера окна Хэннинга     # В нашем случае 320     if 'nfft' in params:         N_fft = int(params['nfft'])     else:         N_fft = int(float(params['winlen'])*fs) # Расчёт ширины окна для преобразования Фурье         # В нашем случае 320     N_hop = int(N_win * float(params["hopfrac"])) # Расчёт прыжка для преобразования Фурье     # В нашем случае 160     win = np.sqrt(np.hanning(N_win)) # Окно Хэннинга      Y = stft(y, N_fft, win, N_hop)     return Y 

Функция Stft проводит преобразование Фурье. Массив делится на части определённой длины (рассчитанной в calcSpec) и для каждой из частей применяется функция преобразования Фурье, взятая из Numpy возвращает готовую спектрограмму.

def stft(x, N_fft, win, N_hop, nodelay=True):     """     short-time Fourier transform     x - Входной сигнал     N_fft - Количество точек, на которых используется преобразование     win - Окно Хэннинга     N_hop - Размер прыжка    nodelay - Удаление первых точек из конечного массива (В них появляется побочный эффект преобразования)     """     # get lengths     if x.ndim == 1:         x = x[:,np.newaxis] # Если подано несколько файлов, то создаётся дополнительная ось     Nx = x.shape[0] # Количество точек во входных данных (в нашем случае 160000)     M = x.shape[1] # Количество файлов во входных данных (в нашем случае 1)     specsize = int(N_fft/2+1)     N_win = len(win) # Размер окна Хэннинга     N_frames = int(np.ceil( (Nx+N_win-N_hop)/N_hop )) # На сколько частей делим входной массив     Nx = N_frames*N_hop # padded length     x = np.vstack([x, np.zeros((Nx-len(x),M))])      # init     X_spec = np.zeros((specsize,N_frames,M), dtype=complex) # Заполненная нулями матрица, которая станет спектрограммой     win_M = np.outer(win,np.ones((1,M))) # Создаём матрицу, в которой каждый столбец равен окну Хэннинга     x_frame = np.zeros((N_win,M)) # Заполненный нулями вектор (вектора в случае если на вход дали несколько файлов)     for nn in range(0,N_frames):         idx = int(nn*N_hop)         x_frame = np.vstack((x_frame[N_hop:,:], x[idx:idx+N_hop,:])) # Разделяем входной массив на куски размера N_hop         x_win = win_M * x_frame         X = np.fft.rfft(x_win, N_fft, axis=0) # Преобразование возвращает столбец комплексных, где действительная часть - амплитуда, а комплексная - фазовый сдвиг         X_spec[:,nn,:] = X # Добавляем полученный столбец в спектрограмму      if nodelay:         delay = int(N_win/N_hop - 1)         X_spec = X_spec[:,delay:,:] # Удаляем лишний столбец из начала      if M==1:         X_spec = np.squeeze(X_spec) # Удаляем лишнюю ось      return X_spec

Также важной функцией является calcFeat, позволяющая нам прологарифмировать спектрограмму, растягивая нижние частоты и сжимая верхние. Голос человека лежит в диапазоне 85-3000Гц, а диапазон звуковых частот в нашей записи 16кГц маленький промежуток на всем диапазоне, и помощью логарифмирования мы растягиваем нужные нам низкие частоты и поджимаем ненужные высокие

def calcFeat(Spec, cfg):     """compute spectral features"""     if cfg['feattype'] == "MagSpec":         inpFeat = np.abs(Spec)     elif cfg['feattype'] == "LogPow":         pmin = 10**(-12)         powSpec = np.abs(Spec)**2 # Все значения спектрограммы возводятся в квадрат         inpFeat = np.log10(np.maximum(powSpec, pmin)) # и логарифмируются с обрезанием слишком низких значений     else:         ValueError('Feature not implemented.')      return inpFeat 

Наша глубокая модель удаления шума обучена на логарифмированных спектрограммах, поэтому предобработка данной функцией обязательна.Чтобы преобразовать спектрограмму, полученную применением фильтра (выход нейросети) на образ-Фурье, полученный с помощью функции calcSpec, в звук используется функция Spec2sig. В ней вычисляются параметры обратного преобразования Фурье и вызывается функция istft (обратное быстрое преобразование Фурье).

def spec2sig(Spec, params):    """Конвертирует спектрограмму в звук"""    # частота дискретизации    fs = int(params["fs"])    # ширина окна     N_win = int(float(params["winlen"])*fs)    if 'nfft' in params:        N_fft = int(params['nfft'])    else:        # длина быстрого преобразования Фурье        N_fft = int(float(params['winlen'])*fs)    #длина сегментов окна    N_hop = int(N_win * float(params["hopfrac"]))    # окно Хеннинга    win = np.sqrt(np.hanning(N_win))    # обратное преобразование Фурье    x = istft(Spec, N_fft, win, N_hop)    return x

В istft обратное преобразование Фурье также выполняется при помощи функции взятой из Numpy.

def istft(X, N_fft, win, N_hop):    # get lengths    specsize = X.shape[0] # Спектрограмма    N_frames = X.shape[1] #  кол-во кадров    if X.ndim < 3:        X = X[:,:,np.newaxis] # Приведение размера до 3    M = X.shape[2] # кол-во каналов    N_win = len(win) # длина окна хеннинга    Nx = N_hop*(N_frames - 1) + N_win    # Умножение матрицы win и единичной матрицы размера 1,M    win_M = np.outer(win,np.ones((1, M)))     x = np.zeros((Nx,M))  # нулевая матрица Nx,M для сохранения ответа        for nn in range(0, N_frames):        X_frame = np.squeeze(X[:,nn,:]) # Вектор по данному фрейму        # обратное преобразование фурье для X_frame ,N_fft        x_win = np.fft.irfft(X_frame, N_fft, axis=0)                x_win = x_win.reshape(N_fft,M) # изменяем размер        # получаем окно хеннинга нужного размера         x_win = win_M * x_win[0:N_win,:]        # добавляем результат для данного фрейма        idx1 = int(nn*N_hop); idx2 = int(idx1+N_win)        x[idx1:idx2,:] = x_win + x[idx1:idx2,:]         if M == 1:        x = np.squeeze(x) # Убираем лишние измерения если канал один         return x

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

Речь до и после удаления шумаРечь до и после удаления шума

Чтобы очистить звук, к цифровому описанию необходимо применить фильтр, который убирает нежелательные шумы. Но возникает другая проблема. Каждый из видов шумов требует свой фильтр, который необходимо подбирать вручную или искать в банках данных фильтров. Отфильтровать шум на частотах, отличающихся от человеческой речи, проблем нет, от них избавлялись еще до этих ваших нейросетей. А вот убрать детский плач или стучание клавиш без значительного ухудшения качества голоса было проблематично.

В решении данной проблемы могут помочь модели глубокого обучения. Основное преимущество нейросетей перед заранее подготовленными фильтрами заключается в большем охвате различных видов шумов. Нейросеть можно натренировать, постоянно добавляя всё новые виды шума.

В нашем случае мы воспользуемся моделью NSNet2. Эта нейронная сеть использовалась в Deep Noise Suppression Challenge, проводимом компанией Microsoft. Целью разработки данной сети было создание модели для очистки звука от шума в реальном времени. Данная модель состоит из полносвязного слоя с ReLU, двух рекуррентных GRU (Gated Recurrent Unit) блоков и полносвязных слоев (FF, feed forward) с ReLU и sigmoid активацией.

На речь влияет большое количество внешних условий. Человек может говорить громко или тихо, быстро или медленно, говорить он может в большой комнате или в маленькой, далеко от микрофона или близко к нему. Для моделирования этих более сложных условий, были применены аугментации. В частности, в данном случае, для модификации звука использовались случайные биквадратные фильтры. Благодаря применения таких аугментаций, зашумление звука наиболее приближено к реальным условиям.

Представленные результаты по качеству работы можно посмотреть в статье Data augmentation and loss normalization for deep noise suppression. Построенная модель имеет хорошие показатели для различных типов шума.

Конвертация модели в OpenVINO

При работе с нашей моделью мы использовали OpenVINO (Open Visual Inference & Neural Network Optimization) - продукт, разрабатываемый компанией Intel. Как видно из названия, OpenVINO - это набор инструментов для исполнения и оптимизации нейронных сетей.

Существует множество фреймворков для создания и тренировки нейросетей. Для того, чтобы можно было запускать нейросети из различных фреймворков на любом интеловском железе, в составе OpenVINO есть модуль Model Optimizer.

Мы берем обученную модель в каком-либо фреймворке, конвертируем в OpenVINO и теперь можем запустить хоть на CPU, хоть на iGPU или dGPU, хоть на FPGAМы берем обученную модель в каком-либо фреймворке, конвертируем в OpenVINO и теперь можем запустить хоть на CPU, хоть на iGPU или dGPU, хоть на FPGA

По факту, Model Optimizer - это набор python-скриптов, которые позволяют привести нейронные сети различных форматов к некоторому универсальному представлению, называемому IR (Intermediate Representation). Это позволяет OpenVINO работать с любой нейросетью, независимо от того, из какого фреймворка она взята.

В процессе своей работы Model Optimizer также оптимизирует структуру сверточных нейронных сетей. Например, объединяя результаты сверток, заменяя слои на последовательность линейных операций и т.д.

В последнее время, с появлением API, в Model Optimizer проводится все меньше оптимизаций, и основная его работа сводится к конвертации моделей без каких-либо серьезных изменений.

Конвертация в IR-представление различается для моделей из Open Model Zoo и других моделей. Open Model Zoo репозиторий глубоких нейросетевых моделей, содержащий большое количество обученных моделей, которые могут исполняться при помощи OpenVINO. Данный репозиторий хранит не только модели, но и параметры для конвертации моделей из разных фреймворков в промежуточный формат OpenVINO.

Для конвертации моделей, загруженных из Open Model Zoo, нужно воспользоваться инструментом Model Optimizer и входящим в него скриптом converter.py. Данный модуль имеет доступ к параметрам конвертации моделей из зоопарка моделей.

Консольная команда для конвертации загруженной модели:

python converter.py --name <имя модели> --download_dir <путь до папки, в которую скачали модель>   

Чтобы сконвертировать собственную модель, необходимо использовать скрипт mo.py с дополнительными параметрами:

python mo.py --input_model <путь до модели> --output_dir <путь до папки, в которую поместить конвертированную модель> --input_shape <размеры входа модели>  

Для конвертации нашей ONNX модели в формат OpenVINO (в Windows) вышеприведенная команда выглядит так:

python mo.py --input_model <путь до папки с моделью>\nsnet2-20ms-baseline.onnx -output_dir <путь до папки, в которую поместить конвертированную модель> --input_shape [1, 1000, 161]   

где 1 - количество каналов, 1000 - временных интервалов, 161 - частот.

Также, можно указать больше дополнительных параметров для удобства. Весь список возможных параметров можно посмотреть командой:

python mo.py --help

Отличие converter.py от mo.py лишь в том, что converter.py использует параметры для конвертации из описания модели в Open Model Zoo и передает их в mo.py


Надо отметить, что проблема шумоподавления до сих пор не решена полностью. Методике улучшения речи с помощью нейронных сетей в последнее время уделяется огромное внимание как в научных исследованиях, так и в коммерческих приложениях. Одним из важнейших преимуществ использования нейронных сетей для подавления шума является то, что они способны очищать звук от нестационарных шумов. Ранее известные подходы не позволяли этого сделать.

Нейросети не идеальны и избавиться абсолютно ото всех шумов не представляется возможным. Однако, представленная модель показала хорошие результаты в очистке речи в домашних условиях.

Подробнее..

Как запихать нейронку в кофеварку

27.10.2020 10:11:01 | Автор: admin
Мир машинного обучения продолжает стремительно развиваться. Всего за год технология может стать мейнстримом, и разительно измениться, придя в повседневность.
За прошедший год-полтора, одной из таких технологий, стали фреймворки выполнения моделей машинного обучения. Не то, что их не было. Но, за этот год, те которые были стали сильно проще, удобнее, мощнее.

В статье я попробую осветить всё что повылезало за последнее время. Чтобы вы, решив использовать нейронную сеть в очередном калькуляторе, знали куда смотреть.

Перед началом статьи нужно сразу сказать несколько дисклеймеров:
  • Мой обзор будет со стороны ComputerVision. Не стоит забывать что ML это не только CV. Это ещё классические алгоритмы бустинга, это различный NLP (от трансформеров до синтеза речи). И далеко не все задачи ML можно будет исполнять на тех фреймворках про которые я буду говорить.
  • Из обозначенных технологий я сам работал где-то с третью. Про остальные читал/минимально щупал/общался с людьми которые интегрировали. Отсюда могут возникнуть какие-то ошибки в статье. Если вы видите что-то что вас покоробило/с чем вы не согласны пишите в комментариях/в личку попробую поправить.
  • Мир стремительно меняется. Я пробую верифицировать то что пишу на момент выхода статьи. Но не факт что даже существующая документация соответствует истинному положению дел. Не говоря уже о том, что в ближайшие пару недель любой из указанных фреймворков может катастрофически измениться. Если вдруг такие апдейты вам будут интересны то скорее всего я буду разбирать их в своём блоге CVML (он же в телеге), где я такую мелочь пишу. Тут буду стараться оставлять ссылки на разбор.

Для тех кому лень читать записал видео с содержимым статьи:

Начнём.

Часть 1. Что такое инференс


Мир нейронных сетей можно разбить на две части:
  • обучение
  • использование

Обучение сложнее. Оно требует больше математики, возможность анализировать какие-то параметры по ходу. Нормой является подключение к TensorBoard и прочим инструментам мониторинга. Нужны простые способы интеграции новых слоёв, быстрота модификации.
Чаще всего для обучения используются Nvidia GPU (да, есть TPU от Google, или Intel Xe, но скорее это редкость). Так что софт для обучения должен хорошо поддерживать одну платформу.
Нужно ли обучение при использовании нейронной сети в проде? Очень редко. Да, у нас было несколько проектов с автоматическим дообучением. Но лучше этого избегать. Это сложно и нестабильно.
Да и если нужно, то проще утащить на внешний сервак и там дообучить.
image
Как следствие можно отбросить 90% математики и обвеса, использовать только выполнение. Это и называется инференс. Он быстрее, чем обучение. Требует сильно меньше математики.
Но вот засада. Не тащить же для инференса GPU. Инференс может быть и на десктопах, и на мобильниках, и на серверах, и в браузере.
В чём его сложность?
Нейронные сети едят много производительности нужно либо специальное железо и его поддержка, либо максимальная утилизация существующего, чтобы хоть как-то достать производительность.
А железо очень-очень разное. Например в телефонах Android может существовать с десяток различных вычислителей, каждый из которых имеет свою архитектуру. А значит ваш модуль должен быть универсален для большинства.

Часть 2. Железо.


Железо в ML бывает очень разное. Его хотя бы примерное описание на текущий момент будет требовать десятка статей. Могу вам посоветовать очень крутую статью от 3Dvideo про технологии аппаратного ускорения нейронных сетей. И свою статью про то как устроены embedding системы в последнее время.
И то и то уже немного неактуально, ведь всё быстро-быстро меняется;)

Для упрощения понимания статьи, или для тех кто не хочет углубляться я накидал упрощённую схему которой мы будем оперировать (кликабельно):

Тут мы видим несколько направлений инференса:
  • Серверный инференс. Мы не полезем в него глубоко. Обычно когда вам нужно чтобы сеть выполнялась на сверхпроизводительном сервере редко нужно чтобы данный алгоритм крутился на старой мобилке. Но, тем не менее, тут будет что помянуть
  • Десктопный инференс. Это то, что может крутиться у вас на домашнем компе. Обработка фотографий. Анализ видео, компы которые ставятся на предприятия, и прочее и прочее будет именно здесь.
  • Мобильный инференс всё что касается телефонов, Android и Ios. Тут огромное поле. Есть GPU, есть специальные ускорители, есть сопроцессоры, и прочее и прочее.
  • Embedded. Частично эта часть пересекается с десктопами, а частично с мобильными решениями. Но я вынес её в отдельную ветку, так как часть ускорителей ни на что не похожи.

Основная проблема видна уже тут. Большинство конечных точек требуют свою систему инференса. Если вы хотите универсальную систему выполнения нейронной сети которая будет использовать 100% мощности, тогда вам нужно иметь модуль для каждой конечной точки этого дерева.
Есть фреймворки которые теряют в разветвленности, но неплохо работают там где работают. Есть которые используют максимально популярные устройства, но не работают на других. Опять же, если показать это ооочень примерно, то выйдет как-то так:

Поговорим подробнее, скорее слева на право.

Специализированное железо: Gyrfalcon, Khadas, Hikvision, и.т.д.


Всё это добро я решил выделить в одну группу. Фреймворком это назвать сложно. Обычно это специализированный софт который может перенести нейронку на железо, чтобы она выполнялась там оптимально. При этом железяка у производителей зачастую одна или две.
Отличительной особенностью таких платформ является:
  • Не все слои поддерживаются
  • Софт часто не выложен в OpenSource. Поставляется вместе с аппаратной частью или требует подписания дополнительного NDA

Из-за отсутствия публичности работа с такими фреймворками это обычно боль. Но да, есть ситуации когда такое решение это единственный выход, и приходится осваивать.
Более подробно рассказывать про эту часть наверное нет смысла.

Специализированное железо но от крупных фирм


Крупные производители железа обычно имеют много разного железа, но всё оно программируется в рамках одного фреймворка. Это открытые фреймворки, которые можно спокойно скачать и использовать.
Они позволяют портировать нейронку и использовать её максимально эффективно.
В большинстве случаев поддерживаются все современные архитектуры, а если не поддерживаются, то есть другие способы их использовать или добавить.
Поговорим про них подробнее:

Nvidia TensorRT


Наверное это самая классика, и не надо рассказывать почему. Больше 90% ресёрча использует именно видюхи NVIDIA. Инференс часто на них же => все стараются выжать максимум, а для этого нужны TensorRT специальный фреймворк который максимально утилизирует мощь видеокарты для нейронных сетей.
Более того, если вы пишите на CUDA то можете в рамках одного обработчика обрабатывать данные. Самый классический пример NMS. Например, когда-то давно мы переписывали кусок одного детектора поз на CUDA чтобы не гонять данные на процессор. И это очень ускоряло его работу.
Нужно понимать, что NVIDIA целит в три области, и везде TensorRT используется:
  • Серверные платформы (Tesla)
  • Десктопы (видеокарты обычные и полу специализированные)
  • Embedded платформы серии Jetson.

Плюс TensorRT в том, что он достаточно стандартен. Он есть в TensorFlow (tf-trt), есть в OpenCV. Основной минус для меня под Windows нет поддержки в Python, только через сторонние проекты. А я люблю иногда что-то под виндой потыкать.
Мне кажется, что если вы делаете инференс на Nvidia, то у вас просто нет альтернатив надо использовать TensorRT. Всё остальное приведёт к падению производительности.

Triton Inference Server


Но, так как мы говорим про инференс, то нужно упомянуть Triton Inference Server ( github.com/triton-inference-server/server ). Это не совсем TensorRT (хотя TensorRT подразумевается как оптимальный для него фреймворк). Triton может использовать TensorFlow, PyTorch, Caffe. Он сам ограничивает память и настраивает любой из упомянутых фреймворков, управляя выполняющимися сетями.
Triton сам решает какие модели загружать-выгружать из памяти, сам решает какой batch использовать. Может использовать не только TensorRT модели, но и модели *.pd, *.pth, ONNX, и.т.д., (ведь далеко не все можно сконвертировать в tensorrt). Triton может раскладывать по нескольким GPU. И прочее и прочее.
Мы использовали его в продакшне в нескольких проектах и остались ужасно довольны. Максимальная утилизация GPU с минимумом проблем.
Но Он не сможет выполнить модель где-то за пределами Nvidia

Intel OpenVino


Intel представлен на рынке:
  • Серверных вычислителей (Intel FPGA, Xe GPU, Xeon)
  • Десктопов (с i3 начиная года с 2015 поддерживается почти всё). Intel GPU работает но не сверх круто.
  • Embedded платформ (movidius)

И для всего можно сварить модель через OpenVino.
Мне нравиться OpenVino так как он достаточно стабилен, прост, и имеет инференс почти везде. Я писал на Хабре статью в которой рассказывал опыт одного хоббийного проекта под Intel. Там я отлаживал на десктопах, а тестировал на RPi с мовидиусом. И все было норм.
В целом, все 2-3 проекта которые мы делали в своей практике под OpenVino, прошли примерно так же. Минимум сложностей, удобный инференс, заказчик доволен.
Open Vino интегрирован в OpenCV про который мы ещё поговорим. OpenCV тоже от Intel, но я бы рассматривал его как отдельный фреймворк/способ инференса и подхода к данным.
Отдельно я бы хотел отметить один забавный момент. Аренда GPU сервера для того чтобы развернуть модель в онлайне будет стоить где-то от 10к. рублей, где-то до 100к. рублей, в зависимости от используемых видюх.
Аренда сервака с I5 на каком-нибудь клауде зачастую возможна за 500-1000 рублей в месяц.
Разница в производительности между TensorRT и ONNX на сравнимых по цене процах может быть в 2-20 раз (зависит от сети). Как следствие часто можно неплохо сэкономить перенеся онлайн инференс на ONNX.

Google Edge


Google очень неоднозначная фирма сама по себе. Мало того, что Google имеет свой стек аппаратуры для инференса нейронных сетей. Ещё у Google целый стек различных фреймворков про которые мы поговорим позже. Запутанный и местами бажный. Но в целом это:
  • TensorFlow Edge
  • TensorFlow lite
  • TensorFlow JS
  • TensorFlow (чистый, или оптимизированный под какую-то платформу, например Intel TensorFlow)

В этой части мы говорим именно про отдельную ветку Edge ( coral.ai ). Она используется в Embedded продуктах, и в некоторых mobile продуктах.
Минусом Edge является то, что из коробки поддерживаются не все модели, гарантирована поддержка лишь нескольких ( coral.ai/models ). А конвертация достаточно усложнена (TF -> TF Lite -> Edge TPU):
image
Конвертация в Cloud TPU чуть проще, но и там есть свои особенности.
Я не буду более подробно рассказывать про особенности этого фреймворка. Скажу лишь что каждый раз когда с ним сталкивался оставались неприятные впечатления, так что не стали тащить его нигде в прод.

Универсализация с хорошей аппаратной поддержкой


Мы переходим в область подходов, которые достаточно оптимально используют железо, но будут требовать немного разный код на разных платформах (нельзя же на Android всё сделать на Python).
Тут я бы упорядочил как-то так (опять же, очень условно):

Пройдем по этому списку

OpenCV


OpenCV это легендарный фреймворк ComputerVision появившийся более 15 лет назад. За свои 12 лет занятий ComputerVision я запускал его на Arm году в 2010, на BlackFin примерно тогда же, на мобильниках, на серверах, на RPi, на Jetson, и много-много где. У него есть классные биндинги на Python, Java, JavaScript, когда-то я вовсю использовал его на C#.
OpenCV более чем живой и сейчас. И нейронные сети активно просачиваются туда с самого их появления.
Мне кажется, что на сегодня у OpenCV есть несколько глобальных минусов:
  • Очень мало актуальной документации. Попробуйте найти где-нибудь гайд по всем бекендам модуля DNN в OpenCV и по поддержке их на всех платформах?:) Конечно, можно изучить хедеры, но это не говорит о том что где будет работать. Порог входа и первого прототипа достаточно высок. Не в пример того же OpenVino.
  • Нейронные сети под Android поддерживаются ( docs.opencv.org/master/d0/d6c/tutorial_dnn_android.html ). Но я не находил актуального гайда про аппаратную поддержку GPU или других вычислителей.

Тут кажется, что всё грустно. Но! Стоит поставить НО даже большими буквами. Главный плюс OpenCV очень хорошая поддержка CUDA и OpenVINO. Судя по всему запланирована и поддержка любых OpenCL устройств, но пока информации мало.
По нашим тестам в OpenCV нейронные сети на CUDA выполняются медленнее чем в TensorRT всего на 5-10%, что отлично.
Это делает OpenCV весьма ценным фреймворком для серверных решений, где нужно выжимать максимум из имеющегося ускорителя, какой бы он не был.

Так же OpenCV очень неплохо поддерживает различные CPU на ARM-устройствах. На том же RPi он использует NEON.

Tensorflow lite


Чуть ближе к мобильному миру лежит TensorFlow lite. Он может исполнять нейронные сети как и на обычном GPU мобильного устройства, так и на возможных сопроцессорах, если производитель телефона соблюдает какой-то набор стандартов. Для мобильников возможные варианты выглядят примерно так:
image
В большинстве случаев вы автоматически можете проверить максимальный уровень ускорения и запустить именно на нём. Детальной карты того какие вендоры предоставляют какую поддержку железа я не нашёл. Но я видел несколько примеров которые начались поддерживаться. Например под Snapdragon мы когда-то портировали сети, а потом TFLite начал его поддерживать.
Чуть более подробно о том что насколько даёт прирост можно посмотреть, например, тут ai-benchmark.com/ranking_detailed
Так же, стоит упомянуть, что изначально поддержка GPU шла за счёт OpenGL, но в последнее время Google добавила и OpenCL, чем почти в 2 раза ускорила выполнение сетей.
Но прелесть TFlite не только в том что он работает под мобильниками. Он ещё достаточно неплох для части embedded устройств. Например, он достаточно эффективно работает под RaspberryPI.
На последнем DataFest ребята из X5 рассказывали что они используют эту конструкцию в продакшне.
Так же, TFlite, за счёт XNNPACK неплохо работает на процессорах (но не так эффективно как OpenVino на Intel). Это даёт возможность использовать TFLite как способ инферить модели на десктопах. Хоть и без поддержки GPU. Зато без тонны лишних зависимостей.

ONNX runtime


В какой-то момент в гонку ворвался Microsoft, который решил зайти со стороны. Сначала они выпустили универсальный формат сохранения нейронных сетей, который сейчас достаточно популярен, ONNX.
И, когда уже форматом многие начали пользоваться, решили сделать свой рантайм для него.

Если честно, то я даже не знаю половину того что представлено в списке..:)
Выглядит всё идеально (а ещё всё поддерживается на Python, Java, C, C++, C# и.т.д.)
Но пока что мне не довелось использовать ONNX-runtime на практике, кроме ONNX.js, о котором будет ниже. Так что не могу гарантировать что всё работает идеально. В интернетах слишком мало примеров того как всё работает. И знакомых которые бы на этом разворачивали продакшн полноценный тоже не знаю (максимум тестировали но не решили развернуть).
Но в гайдах уверяется что даже для Raspberry PI и Jetson есть поддержка.
Про поддержку ios явно ничего не сказано. Но местами ios встречается по коду. А кто-то билдит.

PyTorch


Одна из проблем использования OpenCV, TFlite, ONNX Runtime: А почему мне надо обучать в одном фреймворке, а использовать в другом?. И авторы PyTorch тоже задают себе такой вопрос. Так что, прямо из коробки, предоставляют способ использования PyTorch на мобильниках:
image
Скажу честно, я не тестировал скорости инференса. Но по опросу знакомых в целом все считают это медленным вариантом. По крайней мере инференс PyTorch на процессорах и GPU тоже не самый быстрый. Хотя, опять же, XNNPACK используют.
Но, данный вариант вполне удобен для разработки и пуша в продакшн (нет лишних конвертаций, и.т.д.). Мне кажется, что иногда это хороший вариант (например когда нет требований на очень высокую производительность).

TensorFlow


Кроме использования TFlite можно использовать и оригинальный TF, для развертывания на embedded устройствах и десктопах. При этом TF имеет достаточно высокую степень конфигурируемости. Но, на мой взгляд, это достаточно мертвый вариант в будущем.

Китайское вторжение


Теперь мы переходим к двум вариантам, которые не тестировал почти никто из моих знакомых. Но которые выглядят весьма перспективно.
Первый из них MNN, вариант от alibaba.
image
Авторы уверяют, что MNN самый легковесный из фреймворков, который обеспечивает инференс на большом числе устройств, при использовании GPU или CPU. При этом поддерживает большой спектр моделей.

Второй вариант интереснее, это ncnn от Tencent. Согласно документации библиотека работает на большом числе устройств. Реализована на c++:

На неё даже Yolov4 спортировано. И в целом, AlexeyAB очень позитивно отзывался.
Но, опять же, у меня достаточно мало информации о практическом применении. Сам не использовал, в проектах которые видел/консультировал никто не использовал.
При этом можно отметить, что не так много нативных решений одновременно поддерживают и телефоны и nvidia.

JS Tensorflow.JS, ONNX.js


Я бы объединил эти две категории в общий раздел, хотя, конечно, у TensorFlow.js и ONNX.js есть много различий. TensorFlow чуть лучше поддерживает оптимизацию. ONNX чуть быстрее. LSTM не присутствует в ONNX. И прочее и прочее.
image
В чем особенность этого класса инференс фреймворков? В том, что они выполняются только на CPU, или на GPU. По сути через WebGL или через WebAssembly. Это наборы инструкций, которые доступны через браузерный JS.
Как плюс имеем офигенную универсальность такого подхода. Написав один раз код на JS можно исполнять его на любом устройстве. Если есть GPU на нём. Если только CPU на нём.
Основной минус тоже понятен. Никакой поддержки за пределами CPU или GPU (ускорители/сопроцессоры). Невозможно использовать эффективные способы утилизации CPU и GPU (те же OpenVino или TensorRT).
Правда, под Node.js, TF.js умеет в TPU:
image

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

Мы использовали в проектах оба инференса, всё работало хорошо. Но, конечно, производительность немного жалко.

Как небольшое резюме


Выводы сложно делать. Я не смог придумать какого-то однозначного алгоритма который бы помогал выбрать платформу на которой нужно разворачивать ML решение в зависимости от бизнес требований, аппаратуры и сложности сетей.
Как мне кажется:
  • Если вам нужно максимально широкое использование на всех платформах, то, похоже, наиболее универсален ONNX runtime. Единственное, он может местами сыроват + ios поддержан весьма просто. Не уверен, что все ускорители одинаково хорошо работают. Вариант Tencent-ncnn
  • Если вам нужно максимум платформ, без мобильных, то я бы взял OpenCV. Он достаточно удобен и стандартен.
  • Если вам не нужны десктопные платформы, тогда я бы использовал TFlite
  • Если же вы хотите делать на какой-то стандартной платформе, и чётко понимаете что никуда за её пределы не пойдёте то я бы использовал специализированную платформу под эту платформу. Будь это TensorRT или OpenVino. И на TensorRT и на OpenVino мы разворачивали очень сложные проекты. Непреодолимых проблем не встретили.

В целом (очень условно) как-то так:


А так Тема объёмная. Ведь чтобы достаточно подробно рассмотреть TensorFlow lite со всеми плюсами и минусами надо написать две таких статьи. А для полного обзора OpenCV и десяти не хватит. Но сил написать столько нет.
С другой стороны, я надеюсь, написанное поможет кому-то хоть немного структурировать логику при выборе платформы для инференса.
А если у вас есть и другие идеи как выбирать пишите!

P.S.


В последнее время делаю много мелких статей/видеороликов. Так как это не формат Хабра то публикую их в блоге или на ютубе. Подборка всего есть в телеге и вк.
На Хабре обычно публикую, когда рассказ становится уже более самозамкнутым, иногда собрав 2-3 разных мини-рассказа на соседние темы.
Подробнее..

OpenVINO Toolkit залог успешного внедрения видеоаналитики для качественной скоринговой оценки недвижимости

08.12.2020 04:14:55 | Автор: admin

Всем привет! Сегодня расскажем и покажем, как машинное обучение и компьютерное зрение в очередной раз помогают в решении различных задач. В этот раз наша команда приняла участие в кейсе от ООО Финкейс в рамках конкурса Цифровой прорыв Северо-Кавказского IT-хаба.

Нам предложили разработать прототип интеллектуальной системы по определению качества ремонта квартир на основе алгоритмов компьютерного зрения с использованием инструментария Intel OpenVINO (Open Visual Inference & Neural Network Optimization).

Кейс:

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

Для решения задачи было реализовано два классификатора: первый для определения типа ремонта (без отделки, косметический ремонт, стандартный ремонт и ремонт класса люкс), второй для определения типа помещения. Для обучения классификатора по типу ремонта использовалась модель нейронной сети Resnet50. Для ее обучения мы собрали датасет из 50 тысяч изображений, по 12500 изображений для каждой категории. Обучили и сконвертировали её в ONNX, а из ONNX уже в OpenVINO.

Для конвертации нашей ONNX модели в формат OpenVINO при помощи инструмента Model Optimizer использовалась следующая команда:

python3 mo.py --input_model <INPUT_MODEL>.onnx

Затем реализовали код для запуска модели в фреймворке OpenVINO. По итогу точность распознавания составила 93%. Исследования продолжаются, поэтому на данный момент собираемся для классификаций помещений по типу ремонта взять модель Resnet152 (если хотите узнать результат, напишите в комментариях, поделимся).

Для второго классификатора, то есть для определения типа помещения использовался инструментарий Monk. Они имеют огромную базу предобученных моделей. Искренне советуем заглянуть и познакомиться!

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

Сегментированные объектыСегментированные объекты

В результате в качестве функционала был реализованы 2 классификатора, определяющие с точностью 93% тип помещения (спальня, кухня, ванная комната, гостиная) и тип ремонта (люкс, стандартный ремонт, косметический ремонт, без отделки). А для более детальной скоринговой оценки недвижимости реализована автоматическая оценка стоимости мебели и предметов декора в квартире. Для более детальной скоринговой-оценки недвижимости реализовали автоматическую оценку стоимости мебели и предметов декора в квартире. Для начала сегментируются все объекты на изображении и выделяются их контуры, можно рассмотреть это на представленном выше изображении. После чего формируется список найденных объектов для оценки. Стоимость каждого найденного объекта интерьера определяется с помощью API Яндекса, суммируя их, получается общая стоимость интерьера помещения.

По завершении выполнения алгоритма система выдает типы комнаты и ремонта, найденные объекты и их суммарная стоимость. Проект на данный момент дорабатывается: проводятся исследования в области применения и использования других нейросетевых алгоритмов для классификации объектов, дополняется интерфейс и его функциональность. Но система и сейчас имеет рабочую версию для демонстрации результатов.

В результате защиты проекта наша команда ISUvision (Божко Мария,Сторожева Ксения, Рязановский Владимир, Данилов Руслан, группа 19-ИСТ-1, ИРИТ) с руководителем команды Багировым Мираббасом Бахтияровичем была признана достойной участия в финале Всероссийского конкурса Цифровой прорыв.

Спасибо за внимание! Советуем заглянуть в другую нашу статью и познакомиться с нашим опытом использования инструментария Intel OpenVINO не только на хакатонах, но и для решения настоящих бизнес задач.

Подробнее..

Из песочницы Быстрый старт в видеоаналитику Опыт использования OpenVINO Toolkit в хакатонах

17.10.2020 18:18:02 | Автор: admin
image alt

Всем привет! Мы активные студенты НГТУ им. Р.Е. Алексеева, и мы хотим рассказать о своем опыте участия в хакатонах и создании IT-решений с использованием набора инструментов Intel OpenVINO (Open Visual Inference & Neural Network Optimization) отличной палочки-выручалочки при разработке систем видеоаналитики.


Для начала расскажем немного о себе. Мы студенты 3 курса ИРИТ, кафедра Информатика и систем управления Татьяна Бородина, Тимофей Карклин, Александр Зенкин и Владимир Салтыков. С 1 курса мы активно участвуем в различных конкурсах IT-сферы, создав команду MirITeam[Прим. модератора: ссылка убрана, чтобы не нарушать правила. Google it.] команду молодых и целеустремленных ребят. Мы разрабатываем стартапы в области компьютерного зрения и видеоаналитики, выступаем на научных конференциях и очень любим Хакатоны, их атмосферу и дух соревнования, где быстро нужно разработать хорошее, качественное решение, привнести в него изюминку, и успешно (из опыта это очень и очень важно) защитить свой проект перед жюри. Это ценный опыт реализации инновационных идей, получения новых знаний и качеств и, конечно же, командного сотрудничества.


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


Итак, наш кейс и его защита выглядит так.


Все ушли на удаленку. И даже учебные заведения. Зачеты, экзамены и сессии никто не отменял. И если в оффлайн экзамене существует тысяча способов списать, чтобы преподаватель не заметил, то при сдаче экзамена онлайн способов списать в разы больше. Эту проблему мы и предлагаем решить в рамках хакатона с помощью машинного обучения и компьютерного зрения.


Наша система использует инструментарий Intel в виде моделей OpenVINO для видеоаналитики (о них мы расскажем чуть более подробно ниже), работу с процессами компьютера, контроль кликов и скроллингов, а также ближайшие соединения по протоколу Bluetooth. Использование последних методов исключает не только возможность использования таких сторонних ресурсов, как открытые вкладки браузера с подсказками, любые текстовые инструменты, но и использование дополнительных систем связи (Discord, Skype, Zoom и т.п.). Обнаружение всех этих аномалий поведения максимально исключает использование уловок и значительно повышает честность сдачи дистанционного экзамена.


image alt

После того, как мы поставили перед собой задачу, требующую решения, началось обсуждение способов анализа параметров студента и сразу всем в голову пришло одно и тоже это классный случай использования библиотеки OpenVINO и репозитория предварительно обученных моделей Open Model Zoo это наш помощник и даже преданный сообщник в кейсе по анализу параметров студента.
Мы использовали каскад из глубоких моделей (facial-landmarks-35-adas, head-pose-estimation-adas, open-closed-eye, gaze-estimation-adas), что в режиме реального времени позволило находить ключевые точки лица и анализировать такие параметры студента, как положение головы и направление движения его глаз. Кроме того, для аутентификации студента и исключения появления посторонних людей в кадре мы использовали Single Shot MultiBox Detector, а именно его Caffe реализацию для быстрого переобучения под конкретного человека и сравнения с полученным с камеры через метод опорных векторов (SVM алгоритм).


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


image alt

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


С нами согласятся многие для нас, студентов, в условиях конкуренции на рынке труда и желании реализоваться в профессии, главное получить знания, но при этом так же важно получить объективную оценку наших знаний. Кроме того, разработанная система позволяет в чем-то облегчить труд преподавателей, хотя бы на экзаменах.


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


По итогу разработчикам мы очень советуем заглядывать в репозиторий c готовыми демо-приложениями и моделями OpenVINO не только в рамках конкурсов с использованием компьютерного зрения и видеоаналитики, но и при построении серьезных приложений, ну а студентам напомним слова Бенджамина Франклина: Незнание не стыдно, стыдно не стремиться к знаниям.

Подробнее..

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru