Оценка положения

Материал из Викиконспекты
Версия от 11:04, 24 апреля 2020; Id0ntmind (обсуждение | вклад) (Отслеживание направления взгляда пользователя в браузере)
Перейти к: навигация, поиск
Эта статья находится в разработке!

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

Области применения

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

  1. Транспортные средства с встроенными системами помощи водителю (автопилот, круиз контроль и др.). Которые помогают водителю с парковкой, контролируют скорость и направление движения, а также предупреждают об объектах, находящихся на дороге, о типе дорожного покрытия и возможных авариях.
  2. Дополненная реальность: устройства в которых в реальное изображение, получаемое с помощью видеокамер, встраивается некоторая информация полезная человеку.
  3. Виртуальная реальность: оценка положения, как технология является критически важной для достижения эффекта погружения в виртуальную реальность. В сочетании с отслеживанием ориентации становится возможным измерять и передавать в ВР все 6 степеней свободы (6-DoF) реального мира
  4. Робототехника: Роботы (Медицинские, научные, промышленные и др.) которые основывают свое движение на построении карты окружения и препятствий.
  5. Веб-технологии: Исследование юзабилити и пользовательского опыта.

Методы решения задачи оценки положения

Акустические методы

Акустические приборы слежения используют ультразвуковые (высокочастотные) звуковые волны для измерения положения и ориентации целевого объекта. Для определения положения объекта измеряется время пролёта (time-of-arrival) звуковой волны от передатчика к приёмникам, либо разность фаз синусоидальной звуковой волны при приёмо-передаче. Алгоритмы трекинга положения при использовании акустических приборов основаны на Трилатерации и расчете Угла Прибытия.При использовании данных методов, разработчики сталкиваются с некоторыми проблемами: акустические трекеры, как правило имею низкую скорость обновления связанную с низкой скорости звука в воздухе, скорость звука в воздухе зависит от внешних факторов среды, таких как температура, давление и влажность

Радиочастотные методы

Методов, основанных на радиочастотах достаточно много.

  1. Позиционирования с использованием пассивных радиочастотных идентификаторов RFID
    Основное назначение систем с пассивными RFID метками – идентификация. Они применяются в системах, традиционно использовавших штрих-коды или магнитные карточки – в системах распознавания товаров и грузов, опознания людей, в системах контроля и управления доступом (СКУД) и т.п.Система включает RFID метки с уникальными кодами и считыватели и работает следующим образом. Считыватель непрерывно генерирует радиоизлучение заданной частоты. ЧИП метки, попадая в зону действия считывателя, использует это излучение в качестве источника электропитания и передает на считыватель идентификационный код. Радиус действия считывателя составляет около метра.
  2. Позиционирование с использованием активных RFID
    Активные радиочастотные метки используются при необходимости отслеживания предметов на относительно больших расстояниях (например, на территории сортировочной площадки). Рабочие частоты активных RFID – 455МГц, 2,4ГГц или 5,8ГГц, а радиус действия – до ста метров. Питаются активные метки от встроенного аккумулятора.Существуют активные метки двух типов: радиомаяки и транспондеры. Транспондеры включаются, получая сигнал считывателя. Они применяются в АС оплаты проезда, на КПП, въездных порталах и других подобных системах.Радиомаяки используются в системах позиционирования реального времени. Радиомаяк отправляет пакеты с уникальным идентификационным кодом по команде либо с заданной периодичностью. Пакеты принимаются как минимум тремя приемниками, расположенными по периметру контролируемой зоны. Расстояние от маячка до приемников с фиксированными координатами определяются по углу направления на маячок Angle of arrival (AoA), по времени прихода сигнала Time of arrival (ToA) или по времени распространения сигнала от маячка до приемника Time-of-flight (ToF).Инфраструктура системы строится на базе проводной сети и в двух последних случаях требует синхронизации.
  3. Ultra Wideband (UWB) позиционирование
    Технология UWB (сверхширокополосная) использует короткие импульсы с максимальной полосой пропускания при минимальной центральной частоте. У большинства производителей центральная частота составляет несколько гигагерц, а относительная ширина полосы – 25-100%. Технология используется в связи, радиолокации, измерении расстояний и позиционировании.Это обеспечивается передачей коротких импульсов, широкополосных по своей природе. Идеальный импульс (волна конечной амплитуды и бесконечно малой длительности), как показывает анализ Фурье, обеспечивает бесконечную полосу пропускания. UWB сигнал не походит на модулированные синусоидальные волны, а напоминает серию импульсов. Производители предлагают разные варианты UWB технологии. Различаются формы импульсов. В некоторых случаях используются относительно мощные одиночные импульсы, в других – сотни миллионов маломощных импульсов в секунду. Применяется как когерентная (последовательная) обработка сигнала, так и не когерентная. Все это приводит к значительному различию характеристик UWB систем разных производителей.

