惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Recorded Future
Recorded Future
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
Forbes - Security
Forbes - Security
N
News and Events Feed by Topic
SecWiki News
SecWiki News
T
The Exploit Database - CXSecurity.com
S
Security @ Cisco Blogs
H
Heimdal Security Blog
Security Latest
Security Latest
T
Threatpost
V2EX - 技术
V2EX - 技术
C
Cybersecurity and Infrastructure Security Agency CISA
GbyAI
GbyAI
The Last Watchdog
The Last Watchdog
Recent Announcements
Recent Announcements
P
Privacy International News Feed
K
Kaspersky official blog
P
Proofpoint News Feed
L
LangChain Blog
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
Security Archives - TechRepublic
Security Archives - TechRepublic
T
Threat Research - Cisco Blogs
博客园_首页
T
Tor Project blog
M
MIT News - Artificial intelligence
The Hacker News
The Hacker News
The GitHub Blog
The GitHub Blog
月光博客
月光博客
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
F
Full Disclosure
MyScale Blog
MyScale Blog
The Register - Security
The Register - Security
Engineering at Meta
Engineering at Meta
Y
Y Combinator Blog
Cyberwarzone
Cyberwarzone
L
LINUX DO - 最新话题
量子位
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
O
OpenAI News
T
The Blog of Author Tim Ferriss
S
Schneier on Security
小众软件
小众软件
The Cloudflare Blog
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
Recent Commits to openclaw:main
Recent Commits to openclaw:main
Know Your Adversary
Know Your Adversary
Microsoft Security Blog
Microsoft Security Blog
H
Hackread – Cybersecurity News, Data Breaches, AI and More
L
Lohrmann on Cybersecurity
Vercel News
Vercel News

Все публикации подряд на Хабре

