Освоив работу с цветовыми фильтрами приступим к изучению ещё одного важного инструмента машинного зрения — функции выделения контуров.
Контур объекта — это его видимый край, который отделяет объект от фона. В действительности, большинство методов анализа изображений работают именно с контурами, а не с пикселями как таковыми. Совокупность методов работы с контурами называется контурным анализом.
Возьмём в качестве подопытного изображения что-нибудь такое, где есть ярко выраженные вложенные контуры, какой-нибудь диск. И попробуем применить к нему стандартные функции OpenCV для поиска и визуализации контуров объектов.
1. Функция OpenCV для поиска контуров findContours
В OpenCV для поиска контуров имеется функцией findContours, которая имеет вид:
findContours( кадр, режим_группировки, метод_упаковки [, контуры[, иерархия[, сдвиг]]])
кадр — должным образом подготовленная для анализа картинка. Это должно быть 8-битное изображение. Поиск контуров использует для работы монохромное изображение, так что все пиксели картинки с ненулевым цветом будут интерпретироваться как 1, а все нулевые останутся нулями. На уроке про поиск цветных объектов была точно такая же ситуация.
режим_группировки — один из четырех режимов группировки найденных контуров:
- CV_RETR_LIST — выдаёт все контуры без группировки;
- CV_RETR_EXTERNAL — выдаёт только крайние внешние контуры. Например, если в кадре будет пончик, то функция вернет его внешнюю границу без дырки.
- CV_RETR_CCOMP — группирует контуры в двухуровневую иерархию. На верхнем уровне — внешние контуры объекта. На втором уровне — контуры отверстий, если таковые имеются. Все остальные контуры попадают на верхний уровень.
- CV_RETR_TREE — группирует контуры в многоуровневую иерархию.
метод_упаковки — один из трёх методов упаковки контуров:
- CV_CHAIN_APPROX_NONE — упаковка отсутствует и все контуры хранятся в виде отрезков, состоящих из двух пикселей.
- CV_CHAIN_APPROX_SIMPLE — склеивает все горизонтальные, вертикальные и диагональные контуры.
- CV_CHAIN_APPROX_TC89_L1,CV_CHAIN_APPROX_TC89_KCOS — применяет к контурам метод упаковки (аппроксимации) Teh-Chin.
контуры — список всех найденных контуров, представленных в виде векторов;
иерархия — информация о топологии контуров. Каждый элемент иерархии представляет собой сборку из четырех индексов, которая соответствует контуру[i]:
- иерархия[i][0] — индекс следующего контура на текущем слое;
- иерархия[i][1] — индекс предыдущего контура на текущем слое:
- иерархия[i][2] — индекс первого контура на вложенном слое;
- иерархия[i][3] — индекс родительского контура.
сдвиг — величина смещения точек контура.
2. Функция OpenCV для отображения контуров drawContours
Полученные с помощью функции findContours контуры хорошо бы каким-то образом нарисовать в кадре. Машине это не нужно, зато нам это поможет лучше понять как выглядят найденные алгоритмом контуры. Поможет в этом ещё одна полезная функция — drawContours.
drawContours( кадр, контуры, индекс, цвет[, толщина[, тип_линии[, иерархия[, макс_слой[, сдвиг]]]]])
кадр — кадр, поверх которого мы будем отрисовывать контуры;
контуры — те самые контуры, найденные функцией findContours;
индекс — индекс контура, который следует отобразить. -1 — если нужно отобразить все контуры;
цвет — цвет контура;
толщина — толщина линии контура;
тип_линии — тип соединения точек вектора;
иерархия — информация об иерархии контуров;
макс_слой — индекс слоя, который следует отображать. Если параметр равен 0, то будет отображен только выбранный контур. Если параметр равен 1, то отобразится выбранный контур и все его дочерние контуры. Если параметр равен 2, то отобразится выбранный контур, все его дочерние и дочерние дочерних! И так далее.
сдвиг — величина смещения точек контура.
3. Программа поиска и отображения контуров
Теперь напишем программу, которая будет искать контуры предметов в кадре с пончиком. Прежде всего, следует подготовить изображение. Помним, что функция findContours работает с монохромной картинкой, и нам потребуется обработать наш пончик цветовым фильтром, чтобы сам пончик стал абсолютно белым, а фон — чёрным.
import sys
import numpy as np
import cv2 as cv
# параметры цветового фильтра
hsv_min = np.array((2, 28, 65), np.uint8)
hsv_max = np.array((26, 238, 255), np.uint8)
if __name__ == '__main__':
print(__doc__)
fn = 'image.jpg' # путь к файлу с картинкой
img = cv.imread(fn)
hsv = cv.cvtColor( img, cv.COLOR_BGR2HSV ) # меняем цветовую модель с BGR на HSV
thresh = cv.inRange( hsv, hsv_min, hsv_max ) # применяем цветовой фильтр
# ищем контуры и складируем их в переменную contours
_, contours, hierarchy = cv.findContours( thresh.copy(), cv.RETR_TREE, cv.CHAIN_APPROX_SIMPLE)
# отображаем контуры поверх изображения
cv.drawContours( img, contours, -1, (255,0,0), 3, cv.LINE_AA, hierarchy, 1 )
cv.imshow('contours', img) # выводим итоговое изображение в окно
cv.waitKey()
cv.destroyAllWindows()
В результате работы программы мы получим пончики с выделенными внешними и вложенными границами.
Теперь разберёмся как параметры кадр и макс_слой влияют на отображаемые контуры.
Алгоритм findContours нашел у пончиков четыре замкнутых контура. Если вывести иерархию в консоль с помощью обычного print, то мы увидим следующую таблицу:
[ 2 -1 1 -1] - внешний контур первого бублика [-1 -1 -1 0] - дырка первого бублика [-1 0 3 -1] - внешний контур второго бублика [-1 -1 -1 2] - дырка второго бублика
На верхнем уровне иерархии имеется два контура. Эти контуры легко вычислить по значению четвертой величины = -1, которая отвечает за указатель на родительский контур. Также имеются два вложенных контура. Один вложен в первый внешний контур, а второй вложен во второй внешний контур.
В программе параметр контур = -1, следовательно drawContours должна вывести все четыре найденных контура. Но есть ещё второй параметр макс_слой, как он будет влиять на вывод? Посмотрим его поведение на анимации:
Примечание! На верхнем бегунке contour = 0, хотя мы почему-то говорим про -1. Это не ошибка! На самом деле в этом положении бегунка в функцию поступает параметр контур = -1. Это несоответствие возникло из-за особенностей бегунка в OpenCV — он не может принимать отрицательные значения, поэтому в программе из значения бегунка contour каждый раз принудительно вычитается единица.
Вернёмся к параметрам.
При макс_слой = 0 отображается первый попавшийся контур на верхнем уровне иерархии. Такая комбинация параметров вообще нетипичная и бесполезная, она эквивалентна комбинации контур = 0, макс_слой=0.
При макс_слой = 1 отобразятся все контуры на самом верхнем уровне иерархии — это уже полезно. Так мы сможем увидеть все бублики в кадре.
Наконец, при макс_слой = 2 отобразятся контуры на верхнем уровне и все контуры на следующем уровне — то есть дырки.
Теперь наоборот, зафиксируем макс_слой = 0, и будем менять контур в диапазоне от 0 до 3.
Тут опять видна путающая всех первая комбинация: контур = -1, макс_слой = 0, игнорируем её. Но затем всё становится логично. Как и ожидалось, мы просто перебираем контуры на всех слоях, внутренних и внешних.
Чтобы самостоятельно поэкспериментировать с параметрами можно написать программу, которая добавит в окно два бегунка для изучаемых параметров. Подобное мы делали на уроке про цветовые фильтры.
import sys
import numpy as np
import cv2 as cv
hsv_min = np.array((2, 28, 65), np.uint8)
hsv_max = np.array((26, 238, 255), np.uint8)
if __name__ == '__main__':
fn = 'donuts.jpg'
img = cv.imread(fn)
hsv = cv.cvtColor( img, cv.COLOR_BGR2HSV )
thresh = cv.inRange( hsv, hsv_min, hsv_max )
_, contours0, hierarchy = cv.findContours( thresh.copy(), cv.RETR_TREE, cv.CHAIN_APPROX_SIMPLE)
index = 0
layer = 0
def update():
vis = img.copy()
cv.drawContours( vis, contours0, index, (255,0,0), 2, cv.LINE_AA, hierarchy, layer )
cv.imshow('contours', vis)
def update_index(v):
global index
index = v-1
update()
def update_layer(v):
global layer
layer = v
update()
update_index(0)
update_layer(0)
cv.createTrackbar( "contour", "contours", 0, 7, update_index )
cv.createTrackbar( "layers", "contours", 0, 7, update_layer )
cv.waitKey()
cv.destroyAllWindows()
К размышлению
Теперь мы можем находить контуры и отображать их прямо в картинке. Имея готовые контуры можно приступить к дальнейшему анализу, включая: поиск геометрических фигур, вычисление их координат и положения, детектирование лиц и жестов.
На следующем уроке начнем с простого — займемся поиском прямоугольников в кадре и вычислением угла их наклона.