Одно время я увлекался сборкой роботов-машинок на Ардуино и Raspberry Pi. Играть в конструктор мне нравилось, но хотелось чего-то большего.

И как-то раз, блуждая по Алиэкспрессу, я набрел на алюминиевое шасси для танка. Выглядело это творение в сравнении с машинками из пластика как Феррари в сравнении с телегой.

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

Дальше на питоне был написан нехитрый REST API для руления, а на Андроиде — такая же простая программка, позволяла управлять танком, дергая этот API.

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

image

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

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

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

Средствами OpenCV искались контуры нужного цвета (с допустимой толерантностью), потом среди контуров искалась окружность.

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

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

Поиск кружка красного цвета:

import cv2
import numpy as np
import sys

def mask_color(img, c1, c2):
    img = cv2.medianBlur(img, 5)
    hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
    mask = cv2.inRange(hsv, c1, c2)
    mask = cv2.erode(mask, None, iterations=2)
    mask = cv2.dilate(mask, None, iterations=2)
    return mask

def find_contours(img):
    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    blurred = cv2.GaussianBlur(gray, (5, 5), 0)
    thresh = cv2.threshold(blurred, 30, 255, cv2.THRESH_BINARY)[1]
    thresh = cv2.bitwise_not(thresh)
    im2, cnts, hierarchy = cv2.findContours(thresh, cv2.RETR_CCOMP, cv2.CHAIN_APPROX_SIMPLE)
    cp_img = img.copy()
    cv2.drawContours(cp_img, cnts, -1, (0,255,0), 3)
    return cp_img

def find_circles(img):
    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    blurred = cv2.medianBlur(gray,5)
    circles = cv2.HoughCircles(blurred,cv2.HOUGH_GRADIENT,1,20,param1=50,param2=30,minRadius=0,maxRadius=0)
    cimg = img
    if circles is not None:
        circles = np.uint16(np.around(circles))
        for i in circles[0,:]:
            cv2.circle(img,(i[0],i[1]),i[2],(255,0,0),2)
            cv2.circle(img,(i[0],i[1]),2,(0,0,255),3)
            print "C", i[0],i[1],i[2]
    return cimg

def find_circle(img, rgb):
    tolerance = 4
    hsv = cv2.cvtColor(rgb, cv2.COLOR_BGR2HSV)
    H = hsv[0][0][0]
    c1 = (H - tolerance, 100, 100)
    c2 = (H + tolerance, 255, 255)
    c_mask = mask_color(img, c1, c2)
    rgb = cv2.cvtColor(c_mask,cv2.COLOR_GRAY2RGB)
    cont_img = find_contours(rgb)
    circ_img = find_circles(cont_img)
    cv2.imshow("Image", circ_img)
    cv2.waitKey(0)

if __name__ == '__main__':
    img_name = sys.argv[1]
    img = cv2.imread(img_name)
    rgb = np.uint8([[[0, 0, 255 ]]])
    find_circle(img, rgb)

Цветовое распознавание стало заходить в тупик, я отвлекся на каскады Хаара, используя танк для фотоохоты на кота. Кот неплохо маскировался, заставляя каскад ошибаться в половине случаев (если кто не знает, OpenCV идет со специально обученным на котиках каскадом Хаара — бери и пользуйся).

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

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

Эксперименты эти проводились на компьютере, и дело осталось за малым — перенести TF на Raspberry Pi. К счастью, на гитхабе живет уникальный человек, который набрался терпения и прорвался через установку всех зависимостей и многочасовое время компиляции — и выложил в общий доступ собранный Tensorflow для Raspberry Pi.

Однако, дальнейшее изучение темы открыло, что OpenCV не стоит на месте и его контрибуторы выпустили модуль DNN (Deep Neural Networks), предлагающий интеграцию с нейросетями, обученными на TensorFlow. Это решение гораздо удобнее в разработке, плюс отпадает необходимость в самом TF. Пришлось немного поколдовать, так как свежая версия Mobile SSD нейросети для TF, уже не подхватывалась последней версией OpenCV. Надо было искать
и проверять рабочую версию Mobile SSD. Плюс к этому, DNN нормально работает только под OpenCV 3.4, а этой версии для Raspberry я не нашел. Пришлось собирать самому, благо это гораздо проще, чем возиться с TensorFlow. При этом собрать OpenCV под последнюю весию Raspbian (Stretch) не удалось, а вот на последней версии предыдущего поколения (Jessie) все взлетело как надо.

Пример кода, использующий DNN и не использующий Tensorflow.

Несколько файлов, отвечающих за имена объектов были вытянуты из TF и зависимость от самого TF убрана (там было только чтение из файла).
Исходный код на гитхабе.


