Отслеживание маркера роботом
Отслеживание маркера роботом

Введение

Данный проект, является логическим продолжением развития темы "Солнечный трекер на Arduino".

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

Распознавать маркеры можно по разным критериям: выбранному цвету, текстуре, форме и т.д.

Я выбрал второй вариант. Делаю фото объекта web-камерой, с выбранным однородным цветом, которую буду использовать на роботе.

Подготовка ПО

Для корректной работы программы вам нужно:

1. Установить Python https://www.python.org/

2. Установить модули numpy, opencv и pyserial используя инструмент pip https://pypi.org/project/numpy/

https://pypi.org/project/opencv-python/

https://pypi.org/project/pyserial/

3. Установить Arduino ide

https://learnlange.blogspot.com/p/blog-page_28.html

Сборка робота

Система представляет стационарную установку с двумя степенями свободы, которые позволяют вращаться в пространстве цилиндрической формы. На верхней части системы закреплена USB web-камера (её можно заменить на smart камеру или ip камеру).

Для быстрого создания прототипа использую образовательный набор КЛИК: базовый и ресурсный
Поворотные механизмы реализованы с использованием DC моторов с понижающей передачей.

Прототип роботизированной системы
Прототип роботизированной системы

В качестве альтернативы можно использовать теже компоненты, что и для создания солнечного трекера.

В любом случае, для данных контроллеров можно писать программу в arduino ide и компилировать её.

Программа для Arduino ide

                 Данная программа пишется в среде Arduino ide. С помощью неё мы считываем данный, которые должны прийти через последовательный порт (COM). И на основании полученных данных задавать команду для первого и второго мотора.

К порту M1 подключён мотор для вращения основания всей конструкции (ось Y). К порту М2 подключён второй мотор, который отвечает за вращение непосредственно web-камеры по оси X.

Выведенные порты контроллера КЛИК
Выведенные порты контроллера КЛИК

М 1

D6, D7

Мотор 1. Для управления мотора используется цифровой выход Ардуино. D6 - скорость, D7 - направление

М 2

D4, D5

Мотор 2. Для управления мотора используется цифровой выход Ардуино. D5 - скорость, D4 - направление

Ниже представлен код для управление роботом. Получаем команду через последовательный порт (COM) в строковом формате и запускаем нужные моторы в нужном направлении.

int m1 = 7;
int m2 = 4;
int v1 = 6;
int v2 = 5;

void setup() {
  Serial.begin(9600); // Инициализация порта
  pinMode(m1,OUTPUT);
  pinMode(m2,OUTPUT);
  pinMode(v1,OUTPUT);
  pinMode(v2,OUTPUT);
  analogWrite(v1, 0);
  analogWrite(v2, 0);
}

void loop() {
  if (Serial.available() > 0) {
    String command = Serial.readStringUntil('\n'); // Читаем до новой строки
  // вращаем первый мотор вправо
  if (command == "1")
  {
    analogWrite(v2, 0);
    analogWrite(v1, 50);    
    digitalWrite(m1, HIGH);
    delay(20);
    analogWrite(v1, 0); 
    delay(1);   
    }
    // Вращаем второй мотор вниз
   else if (command == "2")
  {
    analogWrite(v1, 0);
    analogWrite(v2, 50);    
    digitalWrite(m2, LOW);
    delay(20);
    analogWrite(v2, 0); 
    delay(1);   
    }
    // вращаем первый мотор влево
  else if (command == "3") 
  {
    analogWrite(v2, 0);
    analogWrite(v1, 50);    
    digitalWrite(m1, LOW);
    delay(20);
    analogWrite(v1, 0); 
    delay(1);   
    }
    // вращаем первый мотор вверх
  else if (command == "4")
  {
    analogWrite(v1, 0);
    analogWrite(v2, 50);    
    digitalWrite(m2, HIGH);
    delay(20);
    analogWrite(v2, 0); 
    delay(1);   
    }
   // полная остановка всех моторов 
  else if (command == "0")
  {
    analogWrite(v1, 0);
    analogWrite(v2, 0);        
    }  
  }  
}

Программа для отслеживания маркера через web-камеру

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

Для создания фото маркера, нужно написать простую программу подключения к web –камере захвата одного кадра. Всё это может модуль OpenCV.

Для этого уже написана программа foto.py

import cv2
import time

# подключаемся к камере
cap = cv2.VideoCapture(0)
time.sleep(3)

# храним в переменной frame созданный кадр
b, frame = cap.read()
# отображаем кадр
cv2.imshow('frame', frame)
# сохраняем кадр под именем
cv2.imwrite('obrab.jpg', frame)
time.sleep(1)

