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

Или другой пример — автоматическая пейтбольная турель. Это такой робот, который выискивает перед собой цель, поворачивает орудие на нужные углы и стреляет шариками с краской. Опять таки, цель может иметь яркую цветную метку, отличающуюся от фона (оранжевое пятно на куртке игрока).
На этом уроке мы разберем алгоритм детектирования и вычисления координат цветового пятна в кадре. То есть будем водить объектом какого-то конкретного цвета перед камерой, а программа будет выдавать нам координаты этого объекта относительно краев видеокадра.
Алгоритм поиска цветового пятна в OpenCV. Моменты изображения
Стандартный прием в OpenCV для решения поставленной задачи складывается из двух этапов.
На первом этапе мы применяем цветовой фильтр и превращаем каждый кадр в черно-белую картинку. После наложения фильтра объект заданного цвета превращается в белое пятно, а всё остальное заливается черным цветом. Вот так выглядит результат обработки фильтром кадра из прошлого урока.

На следующем этапе мы используем алгоритм вычисления моментов. Момент изображения — это суммарная характеристика пятна, представляющая собой сумму всех точек (пикселей) этого пятна. При этом, имеется множество подвидов моментов, характеризующие разные свойства изображения.
Например, момент нулевого порядка 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)
Программа для поиска цветового пятна на 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 не может различить одно пятно в кадре или несколько. Так что, если в кадре окажется, например, два зеленых объекта, то они интерпретируются как один большой, и центр будет где-то между ними.
Программа для поиска цветового пятна на 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 с обычной веб-камерой.
Программа для поиска цветового пятна на 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)
cv2.imshow('result', img)
ch = cv2.waitKey(5)
if ch == 27:
break
cap.release()
cv2.destroyAllWindows()
Запускаем программу и проверяем работу алгоритма. Снова используем Raspberry Pi. Как видно на видео, мощности Raspberry Pi вполне хватает для выполнения поиска цветного объекта с помощью моментов.
Ну вот и все на сегодня. На следующем уроке заставим роботов искать не просто цветные пятна, а целые геометрические фигуры!
Не работает строчка out.write(img). Текст ошибки: NameError: name ‘out’ is not defined
Что делать?
Убрать эту строчку! Она лишняя, так бывает 🙂
У меня работает только с USB камеры. Как задействовать штатную камеру подключаемую к разъему MIPI Camera Serial Interface (CSI-2) ?
Нужно ставить пакет picamera. Напишем про него туториал позже.
Спасибо! Будем ждать.
Еще нет туториала? Если есть подскажите пожалуйста
Доброго дня! Не подскажете ли вы мне, как можно изменять разрешение съёмки и частоту кадров применительно к этой программе (-ам)? Я видел похожий код от зарубежного автора, но там была лишь строчка изменяющая разрешение (представлена ниже), но о регулировке частоты нигде и ничего не находится.
(cap = cv2.VideoCapture(0)
# Reduce the size of video to 320×240 so rpi can process faster
cap.set(3,320)
cap.set(4,240))
Частота кадров зависит от вычислительной мощности вашего компьютера и размера кадра. Уменьшая размер, вы тем самым ускоряете обработку каждого кадра, соответственно, увеличиваете частоту видеопотока на выходе.
Да если бы это было так просто.. Я задаю разрешение 720p и загрузка процессора становится равной 60%, меняю на 480p и загрузка уменьшается на 5-10% (нет чёткой зависимости), при этом прироста частоты кадров не наблюдается (на 1-2, может, 3 больше становится, но это несерьёзно). Может ли проблема быть в самом OpenCV (в его неэффективных алгоритмах)?
В OpenCv можно уменьшить частоту камеры, задается как у вас в set (точно не помню как называется, там для этого макрос есть, вродебы на CAP начинается). Так же можно через waitKey(задержка) задать, где задержка — число в миллисекундах. Но это повлияет на всю работы программы.
Где взять import video ?
Не использую его. Замени его на cap = cv2.VideoCapture(0) и не чего лишнего. тот же самый захват камеры.
Подскажите пожалуйста как можно сделать чтобы при появлении в кадре «зеленого обьекта» загорелся светодиод на определненное время?
Есть урок про зажигание светодиода на Raspberry Pi
Увидели объект — зажигаем светодиод — запускаем таймер
постоянно проверяем значение таймера
как только таймер дотикал до таймаута — гасим светодиод
у меня распознаются объекты на видео и отмечаются.. как записать их координаты?
Как вариант, можно воспользоваться функциями python для работы с файлами. Типа:
file = open(«C:\\my_file.txt», «a»)
file.write(«x=» + x + «,y=» + y)
file.close()
В этом кусочке:
if lastx > 0 and lasty > 0:
cv2.line(path, (lastx, lasty), (x,y), path_color, 5)
lastx = x
lasty = y
PyCharm ругается на то, что не определены переменные x/y
Не подскажете, они определены где-то ранее, просто я не понял?
Как это решить ?
SyntaxError: Non-ASCII character ‘\xd1’ in file aza.py on line 12, but no encoding declared; see http://python.org/dev/peps/pep-0263/ for details
Скорее всего при копировании кода из браузера скопировался скрытый символ, который все портит.
Здравствуйте. У меня проблема. Возникает ошибка
flag, img = cap.read()
AttributeError: ‘NoneType’ object has no attribute ‘read’
Хотя драйвер камеры правильный и в прошлом уроке всё работало.
измените значения захвата,
cap = video.create_capture(0)
на 1 возможно камера занята чем-то. или перезагрузите компьютер.
Здравствуйте. Прошла прошлый урок со встроенной распберри камерой и настроенным драйвером. Соответственно, кусок с настройкой вставлен в то жже начало программы. В первой-же задаче возникает
flag, img = cap.read()
AttributeError: ‘NoneType’ object has no attribute ‘read’
Подскажите пожалуйста, что делать в этом случае.
Если что, Код вот тут:
https://pp.userapi.com/c844416/v844416612/1fd172/1A-_CJ7y8w0.jpg
Видимо скрипт не находит камеру #0. Попробуйте cap=video.create_capture(1)
Нее. То же самое….
File «redline_col.py», line 28, in
flag, img = cap.read()
AttributeError: ‘NoneType’ object has no attribute ‘read’
Здравствуйте, а есть идеи как найти 2 цветовых пятна ? Функция moment не может различить одно пятно в кадре или несколько.
Подскажите пожалуйста вариант, если в кадре находится несколько объектов одного цвета
Присоединяюсь ко всем выше сказанным =))
Подскажите пожалуйста, как вычислять координаты 2-х и более цветных пятен одного цвета.
Спасибо!!!!!
Накладываете цветовой фильтр, потом обнаруживаете прямоугольники/эллипсы объектов. Выделяете по площади — получаете нужное количество объектов.
Здравствуйте. Могли бы вы добавить уроки по openCV в отдельный блок статей? Сейчас вкладка блоки не работает, и полагаю такой блок уже есть, но проверить это я не могу
Что делать если при импорта библиотеки video выдаёт ошибку?
Пишу всем у кого ошибка !
Import Video
cap = video.create_capture(0)
^^^^^
NameError: name ‘video’ is not defined
Используйте cap = cv2.VideoCapture(0) и все заработает
Выводит ошибку к первому тексту программы
ch = cv2.waitKey(5)
IdentificationError: unexpected indent
Проверьте все отступы, возможно после копирования с сайта что-то сдвинулось. В Python тело оператора выделяется отступами, а не фигурными скобками как в языке Си (в том же Arduino IDE).