Основы Opencv 60- Использование алгоритма водораздела cv2.distanceTransform() для реализации принципов и примеров сегментации и извлечения изображений

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

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

Принцип алгоритма

Гонсалес сделал подробный анализ и представил алгоритм водораздела в книге «Цифровая обработка изображений». Официальный веб-сайт OpenCV рекомендует учащимся прочитать введение и анимационную демонстрацию алгоритма водораздела на веб-сайте CMM (Центр математической морфологии) Лаборатории обработки изображений MINES ParisTech.

Ниже приводится краткое введение в соответствующее содержание алгоритма водораздела.

Любое изображение в градациях серого можно рассматривать как географическую топографическую поверхность.Области с высокими значениями градаций серого можно рассматривать как горные вершины, а области с низкими значениями градаций серого можно рассматривать как долины. Как показано на рис. 17-1, левое изображение — это исходное изображение, а правое — это соответствующая ему «поверхность местности».

Если мы «вливаем» воду разных цветов в каждую долину (здесь используется официальный сайт OpenCV, Гонсалес выражает вливание как проделывание отверстия в долине, а затем позволяя воде подниматься через отверстие с одинаковой скоростью). Затем, по мере того, как уровень воды продолжает подниматься, вода из разных долин будет собираться вместе. В этом процессе, чтобы предотвратить схождение воды из разных долин, нам нужно построить дамбы, где водные потоки могут встречаться. Этот процесс разделяет изображения на два отдельных набора: водосборные бассейны и линии водоразделов. Дайки, которые мы строим, являются линиями водораздела, которые представляют собой сегментацию исходного изображения. Это алгоритм водораздела.

вставьте сюда описание изображения
На рисунке 17-2 левое изображение — исходное изображение, а правое изображение — результат сегментации изображения, полученный с помощью алгоритма водосбора. Веб-сайт CMM не только предоставляет образец изображения, но также предоставляет демонстрационные эффекты анимации, заинтересованные читатели могут зайти на веб-сайт, чтобы посмотреть.

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

На рис. 17-3 показано чрезмерно сегментированное изображение. Изображение слева - это изображение электрофореза, а изображение справа - результат чрезмерной сегментации. Видно, что явление чрезмерной сегментации очень серьезное.

вставьте сюда описание изображения

Для улучшения эффекта сегментации изображения предлагается усовершенствованный алгоритм водораздела на основе маски. Усовершенствованный алгоритм водораздела позволяет пользователю выделить то, что он считает одной и той же сегментированной областью (отмеченная часть называется маской). Таким образом, при обработке алгоритма водосбора помеченная часть будет обрабатываться как одна и та же сегментированная область.
Вы можете попробовать использовать функцию «удалить фон» в Microsoft PowerPoint , чтобы углубить свое понимание этого улучшенного алгоритма.

На рис. 17-4 левое изображение — это исходное изображение, которое мы пометили, а три небольших цветных блока, помеченных темным цветом, указывают: при использовании алгоритма водораздела маски цвета, содержащиеся в этих частях, будут разделены на одинаковые область. Результат сегментации, полученный с использованием алгоритма водораздела маски, показан на правом рисунке на рисунке 17-4.

вставьте сюда описание изображения

Используйте улучшенный алгоритм водораздела, чтобы замаскировать электрофоретическое изображение слева на рис. 17-5 и получить результат сегментации справа. Видно, что результаты сегментации значительно улучшились.

вставьте сюда описание изображения

Введение в функцию cv2.watershed()

В OpenCV функция cv2.watershed() может использоваться для реализации алгоритма водораздела. В конкретном процессе реализации также необходимо
выполнить сегментацию изображения с помощью морфологических функций и функций преобразования расстояния cv2.distanceTransform() и cv2.connectedComponents(). Ниже приводится краткое описание функций, используемых в алгоритме водораздела.

1. Обзор морфологических функций

Прежде чем использовать алгоритм водораздела для сегментации изображения, необходимо выполнить простую морфологическую обработку изображения. Сначала рассмотрим основные операции в морфологии.
(1) Операция открытия
Операция открытия — это сначала операция эрозии, а затем расширения, и операция открытия может удалить шум в изображении. Например, на рис. 17-6, если левое изображение подвергнется коррозии первым, будет получено среднее изображение, а затем среднее изображение будет расширено для получения правого изображения. Открыв левое изображение (сначала коррозия, затем расширение), мы получаем правое изображение.

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

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

