+7-960-0655211 (Билайн)
+7-987-4207734 (МТС)
интернет-магазин
доставка по России и СНГ
работаем с 2010 года

Автоматическое слежение за цветными объектами

ГлавнаяИнформацияСтатьи

Автоматическое слежение за цветными объектами



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



Кроме деталей, используемых ранее (Raspberry Pi 3 Модель B, модуль камеры и 2 сервопривода) нам понадобится ещё светодиод и резистор 221 Ом.

Для создания системы компьютерного зрения будет использоваться библиотека OpenCV. Это мой первый опыт работы с OpenCV, и я остался очень доволен этой библиотекой. OpenCV бесплатна для академического и коммерческого использования. Она имеет интерфейсы для C++, C, Python, Java и многих других языков, так же поддерживается работа на Windows, Linux, Mac OS, iOS и Android. В этом проекте для работы будет использоваться Python. OpenCV хорошо оптимизирован и позволяет создавать приложения компьютерного зрения для систем реального времени.

Установка пакета OpenCV

Я использую Raspberry Pi V3, с ОС Raspbian (Stretch), поэтому лучший способ установить OpenCV - это следовать прекрасному руководству, разработанному Адрианом Роузброком «Установка OpenCV 3 и Python на Raspberry Pi». Я попробовал несколько разных руководств, чтобы установить OpenCV и руководство Адриана на мой взгляд самое лучшее.

После установки всего необходимого, у Вас будет виртуальная среда OpenCV, готовая для проведения наших экспериментов. Давайте перейдем в нашу виртуальную среду и проверим, что OpenCV 3 правильно установлена. Адриан рекомендует запускать команду «source» при каждом новом запуске терминала, чтобы убедиться, что системные переменные установлены правильно.

source ~/.profile

Далее запустим виртуальную среду:

workon cv 

Если всё установлено и настроено правильно, в терминале в начале строки будет написано «(cv)», примерно так:

(cv) pi@raspberry:~$

Адриан обращает внимание, что виртуальная среда cv Python полностью независима и отделена от версии Python по умолчанию, включенной в сборку Raspbian Stretch. Таким образом, любые пакеты Python в глобальном каталоге не будут доступны для виртуальной среды cv. Аналогично, любые пакеты cv Python, не будут доступны для глобальной установки Python.

Теперь запустим интерпретатор Python:

python 

и убедимся, что используется версию 3.5 (или выше).

В интерпретаторе (появится «>>>») импортируем модуль cv2:

import cv2

Если сообщения об ошибках не появляются, OpenCV правильно установлен в вашей виртуальной среде PYTHON.

Вы также можете проверить установленную версию OpenCV:

cv2.__version__ 

Вид терминала после выполнения вышеописанных команд:


Тестирование камеры

После установки OpenCV, давайте проверим работоспособность камеры. Если на данный момент вы не подключили и не настроили камеру, посмотрите в предыдущем проекте, как это сделать.

В среде разработки введите следующий (или скачайте файл simpleCamTest.py из репозитория GitHub):

import numpy as np
import cv2
cap = cv2.VideoCapture(0)
while(True):
    ret, frame = cap.read()
    frame = cv2.flip(frame, -1) # Flip camera vertically
    gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
    
    cv2.imshow('frame', frame)
    cv2.imshow('gray', gray)
    if cv2.waitKey(1) & 0xFF == ord('q'):
        break
cap.release()
cv2.destroyAllWindows()

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

Обратите внимание, что в коде сделан поворот изображения с камеры. Если Вы иначе закрепили камеру и поворот не нужен, закомментируйте или удалите в коде строку «flip»:

frame = cv2.flip(frame, -1) # Flip camera vertically

Для запуска этого скрипта, выполним команду:

python simpleCamTest.py



Для прерывания выполнения скрипта, нажмите Ctrl+C.

Определение цвета в Python с OpenCV

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

