# Лабораторная работа 3. «Кластеризация изображений» [назад](README.md) В рамках данной лабораторной работы будет производится кластеризация собранных из социальной сети изображений. ## 1. Скачивание фотографий из социальной сети При выполнении данной лабораторной работы будем использовать `vk_api` для выполнения запросов https://vk.com/dev/photos к социальной сети вконтакте. Код загрузки фотографий приведён ниже. ```python 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() ```