(2) Получение границы изображения
Граница изображения может быть получена с помощью морфологических операций и операций вычитания.

Например, на рисунке 17-7 левое изображение — это исходное изображение, а среднее изображение — это изображение, полученное путем его коррозии Вычитание двух результатов дает изображение справа. Из наблюдения видно, что правое изображение является границей левого изображения.

вставьте сюда описание изображения

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

Исходное изображение:

вставьте сюда описание изображения

import cv2
import numpy as np
import matplotlib.pyplot as plt
o=cv2.imread("rice.png",cv2.IMREAD_UNCHANGED)
#构造一个5*5的结构元素
k=np.ones((5,5),np.uint8)
#腐蚀
e=cv2.erode(o,k)
#膨胀
b=cv2.subtract(o,e)

plt.subplot(131)
plt.imshow(o)
plt.axis('off')
plt.subplot(132)
plt.imshow(e)
plt.axis('off')
plt.subplot(133)
plt.imshow(b)
plt.axis('off')
plt.show()

результат операции:

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

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

2. Функция преобразования расстояния DistanceTransform

Когда подграфы на изображении не связаны, объект переднего плана можно определить непосредственно с помощью операции морфологической эрозии, но если подграфы на изображении соединены вместе, определить объект переднего плана сложно. В этот момент
объект переднего плана можно легко извлечь с помощью функции преобразования расстояния cv2.distanceTransform().

Преобразование расстояния 函数 cv2.distanceTransform()вычисляет расстояние от любой точки бинарного изображения до ближайшей точки фона. В общем, эта функция вычисляет расстояние от пикселя с ненулевым значением в изображении до ближайшего пикселя с нулевым значением, то есть вычисляет расстояние между всеми пикселями в бинарном изображении и его ближайшим пикселем со значением 0. Конечно, если значение самого пикселя равно 0, то и это расстояние равно 0.

Результат вычисления функции преобразования расстояния cv2.distanceTransform() отражает
отношение расстояния между каждым пикселем и фоном (пикселем со значением 0). обычно:

  • Если центр (центроид) объекта переднего плана находится дальше от пикселя со значением 0, он получит большее значение.
  • Если край объекта переднего плана находится ближе к пикселю со значением 0, он получит меньшее значение.

Если приведенные выше результаты вычислений имеют пороговое значение, можно получить такую ​​информацию, как центр и скелет части изображения в изображении. Функция преобразования расстояния cv2.distanceTransform() может использоваться для вычисления центра объекта, а также может уточнять контур, получать передний план изображения и т. д. и имеет множество функций.
Формат синтаксиса функции преобразования расстояния cv2.distanceTransform():

dst=cv2.distanceTransform(src, DistanceType, maskSize[, dstType]])

В формуле:

  • src представляет собой 8-битное одноканальное двоичное изображение.
  • DistanceType — это параметр типа расстояния, и его конкретное значение и смысл показаны в Таблице 17-1.

вставьте сюда описание изображения
вставьте сюда описание изображения

  • maskSize — это размер маски, и его возможные значения показаны в Таблице 17-2. Обратите внимание, что когда DistanceType = cv2.DIST_L1 или cv2.DIST_C, maskSize принудительно принимает значение 3 (поскольку нет никакой разницы между установкой значения 3 и значением 5 или более).

вставьте сюда описание изображения

  • dstType — тип целевого изображения, значение по умолчанию — CV_32F.
  • dst представляет рассчитанное целевое изображение, которое может быть 8-битным или 32-битным числом с плавающей запятой, и его размер такой же, как у src.

Пример: используйте функцию преобразования расстояния cv2.distanceTransform() для вычисления определенного переднего плана изображения и наблюдения за эффектом.