OpenCV может работать с изображениями в различных цветовых пространствах. Это BGR, RGB, RGBA, BGRA, HSV, HLS, XYZ, LAB, Gray, YUV и т.д.

В модели RGB цвет каждого пикселя формируется комбинацией трёх базовых цветов: красный (red), зелёный (green) и синий (blue). Отличия между моделью RGB и BGR заключается только в порядке цветовых компонент. На данный момент очень часто для хранения и работы с изображениями используют не BGR, а RGB. Будьте внимательны, т.к. в OpenCV по умолчанию используется BGR. Так исторически сложилось - на ранних этапах разработки OpenCV, в видеокартах, камерах и программном обеспечении очень популярным был именно BGR.

В BGR для кодирования каждой компоненты используется один байт, т.е. значение может принимать значения в диапазоне от 0 до 255 (или от 0 до FF в шестнадцатеричном формате). Например, чистый синий пиксель на экране вашего компьютера будет иметь значение B 255, значение G 0 и значение R 0.



Цветовая модель HSV (Hue, Saturation, Value — тон, насыщенность, значение) является альтернативным представлением цветовой модели RGB, разработанной в 1970-х годах исследователями компьютерной графики для большего соответствия тому, как человечек воспринимает цвета.



Для нахождения и отслеживания цветных предметов с помощью OpenCV, мы будем использовать цветовую модель HSV. К примеру, нужно отследить желтый объект:



Что бы определиться, какой цвет в программе нужно искать, можно открыть изображение в какой-нибудь графической программе (графический редактор, видеоредактор и т.д.). Я использовал PowerPoint. В данном случае для указанного пикселя следующие значения цветовых компонент:

Синий - 71
Зеленый - 234
Красный - 213

Полученные значения цвета в модели BGR (71, 234, 213) нужно преобразовать в HSV, с указанием верхней и нижней границ диапазона. Для этого будем использовать следующий код (файл bgr_hsv_converter.py):

import sys
import numpy as np
import cv2
blue = sys.argv[1]
green = sys.argv[2]
red = sys.argv[3]  
color = np.uint8([[[blue, green, red]]])
hsv_color = cv2.cvtColor(color, cv2.COLOR_BGR2HSV)
hue = hsv_color[0][0][0]
print("Lower bound is :"),
print("[" + str(hue-10) + ", 100, 100]\n")
print("Upper bound is :"),
print("[" + str(hue + 10) + ", 255, 255]")

Для выполнения введите команду ниже, имеющую в качестве параметров значения BGR, найденные ранее:

python bgr_hsv_converter.py 71 234 213

Программа выведет верхнюю и нижнюю границы цвета нашего объекта:



С цветом разобрались, теперь сделаем что бы OpenCV создал маску для нужного цветового диапазона. Скрипт colorDetection.py:

import cv2
import numpy as np
# Read the picure - The 1 means we want the image in BGR
img = cv2.imread('yellow_object.JPG', 1) 
# resize imag to 20% in each axis
img = cv2.resize(img, (0,0), fx=0.2, fy=0.2)
# convert BGR image to a HSV image
hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV) 
# NumPy to create arrays to hold lower and upper range 
# The “dtype = np.uint8” means that data type is an 8 bit integer
lower_range = np.array([24, 100, 100], dtype=np.uint8) 
upper_range = np.array([44, 255, 255], dtype=np.uint8)
# create a mask for image
mask = cv2.inRange(hsv, lower_range, upper_range)
# display both the mask and the image side-by-side
cv2.imshow('mask',mask)
cv2.imshow('image', img)
# wait to user to press [ ESC ]
while(1):
  k = cv2.waitKey(0)
  if(k == 27):
    break
cv2.destroyAllWindows()

Для выполнения этого демонстрационного скрипта нужно что бы в папке был файл с изображением, в моем случае это файл «yellow_object.JPG». Исли у Вас другое имя файла, переименуйте его или в коде замените на нужное. Запускаем скрипт:

python colorDetection.py

На экране появится два изображения:



Это исходное изображение и полученная маска.

Отслеживание движения объекта

Теперь, когда мы знаем, как получить маску для объекта, перейдём к отслеживать его движения. Для этого я адаптировал код «отслеживание мяча» Адриана Роузброка. Перед использованием моего кода, убедитесь, что у Вас установлена библиотека imutils. Это коллекция удобных функций OpenCV, созданная Адрианом, которая значительно упрощает выполнение нескольких основных задач (таких как изменение размера или отображение). Если её нет, для установки в среде Virtual Python введите команду:

pip install imutils

Затем загрузите код ball_tracking.py с моего GitHub и выполните его с помощью команды:

python ball_traking.py

В результате Вы увидите нечто похожее:



По сути, это тот же код, что и у Адриана, за исключением переворачивания изображения вот такой строкой кода:

frame = imutils.rotate(frame, angle=180)

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

Тестирование GPIO

Теперь, когда мы познакомились с основами OpenCV, давайте установим светодиод на наш RPi и начнем взаимодействовать с нашими GPIO. Этот светодиод мы будем использовать для индикации, когда в кадре с камеры будут обнаружен объект нужного цвета.

Схема подключения светодиода очень проста:



Катод светодиода подключаем к GPIO 21, а анод к GND через резистор 220Ом.

Давайте проверим наш светодиод в нашей виртуальной среде cv Python.

Помните, что возможно, что RPi.GPIO не установлен в вашей виртуальной среде cv Python. Если нужно установить (не забудьт, что для cv Python в консоли вначале строки отображается «cv»), выполните следующую команду:

pip install RPi.GPIO

Для управления светодиодом будет использоваться скрипт GPIO_LED_test.py:

import sys
import time
import RPi.GPIO as GPIO
# initialize GPIO and variables
redLed = int(sys.argv[1])
freq = int(sys.argv[2])
GPIO.setmode(GPIO.BCM)
GPIO.setup(redLed, GPIO.OUT)
GPIO.setwarnings(False)
print("\n [INFO] Blinking LED (5 times) connected at GPIO {0} at every {1} second(s)".format(redLed, freq))
for i in range(5):
    GPIO.output(redLed, GPIO.LOW)
    time.sleep(freq)
    GPIO.output(redLed, GPIO.HIGH)
    time.sleep(freq)
# do a bit of cleanup
print("\n [INFO] Exiting Program and cleanup stuff \n")
GPIO.cleanup()

Этот скрипт получает в качестве аргумента номер GPIO и частоту, с которой светодиод должен мигать. Светодиод будет мигать 5 раз, после чего программа завершиться. Обратите внимание, что перед завершением мы освободим GPIO.

Пример использования:

python LED_simple_test.py 21 1



Теперь сделаем что бы при обнаружении объекта загорался светодиод. «Создаём» в скрипте светодиод:

import RPi.GPIO as GPIO
redLed = 21
GPIO.setmode(GPIO.BCM)
GPIO.setwarnings(False)
GPIO.setup(redLed, GPIO.OUT)

Инициализируем (делаем изначально выключенным):

GPIO.output(redLed, GPIO.LOW)
ledOn = False

Теперь внутри цикла, где отрисовывается круг, когда объект найден, включаем светодиод:

GPIO.output(redLed, GPIO.HIGH)
ledOn = True

Полный код получившегося скрипта можно скачать по ссылке: object_detection_LED.py.

Запускаем скрипт:

python object_detection_LED.py

Здесь результат. Обратите внимание, что светодиод (левый нижний угол) загорается при каждом обнаружении объекта:



Поэкспериментируем с объектами разного цвета и формы:



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



Если у Вас ещё не собран или не настроен механизм поворота/наклона, сделайте это как описано в прошлых проектах (см. ссылки в самом начале).