Магнитные методы

Магнитный трекинг основан на измерении интенсивности магнитного поля в различных направлениях. Как правило, в таких системах есть базовая станция, которая генерирует переменный или постоянный ток. Так как сила магнитного поля уменьшается с увеличением расстояния между точкой измерения и базовой станции, можно определить местоположение контроллера. Если точка измерения вращается, распределение магнитного поля изменяется по различным осям, что позволяет определить ориентацию. Наиболее известными продуктами на основе магнитного трекинга являются VR контроллер Razer Hydra и система STEM от компании Sixense. Точность данного метода может быть достаточна высока в контролируемых условиях (в спецификациях Hydra говорится о 1 мм позиционной точности и 1 градусе точности ориентации), однако магнитное отслеживание подвержено помехам от токопроводящих материалов вблизи излучателя или датчика, от магнитных полей, создаваемых другими электронными устройствами и ферромагнитных материалов в пространстве отслеживания.

Оптические методы

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

  • Без маркерный трекинг: как правило строится на сложных алгоритмах с использованием двух и более камер, либо стерео камер с сенсорами глубины. Используется наибольшим образом в автомобилях с автопилотам и иными системами помощи водителю.
  • Трекинг с использованием маркеров: предполагает заранее заданную модель объекта, которую можно отслеживать даже с одной камерой. Маркерами обычно служат источники инфракрасного излучения (как активные, так и пассивные), а также видимые маркеры наподобие QR-кодов. Такой вид трекинга возможен только в пределах прямой видимости маркера.

Задача Perspective-n-Point (PnP)

Рис. 1 Задача (PnP)

При оптическом отслеживании для определения положения объекта в пространстве решается так называемая задача PnP (Perspective-n-Point),когда по перспективной проекции объекта на плоскость сенсора камеры необходимо определить положение объекта в 3D-пространстве.

Для заданной 3D-модели объекта и 2D-проекции объекта на плоскость камеры решается система уравнений. В результате чего получается множество возможных решений. Количество решений зависит от числа точек в 3D-модели объекта.\ Однозначное решение для определения 6-DoF положения объекта можно получить как минимум при 4 точках. Для треугольника получается от 2 до 4 возможных решений, то есть положение не может быть определено однозначно
Рис. 2 Решение "треугольников"

Решение предлагается достаточно большим количеством алгоритмов, реализованных в виде библиотек:

  1. POS (Pose from Orthography and Scaling), аппроксимирующий перспективную проекцию с помощью масштабированной ортогональной проекции и находящий матрицу поворота и вектор сдвига объекта путём решения линейной системы
  2. POSIT (POS with ITerations), который использует в цикле аппроксимацию нахождения положения POS для нахождения более хорошей масштабированной ортогональной проекции особых точек, а затем применяет POS к этим точкам, а не к исходным. POSIT сходится к точному решению за несколько итераций.
  3. OpenCV — библиотека компьютерного зрения широкого назначения с открытым исходным кодом. Основные части библиотеки — интерпретация изображений и алгоритмы машинного обучения. Список возможностей, предоставляемых OpenCV, весьма обширен: интерпретация изображений, калибровка камеры по эталону, устранение оптических искажений, анализ перемещения объекта, определение формы объекта и слежение за объектом, сегментация объекта и др. Нам же интереcтно solvePnP

SLAM — Simultaneous Localization and Mapping

Метод одновременной локализации и построения карты (SLAM) — наиболее популярный способ позиционирования, который применяется для отслеживания положения в пространстве.
Рис. 3 Метод SLAM

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

Инерциальный трекинг

Современные инерциальные измерительные системы (IMU) на основе MEMS-технологии позволяют отслеживать ориентацию (roll, pitch, yaw) в пространстве с большой точностью и минимальными задержками.
Рис. 4 MEMS

Благодаря алгоритмам «sensor fusion» на основе комплементарного фильтра или фильтра Калмана данные с гироскопа и акселерометра успешно корректируют друг друга и обеспечивают точность как для кратковременных измерений, так и для длительного периода. Однако определение координат (перемещения) за счёт двойного интегрирования линейного ускорения (dead reckoning), вычисленного из сырых данных с акселерометра, не удовлетворяет требованиям по точности на длительных периодах времени. Акселерометр сам по себе даёт сильно зашумленные данные, и при интегрировании ошибка увеличивается со временем квадратично. Решить данную проблему помогает комбинирование инерциального подхода к трекингу с другими методами, которые периодически корректируют, так называемый, дрифт акселерометра.

Гибридные методы

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

Примеры задач, решаемых с помощью ML

Обнаружение и обработка дорожных знаков и пешеходов

Введение

Обнаружение объектов является хорошо известной проблемой в области компьютерного зрения и глубокого обучения. Существует два компонента в модели обнаружения объекта, а именно, базовая нейронная сеть и нейронная сеть обнаружения . Во-первых, базовые нейтральные сети -это CNN, которые извлекают объекты из изображения, из объектов низкого уровня, таких как линии, ребра или круги, в объекты более высокого уровня, такие как лицо, человек, светофор или знак стоп и т. д. Несколько хорошо известных базовых нейронных сетей-это LeNet, InceptionNet(он же. GoogleNet), ResNet, VGG-Net, AlexNet, MobileNet и др. Эта превосходная статья обсуждает различия между этими базовыми нейтральными сетями ниже.

Затем нейронные сети обнаружения присоединяются к концу базовой нейронной сети и используются для одновременной идентификации нескольких объектов из одного изображения с помощью извлеченных признаков. Некоторые из популярных сетей обнаружения-SSD (Single Shot MultiBox Detector), R-CNN (регион с функциями CNN), более быстрый R-CNN и YOLO (вы смотрите только один раз) и т. д. В этой статье рассматриваются различия между этими нейронными сетями обнаружения.

Modeling training

Выделим несколько этапов связанных с модельным обучением.

  1. Сбор и маркировка изображений (20-30 мин)
  2. Выбор модели
  3. Трансферное обучение / модельное обучение (3-4 часа)
  4. Сохранить вывод модели в формате Edge TPU (5 мин)
  5. Запуск вывода модели на Raspberry Pi

Сбор и маркировка изображений

У нас есть 6 типов объектов, а именно: красный свет, зеленый свет, знак остановки, ограничение скорости 40 миль в час, ограничение скорости 25 миль в час и несколько фигурок Lego в качестве пешеходов.
Рис. 5 Установленные объекты
Таким образом, у нас будет около 50 подобных фотографий, с объектами размещенными случайным образом на каждом изображении. Затем пометим каждое изображение с помощью ограничительной рамки для каждого объекта на изображении. С помощью бесплатного инструмента, называемый labelImg (для Windows / Mac / Linux) это можно сделать довольно быстро и просто.


Выбор модели

На Raspberry Pi, так как мы имеем ограниченные вычислительные мощности, мы должны выбрать модель, которая работает относительно быстро и точно. После экспериментов с несколькими моделями, свой выбор остановим на MobileNet v2 SSD COCO модель как оптимальный баланс между скоростью и точностью.