import numpy as np
import cv2
import matplotlib.pyplot as plt
img = cv2.imread('water_coins.jpg')
gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
img=cv2.cvtColor(img,cv2.COLOR_BGR2RGB)
ishow=img.copy()
#对图像阈值化处理,得到二值图像,黑色背景,白色前景,前景为我们要提取的目标,背景为0,前景为1
ret, thresh = cv2.threshold(gray,0,255,cv2.THRESH_BINARY_INV+cv2.THRESH_OTSU)
#定义一个3*3的卷积核,卷积核的作用是将前景与背景分离
kernel = np.ones((3,3),np.uint8)
#对二值图像进行开运算,去除噪声,开运算是先腐蚀再膨胀的过程,它可以去除小的干扰块,平滑较大的对象边界,同时并不明显改变其面积
opening = cv2.morphologyEx(thresh,cv2.MORPH_OPEN,kernel, iterations = 2)
#距离变换,得到每一个前景像素点到最近的背景像素点的距离,然后对其进行阈值化处理,得到的结果就是前景
dist_transform = cv2.distanceTransform(opening,cv2.DIST_L2,5)
#对膨胀后的图像进行阈值化处理,得到前景,前景为我们要提取的目标,背景为0,前景为1,这样就得到了我们要提取的目标,但是目标并不连续
ret, fore = cv2.threshold(dist_transform,0.7*dist_transform.max(),255,0)

plt.subplot(131)
plt.imshow(ishow)
plt.axis('off')
plt.subplot(132)
plt.imshow(dist_transform)
plt.axis('off')
plt.subplot(133)
plt.imshow(fore)
plt.axis('off')
plt.show()

Результат выглядит следующим образом:

вставьте сюда описание изображения

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

Изображение справа более точно показывает «определенный передний план» на изображении слева. Определенный передний план здесь обычно
относится к центру объекта переднего плана. Причина, по которой эти точки считаются передним планом, заключается в том, что они находятся достаточно далеко от фоновых точек, и все они являются точками, расстояние до которых превышает достаточно большой фиксированный порог (0,7*dist_transform.max()).

3. Определите неизвестные регионы

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

Далее фон определения обозначен как B для удобства описания. Функция преобразования расстояния cv2.distanceTransform()может получить «центр» изображения, чтобы получить «определенный передний план». Для удобства описания определяемый передний план обозначен буквой F. С определенным передним планом F и определенным фоном B на изображении оставшаяся область является неизвестной областью UN.
Эта часть области как раз и является областью, подлежащей дальнейшему определению алгоритмом водораздела.
Для изображения O неизвестную область UN можно получить из следующего соотношения:

Неизвестная область UN = изображение O - определить фон B - определить передний план F

Упорядочивая приведенные выше выражения, мы можем получить:

Неизвестная область UN = (изображение O - определить фон B) - определить передний план F

«Изображение O - определение фона B» в приведенной выше формуле можно получить, выполнив операцию морфологического расширения изображения.

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

import numpy as np
import cv2
import matplotlib.pyplot as plt
img = cv2.imread('water_coins.jpg')
gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
img=cv2.cvtColor(img,cv2.COLOR_BGR2RGB)
ishow=img.copy()
ret, thresh = cv2.threshold(gray,0,255,cv2.THRESH_BINARY_INV+cv2.THRESH_OTSU)
kernel = np.ones((3,3),np.uint8)
opening = cv2.morphologyEx(thresh,cv2.MORPH_OPEN,kernel, iterations = 2)
#对图像进行膨胀操作,得到背景
bg = cv2.dilate(opening,kernel,iterations=3)

dist = cv2.distanceTransform(opening,cv2.DIST_L2,5)
ret, fore = cv2.threshold(dist,0.7*dist.max(),255,0)
fore = np.uint8(fore)
un = cv2.subtract(bg,fore)
plt.subplot(221)
plt.imshow(ishow)
plt.axis('off')
plt.subplot(222)
plt.imshow(bg)
plt.axis('off')
plt.subplot(223)
plt.imshow(fore)
plt.axis('off')
plt.subplot(224)
plt.imshow(un)
plt.axis('off')
plt.show()

результат операции:

вставьте сюда описание изображения

  • В левом верхнем углу находится исходное изображение ishow.
  • Верхний правый угол — это изображение bg, полученное после увеличения изображения isow, фоновое изображение — это определенный фон, а изображение переднего плана — это «исходное изображение — определенный фон».
  • Нижний левый угол предназначен для определения переднего плана изображения.
  • Небольшой кружок на изображении в правом нижнем углу — это изображение неизвестной области un, которое получается путем вычитания изображения bg и изображения fore. То есть изображение неизвестной области un получается из «исходного изображения, определяемого фоном, определяемым передним планом».

4. Функция ConnectedComponents

После определения определенного переднего плана можно пометить определенное изображение переднего плана. В OpenCV доступны функции
cv2.connectedComponents()进行标注. Эта функция пометит фон как 0, а другие объекты пометит положительными целыми числами, начиная с 1.
Формат синтаксиса функции cv2.connectedComponents():

retval, labels = cv2.connectedComponents(изображение)

В формуле:

  • image представляет собой 8-битное одноканальное изображение, которое необходимо пометить.
  • retval — количество возвращенных меток.
  • labels — это размеченное изображение результата.

Пример: используйте функцию cv2.connectedComponents(), чтобы аннотировать изображение и наблюдать за эффектом аннотации.

import numpy as np
import cv2
import matplotlib.pyplot as plt
img = cv2.imread('water_coins.jpg')
gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
img=cv2.cvtColor(img,cv2.COLOR_BGR2RGB)
ishow=img.copy()
ret, thresh = cv2.threshold(gray,0,255,
cv2.THRESH_BINARY_INV+cv2.THRESH_OTSU)
kernel = np.ones((3,3),np.uint8)
opening = cv2.morphologyEx(thresh,cv2.MORPH_OPEN,kernel, iterations = 2)
sure_bg = cv2.dilate(opening,kernel,iterations=3)
dist_transform = cv2.distanceTransform(opening,cv2.DIST_L2,5)
ret, fore = cv2.threshold(dist_transform,0.7*dist_transform.max(),255,0)
fore = np.uint8(fore)
ret, markers = cv2.connectedComponents(fore)
print(markers)
plt.subplot(131)
plt.imshow(ishow)
plt.axis('off')
plt.subplot(132)
plt.imshow(fore)
plt.axis('off')
plt.subplot(133)
plt.imshow(markers)
plt.axis('off')
print(ret)
plt.show()

результат операции:

  • Левое изображение — исходное изображение ishow.
  • Средний — это изображение центральной точки перед изображением переднего плана, полученное после преобразования расстояния.
  • Изображение справа представляет собой маркеры результирующего изображения после маркировки изображения центральной точки изображения переднего плана.
    Видно, что центральная точка изображения переднего плана отмечена по-разному.Когда вставьте сюда описание изображения
    функция cv2.connectedComponents() помечает изображение, она помечает фон как 0, а
    другие объекты помечает положительными целыми числами, начиная с 1. Конкретное соответствующее отношение:
  • Значение 0 представляет фоновую область.
  • Значения, начинающиеся с 1, представляют разные области переднего плана.
    В алгоритме водораздела значение метки 0 представляет неизвестную область. Поэтому нам нужно скорректировать результаты, отмеченные функцией cv2.connectedComponents(): добавить к отмеченным результатам значение 1. После вышеуказанной обработки в результате маркировки:
  • Значение 1 представляет фоновую область.
  • Значения, начинающиеся с 2, представляют разные области переднего плана.

Чтобы иметь возможность использовать алгоритм водораздела, также необходимо пометить неизвестную область на исходном изображении и пометить рассчитанную неизвестную область как 0.
Ключевой код здесь:

ret, markers = cv2.connectedComponents(fore)
markers = markers+1
markers[未知区域] = 0

Пример: используйте функцию cv2.connectedComponents(), чтобы пометить изображение и исправить его так, чтобы неизвестная область была помечена как 0, и наблюдайте за эффектом пометки.