Для теста работоспособности в cv Python запустим скрипт angleServoCtrl.py:

python angleServoCtrl.py 17 45 

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

Определение координат объекта

Чтобы в кадре объект находился примерно по центру, нам при его обнаружении нужно знать его координаты.

За основу возьмем скрипт «object_detect_LED», использованный ранее, и изменим его, чтобы вывести координаты найденного объекта. В итоге получится такой скрипт objectDetectCoord.py.

В той части где, где происходило обнаружение объекта и отрисовка вокруг него круга с красной точкой внутри, добавлен вызов функции, которая выводит координаты объекта:

if radius > 10:
	# draw the circle and centroid on the frame,
	# then update the list of tracked points
	cv2.circle(frame, (int(x), int(y)), int(radius),
		(0, 255, 255), 2)
	cv2.circle(frame, center, 5, (0, 0, 255), -1)
			
	# print center of circle coordinates
	mapObjectPosition(int(x), int(y))
			
	# if the led is not already on, turn the LED on
	if not ledOn:
		GPIO.output(redLed, GPIO.HIGH)
		ledOn = True

Функция вывода координат:

def mapObjectPosition (x, y):
    print ("[INFO] Object Center coordenates at X0 = {0} and Y0 =  {1}".format(x, y))

При выполнении скрипта, в терминале будут выводиться координаты центра найденного объекта:



Теперь эти координаты мы будем использовать для управления сервоприводами.



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

220 < х < 280
160 < у < 210

Когда полученные координаты будут выходить за пределы этого диапазона, мы будем соответствующим образом управлять механизмом поворота/наклона, чтобы компенсировать это отклонение. Для этого добавим функцию «mapServoPosition (x, y)»:

def mapServoPosition (x, y):
    global panAngle
    global tiltAngle
    if (x < 220):
        panAngle += 10
        if panAngle > 140:
            panAngle = 140
        positionServo (panServo, panAngle)
    if (x > 280):
        panAngle -= 10
        if panAngle < 40:
            panAngle = 40
        positionServo (panServo, panAngle)
    if (y < 160):
        tiltAngle += 10
        if tiltAngle > 140:
            tiltAngle = 140
        positionServo (tiltServo, tiltAngle)
    if (y > 210):
        tiltAngle -= 10
        if tiltAngle < 40:
            tiltAngle = 40
        positionServo (tiltServo, tiltAngle)

Обратите внимание, что «x» и «y», используемые в качестве параметров в этой функции, это координаты центра найденного объекта. На основе этих координат определяем какой сервопривод и на какой угол нужно установить, затем вызываем функцию «positionServo (panServo, panAngle)».

Например, предположим, что текущий угол наклона примерно равен 120 градусам, а y=50. Это означает, что объект где-то в верхней части изображения. Чтобы это скомпенсировать, нам нужно уменьшить угол наклона (допустим, до 100 градусов), чтобы центр объекта оказался в центре изображения с камеры. Для наглядности:



Обратите внимание, что изображение с камеры не зеркальное. Это означает, что, если Вы провернёте камеру влево, то объект на изображении переместиться вправо.

Функцию «positionServo(servo, angle)» можно реализовать так:

def positionServo (servo, angle):
    os.system("python angleServoCtrl.py " + str(servo) + " " + str(angle))
    print("[INFO] Positioning servo at GPIO {0} to {1} degrees\n".format(servo, angle))

Обратите внимание, что скрипт angleServoCtrl.py должен находиться в том же каталоге, что и objectDetectTrack.py. Полный код можно скачать с моего GitHub: objectDetectTrack.py

Ниже приведен пример работы нашего проекта:



Все файлы проекта Вы можете скачать из моего репозитория на GitHub: OpenCV-Object-Face-Tracking. В этом репозитории некоторые файлы от следующего проекта, над которым работаю:



Автор: Marcelo Rovai
Перевод и адаптация: RobotoTehnika.ru