Мы должны найти однородный по цвету маркер, поднести к камере и запустить программу клавишей F5 (если используете стандартный Python IDLE Shell). Изображение сохраняется там же где лежит программа foto.py. Теперь с помощью Paint вырежи только нужный участок фото кадра, где отображён маркер.

В качестве примера, я использовал аккумуляторную батарею жёлтого цвета и обрезал нужный мне участок. Назвал этот файл “obrabM.jpg”.

obrabM.jpg
obrabM.jpg

Здесь же нужно создать вторую программу на Python, которая будет считывать фото полученного маркера и каждый кадр потокового видео с камеры и сравнивать их, ища схожие области. Назовём программу сетка.py, так как всю область видимости камерой разобьём на девять секторов. В зависимости от того в каком секторе найдётся маркер такую команду и нужно отправить к роботу.

                Размер кадра будет варьироваться по ширине от 100 до 500 пикселей, по высоте от 50 до 400 пикселей.  Определите COM порт вашего подключённого робота (можно через Arduinoide). В моём случае это COM3.

import cv2
import numpy as np
import math

import serial
import time
# подключаемся к COM порту
r = serial.Serial('com3', 9600)

cap = cv2.VideoCapture(0)
ret, frame = cap.read()
# записываем видеопоток
fourcc = cv2.VideoWriter_fourcc(*'XVID')
out = cv2.VideoWriter('Woutput181.avi', fourcc, 20.0, (640,480))
# получаем исходное изображение маркера
template = cv2.imread("obrabM.jpg",0)
# сопоставляем размер изображения с получаемым кадром
w, h = template.shape[::-1]
x,y = 0,0
# размеры кадра
dx = [100, 500]
dy = [50, 400]

# центр кадра
sx = 400//2
sy = 350//2
# команды на контроллер
command="0"

while True:  
    # считываем потоковый кадр
    _, frame = cap.read ()
    # получаем серую градацию кадра
    i= cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
    
    # ищем маркер по исходному изображению на кадре    
    res1 = cv2.matchTemplate(i,template,3)
    # вычисляем границы маркера на кадре
    min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(res1)
    top_left = max_loc
    bottom_right = (top_left[0] + w, top_left[1] + h)
    # определяем центр этого маркера
    x = (top_left[0] + bottom_right[0])/2
    y = (top_left[1] + bottom_right[1])/2
    
    # строим сетку
    cv2.line(frame, (5,0), (5,500), (250,255,255), 2)
    cv2.line(frame, (210,0), (210,500), (250,255,255), 2)
    cv2.line(frame, (410,0), (410,500), (250,255,255), 2)
    cv2.line(frame, (635,0), (635,500), (250,255,255), 2)
    cv2.line(frame, (700,0), (0,0), (250,255,255), 2)
    cv2.line(frame, (700,150), (0,150), (250,255,255), 2)
    cv2.line(frame, (700,320), (0,320), (250,255,255), 2)
    cv2.line(frame, (700,477), (0,477), (250,255,255), 2)
    
    # выводим координаты положения маркера и выделяем его прямоугольником
    if y<500:
        cv2.rectangle(frame, top_left, bottom_right, (0, 255, 0), 3)
        cv2.putText(frame, f"{[x, y]}", (bottom_right[0]+10, bottom_right[1]+10), cv2.FONT_HERSHEY_SIMPLEX, 1, (255,255,0), 2)
    
    # отображаем кадр со всеми выделениясми
    out.write(frame)
    
    cv2.imshow('frame', frame)
    # отправляем команды в зависимости от того где расположен маркер
    
    if (45<y<=170):
        if (99<x<=230):
            command = "1"    
        elif (230<x<=330) :
            command = "2"
        elif (x>330):
            command = "3"
    elif (170<y<=300):
        if (99<x<=230):
            command = "1"    
        elif (230<x<=330) :
            command = "0"
        elif (x>330):
            command = "3"
    elif (y>300):
        if (99<x<=230):
            command = "1"    
        elif (230<x<=330) :
            command = "4"
        elif (x>330):
            command = "3"
    
    r.write((command + '\n').encode('utf-8'))
    print(f"Отправлено: {command}")
    time.sleep(0.1)   
    
    # команды прерывание программы 
    k = cv2.waitKey(5) & 0xFF
    if k == 27:
        break

# закрываем окно и останавливаем программу
cv2.destroyAllWindows()
cap.release()

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

Пример работы устройства представлено на видео. Доступно на:

YouTube

Rutube