Computer vision, Machine Learning

OpenCV. Детектирование светофора.

Время прочтения: 5 мин.

Как я выяснил в предыдущей статье, свёрточные, нейронные и другие сети для этого применять очень проблематично из-за их долгого времени работы. Так как в перспективе данная разработка будет установлена в автомобиль, соответственно время работы алгоритма должно быть приближено к реальному времени. Напомню, что даже самая быстрая нейронная сеть, построенная на базе YOLO архитектуры, работала больше минуты. В статье я более подробно описывал измерение времени работы, но кратко напомню. Вычисления производятся на обычном ноутбуке со встроенной видеокартой. Я сознательно не использую Google Colab, так как в перспективе код будет залит на портативный компьютер на базе RaspberryPI, мощность которого точно не будет превышать мощность моего ноутбука.  Соответственно и в этот раз выбор остановился на OpenCV.

Следующим этапом стал выбор алгоритма работы. Именно его я считаю наиболее ценным в этой статье и сейчас объясню почему. На просторах интернета можно найти два наиболее распространенных алгоритма: первый это перевод в цветовое пространство HSV и работа с цветами, накладывая маски на ненужные цвета, а второй это нахождение контуров на ЧБ изображении функцией Canny. Я решил остановиться на втором алгоритме.

Для нахождения контуров с помощью функции Canny нужно сделать изображение ЧБ и применить функцию Canny, после чего замкнуть контуры. Но, функция находит буквально все контуры и из-за этого выделить конкретный объект (светофор) не получается.

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

Код функции Canny :

image = cv.imread(img)
frame_hsv = cv.cvtColor(image, cv.COLOR_BGR2GRAY)
cv.imshow('hsv', frame_hsv)
counturs = cv.Canny(frame_hsv, 350, 500)
cv.imshow('counturs', counturs)
kernel = cv.getStructuringElement(cv.MORPH_RECT, (2, 2))
cv.imshow('kernel', kernel)
closed = cv.morphologyEx(counturs, cv.MORPH_CLOSE, kernel)
cv.imshow('closed', closed)

contours, hierarchy = cv.findContours(closed.copy(), cv.RETR_EXTERNAL, cv.CHAIN_APPROX_SIMPLE)

sort = []
for i in contours:
    area = cv.contourArea(i)

    if 4 < area < 100000:  # можно регулировать дальность
        sort.append(i)

cv.drawContours(image, sort, -1, (0, 203, 44), 2)

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

После небольшого анализа и проб, я пришел к выводу, что правильнее всего будет использовать пространство RGB. В нем лучше всего получается замаскировать все цвета кроме чёрного.

img = cv.imread('images/images/img_2.png')
spisok = []
image = cv.cvtColor(img, cv.COLOR_BGR2RGB)
frame_mask = cv.inRange(image, (0, 0, 0), (51, 38, 51))
cv.imshow('mask', frame_mask)

frame_dilate = cv.dilate(frame_mask, None, iterations=2)

contours, hierarchy = cv.findContours(frame_dilate.copy(), cv.RETR_EXTERNAL, cv.CHAIN_APPROX_SIMPLE)
sort = spisok
for i in contours:
    area = cv.contourArea(i)

    if 400 < area:  # можно регулировать дальность
        sort.append(i)

cv.drawContours(image, sort, -1, (0, 232, 44), 2)
cv.imshow('img', image)

(x, y, w, h) = cv.boundingRect(sort[7])
detect = img[y:y + h, x:x + w]
cv.imshow('cadr', detect)
cv.imwrite('cadr.jpg', detect)

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

Протестируем работоспособность программы еще на нескольких изображениях. Ниже приведу фотографии: слева будет входное изображение, а справа кадрированное изображение светофора.

Программа обрезает фото не идеально, задевая часть заднего фона. Особенно тяжело, когда светофор находится на фоне листвы или других темных объектов. Но я считаю, что такого качества будет достаточно для работы с кадрированным изображением в дальнейшем.

Вывод: думаю, моя статья будет полезна тем, кому нужно детектировать темные или же совсем черные объекты и функция Canny, которую советуют использовать чаще всего, не подходит. Также полезно почерпнуть значения функции inRange, которая определяет разрешенные цвета и накладывает маску на все остальные. Я старался подобрать оптимальные значения, чтобы программа работала не в одном конкретном случае, а при разных обстоятельствах. На этом у меня все, желаю всем удачи в изучении компьютерного зрения. 

Советуем почитать