Трансферное Обучение / Модельное Обучение

Для этого шага будем использовать Google Colab.

Подготовка данных для обучения:

repo_dir_path =  '/content/DeepPiCar'
% cd{ repo_dir_path} / models/ object_detection
# Convert train folder annotation xml files to a single csv file,
# generate the `label_map.pbtxt` file to `data/` directory as well.
!python code/xml_to_csv.py -i data/ images/ train - o data/ annotations/ train_labels.csv -l data/ annotations
# Convert test folder annotation xml files to a single csv.
!python code/xml_to_csv.py -i data/ images/ test -o data/ annotations/ test_labels.csv
# Generate `train.record`
!python code/generate_tfrecord.py --csv_input=data/annotations/train_labels.csv - - output_path= data/ annotations/ train.record -- 
img_path=data/images/train --label_map data/annotations/label_map.pbtxt
# Generate `test.record`
!python code/generate_tfrecord.py --csv_input=data/annotations/test_labels.csv - - output_path= data/ annotations/ test.record -- 
img_path=data/images/test --label_map data/annotations/label_map.pbtxt

Приведенный выше код преобразует xml-файлы меток, созданные инструментом LabelImg, в двоичный формат, так что TensorFlow может обрабатывать быстро.

Загружаем Pre-trained Model:

MODEL_FILE = MODEL + '.tar.gz'
DOWNLOAD_BASE = 'http://download.tensorflow.org/models/object_detection/'
DEST_DIR =  '/content/models/research/pretrained_model'
if not( os.path.exists( MODEL_FILE)):
    urllib.request.urlretrieve( DOWNLOAD_BASE +  MODEL_FILE, MODEL_FILE)
tar =  tarfile.open( MODEL_FILE)
tar.extractall()
tar.close()
os.remove( MODEL_FILE)
if(os.path.exists( DEST_DIR)):
    shutil.rmtree(DEST_DIR)
os.rename( MODEL, DEST_DIR)
fine_tune_checkpoint = os.path.join( DEST_DIR, "model.ckpt")
print( fine_tune_checkpoint)

Приведенный выше код загрузит предварительно обученные файлы модели для ssd_mobilenet_v2_quantized_300x300_coco_2019_01_03 модель и мы будем только использовать model.ckpt файл.

Тренируем модель:

num_steps = 2000
num_eval_steps = 50
model_dir =  '/content/gdrive/My Drive/Colab Notebooks/TransferLearning/Training'
pipeline_file =  'ssd_mobilenet_v2_quantized_300x300_coco.config'
!python /content/models/research/object_detection/model_main.py \
    - - pipeline_config_path= { pipeline_fname} \
    --model_dir='{model_dir}' \
    --alsologtostderr \
    --num_train_steps={num_steps} \
    --num_eval_steps={num_eval_steps}

Этот шаг занимает 3-4 часа, в зависимости от количества шагов, которые вы тренируете (они же эпохи или num_steps). После того, как тренировка будет завершена, вы увидите кучу файлов в model_dir. Мы ищем самое последнее model.ckpt-xxxx.meta файл.Во время обучения мы можем отслеживать прогрессирование потерь и точности с помощью TensorBoard.

Тестируем обученную модель:
Рис. 6 Опознанные объекты

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

Сохраняем вывод модели в формате Edge TPU:

После того, как модель обучена, мы должны экспортировать модель meta файл в диаграмму вывода в формате Google ProtoBuf, а затем в формат, который Edge TPU accelerator может понимать и обрабатывать.

Планирование и управление движением

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

  1. Подход, основанный на правилах значит, нам нужно точно сказать машине, что делать, когда она сталкивается с каждым объектом. Например, скажите автомобилю остановиться, если он видит красный свет или пешехода, или ехать медленнее, если он видит более низкий знак ограничения скорости и т. д. Это сродни тому, что мы сделали в части 4, где мы рассказали автомобилю, как перемещаться по полосе движения с помощью набора кодов/правил.
  2. Сквозной подход просто кормит автомобиль большим количеством видеоматериалов хороших водителей, и автомобиль, через глубокое обучение, выясняет сам по себе, что он должен остановиться перед красными огнями и пешеходами или замедлиться, когда ограничение скорости падает.

