Ответ 1
Прежде всего: функция minMaxLoc
находит только глобальный минимум и глобальный максимум для данного ввода, поэтому в большинстве случаев бесполезно определять региональные минимумы и/или региональные максимумы. Но ваша идея правильная, извлечение маркеров на основе региональных минимумов/максимумов для выполнения преобразования Watershed Transform на основе маркеров полностью прекрасен. Позвольте мне прояснить, что такое Преобразование Watershed и как вы должны правильно использовать реализацию, присутствующую в OpenCV.
Некоторое приличное количество документов, которые касаются водораздела, описывает его аналогично тому, что следует (я могу пропустить некоторые детали, если вы не уверены: спросите). Рассмотрим поверхность какого-то региона, который, как вы знаете, содержит долины и пики (среди других деталей, которые для нас здесь неактуальны). Предположим, что ниже этой поверхности у вас есть вода, цветная вода. Теперь сделайте отверстия в каждой долине вашей поверхности, а затем вода начнет заполнять всю площадь. В какой-то момент будут встречаться разноцветные воды, и когда это произойдет, вы построите плотину, чтобы они не касались друг друга. В итоге у вас есть коллекция плотин, которая является водоразделом, разделяющим всю цветную воду.
Теперь, если вы сделаете слишком много отверстий на этой поверхности, вы получите слишком много областей: чрезмерная сегментация. Если вы делаете слишком мало, вы получаете недостаточную сегментацию. Таким образом, практически любая бумага, которая предлагает использовать водораздел, представляет собой методы, позволяющие избежать этих проблем для приложения, с которым имеет дело дело.
Я написал все это (что, возможно, слишком наивно для тех, кто знает, что такое Преобразование Водораздела), потому что он напрямую отражает то, как вы должны использовать реализации водораздела (которые текущий принятый ответ делает совершенно неправильно). Давайте начнем с примера OpenCV, используя привязки Python.
Изображение, представленное в вопросе, состоит из многих объектов, которые в основном слишком близки и в некоторых случаях перекрываются. Полезность водораздела здесь состоит в том, чтобы правильно отделить эти объекты, а не группировать их в один компонент. Поэтому вам нужен по крайней мере один маркер для каждого объекта и хорошие маркеры для фона. В качестве примера, сначала выровняйте входное изображение Otsu и выполните морфологическое открытие для удаления небольших объектов. Результат этого шага показан ниже на левом изображении. Теперь, когда бинарное изображение рассмотрит применение к нему преобразования расстояния, вернитесь вправо.
С результатом преобразования расстояния мы можем рассмотреть некоторый порог такой, что мы рассматриваем только области, наиболее отдаленные от фона (левое изображение ниже). Поступая таким образом, мы можем получить маркер для каждого объекта, маркируя разные области после более раннего порога. Теперь мы можем рассмотреть границу расширенной версии левого изображения выше, чтобы составить наш маркер. Полный маркер показан ниже справа (некоторые маркеры слишком темные, чтобы их можно было увидеть, но каждая белая область в левом изображении изображена на правом изображении).
Этот маркер, который у нас здесь, имеет большой смысл. Каждый colored water == one marker
начнет заполнять регион, а преобразование водоразделов построит плотины, чтобы помешать слиянию разных "цветов". Если мы сделаем преобразование, мы получим изображение слева. Учитывая только плотины, составив их с исходным изображением, мы получим результат справа.
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)