import numpy as np
import cv2
import matplotlib.pyplot as plt
img = cv2.imread('water_coins.jpg')
gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
img=cv2.cvtColor(img,cv2.COLOR_BGR2RGB)
ishow=img.copy()
ret, thresh = cv2.threshold(gray,0,255,
cv2.THRESH_BINARY_INV+cv2.THRESH_OTSU)
kernel = np.ones((3,3),np.uint8)
opening = cv2.morphologyEx(thresh,cv2.MORPH_OPEN,kernel, iterations = 2)
sure_bg = cv2.dilate(opening,kernel,iterations=3)
dist_transform = cv2.distanceTransform(opening,cv2.DIST_L2,5)
ret, fore = cv2.threshold(dist_transform,0.7*dist_transform.max(),255,0)
fore = np.uint8(fore)
ret, markers1 = cv2.connectedComponents(fore)
foreAdv=fore.copy()
unknown = cv2.subtract(sure_bg,foreAdv)
ret, markers2 = cv2.connectedComponents(foreAdv)
markers2 = markers2+1
markers2[unknown==255] = 0
plt.subplot(121)
plt.imshow(markers1)
plt.axis('off')
plt.subplot(122)
plt.imshow(markers2)
plt.axis('off')
plt.show()

результат операции:

вставьте сюда описание изображения

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

Функция алгоритма водораздела cv2.watershed()

После завершения вышеуказанной обработки изображение результата предварительной обработки может быть сегментировано с использованием алгоритма водораздела. В OpenCV функция, реализующая алгоритм водораздела, называется cv2.watershed(), а ее синтаксис имеет следующий формат:

маркеры = cv2.watershed (изображение, маркеры)

В формуле:

  • image — входное изображение, которое должно быть 8-битным трехканальным изображением. Перед использованием функции cv2.watershed() на изображении желаемая сегментированная область на изображении должна быть грубо обведена положительным числом. Каждая сегментированная область будет помечена цифрами 1, 2, 3 и т. д. Для областей, которые не были определены, они должны быть отмечены как 0. Мы можем понимать отмеченную область как «исходную» область для сегментации алгоритма водораздела.
  • markers — это 32-битный одноканальный результат аннотации, и он должен иметь тот же размер, что и изображение. В маркерах каждому пикселю либо присваивается начальное «начальное значение», либо присваивается значение «-1» для границ. маркеры могут быть опущены.

Основные шаги при использовании алгоритма водораздела для сегментации изображения:

  1. Исходное изображение O очищается от шума с помощью операции морфологического открытия.
  2. Получите «определенный фон B» с помощью операции травления. Следует отметить, что здесь достаточно получить «исходное изображение-определить фон».
  3. Используйте функцию преобразования расстояния cv2.distanceTransform() для работы с исходным изображением и выполните пороговую обработку для него,
    чтобы получить «определенный передний план F».
  4. Вычислите неизвестную область UN (UN = O –B – F).
  5. Используйте функцию cv2.connectedComponents(), чтобы аннотировать исходное изображение O.
  6. Исправьте результат аннотации функции cv2.connectedComponents().
  7. Сегментация изображения выполняется с помощью функции водораздела.

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

import numpy as np
import cv2
import matplotlib.pyplot as plt
img = cv2.imread('water_coins.jpg')
gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
img=cv2.cvtColor(img,cv2.COLOR_BGR2RGB)
ishow=img.copy()
ret, thresh = cv2.threshold(gray,0,255,
cv2.THRESH_BINARY_INV+cv2.THRESH_OTSU)
kernel = np.ones((3,3),np.uint8)
opening = cv2.morphologyEx(thresh,cv2.MORPH_OPEN,kernel, iterations = 2)
sure_bg = cv2.dilate(opening,kernel,iterations=3)
dist_transform = cv2.distanceTransform(opening,cv2.DIST_L2,5)
ret, sure_fg = cv2.threshold(dist_transform,0.7*dist_transform.max(),255,0)
sure_fg = np.uint8(sure_fg)
unknown = cv2.subtract(sure_bg,sure_fg)
ret, markers = cv2.connectedComponents(sure_fg)
markers = markers+1
markers[unknown==255] = 0
markers = cv2.watershed(img,markers)
img[markers == -1] = [255,0,0]
plt.subplot(121)
plt.imshow(ishow)
plt.axis('off')
plt.subplot(122)
plt.imshow(img)
plt.axis('off')
plt.show()

текущий результат:

вставьте сюда описание изображения

рекомендация

отblog.csdn.net/hai411741962/article/details/132228072