В статье рассматривается первый подход.

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

Во-первых, определяем базовый класс, TrafficObject, который представляет собой любые дорожные знаки или пешеходов, которые могут быть обнаружены на дороге. Он содержит метод, set_car_state(car_state). car_stateСловарь содержит две переменные, а именно: speed, и speed_limit, который будет изменен этим методом. Он также имеет вспомогательный метод, is_close_by(), который проверяет, находится ли обнаруженный объект достаточно близко. (Ну, поскольку наша единственная камера не может определить расстояние, приближаем расстояние с высотой объекта. Чтобы точно определить расстояние, нам понадобится лидар или его маленький кузен, ультразвуковой датчик или система камер стереовидения, как в Тесле.)

class TrafficObject(object):
    def set_car_state( self, car_state):
        pass
    @staticmethod
    def is_close_by( obj, frame_height, min_height_pct= 0,05):
        # default: if a sign is 10% of the height of frame
        obj_height = obj.bounding_box[ 1] [1] - obj.bounding_box[0][1]
        return obj_height /  frame_height >>  min_height_pct

Реализация для красного света и пешехода тривиальны, просто устанавливают скорость автомобиля 0.

class RedTrafficLight(TrafficObject):
    def set_car_state( self, car_state):
        logging.debug('red light: stopping car')
        car_state['speed'] =  0
class Pedestrian(TrafficObject):
    def set_car_state( self, car_state):
        logging.debug('pedestrian: stopping car')
        car_state['speed'] =  0

Как 25 миль в час, так и 40 миль в час ограничения скорости могут использовать только один класс SpeedLimit, который принимает speed_limit в качестве параметра инициализации. Когда знак обнаружен, просто установливаем ограничение скорости автомобиля до соответствующего предела.


class SpeedLimit( TrafficObject):
    def __init__(self, speed_limit):
        self.speed_limit = speed_limit
    def set_car_state( self, car_state):
        logging.debug('speed limit: set limit to %d' %  self.speed_limit)
        car_state['speed_limit'] =  self.speed_limit

Реализация зеленого света еще проще, так как он ничего не делает, но печать зеленого света обнаруживается (код не показан).

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

