Обнаружение конгуров с использованием OpenCV

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

Итак, давайте узнаем о контурах и обнаружении контуров с помощью OpenCV, как их можно использовать для создания различных приложений.

Содержание

  1. Контуры в компьютерном зрении
  2. Что такое контуры?
  3. Последовательность поиска и отрисовки контуров с помощью OpenCV
  4. Поиск и отрисовка контуров с помощью OpenCV
    1. Отрисовка с помощью CHAIN_APPROX_NONE
    2. Отрисовка с помощью CHAIN_APPROX_SIMPLE.
  5. Иерархии контуров
    1. Родительско-дочерние отношения.
    2. Контурное представление отношений
    3. Различные методы извлечения контуров
  6. Резюме

Контуры в компьютерном зрении   

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

  • Обнаружение движения: в видеонаблюдении технология обнаружения движения имеет множество приложений, начиная от внутренней и внешней безопасности, контроля дорожного движения, обнаружения поведения во время занятий спортом, поиск оставленных без присмотра объектов и даже сжатие видео. На рисунке ниже показано, как обнаружение движения людей в видеопотоке может быть полезно в приложении для наблюдения. Обратите внимание, как группа людей, стоящих в левой части изображения, не обнаруживается. Захватываются только те, кто находится в движении. Обратитесь к этой статье для подробного изучения этого подхода.
    Пример обнаружения движущегося объекта, определение движущихся людей. Обратите внимание, что группа людей, стоящяя слева, не обнаруживается
    Пример обнаружения движущегося объекта, определение движущихся людей. Обратите внимание, что группа людей, стоящяя слева, не обнаруживается
  • Поиск подозрительных объектов. Любой объект, оставленный без присмотра в общественных местах, обычно, считается подозрительным. Эффективным и безопасным решением может быть: (Автоматическое обнаружение объекта посредством формирования контура с использованием вычитания фона).
    Поиск подозрительных объектов

    Изображение из процитированного документа — фоновая рамка без и с оставленным без присмотра предметом — идентификация и маркировка оставленного без присмотра предмета

  • Сегментация фона/переднего плана: чтобы заменить фон изображения другим, необходимо выполнить извлечение изображения переднего плана (аналогично сегментации изображения). Использование контуров — это один из подходов, который можно использовать для сегментации. Обратитесь к этому сообщению для получения более подробной информации. На следующих изображениях показаны простые примеры такого приложения:
    Пример простого извлечения переднего плана изображения и добавления нового фона к изображению с помощью определения контура
    Пример простого извлечения переднего плана изображения и добавления нового фона к изображению с помощью определения контура

Что такое контуры?   

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

  1. findContours()
  2. drawContours()

Также, у них есть два разных алгоритма обнаружения контура:

  1. CHAIN_APPROX_SIMPLE
  2. CHAIN_APPROX_NONE

Рассмотрим их подробно в приведенных ниже примерах. На следующем рисунке показано, как эти алгоритмы могут обнаруживать контуры простых объектов.

Сравните входное изображение и выходное изображение с наложенными контурами
Сравните входное изображение и выходное изображение с наложенными контурами

Теперь, когда вы знаете, что такое контуры, обсудим последовательность их обнаружения.

Последовательность поиска и отрисовки контуров с помощью OpenCV   

OpenCV делает это довольно простой задачей. Просто выполните следующие действия:

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

2. Применить двоичный порог
При нахождении контуров сначала всегда применяйте двоичное пороговое значение или обнаружение границ Canny к изображению в градациях серого. Здесь мы применим двоичное пороговое значение. Такое преобразование изображения в черно-белое, выделяет интересующие объекты и упрощает алгоритм обнаружения контуров. При установке порога граница объекта на изображении становится полностью белой, при этом все пиксели имеют одинаковую интенсивность. Теперь алгоритм может определять границы объектов по этим белым пикселям.

Примечание. Черные пиксели, имеющие значение 0, воспринимаются как пиксели фона и игнорируются.

