Сегментация изображения на кластеры.

Алгоритмы кластеризации — это тип алгоритмов обучения без учителя, широко используемый для выполнения исследовательского анализа данных без использования целевых меток или когда мы просто хотим разделить набор данных на несколько кластеров. В дополнение к типичным сценариям анализа данных, обычно встречающимся в большинстве задач по науке о данных, оказывается, что алгоритмы кластеризации также используются в других областях, таких как компьютерное зрение! В этой статье мы рассмотрим, как выполнять сегментацию изображений с помощью K-средних!

Алгоритм кластеризации K-средних

Алгоритм кластеризации K-средних пытается разбить набор данных на k кластеров, при этом i-й кластер определяется его центроидом µ. Центроид каждого кластера µ по существу представляет собой среднее значение всех точек nᵢ в этом кластере.

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

  1. Случайным образом выберите k точек данных из набора данных в качестве исходных k значений центроида.
  2. Для каждой точки данных в наборе данных назначьте ее ближайшему центроиду. Расстояние измеряется с помощью евклидова расстояния.
  3. Пересчитайте значение k центроидов как среднее значение всех nᵢ точек данных, назначенных ему.
  4. Повторяйте шаги 2–3 до тех пор, пока не будет достигнуто максимальное количество итераций или некоторое значение допуска, или назначения кластеров не сойдутся.

После завершения шага 4 алгоритм k-средних должен минимизировать инерцию I всех k кластеров:

I = ΣΣ||xᵢₗ - µ||²,

где первый индекс суммирования i проходит по всем k кластерам, а второй индекс суммирования l проходит по всем nᵢ точки данных в каждом кластере. xᵢₗ — это l-я точка данных в i-м кластере, а µ  — значение центра тяжести i-го кластера.

Какое значение k использовать?

Одна из основных проблем с алгоритмом кластеризации K-средних заключается в том, что нам нужно указать количество кластеров k для использования. Для чрезвычайно простых низкоразмерных наборов данных, как показано на рисунке ниже, идеальное значение k можно легко угадать, визуализируя точки данных.

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

Один из методов, который мы можем использовать для определения идеального количества кластеров, — это инерция кластеров I. Обычно, когда мы увеличиваем количество кластеров k с 2, инерция I быстро уменьшается. По мере того, как мы приближаемся к идеальному значению кластеров, уменьшение I начинает выходить на плато, образуя изгиб на графике зависимости I от k. . Для простого набора 2D-данных выше мы обнаружили, что изгиб инерции I возникает при k = 4.

Сегментация изображения с использованием K-средних

Теперь, когда мы знаем, как работают алгоритмы кластеризации K-средних, мы будем использовать алгоритм K-средних для сегментации изображения, загруженного с Pexels.

Кажется, что изображение имеет 4 различных цвета: белые стены, мебель и платье, оранжевые настенные плитки и пол, темно-синее платье и бежевый оттенок кожи, но давайте посмотрим, как алгоритм K-средних сегментирует изображение!

Подготовка изображения для кластеризации с помощью K-средних

Во-первых, мы загружаем загруженное изображение, используя cv2 как трехмерный массив numpy. Исходное изображение очень большое — мы изменим размер изображения до гораздо меньшего размера, чтобы сократить время вычислений.

Кроме того, поскольку мы будем использовать sklearn для выполнения кластеризации K-средних, нам нужно будет изменить форму массива в форму, которую может принять sklearn API.

import cv2
import numpy as np
import matplotlib.pyplot as plt
from sklearn.cluster import KMeans
import tqdm
# Photo by Ferdinand Studio, downloaded from Pixels.
file_path = "pexels-ferdinand-studio-1020057.jpg"
img = cv2.imread(file_path)
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
def resize(image, width = None, height = None, scale = 1):
    # Resizes an RGB image.
    if width is None or height is None:
        width, height = image_width_height(image)
        width = int(width * scale)
        height = int(height * scale)
    return cv2.resize(image, (width, height))
def image_width_height(image):
    # Get an RGB image's width and height.
    width = image.shape[1]
    height = image.shape[0]
    return width, height
def image2X(image, width, height, channels = 3):
    # Converts an RGB image to an array X for use with sklearn.
    return image.reshape([width * height, channels])