class ObjectsOnRoadProcessor(object):
    """
 This class 1) detects what objects (namely traffic signs and people) are on the road
 and 2) controls the car navigation (speed/steering) accordingly
    """
    def __init__(self,
                 car= None,
                 speed_limit=40,
                 model= '/home/pi/DeepPiCar/models/object_detection/data/model_result/road_signs_quantized_edgetpu.tflite',
                 label= '/home/pi/DeepPiCar/models/object_detection/data/model_result/road_sign_labels.txt',
                 width= 640,
                 height= 480):
        # model: This MUST be a tflite model that was specifically compiled for Edge TPU.
        # https://coral.withgoogle.com/web-compiler/
        logging.info('Creating a ObjectsOnRoadProcessor...')
        self.width =  width
        self.height =  height
        # initialize car
        self.car =  car
        self.speed_limit = speed_limit
        self.speed = speed_limit
        # initialize TensorFlow models
        with open(label, 'r') as f:
            pairs = (l.strip().split(maxsplit= 1) for l in f.readlines())
            self.labels =  dict((int(k), v) for k, v in )
        # initial edge TPU engine
        logging.info('Initialize Edge TPU with model %s...' %  model)
        self.engine = edgetpu.detection.engine.DetectionEngine(model)
        self.min_confidence = 0.30
        self.num_of_objects = 3
        logging.info('Initialize Edge TPU with model done.')
        self.traffic_objects = {0: GreenTrafficLight(),
                                1: Person(),
                                2: RedTrafficLight(),
                                3: SpeedLimit(25),
                                4: SpeedLimit(40),
                                5: StopSign()}
    def process_objects_on_road(self, frame):
        # Main entry point of the Road Object Handler
        objects, final_frame = self.detect_objects(frame)
        self.control_car(objects)
        return final_frame
    def control_car(self, objects):
        logging.debug('Control car...')
        car_state = {"speed": self.speed_limit, "speed_limit": self.speed_limit}
        if len(objects) == 0:
            logging.debug('No objects detected, drive at speed limit of %s.' % self.speed_limit)
        contain_stop_sign = False
        for obj in objects:
            obj_label = self.labels[obj.label_id]
            processor = self.traffic_objects[obj.label_id]
            if processor.is_close_by(obj, self.height):
                processor.set_car_state(car_state)
            else:
                logging.debug("[%s] object detected, but it is too far, ignoring. " % obj_label)
            if obj_label == 'Stop':
                contain_stop_sign = True
        if not contain_stop_sign:
            self.traffic_objects[5].clear()
        self.resume_driving(car_state)
    def resume_driving(self, car_state):
        old_speed = self.speed
        self.speed_limit = car_state['speed_limit']
        self.speed = car_state['speed']
        if self.speed == 0:
            self.set_speed(0)
        else:
            self.set_speed(self.speed_limit)
        logging.debug('Current Speed = %d, New Speed = %d' % (old_speed, self.speed))
        if self.speed == 0:
            logging.debug('full stop for 1 seconds')
            time.sleep(1)
    def set_speed(self, speed):
        # Use this setter, so we can test this class without a car attached
        self.speed = speed
        if self.car is not None:
            logging.debug("Actually setting car speed to %d" % speed)
            self.car.back_wheels.speed = speed

Обратите внимание, что каждый объект TrafficObject просто изменяет speed и speed_limit в car_state объекте, но на самом деле не меняет скорость автомобиля. Это ObjectsOnRoadProcessor изменяет фактическую скорость автомобиля, после обнаружения и обработки всех дорожных знаков и пешеходов.

Полный исходный код интереса находится на Github DeepPiCar.

Итоги

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

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

Описание

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

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

Для обнаружения лица на изображении воспользуемся библиотекой, которая называется clmtrackr.

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

Алгоритм:

1. Подготовка.
Загрузка библиотек, подготовка пустого HTML-документа с которым будем работать.

2. Получение видеопотока с веб-камеры
Запрашиваем разрешение пользователя на активацию веб-камеры. Начинаем получать видео с камеры и создаем функции контроля за потоком.

3. Поиск лица.
Используем библиотеку clmtrackr.js для поиска лица на видео. Для начала инициализируем систему слежения за лицом.
const ctrack = new clm.tracker();
ctrack.init();

Теперь, в функции onStreaming(), подключяем систему поиска лица, добавляя туда следующую команду:ctrack.start(video);Теперь, каждый раз, когда браузер выводит очередной кадр видео, мы собираемся рисовать что-то на элементе <canvas>. Выполнение какого-либо кода при выводе каждого кадра выполняется с помощью механизма requestAnimationLoop().Теперь вызовем функцию trackingLoop() в функции onStreaming() сразу после ctrack.start(). Эта функция будет сама планировать собственный перезапуск в каждом кадре.

const overlay = $('#overlay')[0];
const overlayCC = overlay.getContext('2d');
function trackingLoop() {
 // Проверим, обнаружено ли в видеопотоке лицо, 
 // и если это так - начнём его отслеживать.
 requestAnimationFrame(trackingLoop);
 let currentPosition = ctrack.getCurrentPosition();
 overlayCC.clearRect(0, 0, 400, 300);
 if (currentPosition) {
   ctrack.draw(overlay);
 }
}

4. Выявление области изображения, содержащей глаза.

Рис. 8 Контрольные точки.
Решим, что глаза — это прямоугольная часть изображения, границы которой касаются точек 23, 28, 24 и 26, расширенная на 5 пикселей в каждом направлении. Этот прямоугольник должен включать в себя всё, что для нас важно, если только пользователь не слишком сильно наклоняет голову.

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