import cv2 as cv
import tf_labels
import sys

DNN_PATH = "---path-to:ssd_mobilenet_v1_coco_11_06_2017/frozen_inference_graph.pb"
DNN_TXT_PATH = "--path-to:ssd_mobilenet_v1_coco.pbtxt"
LABELS_PATH = "--path-to:mscoco_label_map.pbtxt"

tf_labels.initLabels(PATH_TO_LABELS)
cvNet = cv.dnn.readNetFromTensorflow(pb_path, pb_txt)

img = cv.imread(sys.argv[1])
rows = img.shape[0]
cols = img.shape[1]
cvNet.setInput(cv.dnn.blobFromImage(img, 1.0/127.5, (300, 300), (127.5, 127.5, 127.5), swapRB=True, crop=False))
cvOut = cvNet.forward()

for detection in cvOut[0,0,:,:]:
    score = float(detection[2])
    if score > 0.25:
        left = int(detection[3] * cols)
        top = int(detection[4] * rows)
        right = int(detection[5] * cols)
        bottom = int(detection[6] * rows)
        label = tf_labels.getLabel(int(detection[1]))
        print(label, score, left, top, right, bottom)
        text_color = (23, 230, 210)
        cv.rectangle(img, (left, top), (right, bottom), text_color, thickness=2)
        cv.putText(img, label, (left, top), cv.FONT_HERSHEY_SIMPLEX, 1, text_color, 2)

cv.imshow('img', img)
cv.waitKey()

В общем, теперь фотки танка можно распознавать нейросетью, и это очень важный шаг в навигации в плане узнавания ориентиров. Тем не менее, одних картинок для полноценной навигации не хватало, требовалось измерять расстояния до препятствий. Так у робота появился эхолот. Чтобы подключить эхолот к Raspberry, надо немного потрудиться — эхолот возврашает сигнал на 5V, а Raspberry принимает 3.3V. На коленке эту проблему решают в основном резисторами на бредборде, однако мне не хотелось городить такую кустарщину на роботе. В итоге была найдена микросхема Level Shifter, которая делает все, что надо, и размером она с ноготь.

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

image

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

REST интерфейс, который предоставляет робот в качестве базы для дальнейшего использования:

GET /ping
GET /version
GET /name
GET /dist

POST /fwd/on
POST /fwd/off
POST /back/on
POST /back/off
POST /left/on
POST /left/off
POST /right/on
POST /right/off

POST /photo/make
GET /photo/:phid
GET /photo/list

POST /cam/up
POST /cam/down
POST /cam/right
POST /cam/left

POST /detect/haar/:phid
POST /detect/dnn/:phid

Ссылки:


  1. OpenCV DNN
  2. SSD MobileNet совместимая с OpenCV-3.4.1
  3. Tensorflow для Raspberry Pi
  4. Код рест-сервера для робота на гитхабе
  5. Собранная OpenCV 3.4.1 с поддержкой DNN для Raspbian Jessie

