OpenCV на python: выделение контуров
Освоив работу с цветовыми фильтрами приступим к изучению ещё одного важного инструмента машинного зрения — функции выделения контуров.
Контур объекта — это его видимый край, который отделяет объект от фона. В действительности, большинство методов анализа изображений работают именно с контурами, а не с пикселями как таковыми. Совокупность методов работы с контурами называется контурным анализом.
Возьмём в качестве подопытного изображения что-нибудь такое, где есть ярко выраженные вложенные контуры, какой-нибудь диск. И попробуем применить к нему стандартные функции 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()
К размышлению
Теперь мы можем находить контуры и отображать их прямо в картинке. Имея готовые контуры можно приступить к дальнейшему анализу, включая: поиск геометрических фигур, вычисление их координат и положения, детектирование лиц и жестов.
На следующем уроке начнем с простого — займемся поиском прямоугольников в кадре и вычислением угла их наклона.