# Лабораторная работа 3. «Кластеризация изображений» [назад](README.md) В рамках данной лабораторной работы будет производится кластеризация собранных из социальной сети изображений. ## 1. Скачивание фотографий из социальной сети При выполнении данной лабораторной работы следует использовать пакет VK (не VK_api). Установим данный пакет с помощью следующей команды. ```bash $ pip install vk ``` Необходимо создать приложение VK для того, чтобы получить доступ к данным. Для этого перейдем на страницу разработчиков VK и в разделе «Мои приложения» создадим новое приложение. Для дальнейшей работы нам потребуется ID приложения и Защищённый ключ. Войдем в VK. ```python from urllib.request import urlretrieve import vk, os, time, math # Авторизация login = '' password = '' vk_id = 'ID_ВАШЕГО_ПРИЛОЖЕНИЯ' session = vk.AuthSession(app_id=vk_id, user_login=login, user_password=password) vkapi = vk.API(session) ``` Для удобства, входными данными можно указать ссылки на альбомы. Только целиком url не подойдёт, понадобится id хозяина альбома (группы или человека) и id самого альбома, которые и можно достать из ссылки. К примеру, в https://vk.com/album-54530371_212428070 id владельца (в данном случае сообщества) это -54530371, а id альбома – 212428070. Следует обратить внимание, если загружать из альбома сообщества, то «-» (дефис) перед id владельца обязателен. Получаем на вход ссылку на альбом, затем разбираем её и раскладываем по переменным `album_id` и `owner_id` соответствующие `id`. Далее нужно получить количество фото, а также инициализировать переменные для статистики. ```python #url = input("Введите url альбома: ") url = "https://vk.com/album223007487_199949846" # Разбираем ссылку album_id = url.split('/')[-1].split('_')[1] owner_id = url.split('/')[-1].split('_')[0].replace('album', '') photos_count = vkapi.photos.getAlbums( owner_id=owner_id, album_ids=album_id)[0][‘size’] counter = 0 # текущий счетчик prog = 0 # процент загруженных breaked = 0 # не загружено из-за ошибки time_now = time.time() # время старта ``` Проблема при загрузке большого количества фотографий в том, что за один запрос нельзя забрать больше 1000 штук, в то время как в альбоме их может быть десяток тысяч. Процесс загрузки описывается следующим образом. ```python #Создадим каталоги if not os.path.exists('saved'): os.mkdir('saved') photo_folder = 'saved/album{0}_{1}'.format(owner_id, album_id) if not os.path.exists(photo_folder): os.mkdir(photo_folder) # Подсчитаем, сколько раз нужно получать список фото, так как число получится не целое - округляем в большую сторону for j in range(math.ceil(photos_count / 1000)): photos = vkapi.photos.get( owner_id=owner_id, album_id=album_id, count=1000, offset=j*1000, v=5.95)['items'] # Получаем список фото for photo in photos: counter += 1 sizes = photo['sizes'] s = photo['sizes'][0] value_x = 0 value_y = 0 # выбираем самый большой размер for size in sizes: if value_x < size['width']: value_x = size['width'] s = size if value_y < size['height']: value_y = size['height'] s = size print(s['url']) # Получаем адрес изображения url_ = s['url'] print('Загружаю фото № {} из {}. Прогресс: {}'.format(counter, photos_count, prog)) prog = round(100/photos_count*counter, 2) try: # Загружаем и сохраняем файл urlretrieve(url_, photo_folder + "/" + os.path.split(url_)[1]) except Exception: print('Произошла ошибка, файл пропущен.') breaked += 1 continue time_for_dw = time.time() - time_now print("\nВ очереди было {} файлов. Из них удачно загружено {} файлов, {} не удалось загрузить.") ``` ## 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() ```