OpenCV на python: поиск цветного объекта

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

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

OpenCV на python. Поиск цветного объекта

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

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

1. Алгоритм поиска цветового пятна в OpenCV. Моменты изображения

Стандартный прием в OpenCV для решения поставленной задачи складывается из двух этапов.

На первом этапе мы применяем цветовой фильтр и превращаем каждый кадр в черно-белую картинку. После наложения фильтра объект заданного цвета превращается в белое пятно, а всё остальное заливается черным цветом. Вот так выглядит результат обработки фильтром кадра из прошлого урока.

OpenCV на python. Цветовой фильтр

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

Например, момент нулевого порядка m00 — это количество всех точек, составляющих пятно. Момент первого порядка m10 представляет собой сумму X координат точек, а m01 — сумму Y координат. Имеются также моменты m11, m20, m02, m22 и т.д.

Формула для вычисления моментов очень проста, и мы можем посчитать пиксели вручную. Однако, стандартные функции OpenCV написаны на языках более низкого уровня, чем python, и работают быстрее. Воспользуемся стандартной функцией для вычисления моментов кадра:

moments( кадр, двоичный )

Аргумент кадр представляет собой нашу предобработанную картинку. Аргумент двоичный определяет то, как алгоритм будет вычислять вес каждой точки. Напомню, что предыдущий метод inRange дал нам черно-белую картинку, в которой пиксели могут быть черными, белыми, а могут быть и серыми. Так вот, если аргумент двоичный равен 1, то вес всех точек с цветом, отличным от нуля будет равен единице. В противном случае, вес черной точки будет равен 0, а белой точки — 255.

Функция moments вернет нам массив моментов вплоть до третьего порядка. Но для вычисления координат центра пятна нам потребуются только моменты первого порядка m01 и m10, а также момент m00.

dM01 = moments['m01']
dM10 = moments['m10']
dArea = moments['m00']

Чтобы получить координаты X и Y искомого пятна, нам следует поделить полученные моменты m10 и m01 на нулевой момент m00. Таким образом мы найдем средние координаты X и Y всех точек, а это и есть центр пятна.

x = int(dM10 / dArea)
y = int(dM01 / dArea)

2. Программа для поиска цветового пятна на Raspberry Pi и python

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

import cv2
import numpy as np
import video

if __name__ == '__main__':
    def callback(*arg):
        print (arg)

cv2.namedWindow( "result" )

cap = video.create_capture(0)
# HSV фильтр для зеленых объектов из прошлого урока
hsv_min = np.array((53, 55, 147), np.uint8)
hsv_max = np.array((83, 160, 255), np.uint8)

while True:
    flag, img = cap.read()
    # преобразуем RGB картинку в HSV модель
    hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV )
    # применяем цветовой фильтр
    thresh = cv2.inRange(hsv, hsv_min, hsv_max)

    # вычисляем моменты изображения
    moments = cv2.moments(thresh, 1)
    dM01 = moments['m01']
    dM10 = moments['m10']
    dArea = moments['m00']
    # будем реагировать только на те моменты,
    # которые содержать больше 100 пикселей
    if dArea > 100:
        x = int(dM10 / dArea)
        y = int(dM01 / dArea)
        cv2.circle(img, (x, y), 10, (0,0,255), -1)

    cv2.imshow('result', img) 
 
    ch = cv2.waitKey(5)
    if ch == 27:
        break

cap.release()
cv2.destroyAllWindows()

Запускаем программу и пробуем водить перед камерой зеленым объектом.

Примечание! Функция moment не может различить одно пятно в кадре или несколько. Так что, если в кадре окажется, например, два зеленых объекта, то они интерпретируются как один большой, и центр будет где-то между ними.

3. Программа для поиска цветового пятна на Raspberry Pi и python. Подсветка траектории

Для большей наглядности будем выводить в кадр траекторию движения объекта.

import cv2
import numpy as np
import video

if __name__ == '__main__':
    def callback(*arg):
        print (arg)

def createPath( img ):
    h, w = img.shape[:2] 
    return np.zeros((h, w, 3), np.uint8)

cv2.namedWindow( "result" )

cap = video.create_capture(0)
hsv_min = np.array((53, 55, 147), np.uint8)
hsv_max = np.array((83, 160, 255), np.uint8)

lastx = 0
lasty = 0
path_color = (0,0,255)

flag, img = cap.read()
path = createPath(img)

while True:
    flag, img = cap.read()
    hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV )
    thresh = cv2.inRange(hsv, hsv_min, hsv_max)

    moments = cv2.moments(thresh, 1)
    dM01 = moments['m01']
    dM10 = moments['m10']
    dArea = moments['m00']

    if dArea > 100:
        x = int(dM10 / dArea)
        y = int(dM01 / dArea)
        cv2.circle(img, (x, y), 10, (0,0,255), -1)

    if lastx > 0 and lasty > 0:
        cv2.line(path, (lastx, lasty), (x,y), path_color, 5)
    lastx = x
    lasty = y

    # накладываем линию траектории поверх изображения
    img = cv2.add( img, path)

    cv2.imshow('result', img)

    ch = cv2.waitKey(5)
    if ch == 27:
        break

cap.release()
cv2.destroyAllWindows()

Пример работы программы на Raspberry Pi с обычной веб-камерой.

4. Программа для поиска цветового пятна на Raspberry Pi и python. Вывод координат

И еще одна модификация. Будем рядом с обнаруженным центром пятна выводить его координаты.

import cv2
import numpy as np
import video

if __name__ == '__main__':
    def callback(*arg):
        print (arg)

cv2.namedWindow( "result" )

cap = video.create_capture(0)
hsv_min = np.array((53, 55, 147), np.uint8)
hsv_max = np.array((83, 160, 255), np.uint8)

color_yellow = (0,255,255)

while True:
    flag, img = cap.read()
    img = cv2.flip(img,1) # отражение кадра вдоль оси Y
    hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV )
    thresh = cv2.inRange(hsv, hsv_min, hsv_max)

    moments = cv2.moments(thresh, 1)
    dM01 = moments['m01']
    dM10 = moments['m10']
    dArea = moments['m00']

    if dArea > 100:
        x = int(dM10 / dArea)
        y = int(dM01 / dArea)
        cv2.circle(img, (x, y), 5, color_yellow, 2)
        cv2.putText(img, "%d-%d" % (x,y), (x+10,y-10), 
                    cv2.FONT_HERSHEY_SIMPLEX, 1, color_yellow, 2)

    out.write(img)
    cv2.imshow('result', img)
 
    ch = cv2.waitKey(5)
    if ch == 27:
        break

cap.release()
cv2.destroyAllWindows()

Запускаем программу и проверяем работу алгоритма. Снова используем Raspberry Pi. Как видно на видео, мощности Raspberry Pi вполне хватает для выполнения поиска цветного объекта с помощью моментов.

Ну вот и все на сегодня. На следующем уроке заставим роботов искать не просто цветные пятна, а целые геометрические фигуры!


Изменено: