# Лабораторная работа 3. «Кластеризация изображений»

[назад](README.md)

В рамках данной лабораторной работы будет производится кластеризация собранных из социальной сети изображений.

## 1. Скачивание фотографий из социальной сети

При выполнении данной лабораторной работы будем использовать `vk_api` для выполнения запросов https://vk.com/dev/photos к социальной сети вконтакте. 

Код загрузки фотографий приведён ниже.

```
import os
import sys
import vk_api
import urllib

LOGIN = ''
PASSWORD = ''

target_ids = [123124, -1212412] 
# идентификаторы сообщества имеют отрицательные значения.

def auth_handler():
    """ При двухфакторной аутентификации вызывается эта функция.
    """
    # Код двухфакторной аутентификации
    key = input("Enter authentication code: ")
    # Если: True - сохранить, False - не сохранять.
    remember_device = True
    return key, remember_device

def main():
    # ======= Открываем сессию  с VK =======
    vk_session = vk_api.VkApi(LOGIN, PASSWORD,
                              auth_handler=auth_handler  # функция для обработки двухфакторной аутентификации
    )
    try:
        vk_session.auth()
    except vk_api.AuthError as error_msg:
        print(error_msg)
        #return

    vk = vk_session.get_api()
    tools = vk_api.VkTools(vk_session)

    # ======= начинаем перебирать каждого пользователя =======
    for target_id in target_ids:

        # создаем директорию с именем пользователя, если нет
        newpath = os.path.join(sys.path[0], id_user)
        if not os.path.exists(newpath):
            os.makedirs(newpath)

        # посылаем запрос к VK API, count свой, но не более 200
        
        if target_id >= 0:
            # Вариант 1 - скачать все фотографии пользователя
            response = vk.photos.getAll(owner_id=int(target_id), count=3)
        else:
            # Вариант 2 - скачать все фотографии сообщества
            response = tools.get_all("photos.getAll", 100, {'owner_id': target_id})

        # работаем с каждой полученной фотографией
        for i in range(len(response["items"])):
            # берём ссылку на максимальный размер фотографии
            photo_url = str(response["items"][i]["sizes"][len(response["items"][i]["sizes"]) - 1]["url"])

            # скачиваем фото в папку с ID пользователя
            urllib.urlretrieve(photo_url, newpath + '/' + str(response["items"][i]['id']) + '.jpg')


if __name__ == "__main__":
    main()
```

## 2. Кластеризация цветов на изображении

Кластерный анализ (англ. Data clustering) – задача разбиения заданной выборки объектов (ситуаций) на непересекающиеся подмножества, называемые кластерами, так, чтобы каждый кластер состоял из схожих объектов, а объекты разных кластеров существенно отличались.

В рамках данной лабораторной работы будет решаться задача кластерного анализа изображений, собранных из задания 1.

Для начала попробуем обработать одну картинку, а именно, решим задачу определения доминирующих цветов.

Американский веб-разработчик Чарльз Лейфер (Charles Leifer) использовал метод k-средних для кластеризации цветов на изображении. Идея метода при кластеризации любых данных заключается в том, чтобы минимизировать суммарное квадратичное отклонение точек кластеров от центров этих кластеров. На первом этапе выбираются случайным образом начальные точки (центры масс) и вычисляется принадлежность каждого элемента к тому или иному центру. Затем на каждой итерации выполнения алгоритма происходит перевычисление центров масс – до тех пор, пока алгоритм не сходится.

В применении к изображениям каждый пиксель позиционируется в трёхмерном пространстве RGB, где вычисляется расстояние до центров масс. Для оптимизации картинки уменьшаются до 200х200 с помощью библиотеки PIL. Она же используется для извлечения значений RGB.

```python
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
import cv2
#read image
img = cv2.imread('colors.jpg')
#convert from BGR to RGB
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
#get rgb values from image to 1D array
r, g, b = cv2.split(img)
r = r.flatten()
g = g.flatten()
b = b.flatten()
#plotting
fig = plt.figure()
ax = Axes3D(fig)
ax.scatter(r, g, b)
plt.show()
```

Данный код обрабатывает изображение и определяет пять самых доминирующих цветов на изображении.

В результате исполнения кода получим график примерно такого
вида (рис. 31). На нем можно увидеть распределение пикселей в трехмерном пространстве. Здесь также легко выделить пять доминирующих цветов (рис. 32).

![Рис. 31. Распределение пикселей в трехмерном пространстве](images/2021-09-05_17-05-14.png)  
Рис. 31. Распределение пикселей в трехмерном пространстве

![Рис. 32. Выделение доминирующих цветов](images/2021-09-05_17-05-47.png)  
Рис. 32. Выделение доминирующих цветов
  