Здесь может возникнуть один вопрос. Что, если мы будем использовать отдельные каналы, такие как R (красный), G (зеленый) или B (синий), вместо изображений в градациях серого (с пороговыми значениями)? В таком случае алгоритм определения контура не сработает. Как мы обсуждали ранее, алгоритм ищет границы и пиксели аналогичной интенсивности для обнаружения контуров. Двоичное изображение предоставляет эту информацию намного лучше, чем изображение с одним цветовым каналом (RGB). Далее будет показаны результирующие изображения при использовании только одного канала R, G или B вместо изображений в оттенках серого и изображений с пороговыми значениями.

3. Найдите контуры
Используйте функцию findContours() для обнаружения контуров на изображении.

4. Нарисуйте контуры на исходном изображении RGB
После определения контуров используйте функцию drawContours(), чтобы наложить контуры на исходное изображение RGB.

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

Поиск и отрисовка контуров с помощью OpenCV   

Начните с импорта OpenCV и чтения входного изображения.

import cv2

# прочитать изображение
image = cv2.imread('input/image_1.jpg')

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

# преобразовать изображение в формат оттенков серого
img_gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)

Теперь используйте функцию threshold() для применения бинарного порога к изображению. Любому пикселю со значением больше 150 будет присвоено значение 255 (белый). Все оставшиеся пиксели в результирующем изображении будут установлены в 0 (черный). Пороговое значение 150 — это настраиваемый параметр, поэтому вы можете с ним поэкспериментировать.

После установления порога визуализируйте двоичное изображение, используя функцию imshow(), как показано ниже.

# apply binary thresholding
ret, thresh = cv2.threshold(img_gray, 150, 255, cv2.THRESH_BINARY)

# визуализировать двоичное изображение
cv2.imshow('Binary image', thresh)
cv2.waitKey(0)
cv2.imwrite('image_thres1.jpg', thresh)
cv2.destroyAllWindows()

Посмотрите на изображение ниже! Это двоичное представление исходного изображения RGB. Можно ясно видеть, как перо, границы планшета и телефона все белые. Алгоритм контура будет рассматривать их как объекты и находить точки контура вокруг границ этих белых объектов.

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

Результирующее двоичное изображение после применения пороговой функции
Результирующее двоичное изображение после применения пороговой функции

Отрисовка с помощью CHAIN_APPROX_NONE   

Теперь давайте найдем и нарисуем контуры с помощью метода CHAIN_APPROX_NONE.

Начнем с функции findContours(). У неё, как показано ниже, есть три обязательных аргумента. Дополнительные аргументы см. На странице документации здесь.

  • image: двоичное входное изображение, полученное на предыдущем шаге.
  • mode: это режим поиска контура. Мы предоставили его как RETR_TREE и это означает, что алгоритм извлечет все возможные контуры из двоичного изображения. Доступны и другие режимы извлечения контуров и их обсудим тоже. Вы можете узнать больше об этих вариантах здесь.
  • method: определяет метод аппроксимации контура. В этом примере мы будем использовать CHAIN_APPROX_NONE. Хотя он немного медленнее, чем CHAIN_APPROX_SIMPLE, здесь для хранения ВСЕХ точек контура будем использовать этот метод.

Здесь надо подчеркнуть, что mode относится к типу извлекаемых контуров, а method относится к тому, какие точки внутри контура сохраняются. Ниже обсудим оба более подробно.

Легко визуализировать и понять результаты различных методов на одном и том же изображении.

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

Затем используйте функцию drawContours(), чтобы наложить контуры на изображение RGB. Эта функция имеет четыре обязательных и несколько необязательных аргументов. Первые четыре аргумента ниже обязательны. Для необязательных аргументов пожалуйста, обратитесь к странице документации здесь.

  • image: это входное изображение RGB, на котором вы хотите нарисовать контур.
  • contours: указывает контуры, полученные с помощью функции findContours().
  • contourIdx: координаты точек контура в пикселях перечислены в полученных контурах. Используя этот аргумент, вы можете указать позицию индекса из этого списка, указывая, какую именно точку контура вы хотите нарисовать. Если указать отрицательное значение, будут нарисованы все точки контура.
  • color: указывает цвет точек контура, которые вы хотите нарисовать. Мы рисуем точки зеленым цветом.
  • thickness: это толщина точек контура.
