Подтвердить что ты не робот

Как определить маркеры для Watershed в OpenCV?

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

minMaxLoc() дал бы мне значение, но как я могу ограничить его блоками, которые меня интересуют? Могу ли я использовать результаты из findContours() или cvBlob blobs, чтобы ограничить ROI и применить максимальные значения к каждому блоку?

input image

4b9b3361

Ответ 1

Прежде всего: функция minMaxLoc находит только глобальный минимум и глобальный максимум для данного ввода, поэтому в большинстве случаев бесполезно определять региональные минимумы и/или региональные максимумы. Но ваша идея правильная, извлечение маркеров на основе региональных минимумов/максимумов для выполнения преобразования Watershed Transform на основе маркеров полностью прекрасен. Позвольте мне прояснить, что такое Преобразование Watershed и как вы должны правильно использовать реализацию, присутствующую в OpenCV.

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

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

Я написал все это (что, возможно, слишком наивно для тех, кто знает, что такое Преобразование Водораздела), потому что он напрямую отражает то, как вы должны использовать реализации водораздела (которые текущий принятый ответ делает совершенно неправильно). Давайте начнем с примера OpenCV, используя привязки Python.

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

enter image description hereenter image description here

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

enter image description hereenter image description here

Этот маркер, который у нас здесь, имеет большой смысл. Каждый colored water == one marker начнет заполнять регион, а преобразование водоразделов построит плотины, чтобы помешать слиянию разных "цветов". Если мы сделаем преобразование, мы получим изображение слева. Учитывая только плотины, составив их с исходным изображением, мы получим результат справа.

enter image description hereenter image description here

import sys
import cv2
import numpy
from scipy.ndimage import label

def segment_on_dt(a, img):
    border = cv2.dilate(img, None, iterations=5)
    border = border - cv2.erode(border, None)

    dt = cv2.distanceTransform(img, 2, 3)
    dt = ((dt - dt.min()) / (dt.max() - dt.min()) * 255).astype(numpy.uint8)
    _, dt = cv2.threshold(dt, 180, 255, cv2.THRESH_BINARY)
    lbl, ncc = label(dt)
    lbl = lbl * (255/ncc)
    # Completing the markers now. 
    lbl[border == 255] = 255

    lbl = lbl.astype(numpy.int32)
    cv2.watershed(a, lbl)

    lbl[lbl == -1] = 0
    lbl = lbl.astype(numpy.uint8)
    return 255 - lbl


img = cv2.imread(sys.argv[1])

# Pre-processing.
img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)    
_, img_bin = cv2.threshold(img_gray, 0, 255,
        cv2.THRESH_OTSU)
img_bin = cv2.morphologyEx(img_bin, cv2.MORPH_OPEN,
        numpy.ones((3, 3), dtype=int))

result = segment_on_dt(img, img_bin)
cv2.imwrite(sys.argv[2], result)

result[result != 255] = 0
result = cv2.dilate(result, None)
img[result == 255] = (0, 0, 255)
cv2.imwrite(sys.argv[3], img)

Ответ 2

Я хотел бы объяснить простой код о том, как использовать водораздел здесь. Я использую OpenCV-Python, но надеюсь, вам не составит труда понять.

В этом коде я использую водораздел как инструмент для выделения фонового фона. (Этот пример представляет собой копию python кода С++ в кулинарной книге OpenCV). Это простой случай, чтобы понять водораздел. Кроме того, вы можете использовать водораздел для подсчета количества объектов на этом изображении. Это будет немного расширенная версия этого кода.

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

import cv2
import numpy as np

img = cv2.imread('sofwatershed.jpg')
gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
ret,thresh = cv2.threshold(gray,0,255,cv2.THRESH_BINARY+cv2.THRESH_OTSU)

Ниже приведен результат:

enter image description here

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

2 - Теперь нам нужно создать маркер. Маркер - это изображение с таким же размером, как у исходного изображения, которое равно 32SC1 (32-битный подписанный один канал).

Теперь в исходном изображении будут некоторые области, где вы просто уверены, что часть принадлежит переднему. Отметьте такую ​​область 255 в изображении маркера. Теперь область, в которой вы уверены, являетесь фоном, отмечена значком 128. Регион, в котором вы не уверены, отмечен знаком 0. Это мы сделаем дальше.

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

fg = cv2.erode(thresh,None,iterations = 2)

fg:

enter image description here

B - Фоновая область: - Здесь мы расширяем пороговое изображение, чтобы область фона уменьшалась. Но мы уверены, что оставшаяся черная область - это 100% фон. Мы установили его на 128.

bgt = cv2.dilate(thresh,None,iterations = 3)
ret,bg = cv2.threshold(bgt,1,128,1)

Теперь мы получаем bg следующим образом:

enter image description here

C - Теперь добавим и fg и bg:

marker = cv2.add(fg,bg)

Ниже мы получаем:

enter image description here

Теперь мы можем ясно понять из вышеприведенного изображения, что белый регион на 100% впереди, серая область - 100% фон, а черная область - не уверен.

Затем преобразуем его в 32SC1:

marker32 = np.int32(marker)

3 - Наконец, применим водораздел и преобразуем результат обратно в uint8 изображение:

cv2.watershed(img,marker32)
m = cv2.convertScaleAbs(marker32)

m:

enter image description here

4 - Мы порождаем его правильно, чтобы получить маску и выполнить bitwise_and с входным изображением:

ret,thresh = cv2.threshold(m,0,255,cv2.THRESH_BINARY+cv2.THRESH_OTSU)
res = cv2.bitwise_and(img,img,mask = thresh)

res:

enter image description here

Надеюсь, это поможет!!!

КОВЧЕГ