Ловим музу за клавиатуру: как айтишнику стать автором Что умеет Midjourney в 2026? Мой немного грустный разбор этого шикарного инструмента Никто не любит писать тесты, но ИИ может исправить это IPv8 выглядит как мечта. Поэтому почти наверняка не взлетит Производители вернули в продажу материнки с DDR3. Что происходит? Управление агентом с телефона через Telegram теперь в KodaCode От координации к лидерству: как меняется роль руководителя разработки Я сделала родителям бизнес вместо пенсии: зарабатываем 70 тысяч, мама не даёт продать В три раза быстрее приемка товара и оптимизация трудозатрат на 73%: как «РСТ-Инвент» помог Gulliver Group ИИ-шечный мир победил? О влиянии искусственного интеллекта на игропром Кремль снижает давление на Телеграмм пока Европа строит интернет по паспорту Как CEO, CTO и CIO за 8 часов собрали ИИ-директора, который умеет держать позицию под давлением Как (не) потерять домен за выходные Вместо 8 разных VPS: как я организовал практику студентам на одном сервере Почему твой Open Source проект не замечают? R&D: искусство управления неопределенностью в разработке AI-дефляция: вакансий для разработчиков больше, а рост зарплат — худший за 15 лет Мы отдали управление роботами OpenClaw. Что из этого вышло Галактический ID: система идентификации для всех форм разумной жизни Шесть основ бизнес-анализа: начинаем с вопроса «Кто в игре?» Код-ревью, в котором дело не в коде Данные переехали. Команда — нет Системной подход к сдаче OSWE в 2025 Почему комната управления реактором покрашена в цвет морской пены 4 YAML-файла вместо PySpark: как аналитикам строить пайплайны без разработчиков LLM-агент для поиска свободных доменов: автоматизируем подбор Когда, зачем и как правильно начинать новую сессию в Claude Code? Как я заставил нейросеть писать макросы для FreeCAD Анатомия ИИ‑агента для подбора персонала. От тысячи резюме к топ‑10 за минуты Опыт разработчика как экономика внимания Автономность как точка невозврата: кто будет субъектом в цифровом будущем Обучение ИИ в «диких» условиях: как рутинные действия превращаются в датасеты Как измерить LLM для задач кибербеза: обзор открытых бенчмарков Где хранить код? Сравнение GitHub, GitLab и Bitbucket Математика объясняет, почему нормальное распределение встречается повсюду Почему ваш FinOps не работает: 12 тезисов от практиков Как подписать проектную документацию УКЭП с использованием бесплатных лицензий Pilot Адаптивное администрирование Sigla Vision Я грузил уран в бочки, а потом 20 лет строил ИТ в атомной отрасли Чем позвонить с Эвереста? История и обзор спутниковой связи. Часть 2 Как языковая модель помогает контролировать качество инструктажей по охране труда в металлургии Как не передать на desktop свой IP в РКН Анатомия SAP Privileges: как устроено управление правами в macOS MoneyDev: Сказка про три главных слова Обновлённый токенизатор видео K-VAE 2.0 от Сбера Как сделать диспетчеризацию дома на 1284 квартиры почти бесплатно Как мы разогнали железную дорогу Мы дали агентам рутину. Теперь надо решить — что делать с освободившимся временем Токсичный контент, промпт-хакинг и защита ИИ — всё о Guardrails для LLM Умный город начинается с точного взгляда: как «Фалькон Тех» меняет пространство к лучшему Навайбкодил приложение для анализа графов Почему Дюну так интересно читать? Упрощаем работу с рутиной или как стать Гендальфом Белым Деконструкция Go: CPU, RAM и что там происходит. Go Assembler база. Часть 1.1 Какие профессии исчезнут из-за ИИ, а какие появятся? И что с этим делать Как мы построили IT-отдел, где хочется расти: архитектурные встречи, прозрачные метрики и книжные подарки Rufler: Делаем из Claude Code автономный рой через один YAML-конфиг Sing-box и белый список приложений Как построить надёжный обмен сообщениями в микросервисах: лучшие практики для enterprise OpenAI строит MLM-пирамиду, а McKinsey и Accenture помогают ей в этом Дом, который не построил Фишер (Часть 2) «Сверхзвуковой математик» против «Вдумчивого логиста»: битва алгоритмов 3D-упаковки Мультимодальные модели – грубый и дорогой инструмент Разговоры ничего не стоят. Код тоже Проверки физических лиц: с кого начнет ФНС Топ-10 бесплатных нейросетей для создания видео в 2026 году Первые слои кода: как наши решения сегодня определяют архитектуру ИИ на десятилетия Разработка нового статического анализатора: PVS-Studio JavaScript Поиск уязвимостей ПО: базовый минимум или роскошный максимум Почему оценка персонала не работает как инструмент управления Как мы разработали ИИ-ассистента и сократили рутину продуктовой команды на 50% Как я ушел из найма, нажарил косточек и продал на маркетплейсах на 168 млн в год Когда 1С:ERP уже внедрена, а нормального производственного плана всё ещё нет Как я сделал Claude мультимодальным, подключив к нему Qwen Omni Как приглашение на вакансию мечты превращается в атаку Infrastructure as Code: философия и лучшие практики IaC Тестируем Yandex Code Assistant на задаче, в которой нужно хранить секреты nxs-universal-chart v3.0: новое поколение универсального Helm-чарта Callback Injection: Техника, которая отправила Microsoft Defender в глухой нокаут «Все идеи на стол»: митап как способ вывести проект из тупика Сегодня я узнал нечто новое о GPU благодаря багу в своей игре Как заставить LLM ̶ ̶г̶а̶л̶л̶ю̶ ̶ эволюционировать Карта событий как фундамент аналитики: практический кейс для E-commerce Что выбрать для AI: x86, ARM или RISC-V? Дайджест железа за март Роль соматических мутаций в развитии аутоиммунных заболеваний: путь к избирательной терапии Mythos от Anthropic — тревожный сигнал для всех, а не только для банков Guardrails для LLM на Java: как приручить промпт‑инъекции и токсичные ответы Green-VLA: как мы собрали VLA-модель для реального антропоморфного робота и не потеряли обобщение Финансовая гонка вооружений: почему умные люди добровольно в ней участвуют Эра ИИ-агентов наступила: выбираем лучшего цифрового сотрудника # Практический опыт внедрения WinCC Redundancy на производственном предприятии Сделал MVP за 3 дня, а потом неделю прикручивал оплату. Оно того стоило? Физика против Маска: почему Starship V3 может оказаться ещё одной катастрофой Нефть Венесуэлы: крупнейшие запасы в мире, но не крупнейшая нефтяная держава JPA 4. Переосмысление Hibernate Почему зеркальная фотокамера Nikon D5 десятилетней давности идеально подошла для миссии «Артемида-2» Проект «Уровень-Спутник» или как мы сделали платформу для гидрологов «Замедлиться, чтобы ускориться»: почему ИИ повышает цену ошибок в требованиях и архитектуре Как с нуля поднять трафик IT-компании на 1657% при бюджете 55 тыс. и выжить Pixel-perfect Downsampling — идеальная отрисовка 50 миллионов точек без потерь
Методы обнаружения контуров в изображении: пространственные фильтры
Adamowicz_I · 2026-04-29 · via Все публикации подряд на Хабре

