Время прочтения: 5 мин.

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

Активная контурная сегментация

Для начала загрузим изображение, а также преобразуем его, чтобы видеть только оттенки серого с помощью функции rgb2gray(). Для загрузки изображения был использован модуль io из scikit-image, а для последующего взаимодействия — класс Image библиотеки Pillow. Для вывода изображений на экран были использованы инструменты библиотеки matplotlib.

Исходная картинка
import matplotlib.pyplot as plt
import numpy as np
from PIL import Image
from skimage import io
from skimage.color import rgb2gray
from skimage.segmentation import morphological_geodesic_active_contour, inverse_gaussian_gradient
from skimage.util import img_as_float
#необходимые библиотеки
image = io.imread('flowers.png')
draw = Image.fromarray(image)
image = np.array(image)
image = rgb2gray(image)
image = img_as_float(image)
plt.imshow(image, cmap='gray')
Исходная картинка в серых цветах

Далее выполним предварительную обработку изображения перед сегментацией. Используем функцию inverse_gaussian_gradient(). Она вычисляет величину градиента в изображении и инвертирует результат в диапазоне 0-1. Начальные области получат значение ближе к единице, а области близкие к границе — значения, близкие к 0.

gimage = inverse_gaussian_gradient(image)

Далее применим ещё одну модификацию, именуемую «Balloon Force». Существует проблема, выраженная в том, на плоских участках, где градиент равен 0, алгоритм не будет сходиться. Использование Balloon Force призвано решить эту проблему. Данная модификация основана на области (рамке), которая будет направлять контур в тех частях изображения, где информация, полученная с помощью градиента, не является достаточной (градиент слишком мал, чтобы «подтолкнуть» контур к границе).

Эта модификация уже встроена в метод morphological_geodesic_active_contour().

init_ls = np.zeros(image.shape, dtype=np.int8)  # создание
init_ls[350:-150, 100:-130] = 1                 # рамки
evolution = []   # для сохранения итераций
callback = store_evolution_in(evolution)
ls = morphological_geodesic_active_contour(gimage, 50, init_ls, smoothing=1, balloon=1, iter_callback=callback)

Для наглядности я реализовал возможность сохранить результат итерации и показать его на экране (функция — store_evolution_in()). Вот как данный алгоритм справился с сегментацией цветочной клумбы.

fig, axes = plt.subplots(1, 2, figsize=(8, 8))
ax = axes.flatten()
ax[0].imshow(image, cmap="gray")
ax[0].set_axis_off()
ax[0].contour(ls, [0.5], colors='b')
ax[0].set_title("Morphological GAC segmentation", fontsize=12)
ax[1].imshow(gimage, cmap="gray")
ax[1].set_axis_off()
contour = ax[1].contour(evolution[0], [0.5], colors='r')
contour.collections[0].set_label("Starting Contour")
contour = ax[1].contour(evolution[5], [0.5], colors='g')
contour.collections[0].set_label("Iteration 5")
contour = ax[1].contour(evolution[-1], [0.5], colors='b')
contour.collections[0].set_label("Last Iteration")
ax[1].legend(loc="upper right")
title = "Morphological GAC Curve evolution"
ax[1].set_title(title, fontsize=12)
plt.show()

Данный код выводит 2 картинки. Слева — результат обработки. Справа можно отследить, как программа вела себя на 0й, 5й и последней итерации

Стоит отметить, что при изменении размеров рамки Balloon Force можно добиться иных результатов.

SLIC

Скажу пару слов о самом алгоритме. Данный алгоритм, в отличие от «Змеиного», основан на машинном обучении. Он генерирует на изображении суперпиксели с помощью кластеризации пикселей на основе их близости на изображении, а также цветового сходства. Упрощая, можно сказать, что суперпиксель — это группа пикселей, имеющих схожие характеристики, например, интенсивность пикселей.

from skimage.segmentation import slic
from skimage.segmentation import mark_boundaries
from skimage.util import img_as_float
from skimage import io
import matplotlib.pyplot as plt
import argparse

Используемые библиотеки

Итак, входные данные программы — это изображение. Я реализовал загрузку изображения в программу с помощью аргументов командной строки и параметра —image с помощью средств библиотеки argparse.

ap = argparse.ArgumentParser()
ap.add_argument("-i", "--image", required=True, help="Path to the image")
args = vars(ap.parse_args())
SLIC.py --image flowers.png

Код, реализующий загрузку файла через параметр командной строки при запуске программы и сама команда запуска

Затем преобразуем изображение так, чтобы оно описывалось не 8-битными целыми беззнаковыми числами, а float числами в промежутке 0-1.

image = img_as_float(io.imread(args["image"]))

Функция, которая выполняет сегментацию изображения с помощью данного алгоритма, называется slic(). Данная функция имеет лишь один обязательный параметр — само изображение. Я также указал количество суперпикселей, которое нужно сгенерировать — 100, 200 а затем 300. По умолчанию генерируется 100 сегментов.

for numSegments in (100, 200, 300):
   segments = slic(image, n_segments=numSegments)

После получения сегментов вывожу результаты сегментации с помощью средств библиотеки matplotlib, которая позволяет вывести изображение на экран. Также была использована функция mark_boundadies() для того, чтобы в изображении на экране подсветить выделенные сегменты.

for numSegments in (100, 200, 300):
   segments = slic(image, n_segments=numSegments)
   fig = plt.figure("Superpixels -- %d segments" % (numSegments))
   ax = fig.add_subplot(1, 1, 1)
   ax.imshow(mark_boundaries(image, segments))
   plt.axis("off")
plt.show()

Далее представлены результаты работы программы с количеством сегментов 100, 200 и 300 соответственно.

100 сегментов

200 сегментов

300 сегментов

В заключение стоит отметить, что оба алгоритма выполняются довольно быстро. Также приятно удивила точность сегментации при использовании SLIC.