# обнаруживаем контуры на двоичном изображении с помощью cv2.CHAIN_APPROX_NONE
contours, hierarchy = cv2.findContours(image=thresh, mode=cv2.RETR_TREE, method=cv2.CHAIN_APPROX_NONE)
                                     
# рисуем контуры на исходном изображении
image_copy = image.copy()
cv2.drawContours(image=image_copy, contours=contours, contourIdx=-1, color=(0, 255, 0), thickness=2, lineType=cv2.LINE_AA)
               
# смотрим результаты
cv2.imshow('None approximation', image_copy)
cv2.waitKey(0)
cv2.imwrite('contours_none_image1.jpg', image_copy)
cv2.destroyAllWindows()

Выполнение приведенного выше кода приведет к созданию и отображению изображения, показанного ниже. Также сохраняем образ на диск.

Контуры, обнаруженные с помощью CHAIN_APPROX_NONE, наложенные на входное изображение
Контуры, обнаруженные с помощью CHAIN_APPROX_NONE, наложенные на входное изображение

На следующем рисунке показано исходное изображение (слева), а также исходное изображение с наложенными контурами (справа).

Исходное изображение (слева), а также исходное изображение с наложенными контурами (справа)
Исходное изображение (слева), а также исходное изображение с наложенными контурами (справа)

Как видете на приведенном выше рисунке, контуры, созданные алгоритмом, отлично справляются с определением границ каждого объекта. Однако, если вы внимательно посмотрите на телефон, то обнаружите, что он содержит более одного контура. Отдельные контуры были выделены для круглых областей, связанных с объективом камеры и источником света. Есть также «второстепенные» контуры вдоль краев телефона. Имейте в виду, что точность и качество алгоритма контура сильно зависят от качества предоставленного двоичного изображения (снова посмотрите на двоичное изображение в предыдущем разделе, где можете увидеть линии, связанные с этими вторичными контурами). Для некоторых приложений требуются контуры высокого качества. В таких случаях, поэкспериментируйте с разными порогами при создании двоичного изображения и посмотрите, улучшит ли это результирующие контуры.

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

Использование одного канала: красный, зеленый или синий

Чтобы получить представление, ниже приведены некоторые результаты при раздельном использовании красного, зеленого и синего каналов при обнаружении контуров. Мы обсуждали это ранее на этапах определения контура. Ниже приведен код для того же изображения, что и выше.

import cv2

# прочитать изображение
image = cv2.imread('input/image_1.jpg')

# Разделение каналов B, G, R
blue, green, red = cv2.split(image)

# обнаруживаем контуры по синему каналу и без порога
contours1, hierarchy1 = cv2.findContours(image=blue, mode=cv2.RETR_TREE, method=cv2.CHAIN_APPROX_NONE)

# рисуем контуры на исходном изображении
image_contour_blue = image.copy()
cv2.drawContours(image=image_contour_blue, contours=contours1, contourIdx=-1, color=(0, 255, 0), thickness=2, lineType=cv2.LINE_AA)
# смотрим результат
cv2.imshow('Contour detection using blue channels only', image_contour_blue)
cv2.waitKey(0)
cv2.imwrite('blue_channel.jpg', image_contour_blue)
cv2.destroyAllWindows()

# обнаруживаем контуры с использованием зеленого канала и без порогового значения
contours2, hierarchy2 = cv2.findContours(image=green, mode=cv2.RETR_TREE, method=cv2.CHAIN_APPROX_NONE)
# рисуем контуры на исходном изображении
image_contour_green = image.copy()
cv2.drawContours(image=image_contour_green, contours=contours2, contourIdx=-1, color=(0, 255, 0), thickness=2, lineType=cv2.LINE_AA)
# смотрим результат
cv2.imshow('Contour detection using green channels only', image_contour_green)
cv2.waitKey(0)
cv2.imwrite('green_channel.jpg', image_contour_green)
cv2.destroyAllWindows()

