Работа с контурами в OpenCV
Контуры изображения определяются как кривая, соединяющая все непрерывные точки (вдоль границы), имеющие одинаковый цвет или интенсивность. Иначе говоря, мы фокусируемся на нахождении границ в бинарном изображении. Официальное определение следующее:
Контуры — полезный инструмент для анализа формы, обнаружения и распознавания объектов.
Чтобы сохранить точность, мы должны использовать бинарные изображения. Во-первых, мы применяем обнаружение порога или края.
В OpenCV поиск контура в бинарном изображении аналогичен поиску белого объекта на черном фоне.
OpenCV предоставляет функцию findContours(), которая используется для поиска контура в бинарном изображении. Синтаксис следующий:
cv2. findContours(thes, cv2.RETR_TREE, cv.CHAIN_APPROX_SIMPLE)
Функция findContours() принимает три аргумента: первый аргумент — это исходное изображение, второй — режим поиска контуров, а третий — аппроксимация контуров.
Рассмотрим следующий пример:
import numpy as np import cv2 as cv im = cv.imread(r'C:\Users\DEVANSH SHARMA\binary.png') imgray = cv.cvtColor(im, cv.COLOR_BGR2GRAY) ret, thresh = cv.threshold(imgray, 127, 255, 0) contours, hierarchy = cv.findContours(thresh, cv.RETR_TREE, cv.CHAIN_APPROX_SIMPLE)
Как рисовать контуры?
OpenCV предоставляет функцию cv2.drawContours(), которая используется для рисования контуров. Он также используется для рисования любой формы, предоставляя ее граничные точки. Синтаксис функции cv2.drawContours() приведен ниже.
Чтобы нарисовать все контуры на изображении:
cv2.drawCounter(img, contours,-1,(0,255,0),3)
Чтобы нарисовать индивидуальный контур, например, третий:
cnt = contours[3] cv2.drawCounter(img,[cnt],0,(0,255,0),3)
Первый аргумент представляет источник изображения, второй аргумент представляет контуры, которые должны быть переданы в виде списка Python, третий аргумент используется как индекс контуров, а другие аргументы используются для толщины цвета.
Метод контурной аппроксимации
Это третий аргумент в cv2.findCounter(). Выше мы описали это, чтобы нарисовать границу фигуры с одинаковой интенсивностью. Он хранит (x, y) – координаты границы формы. Но тут возникает вопрос, хранит ли он все координаты? Это определяется методом контурной аппроксимации.
Если мы передадим cv.CHAIN_APPROX_NONE, он сохранит все граничные точки. Иногда не нужно хранить координаты всех точек, допустим, мы нашли контуры прямой линии, где не требуется хранить все точки контура, требуется хранить только две конечные точки. Поэтому для такого случая мы используем cv.CHAIN_APPROX_NONE, он удаляет все лишние точки и сжимает контуры, тем самым экономя память.
На приведенном выше изображении прямоугольника первое изображение показывает точки, использующие cv.CHAIN_APPROX_NONE(734), а второе изображение показывает точку с cv2.CHAIN_APPROX_SIMPLE (всего 4 точки). Мы видим значительную разницу между двумя изображениями.
Уроки компьютерного зрения на Python + OpenCV с самых азов. Часть 3
Продолжим изучение компьютерного зрения. Начало здесь. Напомню краткое содержание предыдущих уроков. Мы изучили этапы анализ и обработки изображений, установку OpenCV, простейшие действия над изображением, такие как преобразование в черно-белый формат, изменение размеров, накладывание фильтра размытия.
Сегодня продолжим тему обработки изображений. На прошлом уроке мы пытались при помощи размытия удалить из изображения такие дефекты, как гауссовский шум и царапины. С первым что-то более-менее получилось, а вот с царапинами ничего не вышло. Да, кстати, в комментах мне был задан вопрос: «Откуда берется гауссовкий шум?»
Гауссовский шум может возникнуть, например, от помех. Или, если у нас было плохое освещение, картинка получилась темная, и мы попытались как-то исправить это, например, увеличить контрастность. Шумы при этом тоже усилятся.
Ладно. Идем дальше. Как же нам быть с царапинами? А для их удаления можно воспользоваться медианным фильтром:
import cv2 my_photo = cv2.imread('MyPhoto1.jpg') median_image = cv2.medianBlur(my_photo,3) cv2.imshow('MyPhoto', median_image ) cv2.waitKey(0) cv2.destroyAllWindows()
А вот мы применили к ней фильтр 3 на 3:
Как видим, царапины уменьшились. Но не полностью. Попробуем фильтр размером 5 пикселей:
Как видим, с дефектами типа царапин фильтр справляется, хотя не всегда. А с гауссовсикм шумом?
Обработанная медианным фильтром 5:
Как видим, с гауссовским шумом медианный фильтр справляется плохо.
Фильтр на изображение можно наложить и виде линейной свертки с определенной матрицей. Гауссовский фильтр, кстати, частный случай такого линейного фильтра. Как происходит фильтрация? Мы берем скользящее окно, попиксельно умножаем яркость каждого пикселя этого окна на коэффициент в матрице, складываем и результат записываем в центральную точку окна. Потом окно сдвигаем на один пиксель и делаем то же самое. И так пока не пройдем по всему изображению. Например, при помощи фильтра:
Можно повысить резкость изображения:
import cv2 import numpy as np my_photo = cv2.imread('MyPhoto.jpg') kernel = np.array([[-1,-1,-1], [-1,9,-1], [-1,-1,-1]]) im = cv2.filter2D(my_photo, -1, kernel) cv2.imshow('MyPhoto', im ) cv2.waitKey(0) cv2.destroyAllWindows()
Вот как это будет выглядеть:
Среди линейных фильтров есть специальные матричные фильтры, которые подготавливают изображение к следующему этапу – поиску фич. Например, фильтр Собеля:
Вот эффект от применения данного фильтра:
Мы применили его к цветному изображению, но обычно такие фильтры применяют к черно-белому изображению:
import cv2 import numpy as np my_photo = cv2.imread('MyPhoto.jpg',cv2.IMREAD_GRAYSCALE) kernel = np.array([[-1,0,1], [-2,0,2], [-1,0,1]]) im = cv2.filter2D(my_photo, -1, kernel) cv2.imshow('MyPhoto', im ) cv2.waitKey(0) cv2.destroyAllWindows()
И вот как выглядит результат:
Как видим, фильтр Собеля позволяет обозначить на изображении контуры. Правда, только обозначить, а не выделить. Для выделения контуров необходимо повторно обойти изображение, полученного фильтром, и уже на нем найти контуры. Что делать с этими контурами дальше – уже другой вопрос, мы до этого еще доберемся. А пока рассмотрим следующий фильтр – лапласиан:
Вот эффект от его применения (к черно белому изображению):
Как видим, тут контуры обозначены более качественно. Но, тем не менее, это еще не выделение контуров. Это лишь заготовка, называемая контурный препарат. И с ним необходимо так же делать то, что было описано выше: повторно обойти изображение, полученное фильтром, и уже на нем найти контуры.
К счастью, в OpenCV есть функция нахождения контуров, которая делает все, что нужно, чтобы выделить контур. Вот фрагмент кода, который выделяет контур
import cv2 import numpy as np my_photo = cv2.imread('MyPhoto.jpg') img_grey = cv2.cvtColor(my_photo,cv2.COLOR_BGR2GRAY) #зададим порог thresh = 100 #получим картинку, обрезанную порогом ret,thresh_img = cv2.threshold(img_grey, thresh, 255, cv2.THRESH_BINARY) #надем контуры contours, hierarchy = cv2.findContours(thresh_img, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE) #создадим пустую картинку img_contours = np.zeros(my_photo.shape) #отобразим контуры cv2.drawContours(img_contours, contours, -1, (255,255,255), 1) cv2.imshow('contours', img_contours) # выводим итоговое изображение в окно cv2.waitKey() cv2.destroyAllWindows()
И вот результат работы этой программы:
Если качество выделения контуров не устраивает, можно поиграться с порогом, например, если порог поставить 50, то мы увидим вот такую картинку:
А вот так будет выглядеть контур, если порог сделать 150:
Для наглядности попробуем другую картинку:
Выделив контур (порог 180), мы очень наглядно увидим линии крыш зданий
А теперь снова поговорим о предобработке. Вернемся к медианной фильтрации, и попробуем сначала применить к изображению этот фильтр, а уже потом выделить контур:
import cv2 import numpy as np my_photo = cv2.imread('DSCN1311.jpg') median_image = cv2.medianBlur(my_photo,5) img_grey = cv2.cvtColor(median_image,cv2.COLOR_BGR2GRAY) #set a thresh thresh = 180 #get threshold image ret,thresh_img = cv2.threshold(img_grey, thresh, 255, cv2.THRESH_BINARY) #find contours contours, hierarchy = cv2.findContours(thresh_img, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE) #create an empty image for contours img_contours = np.zeros(my_photo.shape) # draw the contours on the empty image cv2.drawContours(img_contours, contours, -1, (255,255,255), 1) cv2.imshow('contours', img_contours) # выводим итоговое изображение в окно cv2.waitKey() cv2.destroyAllWindows()
Проиграемся с порогом, я поставил 100, и вот мы уже видим на контуре вменяемое очертание здания:
Ладно. Вот выделили мы контур. Что дальше? А дальше уже идет следующий этап: промежуточная фильтрации. Собственно говоря, само выделение контура – это уже начало данного этапа, так как по контуру мы можем обнаружить области интереса. Например, границы, углы. Мы можем даже, используя контур, приступить к третьему этапу – поиск фич.
Что можно сделать с контуром? Например, следующее:
- Выявить различные геометрические примитивы (прямые, окружности).
- Превратить в цепочки точек и уже их отдельно анализировать.
- Описать как граф и применять к нему алгоритмы на графах.