def X2image(X, width, height, channels = 3):
    # Converts an array X from sklearn to an RGB image.
    return X.reshape([height, width, channels])
# Resize the image to a 400 x 600 resolution.
img = resize(img, width = 400, height = 600)
width, height = image_width_height(img)
# Convert the image to an array which can be input into
# sklearn's API.
X = image2X(img, width, height, 3)
print(img.shape, X.shape)
>>> (600, 400, 3) (240000, 3)

Использование метода локтя для определения количества кластеров k

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

# Try 2 to 30 clusters.
n_clusters = list(range(2, 30 + 1, 1))
kmeans = []
inertias = []
for i in tqdm.trange(len(n_clusters)):
    kmeans.append(KMeans(n_clusters = n_clusters[i], 
                         random_state = 42))
    kmeans[-1].fit(X)
    inertias.append(kmeans[-1].inertia_)
plt.figure(figsize = [20, 5])
plt.subplot(1, 2, 1)
plt.plot(n_clusters, inertias, "-o")
plt.xlabel("$k$", fontsize = 14)
plt.ylabel("Inertia", fontsize = 14)
plt.grid(True)
plt.subplot(1, 2, 2)
plt.plot(n_clusters[:-1], np.diff(inertias), "-o")
plt.xlabel("$k$", fontsize = 14)
plt.ylabel("Change in inertia", fontsize = 14)
plt.grid(True)
plt.show()

На графике слева показано, как изменяется инерция I при увеличении количества кластеров k от 2 до 30, а на графике справа показана скорость изменения инерция I относительно k: dI/dk.

Оба графика показывают, что инерция I выходит на плато после k = 6, что означает, что загруженное изображение лучше всего сегментировать с использованием 6 кластеров!

Сегментация загруженного изображения с помощью 6 кластеров

Наконец, мы используем 6 кластеров для сегментации исходного изображения и получаем значения центроидов кластера для каждого пикселя изображения. Затем мы преобразуем выходной массив numpy в трехканальное изображение RGB для визуализации.

# Use 6 clusters in the K-means.
kmeans = KMeans(n_clusters = 6, random_state = 42)
kmeans.fit(X)
# Obtain the cluster centroids for each pixel in the image.
# These centroids are essentially our image segments.
X_kmeans = kmeans.cluster_centers_[kmeans.predict(X)]
X_kmeans = X_kmeans.astype("uint8")
# Convert the numpy array into an RGB image.
img_kmeans = X2image(X_kmeans, width, height, 3)
# Visualize the original image with the segmented one.
plt.figure(figsize = [10, 10])
plt.subplot(1, 2, 1)
plt.imshow(img)
plt.subplot(1, 2, 2)
plt.imshow(img_kmeans)
plt.show()

Алгоритму K-средних удалось разделить исходное изображение на следующие 6 сегментов:

  1. Белые стены, мебель, платье и отдельные участки кожи.
  2. Настенная и напольная плитка оранжевого цвета.
  3. Светло-бежевый оттенок кожи.
  4. Бежевые тона кожи.
  5. Темно-бежевые тона кожи и тени на мебели и стенах.
  6. Темное сине-черное платье и темные тени.

Заключительные замечания

K-means — чрезвычайно простой и эффективный алгоритм, используемый для кластеризации данных в k отдельных кластеров без использования целевых меток. Этот мощный алгоритм можно использовать не только в типичных задачах анализа данных, но и для других задач, таких как сегментация изображений. Одной из проблем с алгоритмом K-средних является определение количества кластеров k, которое нужно использовать, и мы показали, как использовать метод локтя для определения идеального значения k. Мы затем использовали алгоритм K-средних для сегментации изображения на несколько сегментов, каждый из которых представлен средним значением всех пикселей в этом кластере.

Рекомендации

  1. Дмитрий Джульгаков, Лю Юйси и Себастьян Рашка (2022). Машинное обучение с помощью PyTorch и Scikit-Learn, Packt Publishing Ltd.
  2. https://docs.opencv.org/4.x/d1/d5c/tutorial_py_kmeans_opencv.html
  3. https://scikit-learn.org/stable/modules/generated/sklearn.cluster.KMeans.html