function getEyesRectangle(positions) {
 const minX = positions[23][0] - 5;
 const maxX = positions[28][0] + 5;
 const minY = positions[24][1] - 5;
 const maxY = positions[26][1] + 5;
 const width = maxX - minX;
 const height = maxY - minY;
 return [minX, minY, width, height];
}

Теперь, в каждом кадре, мы собираемся извлекать из видеопотока прямоугольник с глазами, обводить его красной линией на элементе <canvas>, который наложен на элемент <video>, а затем копировать его в новый элемент <canvas>.

if (currentPosition) {
 // Выведем линии, проведённые между контрольными точками 
 // на элементе <canvas>, наложенном на элемент <video>
 ctrack.draw(overlay);
 // Получим прямоугольник, ограничивающий глаза, и обведём его
 // красными линиями
 const eyesRect = getEyesRectangle(currentPosition);
 overlayCC.strokeStyle = 'red';
 overlayCC.strokeRect(eyesRect[0], eyesRect[1], eyesRect[2], eyesRect[3]);
 // Видеопоток может иметь особые внутренние параметры, 
 // поэтому нам нужны эти константы для перемасштабирования
 // прямоугольника с глазами перед обрезкой
 const resizeFactorX = video.videoWidth / video.width;
 const resizeFactorY = video.videoHeight / video.height;
 // Вырезаем прямоугольник с глазами из видео и выводим его
 // в соответствующем элементе <canvas>
 const eyesCanvas = $('#eyes')[0];
 const eyesCC = eyesCanvas.getContext('2d');
 eyesCC.drawImage(
   video,
   eyesRect[0] * resizeFactorX, eyesRect[1] * resizeFactorY,
   eyesRect[2] * resizeFactorX, eyesRect[3] * resizeFactorY,
   0, 0, eyesCanvas.width, eyesCanvas.height
 );
}

5. Сбор данных

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

6.Отслеживание перемещений мыши

Для того чтобы узнать, где именно на веб-странице расположен указатель мыши, нам понадобится обработчик события document.onmousemove. Функция нормализует координаты таким образом, чтобы они укладывались в диапазон [-1, 1].

const mouse = {
 x: 0,
 y: 0,
 handleMouseMove: function(event) {
   // Получим позицию указателя и нормализуем её, приведя к диапазону [-1, 1]
   mouse.x = (event.clientX / (window).width()) * 2 - 1;
   mouse.y = (event.clientY / (window).height()) * 2 - 1;
 },
}
document.onmousemove = mouse.handleMouseMove;

7. Захват изображения

Для захвата изображения, выводимого элементом <canvas> и сохранения его в виде тензора, TensorFlow.js предлагает вспомогательную функцию tf.fromPixels(). Используем её для сохранения и последующей нормализации изображения.

function getImage() {
 // Захват текущего изображения в виде тензора
 return tf.tidy(function() {
   const image = tf.fromPixels($('#eyes')[0]);
   // Добавление измерения:
   const batchedImage = image.expandDims(0);
   // Нормализация и возврат данных:
   return batchedImage.toFloat().div(tf.scalar(127)).sub(tf.scalar(1));
 });
}

Код который собирает данные и формирует выборки:

const dataset = {
 train: {
   n: 0,
   x: null,
   y: null,
 },
 val: {
   n: 0,
   x: null,
   y: null,
 },
}
function captureExample() {
 // Возьмём самое свежее изображение глаз и добавим его в набор данных
 tf.tidy(function() {
   const image = getImage();
   const mousePos = tf.tensor1d([mouse.x, mouse.y]).expandDims(0);
   // Решим, в какую выборку (обучающую или контрольную) его добавлять
   const subset = dataset[Math.random() > 0.2 ? 'train' : 'val'];
   if (subset.x == null) {
     // Создадим новые тензоры
     subset.x = tf.keep(image);
     subset.y = tf.keep(mousePos);
   } else {
     // Конкатенируем их с существующими тензорами
     const oldX = subset.x;
     const oldY = subset.y;
     subset.x = tf.keep(oldX.concat(image, 0));
     subset.y = tf.keep(oldY.concat(mousePos, 0));
   }
   // Увеличим счётчик
   subset.n += 1;
 });
}

