You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

292 lines
15 KiB
Markdown

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

# Лабораторная работа 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()
```