Свойства контуров
Исследуем возможности метода cv2.findContours()
Для нахождения контуров изображение преобразуется в оттенки серого:
img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
.
Оттенки серого используются, когда требуется упрощенная версия изображения для дальнейшей обработки, например, для бинаризации и поиска контуров.
Бинаризация изображения
Бинаризация — это процесс преобразования градаций серого изображения в черно-белое. Пиксели ниже определенного порога становятся черными (0), а выше — белыми (255). В OpenCV это можно сделать с помощью функции cv2.threshold()
или cv2.inRange()
.
img_bin = cv2.inRange(img_gray, threshold_min, threshold_max)
Здесь threshold_min
и threshold_max
— это нижний и верхний пороги бинаризации соответственно.
threshold_min
: Минимальное значение градации серого, при котором пиксели изображения будут белыми.
threshold_max
: Максимальное значение градации серого, при котором пиксели изображения будут белыми.
Пиксели с значениями, попадающими в диапазон от threshold_min
до threshold_max
, становятся белыми, остальные — черными.
Нахождение контуров
Контуры — это границы объектов на изображении. OpenCV предоставляет функцию cv2.findContours()
для нахождения контуров на бинаризованном изображении:
_, cont, h = cv2.findContours(img_bin, method, cv2.CHAIN_APPROX_SIMPLE)
Параметры:
img_bin
: Входное бинарное изображение, на котором будут искаться контуры.
method
: Режим извлечения контуров, который может принимать следующие значения:
cv2.RETR_EXTERNAL
: Извлекает только внешние контуры.
cv2.RETR_LIST
: Извлекает все контуры, не создавая иерархии.
cv2.RETR_CCOMP
: Извлекает все контуры и организует их в двухуровневую иерархию: внешние и внутренние.
cv2.RETR_TREE
: Извлекает все контуры и организует их в полноценное дерево иерархии.
cv2.CHAIN_APPROX_SIMPLE
: Метод аппроксимации контуров, который удаляет все избыточные точки и сжимает контур, сохраняя его основную структуру.
Отображение контуров
Контуры могут быть нарисованы на изображении с помощью функции cv2.drawContours()
:
cv2.drawContours(img_contours, contours, -1, (0, 128, 255), 2)
Параметры:
img_contours
: Изображение, на котором будут нарисованы контуры.
contours
: Список контуров, которые необходимо нарисовать.
-1
: Указывает, что нужно нарисовать все контуры.
(0, 128, 255)
: Цвет контуров в формате BGR (синий-зеленый-красный).
2
: Толщина линий контуров.
Для подбора диапазонов и настройки методов вручную можно использовать подготовленный скрипт:
import cv2
import numpy as np
def nothing(x):
pass
# Переменные для хранения состояния
method_index = 0
methods = ["RETR_EXTERNAL", "RETR_LIST", "RETR_CCOMP", "RETR_TREE"]
# Функция для обработки событий мыши
def mouse_event(event, x, y, flags, param):
global method_index
if event == cv2.EVENT_LBUTTONDOWN:
# Проверяем, попадает ли клик в одну из "кнопок"
if 10 <= x <= 250:
if 50 <= y <= 90:
method_index = 0
elif 100 <= y <= 140:
method_index = 1
elif 150 <= y <= 190:
method_index = 2
elif 200 <= y <= 240:
method_index = 3
print(methods[method_index])
# Создаем окна для отображения изображения и настройки параметров
cv2.namedWindow("result")
cv2.namedWindow("settings")
cv2.namedWindow("gray")
cv2.namedWindow("binary")
cv2.setMouseCallback("settings", mouse_event)
# Добавляем трекбары для настройки порогов бинаризации
cv2.createTrackbar('threshold_min', 'settings', 0, 255, nothing)
cv2.createTrackbar('threshold_max', 'settings', 255, 255, nothing)
# Создаем изображение для окна settings с увеличенным размером
settings_img = np.zeros((300, 300, 3), np.uint8)
while True:
img = cv2.imread('image_simple.png')
h, w, _ = img.shape
# Преобразование в градации серого
img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# Считываем значения порогов
threshold_min = cv2.getTrackbarPos('threshold_min', 'settings')
threshold_max = cv2.getTrackbarPos('threshold_max', 'settings')
# Бинаризация изображения с двумя порогами
img_bin = cv2.inRange(img_gray, threshold_min, threshold_max)
# Поиск контуров с выбранным методом
method = getattr(cv2, methods[method_index])
contours, _ = cv2.findContours(img_bin, method, cv2.CHAIN_APPROX_SIMPLE)
# Рисование найденных контуров
img_contours = img.copy()
cv2.drawContours(img_contours, contours, -1, (0, 128, 255), 2)
# Очистка изображения для settings
settings_img[:] = (0, 0, 0)
# Рисование "кнопок" на изображении settings
cv2.rectangle(settings_img, (10, 50), (250, 90), (50, 50, 50), -1)
cv2.putText(settings_img, methods[0], (15, 75), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (255, 255, 255), 2)
cv2.rectangle(settings_img, (10, 100), (250, 140), (50, 50, 50), -1)
cv2.putText(settings_img, methods[1], (15, 125), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (255, 255, 255), 2)
cv2.rectangle(settings_img, (10, 150), (250, 190), (50, 50, 50), -1)
cv2.putText(settings_img, methods[2], (15, 175), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (255, 255, 255), 2)
cv2.rectangle(settings_img, (10, 200), (250, 240), (50, 50, 50), -1)
cv2.putText(settings_img, methods[3], (15, 225), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (255, 255, 255), 2)
# Вывод окна с кнопками и трекбарами
cv2.imshow('settings', settings_img)
# Вывод серого изображения
cv2.imshow('gray', img_gray)
# Вывод результата бинаризации
cv2.imshow('binary', img_bin)
# Вывод изображения с контурами и оригинала
cv2.imshow('result', img_contours)
cv2.imshow('original', img)
# Ожидание нажатия клавиши ESC для выхода
ch = cv2.waitKey(5)
if ch == 27:
break
cv2.destroyAllWindows()
Теперь, когда контуры уверенно обнаруживаются на изображении, добавим в скрипт, выше следующие строчки, чтобы по нажатию клавиши пробел выводить список точек в найденном контуре:
if ch == 32:
for contour in contours:
print(contour)
Однако, даже на этом простом изображении контуры состоят более чем из 100 точек и провести их анализ представляется мало возможным!
Для начала, можем исследовать их свойства - площадь и периметр:
for contour in contours:
area = cv2.contourArea(contour)
perimeter = cv2.arcLength(contour, True)
print(f"Площадь: {area}, Периметр: {perimeter}")
# Площадь: 78881.0, Периметр: 1050.0874361991882
# Площадь: 1256.0, Периметр: 131.882248878479
И сейчас становится понятно, что фигуры можно отличить по площади!