Комментарии (27)


  1. P0rt
    10.05.2018 12:21

    Исправьте, пожалуйста:
    В итоге была найдем


    1. Stantin Автор
      10.05.2018 14:31

      готово. спасибо, что заметили.


  1. Areso
    10.05.2018 12:39

    У гусеничных платформ есть свои недостатки. Шумность, вес, цена, скорость движения.

    Эти скользят?


    1. Aquahawk
      10.05.2018 13:42

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


      1. Stantin Автор
        10.05.2018 14:33

        Колеса не столько скользят, сколько застревают на мягком ковре, так как такие машинки поворачивают при помощи skid steering — т.е колеса одной стороны крутятся вперед, а другой — назад.


      1. SuhoffGV
        10.05.2018 14:38

        Вопрос чуть не в тему, но может знаете — как искать выбирать двигатели как на фото выше на замену? Получил с китая гусеничный лего вездеход с похожими моторами (только редуктора другие). Один мотор крутится нормально, второй еле ползет. Из за чего вездеход не ездит прямо. Купил в оффлайне 2 визуально таких же мотора на 6-12 вольт — и теперь оба тормозят и вездеход еле едет. Маркировки на моторах нет. «Быстрый» мотор жив, но он один. Редуктора разбирал — все ок, т.е. дело точно в моторе, но как найти «правильный»?


        1. Stantin Автор
          10.05.2018 14:41

          Эти моторы самые базовые, должно быть много предложений по ним, можно по отзывам выбирать.


          1. SuhoffGV
            10.05.2018 15:38

            Базовые, то базовые. Но на популярных лотах написано 3-6 вольт (а блок Лего выдает 7,2/9 вольт на АКБ/батарейках). Количество оборотов разнится от лота к лоту и в отзывах иногда пишут что они «слабые».
            Знать бы на какое количество оборотов смотреть, как минимум.
            А так мотор ищется по строке «dc motor 130».


      1. BalinTomsk
        10.05.2018 17:47
        +1

        я уже наверно с десяток умерших умерших пылесосов rumba выкинул — мне кажется у них отличные колесные узлы.


        1. Stantin Автор
          10.05.2018 20:39

          Да, на базе румбы очень популярно роботов делать


  1. shadson
    10.05.2018 14:39

    А что за шасси? На али нашел похожее за 75$. Это оно? Имхо, дороговато для творчества…


    1. Stantin Автор
      10.05.2018 14:40

      Да, столько.
      ru.aliexpress.com/item/USB-SD-MP3-Shield/32413892216.html
      Недешево, но очень понравилось, не смог устоять перед игрушкой.


  1. nanshakov
    10.05.2018 16:09

    А я всегда мечтал собрать такое, только с нормальным вооружением ( порох, автомат заряжаниям (револьверный барабан выстрелы на 20) сжатый воздух?) и устраивать дистанционные бои на танках…


    1. oldbay
      10.05.2018 16:41

      Вот Вас точно нельзя к котам подпускать :)
      По теме: недавно смотрел всякие бои «роботов» и откровенно скучал. Схватка радиоуправляемых моделек.
      Насколько бы было интереснее запуск автономных ботов управляемых нейросетями — схватка не только железа но и боевых алгоритмов.


    1. Stantin Автор
      10.05.2018 17:24
      +1

      Лазером можно. как в лазертаге устроено. Была у меня такая идея, может и сделаю ещё, только матчасть по оптике надо подтянуть.


  1. Mhyhr
    11.05.2018 12:10

    Рекомендую всем брать шасси с амортизаторами, иначе робот будет очень жестко ездить по неровностям. (Для экспериментов в доме хватит и обычного).


    Автор — я веду схожий хобби проект, было бы интересно пообщаться.


    1. Stantin Автор
      11.05.2018 16:39

      Можно пообщаться конечно. По какому каналу?


  1. rautate
    11.05.2018 15:28

    Автор, спасибо за статью, у меня как раз стоит в режиме «ожидания» проект по TensorFlow на Raspberry Pi. Он уже собран по той инструкции с гитхаба. Моя задача, распознать например цвета (тюльпаны, бутоны… и т.д). Распберри с камерой будет статичной. Сами цветы будут подноситься к нему. На данный момент, модель ТФ обучена и портирована на Малинку, только времени нету чтобы все проверить, и допилить. Ты пишешь, что OpenCV (с модулем DNN) уже развился до уровня, что TensorFlow не нужен. Я правильно понял? Или для моей задачи, все-таки TensorFlow?


    1. Stantin Автор
      11.05.2018 15:29

      Да, если обученная модель по формату подходит версии OpenCV, то можно дальше обойтись без TF на распберри


      1. rautate
        11.05.2018 15:42

        а на OpenCV тоже обучение происходит на стороннем компьютере, или непосредственно ра Малинке?


        1. Stantin Автор
          11.05.2018 16:40

          на OpenCV нельзя обучить нейросеть. Для этого нужен TF на стороннем компе и помощнее. А OpenCV может использовать обученную.


  1. ChePeter
    11.05.2018 23:18

    А как исправляли искажение перспективы при поиске красного круга? Или ждали пока танк сделает фотку прямо перпендикулярно красному кругу?


    1. Stantin Автор
      12.05.2018 08:06

      OpenCV допускает определенный эллипс при поиске окружности.


  1. ginkage
    12.05.2018 01:11

    Очень интересно, спасибо.
    А вы не смотрели в сторону AIY Vision Kit от гугла? Там к малинке подцепляется дополнительная платка, которая, как я понимаю, напрямую работает с заранее скомпилированными TF-моделями. По задачам, кажется, есть определённая схожесть, плюс не нужно заморачиваться со сборкой OpenCV нужной версии, да и сама малинка там используется вообще Zero, т.к. вся обработка идёт на внешней плате.


    1. Stantin Автор
      12.05.2018 08:04

      Да, смотрел. Главная проблема — эта плата садится на все пины Raspberry, блокируя что-либо кроме распознавания. А у меня на пинах завязано и движение колес, и управление штативом, и эхолотом.


      1. ginkage
        12.05.2018 21:43

        Там на внешней плате выведено четыре GPIO-контакта для произвольных применений, а у вас на фото, кажется, как раз четыре и используется. ;)


        1. Stantin Автор
          13.05.2018 09:24

          Сейчас используется 6 gpio плюс все пины питания, 3 граунда плюс оба пина i2c.