- PVSM.RU - https://www.pvsm.ru -
Привет. Это пост-отчет-тьюториал про беспилотные автомобили — как (начать) делать свой без расходов на оборудование. Весь код доступен на github [1], и помимо прочего вы научитесь легко генерить такие класные картинки:

Поехали!
Краткое содержание для знакомых с темой: традиционно для набора обучающей выборки [2] для автопилота на основе машинного обучения нужен был специально оборудованный автомобиль [3] с достаточно информативной CAN шиной [4] и интерфейсом к ней, что дорого. Мы поступим проще и бесплатно — будем набирать такие же по сути данные просто со смартфона на лобовом стекле. Подходит любой авто, никаких модификаций оборудования. В этой серии — вычисляем поворот руля в каждый момент времени по видео. Если в этом абзаце всё понятно, можно перепрыгивать через введение сразу к сути подхода [5].
Итак, ещё пару лет назад без серьёзных ресурсов большой корпорации в тему автопилотов было не сунуться — один только LIDAR [6] сенсор стоил десятки тысяч долларов [7], но недавняя революция в нейросетях всё изменила. Стартапы из нескольких человек [8] с простейшими наборами сенсоров из пары вебкамер на равных конкурируют по качеству результата [9] со знаменитыми брендами. Почему бы не попробовать и нам, тем более столько качественных компонентов [10] уже в открытом [11] доступе [12].
Автопилот преобразует данные сенсоров в управляющие воздействия — поворот руля и требуемое ускорение/замедление. В системе с лазерными дальномерами, как у Google, это может выглядеть так:

Простейший же вариант сенсора — видеокамера, "смотрящая" через лобовое стекло. С ним и будем работать, ведь камера на телефоне уже есть у каждого.

Для вычисления управляющих сигналов из "сырого" видео хорошо работают сверточные нейросети [13], но, как и любой другой подход машинного обучения, предсказывать правильный результат их нужно научить. Для обучения нужно (а) выбрать архитектуру модели и (б) сформировать обучающую выборку [14], которая будет демонстрировать модели различные входные ситуации и "правильные ответы" (например, угол поворота руля и положение педали газа) на каждую из них. Данные для обучающей выборки обычно записывают с заездов, где машиной управляет человек. То есть водитель демонстрирует роботу, как надо управлять машиной.
Хороших архитектур нейросетей хватает в открытом доступе [11], а вот с данными ситуация более печальная: во-первых данных просто мало, во-вторых почти все выборки — из США [2], а у нас на дорогах много от тех мест отличий [15].
Дефицит открытых данных легко объясним. Во-первых данные — не менее ценный актив, чем экспертиза в алгоритмах и моделях, поэтому делиться никто не торопится:
The rocket engine is the models and the fuel is the data.
Andrew Ng [16]
Во-вторых, процесс сбора данных недёшев, особенно если действовать "в лоб". Хороший пример — Udacity [3]. Они специально подобрали модель автомобиля, где рулевое управление и газ/тормоз завязаны на цифровую шину, сделали интерфейс к шине и считывают оттуда данные напрямую. Плюс подхода — высокое качество данных. Минус — серьезная стоимость, отсекающая подавляющее большинство непрофессионалов. Ведь далеко не каждый даже современный авто пишет в CAN [17] всю нужную нам информацию, да и с интерфейсом придется повозиться.
Мы поступим проще. Записываем "сырые" данные (пока что это будет просто видео) смартфоном на лобовом стекле как видеорегистратором, затем софтом "выжимаем" оттуда нужную информацию — скорость движения и поворотов, на которых уже можно будет обучать автопилот. В результате получаем почти бесплатное решение — если есть держалка для телефона на лобовое стекло, достаточно нажать кнопку, чтобы набирать обучающие данные по дороге на работу.
В этой серии — "выжималка" угла поворота из видео. Все шаги легко повторить своими силами с помощью кода на github [1].
Решаем задачу:
Ожидаемый результат:
Сразу чуть упростим — вместо угла поворота руля будем вычислять угловую скорость в горизонтальной плоскости. Это примерно эквивалентная информация если знать поступательную скорость, которой мы займемся в следующей серии.
Решение можно собрать из общедоступных компонент, немного их доработав:
Первый шаг — восстановление траекториии камеры в трехмерном пространстве с помощью библиотеки SLAM [18] по видео (simultaneous localization and mapping, одновременная локализация и построение карты). На выходе для каждого (почти, см. нюансы) кадра получаем 6 параметров положения: 3D смещение и 3 угла ориентации [19].