Несколько преобразим имеющийся код.

```python
import cv2
from sklearn.cluster import KMeans
class DominantColors:
    CLUSTERS = None
    IMAGE = None
    COLORS = None
    LABELS = None
    def __init__(self, image, clusters=3):
        self.CLUSTERS = clusters
        self.IMAGE = image
    def dominantColors(self):
        #read image
        img = cv2.imread(self.IMAGE)
        #convert to rgb from bgr
        img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
        #reshaping to a list of pixels
        img = img.reshape((img.shape[0] *img.shape[1], 3))
        #save image after operations
        self.IMAGE = img
        #using k-means to cluster pixels
        kmeans = KMeans(n_clusters = self.CLUSTERS)
        kmeans.fit(img)
        #the cluster centers are our dominant colors.
        self.COLORS = kmeans.cluster_centers_
        #save labels
        self.LABELS = kmeans.labels_
        #returning after converting to integer from float
        return self.COLORS.astype(int)
img = 'colors.jpg'
clusters = 5
dc = DominantColors(img, clusters)
colors = dc.dominantColors()
print(colors)
```

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

Далее построим гистограмму использования этих пяти основных цветов. Для этого добавим функцию `plotHistogramm()` в класс DominantColors.

```python
def plotHistogram(self):
    #labels form 0 to no. of clusters
    numLabels = np.arange(0, self.CLUSTERS+1)
    #create frequency count tables
    (hist, _) = np.histogram(self.LABELS, bins= numLabels)
    hist = hist.astype("float")
    hist /= hist.sum()
    #appending frequencies to cluster centers
    colors = self.COLORS
    #descending order sorting as per frequency count
    colors = colors[(-hist).argsort()]
    hist = hist[(-hist).argsort()]
    #creating empty chart
    chart = np.zeros((50, 500, 3), np.uint8)
    start = 0
    #creating color rectangles
    for i in range(self.CLUSTERS):
        end = start + hist[i] * 500
        #getting rgb values
        r = colors[i][0]
        g = colors[i][1]
        b = colors[i][2]
        #using cv2.rectangle to plot colors
        cv2.rectangle(
            chart, 
            (int(start), 0),
            (int(end), 50), 
            (r,g,b), 
            -1)
        start = end
    #display chart
    plt.figure()
    plt.axis("off")
    plt.imshow(chart)
    plt.show()
```

Далее вызовем эту функцию.

```python
 dc.plotHistogram()
```

На выходе получим пропорциональное отображение использования доминирующих цветов на изображении. Пример отображения представлен на рис. 33.

![Рис. 33. Отображение использования доминирующих цветов на изображении](images/2021-09-05_17-06-39.png)  
Рис. 33. Отображение использования доминирующих цветов на изображении 

## 3. Кластеризация изображении на основе доминирующих цветов

Теперь, когда получена характеристика одного изображения, у нас есть инструмент, с помощью которого можно произвести кластеризацию множества изображений на основе их доминирующих цветов. Для этого необходимо обработать каждое изображение в папке, получить его уникальную цветовую характеристику и сравнить ее с другими изображениями. Поэтому немного модифицируем имеющийся код:
1) добавим глобальную переменную, в которой будем хранить цветовые характеристики каждой картинки;
2) за место конкретной картинки будем указывать директорию и будем итерационно обрабатывать каждую картинку.

```python
directory = '<ПУТЬ К ДИРЕКТОРИИ>'

for filename in os.listdir(directory):
    data_iter = []
    filenames.append(filename)
    img = str(directory) + '\\' + filename
    #print (img)
    clusters = 2
    dc = DominantColors(img, clusters)
    colors = dc.dominantColors()
    for i in colors:
        for j in i:
            data_iter.append(j)
    data.append(data_iter)
    print(colors)
```

Преобразуем список в массив данных и добавим код метода главных компонент для того, чтобы понизить размерность вектора характеристик с 15 до 3.

```python
np_data = np.asarray(data, dtype=np.float32)
import numpy as np
from sklearn.decomposition import PCA
pca = PCA(n_components = 3)
XPCAreduced = pca.fit_transform(np_data)
print(XPCAreduced)
print(filenames)
```

Далее представим точки изображения на плоскости, где координатами будут элементы вектора характеристик.

```python
xs, ys, zs = np_data[:, 0], np_data[:, 1], np_data[:,2]

from mpl_toolkits.mplot3d import Axes3D

fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
ax.scatter(xs, ys, zs)
ax.set_xlabel('X')
ax.set_ylabel('Y')
ax.set_zlabel('Z')
plt.show()
```
