лучший способ сделать это, вероятно, через Преобразование ширины штриха .Это не в OpenCV, хотя в некоторых других библиотеках, и вы можете найти некоторые реализации, плавающие в Интернете.Преобразование ширины обводки находит минимальную ширину между ближайшими краями для каждого пикселя изображения.См. Следующий рисунок из бумаги:
После установки порога этого изображения вам сообщается, где есть края, разделенные небольшим расстоянием.Например, все пиксели со значениями <40, скажем, находятся между двумя ребрами, которые разделены менее чем на 40 пикселей. </p>
Итак, как это, вероятно, очевидно, это довольно близко к желаемому ответу.Здесь будет некоторый дополнительный шум, как если бы вы также получили значения, которые находятся между квадратными ребрами на краю вашей фигуры ... которые вы должны были бы отфильтровать или сгладить (аппроксимация контура была бы простым способомочистить их, например, как этап предварительной обработки).
Однако, хотя у меня есть запрограммированный прототип SWT, это не очень хорошая реализация, и я на самом деле не тестировал его (и действительно забыл об этом)в течение нескольких месяцев ....... может быть, год) поэтому я не собираюсь выпускать это прямо сейчас.Но у меня есть другая идея, которая немного проще и не требует чтения исследовательской работы.
У вас есть несколько капель во входном изображении.Представьте, что у вас есть каждый отдельно в своем изображении, и вы выросли каждый шарик на сколь угодно большое расстояние между ними.Если вы увеличили каждый шарик, скажем, на 10 пикселей, и они перекрываются, то они будут в пределах 20 пикселей друг от друга.Однако это не дает нам полную область перекрытия, а только часть того, где два расширенных шарика перекрываются.Другой, но похожий способ измерить это, если сгустки выросли на 10 пикселей и перекрыли друг друга, а кроме того перекрыли исходные сгустки до того, как они были расширены, тогда эти два сгустка находятся в пределах 10 пикселей друг от друга.Мы собираемся использовать это второе определение, чтобы найти близлежащие капли.
def find_connection_paths(binimg, distance):
h, w = binimg.shape[:2]
overlap = np.zeros((h, w), dtype=np.int32)
overlap_mask = np.zeros((h, w), dtype=np.uint8)
kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (distance, distance))
# grows the blobs by `distance` and sums to get overlaps
nlabels, labeled = cv2.connectedComponents(binimg, connectivity=8)
for label in range(1, nlabels):
mask = 255 * np.uint8(labeled == label)
overlap += cv2.dilate(mask, kernel, iterations=1) // 255
overlap = np.uint8(overlap > 1)
# for each overlap, does the overlap touch the original blob?
noverlaps, overlap_components = cv2.connectedComponents(overlap, connectivity=8)
for label in range(1, noverlaps):
mask = 255 * np.uint8(overlap_components == label)
if np.any(cv2.bitwise_and(binimg, mask)):
overlap_mask = cv2.bitwise_or(overlap_mask, mask)
return overlap_mask
Теперь вывод не идеален - когда яРасширив BLOB-объекты, я расширил их внешне с помощью круга (ядра расширения), поэтому области соединения не совсем четкие.Тем не менее, это был лучший способ убедиться, что он будет работать на вещах любой ориентации.Вы могли бы потенциально отфильтровать это / обрезать это.Простой способ сделать это состоит в том, чтобы получить каждый соединительный элемент (показан синим цветом) и многократно разрушать его на пиксель до тех пор, пока не не будет перекрывать исходный BLOB-объект.На самом деле хорошо, давайте добавим, что:
def find_connection_paths(binimg, distance):
h, w = binimg.shape[:2]
overlap = np.zeros((h, w), dtype=np.int32)
overlap_mask = np.zeros((h, w), dtype=np.uint8)
overlap_min_mask = np.zeros((h, w), dtype=np.uint8)
kernel_dilate = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (distance, distance))
# grows the blobs by `distance` and sums to get overlaps
nlabels, labeled = cv2.connectedComponents(binimg)
for label in range(1, nlabels):
mask = 255 * np.uint8(labeled == label)
overlap += cv2.dilate(mask, kernel_dilate, iterations=1) // 255
overlap = np.uint8(overlap > 1)
# for each overlap, does the overlap touch the original blob?
noverlaps, overlap_components = cv2.connectedComponents(overlap)
for label in range(1, noverlaps):
mask = 255 * np.uint8(overlap_components == label)
if np.any(cv2.bitwise_and(binimg, mask)):
overlap_mask = cv2.bitwise_or(overlap_mask, mask)
# for each overlap, shrink until it doesn't touch the original blob
kernel_erode = cv2.getStructuringElement(cv2.MORPH_RECT, (3, 3))
noverlaps, overlap_components = cv2.connectedComponents(overlap_mask)
for label in range(1, noverlaps):
mask = 255 * np.uint8(overlap_components == label)
while np.any(cv2.bitwise_and(binimg, mask)):
mask = cv2.erode(mask, kernel_erode, iterations=1)
overlap_min_mask = cv2.bitwise_or(overlap_min_mask, mask)
return overlap_min_mask
Конечно, если вы все еще хотите, чтобы они были немного больше или меньше, вы могли бы делать все, что выкак с ними, но это выглядит довольно близко к вашему запросу вывода, поэтому я оставлю это там.Кроме того, если вам интересно, я понятия не имею, куда ушла капля справа вверху.Я могу сделать еще один пропуск на этой последней части позже.Обратите внимание, что последние два шага могут быть объединены;проверьте, есть ли перекрытие, если оно есть, остудите - уменьшите его и сохраните в маске.