# detect contours using red channel and without thresholding
contours3, hierarchy3 = cv2.findContours(image=red, mode=cv2.RETR_TREE, method=cv2.CHAIN_APPROX_NONE)
# рисуем контуры на исходном изображении
image_contour_red = image.copy()
cv2.drawContours(image=image_contour_red, contours=contours3, contourIdx=-1, color=(0, 255, 0), thickness=2, lineType=cv2.LINE_AA)
# смотрим результат
cv2.imshow('Contour detection using red channels only', image_contour_red)
cv2.waitKey(0)
cv2.imwrite('red_channel.jpg', image_contour_red)
cv2.destroyAllWindows() 

Посмотрите на результаты обнаружения контура для всех трех отдельных цветовых каналов.

Результаты обнаружения контуров при использовании одиночных каналов синего, зеленого и красного цвета вместо изображений в градациях серого с пороговыми значениями
Результаты обнаружения контуров при использовании одиночных каналов синего, зеленого и красного цвета вместо изображений в градациях серого с пороговыми значениями

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

Отрисовка с помощью CHAIN_APPROX_SIMPLE   

Теперь давайте посмотрим, как работает алгоритм CHAIN_APPROX_SIMPLE и чем он отличается от алгоритма CHAIN_APPROX_NONE.

Вот для этого код:

"""
Теперь давайте попробуем с `cv2.CHAIN_APPROX_SIMPLE`
"""
# обнаруживаем контуры на двоичном изображении с помощью cv2.ChAIN_APPROX_SIMPLE
contours1, hierarchy1 = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
# рисуем контуры на исходном изображении для `CHAIN_APPROX_SIMPLE`
image_copy1 = image.copy()
cv2.drawContours(image_copy1, contours1, -1, (0, 255, 0), 2, cv2.LINE_AA)
# смотрим результат
cv2.imshow('Simple approximation', image_copy1)
cv2.waitKey(0)
cv2.imwrite('contours_simple_image1.jpg', image_copy1)
cv2.destroyAllWindows()

Здесь единственная разница заключается в том, что method для findContours() определён, как CHAIN_APPROX_SIMPLE вместо CHAIN_APPROX_NONE.

Алгоритм CHAIN_APPROX_SIMPLE сжимает горизонтальные, вертикальные и диагональные сегменты вдоль контура и оставляет только их конечные точки. Это означает, что любая из точек на прямых путях будет отклонена и останутся только конечные точки. Например, рассмотрим контур по прямоугольнику. Все точки контура, кроме четырех угловых, будут отклонены. Этот метод быстрее, чем CHAIN_APPROX_NONE, потому что алгоритм не сохраняет все точки, использует меньше памяти и, следовательно, требует меньше времени для выполнения. Ниже показаны результаты.

Контуры, обнаруженные с помощью CHAIN_APPROX_SIMPLE, наложенного на входное изображение
Контуры, обнаруженные с помощью CHAIN_APPROX_SIMPLE, наложенного на входное изображение

Если вы внимательно присмотритесь, почти нет различий между CHAIN_APPROX_NONE и CHAIN_APPROX_SIMPLE.

Итак, почему это так?

Заслуга принадлежит функции drawContours(). Хотя метод CHAIN_APPROX_SIMPLE обычно приводит к меньшему количеству точек, функция drawContours() автоматически соединяет соседние точки, присоединение к ним, даже если их нет в списке контуров.

Итак, как можно подтвердить, что алгоритм CHAIN_APPROX_SIMPLE действительно работает?

  • Самый простой способ — вручную перебрать точки контура и нарисовать круг на обнаруженных координатах контура с помощью OpenCV.
  • Также, используем другое изображение, которое на самом деле поможет нам визуализировать результаты алгоритма.
Новое изображение, демонстрирующее алгоритм обнаружения контура CHAIN_APPROX_SIMPLE
Новое изображение, демонстрирующее алгоритм обнаружения контура CHAIN_APPROX_SIMPLE

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

  • Первый цикл for проходит по каждой области контура, присутствующей в списке контуров.
  • Второй проходит по каждой из координат в этой области.
  • Затем мы рисуем зеленый кружок в каждой координатной точке, используя функцию circle() из OpenCV.
  • Наконец, мы визуализируем результаты и сохраняем их на диск.