Методы обнаружения контуров в изображении: пространственные фильтры

Уровень сложностиСредний

Время на прочтение10 мин

Охват и читатели372

Туториал

❯ Содержание

  1. Теоретическое введение в поиск контуров

  2. Практическая реализация

  3. Выводы


Введение

Большинство современных CV-алгоритмов, как ML, так и классического толка, невозможно представить без выделения границ объектов. В данной статье мы рассмотрим методы выделения контуров, адаптивный медианный фильтр, а также общие математические принципы, на которых они основаны. Области приложения рассмотрим уже в следующих статьях.

❯ Предварительные требования

Для понимания материала вам понадобится:

  • Базовое знание Python

  • Установленные библиотеки: opencv-python, numpy, scipy.

  • Понимание, что такое свёртка и градиент (на уровне школы).

1. Теоретическое введение в поиск контуров. Немного математики

❯ 1.1 Механизм работы масок

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

Яркость, а точнее говоря такое ее свойство, как разрывность, это ключевое понятие в обнаружении контуров изображения. В стандартной RGB модели значения по каждому каналу варьируются от 0 ((0, 0, 0) соответствует черный цвет) до 255 ((255, 255, 255) — белый). Яркость здесь определяется градациями серого цвета. Чтобы ее получить, нужно вычислить сумму значений по всем трем каналам и разделить на 3. В пространственных фильтрах, которые будут рассмотрены далее, будут использоваться производные первого и второго порядка, а также понятие градиента.

Производные первого порядка позволяют отследить динамику изменения яркости, а второго порядка — точки разрыва яркости. В данном случае производную можно интерпретировать как изменение яркости между соседними пикселями:

Формула 1. Производная первого порядка для изображения

\frac{\partial f}{\partial x} = f(x + 1, y) - f(x, y)

Рассмотренная формула применима для горизонтального приращения, для вертикального аналогично:

\frac{\partial f}{\partial y} = f(x, y + 1) - f(x, y)

Стоит заметить, что в данном случае y не имеет функциональной зависимости от x, а используется только в контексте координат. В компьютерной графике также часто используются UV-координаты. Начало координат в левом верхнем углу изображения.

Формула 2. Производная второго порядка для изображения

\frac{\partial^2 f}{\partial x^2} = f(x + 1, y) + f(x - 1, y) - 2f(x, y)

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

Формула 3. Градиент функции

\nabla f = \begin{bmatrix} \dfrac{\partial f}{\partial x} \\[6pt] \dfrac{\partial f}{\partial y} \end{bmatrix}

Так как центр маски, в случае нечетной размерности, или верхний левый угол, в случае четной, соответствует пикселю, к которому применяется маска в данной итерации, то окрестность маски может выходить за пределы изображения (см. Рис. 1). В таком случае нужно либо расширить изображение, либо положить, что недостающие значения равны 0.

Рис. 1 Схема применения маски

Рис. 1 Схема применения маски

Источник: [1]

❯ 1.2 Оператор Робертса

Как было ранее замечено, маски представляют собой матрицы, а точнее градиентные операторы. Один из критериев к ним — сумма коэффициентов в маске должна равняться нулю.

Рассмотрим одну из наиболее простых масок — оператор Робертса. Она представляет собой матрицы 2×2.

Рис. 2 Маски оператора Робертса

Рис. 2 Маски оператора Робертса

Рис. 2 Маски оператора Робертса

Источник: [2]

Маска естественным образом соответствует диагональным направлениями, но корректно действует на достаточно широком диапазоне углов, пусть и не всегда качественно. Из-за размерности 2×2 вызывает трудности в определении центра, но достаточно быстро действует. Оператор Робертса применяется из-за высокой скорости вычислений, но он чувствителен к шуму. Если итоговый градиент получился больше максимального значения яркости, то применяем нормализацию.

Формула 4. Модуль градиента и производные

