Автономная езда по тротуару посредством OpenCV и Tensorflow

в 4:36, , рубрики: opencv, Raspberry Pi, TensorFlow, искусственный интеллект, Компьютерное зрение, машинное обучение, обработка изображений, Разработка на Raspberry Pi, робототехника, роботы

Создание автономных машин — популярная нынче тема и много интересного тут происходит на любительском уровне.
Самым старым и известным курсом была онлайн-степень от Udacity.

Итак, в автономных машинах есть очень модный подход — Behavioral Cloning, суть которого заключается в том, что компьютер учится вести себя как человек (за рулем), опираясь только на записанные входные и выходные данные. Грубо говоря, есть база картинок с камеры и соотвествующий им угол поворота руля.

В теории, обучив на этих данных нейросеть, мы можем дать ей порулить машиной.
Этот подход базируется на статье от Nvidia.
Существует множество реализаций, сделанных в основном студентами Udacity:

Еще более интересно применение в реальных проектах. Например, машинка Donkey Car управляется специально обученной нейросетью.

Такая насыщенная инфосфера прямо подталкивает в действиям, тем более, что мой робот-танк со времени предыдущей статьи зашел в некоторый тупик в своем развитии, и ему срочно требовались свежие идеи. Была смелая мечта — прогуляться по парку со своим танком, который, в общем-то ничем не хуже домашней собаки. Дело за малым — научить танк ездить по тротуару в парке.

Итак, что такое тротуар с точки зрения компьютера?
Некоторая область на картинке, которая отличается цветом от других областей.
Так сложилось, что в доступных мне парках тротуар оказывался наиболее серым объектом на картинке.
(Под наиболее серым имеется в виду минимальная разница между значениями RGB).
Это свойство серого и будет ключевым при распознавании тротуара.

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

tank in a park

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

Установив такие критерии распознавания тротуара, пробегаем по картинке и получаем какое-никакое очертание дороги.

Автономная езда по тротуару посредством OpenCV и Tensorflow - 2

Следующим шагом надо превратить аляповатое пятно в действия — ехать прямо или повернуть направо или налево.

Прямо мы едем, если правая кромка видна и угол меньше 45 градусов от вертикали.
Налево поворачиваем, если правая кромка видна и угол отклоняется от вертикали вниз.
Направо поворачиваем, если не видим правой кромки.

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

Оригинальные картинки размываем, сжимаем и обрезаем, выделяем распознаем серый тротуар и получаем черно-белые маски 64x64.
Эти маски раскладываем на 3 кучки — Left, Right, Straight и обучаем на них классификатор нейросети.
Сбор и подготовка данных — нуднейшая задача, на это ушла пара месяцев.
Вот образцы масок:
Налево:
Автономная езда по тротуару посредством OpenCV и Tensorflow - 3
Направо:
Автономная езда по тротуару посредством OpenCV и Tensorflow - 4
Прямо:
Автономная езда по тротуару посредством OpenCV и Tensorflow - 5

Для работы с нейросетью я использовал Keras + Tensorflow.

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

model = Sequential()
activation = "relu"

model.add(Conv2D(20, 5, padding="same", input_shape=input_shape))
model.add(Activation(activation))
model.add(MaxPooling2D(pool_size=(2, 2), strides=(2, 2)))

model.add(Conv2D(50, 5, padding="same"))
model.add(Activation(activation))
model.add(MaxPooling2D(pool_size=(2, 2), strides=(2, 2)))

model.add(Flatten())
model.add(Dense(500))
model.add(Activation(activation))

model.add(Dense(cls_n))
opt = SGD(lr=0.01)
model.add(Activation("softmax"))
model.compile(loss="categorical_crossentropy", optimizer=opt, metrics=["accuracy"])

Обучив первую версию сети, я уперся в ее несовместимость с Raspberry Pi. До этого я использовал Tensorflow версии 1.1, с помощью шаманства собранную одним очень умным человеком.

К сожалению, эта версия устарела и не могла читать модели из Keras.

Однако недавно люди из Гугла наконец-то снизошли и собрали TF под Raspberry Pi, правда под новую версию Raspbian — Stretch. Stretch был всем хорош, но год назад у меня под него не собрался OpenCV, поэтому танк ездил на Jessie.

Теперь же под давлением перемен пришлось переходить на Stretch. Tensorflow встал без проблем (хоть и заняло это несколько часов). OpenCV за год тоже не стоял на месте и уже вышла версия 4.0. Вот ее и удалось собрать под Stretch, так что препятствий для миграции уже не осталось.

Были сомнения, как Raspberry потянет в реалтайме такого монстра как Tensorflow, но все оказалось в целом приемлемо — несмотря на начальную загрузку сети порядка нескольких секунд, сама классификация способна отрабатывать несколько раз в секунду без значительного отжора памяти и CPU.

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

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

Автономная езда по тротуару посредством OpenCV и Tensorflow - 6

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

Ссылки:

Автор: Stantin

Источник


* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js