# чтобы визуализировать эффект `CHAIN_APPROX_SIMPLE`, нам нужно правильное изображение
image1 = cv2.imread('input/image_2.jpg')
img_gray1 = cv2.cvtColor(image1, cv2.COLOR_BGR2GRAY)

ret, thresh1 = cv2.threshold(img_gray1, 150, 255, cv2.THRESH_BINARY)
contours2, hierarchy2 = cv2.findContours(thresh1, cv2.RETR_TREE,
                                               cv2.CHAIN_APPROX_SIMPLE)
image_copy2 = image1.copy()
cv2.drawContours(image_copy2, contours2, -1, (0, 255, 0), 2, cv2.LINE_AA)
cv2.imshow('SIMPLE Approximation contours', image_copy2)
cv2.waitKey(0)
image_copy3 = image1.copy()
for i, contour in enumerate(contours2): # loop over one contour area
   for j, contour_point in enumerate(contour): # loop over the points
       # draw a circle on the current contour coordinate
       cv2.circle(image_copy3, ((contour_point[0][0], contour_point[0][1])), 2, (0, 255, 0), 2, cv2.LINE_AA)
# смотрим результат
cv2.imshow('CHAIN_APPROX_SIMPLE Point only', image_copy3)
cv2.waitKey(0)
cv2.imwrite('contour_point_simple.jpg', image_copy3)
cv2.destroyAllWindows()

Выполнение приведенного выше кода дает следующий результат:

Обратите внимание, что при использовании CHAIN_APPROX_SIMPLE для определения контура на четырех углах книги есть только четыре контурных точки. Полностью игнорируются вертикальные и горизонтальные прямые линии книги
Обратите внимание, что при использовании CHAIN_APPROX_SIMPLE для определения контура на четырех углах книги есть только четыре контурных точки. Полностью игнорируются вертикальные и горизонтальные прямые линии книги

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

Иерархии контуров

Иерархии обозначают родительско-дочерние отношения между контурами. Вы увидите, как каждый режим поиска контура влияет на обнаружение контура на изображениях и дает иерархические результаты.

Родительско-дочерние отношения   

Объекты, обнаруженные алгоритмами определения контуров на изображении, могут быть:

  • Отдельными объектами, разбросанными по изображению (как в первом примере), или
  • Объектами и формами друг в друге

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

Взгляните на следующий рисунок, он содержит несколько простых форм, которые помогут продемонстрировать иерархию контуров.

Изображение с простыми линиями и формами. Будем использовать это изображение, чтобы узнать больше о контурных иерархиях
Изображение с простыми линиями и формами. Будем использовать это изображение, чтобы узнать больше о контурных иерархиях

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

  • Все отдельные числа, то есть 1, 2, 3 и 4, являются отдельными объектами в соответствии с иерархией контуров и отношениями родитель-потомок.
  • Можно сказать, что 3a — ребенок 3-го. Обратите внимание, что 3а представляет собой внутреннюю часть в контуре 3.
  • Контуры 1, 2 и 4 являются родительскими фигурами без каких-либо связанных дочерних фигур, поэтому их нумерация произвольна. Другими словами, контур 2 можно было бы обозначить как 1 и наоборот.
Числа, показывающие отношения между родителями и детьми между разными фигурами
Числа, показывающие отношения между родителями и детьми между разными фигурами

Контурное представление отношений   

Вы видели, что функция findContours() возвращает список контуров и иерархию. Теперь подробно разберемся с выводом иерархии контуров.

Иерархия контуров представлена ​​в виде массива, который, в свою очередь, содержит массивы из четырех значений. Он представлен как:

[Next, Previous, First_Child, Parent] 

Итак, что означают все эти значения?

Next: обозначает следующий контур в изображении, который находится на том же иерархическом уровне.

  • Для контура 1 следующий контур на том же иерархическом уровне — 2. Здесь Next будет 2.
  • Соответственно, контур 3 не имеет контура на том же иерархическом уровне, что и он сам. Итак, значение Next будет -1.

Previous: обозначает предыдущий контур на том же иерархическом уровне. Это означает, что предыдущее значение контура 1 всегда будет равно -1.