|\nabla f| = |G_x| + |G_y|G_x = f(x, y) - f(x + 1, y + 1)G_y = f(x + 1, y) - f(x, y + 1)

❯ 1.3 Операторы Прюитта и Собеля

Далее рассмотрим операторы размерности 3×3. Операторы Прюитта и Собеля.

Рис. 3 Оператор Прюитта

Рис. 3 Оператор Прюитта [2]

Рис. 3 Оператор Прюитта [2]

Источник: [2]

Оператор Прюитта хорошо работает уже по всем направлениям от центра, в отличии от диагонального оператора Робертса. По нему можно корректно оценить величину и ориентацию границы объекта, но направления границы ограничены 8-ю.

Оператор Собеля (см. Рис. 4) действует схожим образом, отличие в коэффициенте два у средних точек. Это увеличенное значение используется для уменьшения эффекта сглаживания за счет придания большего веса средним точкам. Более чувствителен к диагональному контуру, чем к вертикальным и горизонтальным, как оператор Прюитта.

Рис. 4 Оператор Собеля

Рис. 4 Оператор Собеля

Рис. 4 Оператор Собеля

Источник: [2]

❯ 1.4 Оператор Лапласа

Далее рассмотрим оператор Лапласа, или же Лапласиан. Данный оператор относится к категории изотропных, т.е он инвариантен к повороту изображения, т.е будет выдавать одинаковое преобразование. Это простейший изотропный оператор, основанный на производных второго порядка.

Опираясь на формулу 2, можно вывести дискретную формулу Лапласиана:

Формула 5. Дискретная формула Лапласиана

\nabla^2 f = f(x + 1, y) + f(x - 1, y) + f(x, y + 1) + f(x, y - 1) - 4f(x, y)

И соответствующую ему матрицу оператора:

Рис. 5 Лапласиан в дискретной форме

Рис. 5 Лапласиан в дискретной форме

Рис. 5 Лапласиан в дискретной форме

Источник: [2]

Для получения итогового результата нужно вычесть из исходного изображения дискретную форму Лапласиана (зачастую требуется нормализация полученного результата).

❯ 1.5 Фильтр Уоллеса

Фильтр Уоллеса (Wallis Filter) регулирует значения яркости в локальных областях таким образом, чтобы локальное среднее значение и стандартное отклонение (СКО) соответствовали либо заданным пользователем целевым значениям, либо данным параметрам всего изображения. Это улучшение обеспечивает хороший локальный контраст по всему изображению, одновременно снижая общий контраст между светлыми и темными областями. То-есть, это локально адаптируемый фильтр, который гарантирует, что в каждом заданном пикселе среднее значение и контрастность соответствуют некоторым заданным значениям. Он отлично подходит для устранения неравномерного освещения, делая во всех локальных областях изображения одинаковое отклонение. Данный фильтр делит изображение на k равных областей, в каждой области вычисляет локальное среднее значение.

Формула 6. Формула фильтра Уоллеса

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

❯ 1.6 Детектор границ Канни

Далее рассмотрим алгоритм Канни, или же детектор границ Канни. Он состоит из следующих шагов:

  • Сглаживание. Размытие изображения для удаления шума.

  • Поиск градиентов. Границы отмечаются там, где градиент изображения приобретает максимальное значение.

  • Подавление не-максимумов. Только локальные максимумы отмечаются как границы.

  • Двойная пороговая фильтрация. Потенциальные границы определяются порогами.

  • Трассировка области неоднозначности. Итоговые границы определяются путём подавления всех краёв, несвязанных с определенными (сильными) границами.

Для сглаживания может использоваться фильтр Гаусса:

Формула 7. Фильтр Гаусса

G(x, y) = \frac{1}{2\pi\sigma^2} \cdot e^{-\dfrac{x^2 + y^2}{2\sigma^2}}

Для поиска градиента хорошо сработает фильтр Собеля, рассмотренный ранее.

Для подавления не-максимумов пикселями границ объявляются пиксели, в которых достигается локальный максимум градиента в направлении вектора градиента. Значение направления должно быть кратно 45°.

Рис. 6 Подавление не-максимумов

Рис. 6. Подавление не максимумов [3]

Рис. 6. Подавление не максимумов [3]

Источник: [1]

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

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

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

Алгоритм

Время выполнения (мс)

Кенни

52

Лаплас

38

Прюитт

25

Собель

25