Привяжем данную функцию к какой-нибудь клавише, например пробел.

$('body').keyup(function(event) {
 // Выполняется при нажатии на клавишу Пробел на клавиатуре
 if (event.keyCode == 32) {
   captureExample();
   event.preventDefault();
   return false;
 }
});

8. Обучение модели

Создадим простую свёрточную нейронную сеть. TensorFlow.js предоставляет для этой цели API, напоминающее Keras. У сети есть слой conv2d, слой maxPooling2d, и слой dense c двумя выходными значениями (они представляют экранные координаты), в качестве регуляризатора, слой dropout, и слой flatten для того, чтобы преобразовать двухмерные данные в одномерные. Обучение сети выполняется с помощью оптимизатора Adam.

let currentModel;
function createModel() {
 const model = tf.sequential();
 model.add(tf.layers.conv2d({
   kernelSize: 5,
   filters: 20,
   strides: 1,
   activation: 'relu',
   inputShape: [('#eyes').height(),('#eyes').width(), 3],
 }));
 model.add(tf.layers.maxPooling2d({
   poolSize: [2, 2],
   strides: [2, 2],
 }));
 model.add(tf.layers.flatten());
 model.add(tf.layers.dropout(0.2));
 // Два выходных значения x и y
 model.add(tf.layers.dense({
   units: 2,
   activation: 'tanh',
 }));
 // Используем оптимизатор Adam с коэффициентом скорости обучения 0.0005 и с функцией потерь MSE
 model.compile({
   optimizer: tf.train.adam(0.0005),
   loss: 'meanSquaredError',
 });
 return model;
}

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

function fitModel() {
 let batchSize = Math.floor(dataset.train.n * 0.1);
 if (batchSize < 4) {
   batchSize = 4;
 } else if (batchSize > 64) {
   batchSize = 64;
 }
 if (currentModel == null) {
   currentModel = createModel();
 }
 currentModel.fit(dataset.train.x, dataset.train.y, {
   batchSize: batchSize,
   epochs: 20,
   shuffle: true,
   validationData: [dataset.val.x, dataset.val.y],
 });
}

Теперь добавим на страницу кнопку для запуска обучения. Этот код идёт в HTML-файл:

<button id="train">Train!</button>
<style>
   #train {
       position: absolute;
       top: 50%;
       left: 50%;
       transform: translate(-50%, -50%);
       font-size: 24pt;
   }
</style>

Этот код добавляем в JS-файл:

$('#train').click(function() {
 fitModel();
});

9. Предсказываем куда смотрит пользователь.

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

Сначала добавим на страницу кружок:

<style>
   #target {
       background-color: lightgreen;
       position: absolute;
       border-radius: 50%;
       height: 40px;
       width: 40px;
       transition: all 0.1s ease;
       box-shadow: 0 0 20px 10px white;
       border: 4px solid rgba(0,0,0,0.5);
   }
</style>

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

function moveTarget() {
 if (currentModel == null) {
   return;
 }
 tf.tidy(function() {
   const image = getImage();
   const prediction = currentModel.predict(image);
   // Конвертируем нормализованные координаты в позицию на экране
   const targetWidth = ('#target').outerWidth();
   const targetHeight = ('#target').outerHeight();
   const x = (prediction.get(0, 0) + 1) / 2 * ((window).width() - targetWidth);
   const y = (prediction.get(0, 1) + 1) / 2 * ((window).height() - targetHeight);
   // Переместим в нужное место кружок:
   const target = ('#target');
   target.css('left', x + 'px');
   target.css('top', y + 'px');
 });
}
setInterval(moveTarget, 100);

Итоги

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

Источники информации