First_Child: обозначает первый дочерний контур контура, который мы сейчас рассматриваем.

  • Контуры 1 и 2 вообще не имеют детей. Таким образом, значение индекса для их First_Child будет -1.
  • Но у контура 3 есть ребенок. Итак, для контура 3 значение позиции First_Child будет индексной позицией 3a.

Parent: обозначает позицию индекса родительского контура для текущего контура.

  • Контуры 1 и 2, как очевидно, не имеют Родительского контура.
  • Для контура 3a его Родителем будет контур 3
  • Для контура 4 родительский контур 3a

Приведенные выше объяснения имеют смысл, но как на самом деле визуализировать эти иерархические массивы? Лучше всего:

  • использовать простое изображение с линиями и формами, как на предыдущем изображении;
  • искать контуры и иерархии, используя различные режимы поиска;
  • затем распечатывать значения, чтобы визуализировать их.

Различные методы извлечения контуров   

До сих пор мы использовали один конкретный метод поиска, RETR_TREE, чтобы найти и нарисовать контуры, но в OpenCV есть еще три метода поиска контура, а именно RETR_LIST, RETR_EXTERNAL и RETR_CCOMP.

Итак, давайте теперь воспользуемся предыдущим изображением в каждом из этих четырех методов, а также связанный с ними код для получения контуров. Следующий код считывает изображение с диска, преобразует его в оттенки серого и применяет двоичное пороговое значение.

"""
Обнаружение контуров и отрисовка с использованием различных режимов извлечения для дополнения
понимание иерархий
"""
image2 = cv2.imread('input/custom_colors.jpg')
img_gray2 = cv2.cvtColor(image2, cv2.COLOR_BGR2GRAY)
ret, thresh2 = cv2.threshold(img_gray2, 150, 255, cv2.THRESH_BINARY)

RETR_LIST

Метод извлечения контуров RETR_LIST не создает никаких родительских отношений между извлеченными контурами. Таким образом, для всех обнаруженных контурных областей значения позиции индекса First_Child и Parent всегда равны -1.

Все контуры будут иметь соответствующие предыдущие и следующие контуры, как описано выше.

Посмотрите, как метод RETR_LIST реализован в коде.

contours3, hierarchy3 = cv2.findContours(thresh2, cv2.RETR_LIST, cv2.CHAIN_APPROX_NONE)
image_copy4 = image2.copy()
cv2.drawContours(image_copy4, contours3, -1, (0, 255, 0), 2, cv2.LINE_AA)
# смотрим результат
cv2.imshow('LIST', image_copy4)
print(f"LIST: {hierarchy3}")
cv2.waitKey(0)
cv2.imwrite('contours_retr_list.jpg', image_copy4)
cv2.destroyAllWindows()

Выполнение приведенного выше кода дает следующий результат:

LIST: [ [ [ 1 -1 -1 -1]
[ 2  0 -1 -1]
[ 3  1 -1 -1]
[ 4  2 -1 -1]
[-1  3 -1 -1] ] ]

Ясно видно, что позиции 3-го и 4-го индексов всех обнаруженных областей контура равны -1, как и ожидалось.

RETR_EXTERNAL

Метод извлечения контура RETR_EXTERNAL действительно интересен. Он обнаруживает только родительские контуры и игнорирует любые дочерние контуры. Таким образом, на всех внутренних контурах, таких как 3a и 4, не будет нарисовано никаких точек.