Робертс

15

❯ 2. Практическая реализация

Все рассмотренные методы реализуются достаточно просто через OpenCV и Numpy.
Рассмотрим на примере этого изображения:

Рис. 7 Исходное изображение (любимый район)

Любимый район :>

Любимый район :>

Фотография: vadimpo

❯ 2.1 Оператор Робертса

Метод applyRoberts()
Назначение: Реализует фильтр Робертса.
Алгоритм:

  • Задаёт двумерные маски kernelX и kernelY для фильтра Робертса.

  • Использует applyFilter, передавая обе маски.

Код 1. Оператор Робертса
import cv2
import numpy as np
from scipy import ndimage

THRESHOLD = 50

img = cv2.imread("input.jpg", 0).astype('float64')
roberts_cross_v = np.array([[1, 0], [0, -1]])
roberts_cross_h = np.array([[0, 1], [-1, 0]])
vertical = ndimage.convolve(img, roberts_cross_v)
horizontal = ndimage.convolve(img, roberts_cross_h)
edged = np.sqrt(np.square(horizontal) + np.square(vertical))
edged = np.clip(edged, 0, 255).astype(np.uint8)
_, edged = cv2.threshold(edged, THRESHOLD, 255, cv2.THRESH_BINARY)
cv2.imwrite("output.jpg", edged)
Рис. 7. Оператор Роббертса

Рис. 7. Оператор Роббертса

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

❯ 2.2 Оператор Собеля и Прюитта

Методы applyPrewitt() и applySobel() используют метод applyFilter(), используя маски 3×3, представленные ранее.

Код 2. Оператор Собеля
import cv2
import numpy as np
from scipy import ndimage

THRESHOLD = 50

img = cv2.imread("input.jpg", 0).astype('float64')
sobel_x = np.array([[-1, 0, 1], [-2, 0, 2], [-1, 0, 1]])
sobel_y = np.array([[-1, -2, -1], [0, 0, 0], [1, 2, 1]])
grad_x = ndimage.convolve(img, sobel_x)
grad_y = ndimage.convolve(img, sobel_y)
edged = np.sqrt(np.square(grad_x) + np.square(grad_y))
edged = np.clip(edged, 0, 255).astype(np.uint8)
_, edged = cv2.threshold(edged, THRESHOLD, 255, cv2.THRESH_BINARY)
cv2.imwrite("output.jpg", edged)
Код 3. Оператор Прюитта
import cv2
import numpy as np
from scipy import ndimage

THRESHOLD = 50

img = cv2.imread("input.jpg", 0).astype('float64')
prewitt_x = np.array([[-1, 0, 1], [-1, 0, 1], [-1, 0, 1]])
prewitt_y = np.array([[-1, -1, -1], [0, 0, 0], [1, 1, 1]])
grad_x = ndimage.convolve(img, prewitt_x)
grad_y = ndimage.convolve(img, prewitt_y)
edged = np.sqrt(np.square(grad_x) + np.square(grad_y))
edged = np.clip(edged, 0, 255).astype(np.uint8)
_, edged = cv2.threshold(edged, THRESHOLD, 255, cv2.THRESH_BINARY)
cv2.imwrite("output.jpg", edged)
Рис. 8. Оператор Прюитта, результат

Рис. 8. Оператор Прюитта, результат

Оператор Прюитта выделяет уже большее количество деталей, создавая более точный и подробный контур.

Рассмотрим результат применения фильтра Собеля:

Рис. 9 Оператор Собеля, результат

Рис. 9 Оператор Собеля, результат

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

❯ 2.3 Оператор Лапласа

Назначение: Реализует лапласиан.
Алгоритм: Определяет маску Лапласа kernel. Вызывает applyFilter, передавая только одну маску (так как Лаплас — изотропный оператор, т.е его маска совмещает горизонтальные и вертикальные направления).

Код 4. Оператор Лапласа
import cv2
import numpy as np
from scipy import ndimage

THRESHOLD = 50

img = cv2.imread("input.jpg", 0).astype('float64')
laplacian = np.array([[0, 1, 0], [1, -4, 1], [0, 1, 0]])
filtered = ndimage.convolve(img, laplacian)
edged = np.abs(filtered)
edged = np.clip(edged, 0, 255).astype(np.uint8)
_, edged = cv2.threshold(edged, THRESHOLD, 255, cv2.THRESH_BINARY)
cv2.imwrite("output.jpg", edged)
Рис. 10 Оператор Лапласа, результат

