Главная » Хабрахабр » [Из песочницы] Робот-танк на Raspberry Pi с OpenCV

[Из песочницы] Робот-танк на Raspberry Pi с OpenCV

Одно время я увлекался сборкой роботов-машинок на Ардуино и 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, я обработал его детектором фотки с танка и результаты были многообещающими — телевизор, стол, диван, кот, холодильник распознавались безошибочно. Продолжая изыскания на тему, что можно выжать из фоток комнаты, я закономерно пришел к нейросетям.

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

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

Пример кода, использующий 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

Оставить комментарий

Ваш email нигде не будет показан
Обязательные для заполнения поля помечены *

*

x

Ещё Hi-Tech Интересное!

Автомобиль на водороде. Пора ли прощаться с бензином?

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

Неконференция Web Summit

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