contours4, hierarchy4 = cv2.findContours(thresh2, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
image_copy5 = image2.copy()
cv2.drawContours(image_copy5, contours4, -1, (0, 255, 0), 2, cv2.LINE_AA)
# смотрим результат
cv2.imshow('EXTERNAL', image_copy5)
print(f"EXTERNAL: {hierarchy4}")
cv2.waitKey(0)
cv2.imwrite('contours_retr_external.jpg', image_copy5)
cv2.destroyAllWindows()

В результате получим:

EXTERNAL: [ [ [ 1 -1 -1 -1]
[ 2  0 -1 -1]
[-1  1 -1 -1] ] ]
Контуры обнаружены и нарисованы в режиме RETR_EXTERNAL
Контуры обнаружены и нарисованы в режиме RETR_EXTERNAL

На приведенном выше изображении показаны только точки, нарисованные на контурах 1, 2 и 3. Контуры 3a и 4 опущены, поскольку они являются дочерними контурами.

RETR_CCOMP

В отличие от RETR_EXTERNAL, RETR_CCOMP извлекает все контуры изображения. Наряду с этим, он также применяет двухуровневую иерархию ко всем формам или объектам на изображении.

Это значит, что

  • все внешние контуры будут иметь уровень иерархии 1, а
  • все внутренние контуры будут иметь уровень иерархии 2.

Но что, если у нас есть контур внутри другого контура с уровнем иерархии 2? Точно так же, как у нас есть контур 4 после контура 3a.

В этом случае:

  • контур 4, опять же, будет иметь уровень иерархии 1.
  • Если внутри контура 4 есть контуры, то у них будет уровень иерархии 2.

На следующем изображении контуры пронумерованы в соответствии с их уровнем иерархии, как описано выше.

Изображение, показывающее различные уровни иерархии в контурах при использовании метода извлечения RETR_CCOMP
Изображение, показывающее различные уровни иерархии в контурах при использовании метода извлечения RETR_CCOMP

На изображении выше показан уровень иерархии как HL-1 или HL-2 для уровней 1 и 2 соответственно. Теперь давайте посмотрим на код и массив выходной иерархии.

contours5, hierarchy5 = cv2.findContours(thresh2, cv2.RETR_CCOMP, cv2.CHAIN_APPROX_NONE)
image_copy6 = image2.copy()
cv2.drawContours(image_copy6, contours5, -1, (0, 255, 0), 2, cv2.LINE_AA)

# смотрим результат
cv2.imshow('CCOMP', image_copy6)
print(f"CCOMP: {hierarchy5}")
cv2.waitKey(0)
cv2.imwrite('contours_retr_ccomp.jpg', image_copy6)
cv2.destroyAllWindows()

В результате выполнение приведенного выше кода получим:

CCOMP: [ [ [ 1 -1 -1 -1]
[ 3  0  2 -1]
[-1 -1 -1  1]
[ 4  1 -1 -1]
[-1  3 -1 -1] ] ]

Здесь мы видим, что все отношения Next, Previous, First_Child и Parent поддерживаются в соответствии с методом поиска контуров, поскольку все контуры обнаруживаются. Как и ожидалось, Предыдущее значение первой области контура равно -1. И контуры, у которых нет Родителя, также имеют значение -1.

RETR_TREE

Как и RETR_CCOMP, RETR_TREE также извлекает все контуры. Он также создает полную иерархию с уровнями, не ограниченными 1 или 2. Каждый контур может иметь свою собственную иерархию в соответствии с уровнем, на котором он находится, и соответствующими родительско-дочерними отношениями, которые у него есть.

Уровни иерархии при использовании режима извлечения контура RETR_TREE
Уровни иерархии при использовании режима извлечения контура RETR_TREE

Из рисунка выше видно, что:

  • Контуры 1, 2 и 3 находятся на одном уровне, то есть на уровне 0.
  • Контур 3a присутствует на уровне иерархии 1, поскольку он является дочерним элементом контура 3.
  • Контур 4 — это новая область контура, поэтому его уровень иерархии равен 2.

Следующий код использует режим RETR_TREE для извлечения контуров.

contours6, hierarchy6 = cv2.findContours(thresh2, cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE)
image_copy7 = image2.copy()
cv2.drawContours(image_copy7, contours6, -1, (0, 255, 0), 2, cv2.LINE_AA)
# смотрим резльтат
cv2.imshow('TREE', image_copy7)
print(f"TREE: {hierarchy6}")
cv2.waitKey(0)
cv2.imwrite('contours_retr_tree.jpg', image_copy7)
cv2.destroyAllWindows()

Выполнение приведенного выше кода дает следующий результат:

TREE: [ [ [ 3 -1  1 -1]
[-1 -1  2  0]
[-1 -1 -1  1]
[ 4  0 -1 -1]
[-1  3 -1 -1] ] ]

Наконец, давайте посмотрим на полное изображение со всеми контурами, нарисованными при использовании режима RETR_TREE.

Обнаружение контура при использовании режима поиска RETR_TREE
Обнаружение контура при использовании режима поиска RETR_TREE

Все контуры нарисованы так, как ожидалось, и участки контура хорошо видны. Вы также делаете вывод, что контуры 3 и 3a — это два отдельных контура, поскольку они имеют разные границы контура и площади. В то же время совершенно очевидно, что контур 3a является потомком контура 3.

Теперь, когда вы знакомы со всеми алгоритмами контуров, доступными в OpenCV, вместе с соответствующими входными параметрами и конфигурациями, поэкспериментируйте и убедитесь сами, как они работают.

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

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

Метод получения контура Время (в секундах)
RETR_LIST 0,000382
RETR_EXTERNAL 0,000554
RETR_CCOMP 0,001845
RETR_TREE 0,005594

Сравнение скорости вывода разных методов

Из приведенной выше таблицы следует несколько интересных выводов:

  • RETR_LIST и RETR_EXTERNAL занимают наименьшее количество времени для выполнения, поскольку RETR_LIST не определяет никакой иерархии, а RETR_EXTERNAL только извлекает родительские контуры
  • RETR_CCOMP занимает второе по времени выполнение. Он извлекает все контуры и определяет двухуровневую иерархию.
  • RETR_TREE занимает максимальное время для выполнения, так как извлекает все контуры, а также определяет независимый уровень иерархии для каждого родительско-дочернего отношения.

Хотя приведенное выше время может показаться незначительным, важно знать о различиях для приложений, которые могут потребовать значительного объема обработки контура. Также стоит отметить, что время обработки может варьироваться в зависимости от извлекаемых контуров и определяемых ими уровней иерархии.

Ограничения

Пока что все изученные нами примеры казались интересными, а их результаты обнадеживающими. Однако бывают случаи, когда контурный алгоритм может не дать значимых и полезных результатов. Рассмотрим и такой пример.

  • Когда объекты на изображении сильно контрастируют с их фоном, вы можете четко определить контуры, связанные с каждым объектом. Но что, если у вас есть изображение, как на рисунке ниже. У него не только яркий объект (щенок), но и фон, загроможденный тем же значением (яркостью), что и объект интереса (щенок). Вы обнаружите, что контуры на правом изображении даже не завершены. Кроме того, в области фона есть несколько нежелательных контуров.
    Слева - входное изображение с белым щенком и множеством других краев и цветов фона. Справа - наложены результаты определения контура. Наблюдайте, как контуры не завершены, и обнаружение множественных или неправильных контуров из-за беспорядка на заднем плане
    Слева — входное изображение с белым щенком и множеством других краев и цветов фона. Справа — наложены результаты определения контура. Наблюдайте, как контуры не завершены, и обнаружение множественных или неправильных контуров из-за беспорядка на заднем плане
  • Обнаружение контура также может не сработать, если фон объекта на изображении заполнен линиями.

Резюме   

Мы начали с определения контуров и научились реализовывать это в OpenCV. Увидели, как приложения используют контуры для обнаружения и сегментации движения. Затем мы продемонстрировали использование четырех различных режимов восстановления и двух методов контурной аппроксимации. Мы также научились рисовать контуры, закончили обсуждением контурных иерархий и как различные режимы поиска контуров влияют на прорисовку контуров на изображении.

Основные выводы:

  • Алгоритмы определения контуров в OpenCV работают очень хорошо, когда изображение имеет темный фон и четко определенный интересующий объект.
  • Но когда фон входного изображения загроможден или имеет ту же интенсивность пикселей, что и объект из-Интересно, алгоритмы не так хороши.

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

Источник Contour Detection using OpenCV (Python/C++)

Print Friendly, PDF & Email

CC BY-NC 4.0 Обнаружение конгуров с использованием OpenCV, опубликовано К ВВ, лицензия — Creative Commons Attribution-NonCommercial 4.0 International.


Респект и уважуха

Добавить комментарий