В коде за эту часть отвечает модуль optical_trajectories [20]
Нюансы:
Траектория камеры в трехмерном пространстве — это хорошо, но напрямую еще не дает ответа на конечный вопрос — поворачивать налево или направо, и насколько быстро. Ведь у системы SLAM нет понятий "плоскость дороги", "верх-низ", и т.д. Эту информацию тоже надо добывать из "сырой" 3D траектории.
Здесь поможет простое наблюдение: автомобильные дороги обычно протягиваются гораздо дальше по горизонтали, чем по вертикали. Бывают конечно исключения [23], ими придется пренебречь. А раз так, можно принять ближайшую плоскость (т.е. плоскость, проекция на которую дает минимальную ошибку реконструкции) нашей траектории за горизонтальную плоскость дороги.
Горизонтальную плоскость выделяем прекрасным методом главных компонент [24] по всем 3D точкам траектории — убираем направление с наименьшим собственным числом, и оставшиеся два дадут оптимальную плоскость.

За логику выделения плоскости также отвечает модуль optical_trajectories [20]
Нюанс:
Из сути главных компонент понятно, что кроме горных дорог выделение главной плоскости будет плохо работать если машина всё время ехала по прямой, — ведь тогда только одно направление настоящей горизонтальной плоскости будет иметь большой диапазон значений, а диапазон по оставшемуся перпендикулярному горизонтальному направлению и по вертикали будут сопоставимы.
Чтобы не загрязнять данные большими погрешностями с таких траекторий, проверяем, что разброс по последнему главному компоненту значительно (в 100 раз) меньше, чем по предпоследнему. Не прошедшие траектории просто выкидываем.
Зная базисные векторы горизонтальной плоскости v1 и v2 (два главных компонента с наибольшими собственными значениями из предыдущей части), проецируем на горизонтальную плоскость оптическую ось камеры:

Таким образом из трехмерной ориентации камеры получаем курсовой угол автомобиля (с точностью до неизвестной константы, т.к. ось камеры и ось автомобиля в общем случае не совпадает). Поскольку нас интересует только интенсивность поворота (т.е. угловая скорость), эта константа и не нужна.
Угол поворота между соседними кадрами дает школьная тригонометрия (первый множитель — абсолютная величина поворота, второй — знак, определяющий направление налево/направо). Здесь под at понимаем вектор проекции ahorizontal в момент времени t:

Эта часть вычислений тоже делается модулем optical_trajectories [20]. На выходе получаем JSON файл следующего формата:
{
"plane": [
[ 0.35, 0.20, 0.91],
[ 0.94, -0.11, -0.33]
],
"trajectory": [
...,
{
"frame_id": 6710,
"planar_direction": [ 0.91, -0.33 ],
"pose": {
"rotation": {
"w": 0.99,
"x": -0.001,
"y": 0.001,
"z": 0.002
},
"translation": [ -0.005, 0.009, 0.046 ]
},
"time_usec": 223623466,
"turn_angle": 0.0017
},
.....
}
Значения компонент:
plane — базисные векторы горизонтальной плоскости.trajectory — список элементов, по одному на каждый успешно отслеженный системой SLAM кадр.
frame_id — номер кадра в исходном видео (начиная с 0).planar_direction — проекция отпической оси на горизонтальную плоскостьpose — положение камеры в 3D пространстве
rotation — ориентация оптической оси в формате единичного кватерниона [25].translation — смещение.time_use — время с начала видео в микросекундахturn_angle — горизонтальное вращение относительно предыдущего кадра в радианах.Мы почти у цели, но остается еще проблема. Посмотрим на получившийся (пока что) график угловой скорости:

Визуализируем на видео:
Видно, что в общем направление поворота определяется правильно, но очень много высокочастотного шума. Убираем его Гауссовским размытием [26], которое является низкочастотным фильтром.
Сглаживание в коде производится модулем smooth_heading_directions [27]
Результат после фильтра:

Это уже можно "скормить" обучаемой модели и рассчитывать на адекватные результаты.
Для наглядности по данным из JSON файлов траекторий можно наложить виртуальный руль на исходное видео, как на демках выше, и проверить, правильно ли он крутится. Этим занимается модуль render_turning [28].
Также легко построить покадровый график. Например, в IPython ноутбуке с установленным matplotlib:
import matplotlib
%matplotlib inline
import matplotlib.pyplot as plt
import json
json_raw = json.load(open('path/to/trajectory.json'))
rotations = [x['turn_angle'] for x in json_raw['trajectory']]
plt.plot(rotations, label='Rotations')
plt.show()
На этом пока всё. В следующей серии — определяем поступательную скорость, чтобы обучить еще и управление скоростью, а пока что приветствуются pull-request'ы.
Автор: waiwnf
Источник [29]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/obrabotka-izobrazhenij/251830
Ссылки в тексте:
[1] на github: https://github.com/waiwnf/pilotguru
[2] обучающей выборки: https://medium.com/udacity/open-sourcing-223gb-of-mountain-view-driving-data-f6b5593fbfa5
[3] специально оборудованный автомобиль: https://medium.com/udacity/were-building-an-open-source-self-driving-car-ac3e973cd163
[4] достаточно информативной CAN шиной: http://blog.caranddriver.com/why-ford-lincoln-and-lexus-testers-rule-the-self-driving-roost/
[5] сразу к сути подхода: #zadacha
[6] LIDAR: https://ru.wikipedia.org/wiki/%D0%9B%D0%B8%D0%B4%D0%B0%D1%80
[7] десятки тысяч долларов: http://content.usatoday.com/communities/driveon/post/2012/06/google-discloses-costs-of-its-driverless-car-tests/1#.WOEJhnWGNpg
[8] Стартапы из нескольких человек: http://comma.ai/
[9] конкурируют по качеству результата: http://newatlas.com/geohot-comma-ai-openpilot-open-source/46722/
[10] компонентов: https://github.com/commaai/openpilot
[11] открытом: https://github.com/udacity/self-driving-car/tree/master/steering-models/community-models
[12] доступе: https://github.com/commaai/neo
[13] сверточные нейросети: https://habrahabr.ru/post/309508/
[14] обучающую выборку: https://ru.wikipedia.org/wiki/%D0%9C%D0%B0%D1%88%D0%B8%D0%BD%D0%BD%D0%BE%D0%B5_%D0%BE%D0%B1%D1%83%D1%87%D0%B5%D0%BD%D0%B8%D0%B5#.D0.9E.D0.B1.D1.89.D0.B0.D1.8F_.D0.BF.D0.BE.D1.81.D1.82.D0.B0.D0.BD.D0.BE.D0.B2.D0.BA.D0.B0_.D0.B7.D0.B0.D0.B4.D0.B0.D1.87.D0.B8_.D0.BE.D0.B1.D1.83.D1.87.D0.B5.D0.BD.D0.B8.D1.8F_.D0.BF.D0.BE_.D0.BF.D1.80.D0.B5.D1.86.D0.B5.D0.B4.D0.B5.D0.BD.D1.82.D0.B0.D0.BC
[15] много от тех мест отличий: https://www.youtube.com/watch?v=itMdLTd1l4E
[16] Andrew Ng: https://www.wired.com/brandlab/2015/05/andrew-ng-deep-learning-mandate-humans-not-just-machines/
[17] CAN: https://ru.wikipedia.org/wiki/Controller_Area_Network
[18] библиотеки SLAM: https://github.com/raulmur/ORB_SLAM2
[19] угла ориентации: https://ru.wikipedia.org/wiki/%D0%A3%D0%B3%D0%BB%D1%8B_%D0%AD%D0%B9%D0%BB%D0%B5%D1%80%D0%B0
[20] optical_trajectories: https://github.com/waiwnf/pilotguru#steering-from-video
[21] инструкции: https://github.com/waiwnf/pilotguru#calibrate
[22] теория — актуальны части 1 и 2: https://habrahabr.ru/post/130300/
[23] исключения: https://bikealps.files.wordpress.com/2011/08/dolomites-20110815-dsc_0039.jpg
[24] методом главных компонент: http://www.chemometrics.ru/materials/textbooks/pca.htm
[25] единичного кватерниона: https://ru.wikipedia.org/wiki/%D0%9A%D0%B2%D0%B0%D1%82%D0%B5%D1%80%D0%BD%D0%B8%D0%BE%D0%BD%D1%8B_%D0%B8_%D0%B2%D1%80%D0%B0%D1%89%D0%B5%D0%BD%D0%B8%D0%B5_%D0%BF%D1%80%D0%BE%D1%81%D1%82%D1%80%D0%B0%D0%BD%D1%81%D1%82%D0%B2%D0%B0
[26] Гауссовским размытием: https://en.wikipedia.org/wiki/Gaussian_blur
[27] smooth_heading_directions: https://github.com/waiwnf/pilotguru#steering-smoothing-instructions
[28] render_turning: https://github.com/waiwnf/pilot#steering-visualize-instructions
[29] Источник: https://habrahabr.ru/post/325704/?utm_source=habrahabr&utm_medium=rss&utm_campaign=sandbox
Нажмите здесь для печати.