Рис. 10 Оператор Лапласа, результат

По итоговому изображению можно заметить высокую чувствительность к деталям у Лапласиана. Представленная в работе реализация оператора Лапласа имеет время выполнения 150 мс, аналогичный метод Laplacian библиотеки OpenCV работает за 8 мс.

❯ 2.4 Детектор границ Кенни

Метод applyCanny(double threshold1, double threshold2)
Назначение: Реализует алгоритм Канни.
Алгоритм: Вычисляет градиенты Собеля в горизонтальном (gradX) и вертикальном (gradY) направлениях. Использует applySobel для gradX. Вызывает applyFilter с вертикальной маской для gradY. Приводит градиенты к типу CV_32F. Вычисляет величину (magnitude) и угол (angle) градиента с помощью cv::cartToPolar. Нормализует величину градиента. Применяет пороги threshold1 и threshold2 для выделения границ.

Код 5. Детектор Канни
import cv2

THRESHOLD = 50

img = cv2.imread("input.jpg", 0)
edged = cv2.Canny(img, threshold1=THRESHOLD, threshold2=THRESHOLD * 3)
cv2.imwrite("output.jpg", edged)
Рис 11. Найденные контуры алгоритма Канни

Рис 11. Найденные контуры алгоритма Канни

Здесь представлены методы findContours и drawContours. Они используют библиотечные функции для отображения контуров, а также наш класс для их хранения (boundingBoxes и contours). Зеленым цветом выделяются boundingBoxes, синим — сами контуры.

Код 6. Нахождение и отрисовка контуров
import cv2

img = cv2.imread("canny_output.jpg", 0)
color_img = cv2.cvtColor(img, cv2.COLOR_GRAY2BGR)
contours, _ = cv2.findContours(img, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
for cnt in contours:
    x, y, w, h = cv2.boundingRect(cnt)
    cv2.rectangle(color_img, (x, y), (x + w, y + h), (0, 255, 0), 2)
cv2.drawContours(color_img, contours, -1, (255, 0, 0), 1)
cv2.imwrite("output.jpg", color_img)

В функции cv.findContours() есть три аргумента: первый — исходное изображение, второй — режим поиска контуров, третий — метод аппроксимации контура. И она выводит измененное изображение, контуры и иерархи.

Сам метод find_contours() из библиотеки OpenCV использует топологический анализ бинаризованного изображения. Алгоритм, использующийся в методе, определяет границы контуров и какие контуры окружены другими.

❯ 2.5 Адаптивный медианный фильтр Уоллеса

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

Код 7. Фильтр Уоллеса
import cv2
import numpy as np

def wallis_filter(img, W=15, Amax=1.0, Dd=1.0, p=0.5, Md=128.0):
    img = img.astype(np.float64)
    if W % 2 == 0:
        W += 1
    kernel = np.ones((W, W), np.float64) / (W * W)
    local_mean = cv2.filter2D(img, -1, kernel)
    local_var = cv2.filter2D((img - local_mean) ** 2, -1, kernel)
    local_std = np.sqrt(local_var + 1e-10)
    numerator = (img - local_mean) * (Amax * Dd)
    denominator = Amax * local_std + Dd
    result = numerator / denominator + p * Md + (1 - p) * local_mean
    result = np.clip(result, 0, 255).astype(np.uint8)
    return result

img = cv2.imread("input.jpg", 0)
filtered = wallis_filter(img)
cv2.imwrite("output.jpg", filtered)
Рис 12. Фильтр Уоллеса

Рис 12. Фильтр Уоллеса

❯ Выводы

По результатам применения представленных пространственных фильтров можно заключить:

  • Алгоритм Канни справляется лучше пространственных фильтров по нахождению контура и фильтрации шума.

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

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

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

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


❯ Список источников

  1. Вильгейм Б. Принципы цифровой обработки изображений/Б. Вильгейм, Марк Дж. Бурдж - Лондон: Springer-Verlag, 2009 – 260 с.

  2. Гонсалес Р. Мир цифровой обработки/Р. Гонсалес, Р. Вудс - Москва: Техносфера, 2012 - 1104 с.


Новости, обзоры продуктов и конкурсы от команды Timeweb.Cloud — в нашем Telegram-канале