Загрузка данных
import numpy as np
from PIL import Image
def kmeans_compress(image_path, output_path, k=8, max_iters=20, tol=1e-2):
# 1. Загрузка изображения
img = Image.open(image_path).convert('RGB')
img_np = np.array(img, dtype=np.float32)
height, width, channels = img_np.shape
# Сохраняем исходную матрицу в формате uint8 для сборки финального коллажа
original_uint8 = np.array(img, dtype=np.uint8)
# Вытягиваем в плоскую таблицу пикселей
pixels = img_np.reshape(-1, channels)
# 2. Инициализация случайных центроидов
initial_indices = np.random.choice(pixels.shape[0], k, replace=False)
centroids = pixels[initial_indices]
print(f"Начало кластеризации для K={k}...")
# Главный цикл K-means
for i in range(max_iters):
# Расчет расстояний и назначение кластеров
distances = np.linalg.norm(pixels[:, np.newaxis, :] - centroids, axis=2)
labels = np.argmin(distances, axis=1)
# Пересчет центроидов
new_centroids = np.zeros_like(centroids)
for c in range(k):
cluster_pixels = pixels[labels == c]
if len(cluster_pixels) > 0:
new_centroids[c] = cluster_pixels.mean(axis=0)
else:
new_centroids[c] = pixels[np.random.choice(pixels.shape[0])]
# Проверка сходимости
center_shift = np.linalg.norm(centroids - new_centroids)
if center_shift < tol:
centroids = new_centroids
print(f"Алгоритм сошелся на итерации {i+1}")
break
centroids = new_centroids
print(f"Итерация {i+1}/{max_iters}, сдвиг: {center_shift:.4f}")
# --- ВЫВОД КЛАСТЕРОВ В КОНСОЛЬ ---
print("\n" + "="*40)
print(f" НАЙДЕННЫЕ КЛАСТЕРЫ (ЦВЕРА ПАЛИТРЫ) ДЛЯ K={k}:")
print("="*40)
for idx, color in enumerate(centroids):
r, g, b = int(np.clip(color[0], 0, 255)), int(np.clip(color[1], 0, 255)), int(np.clip(color[2], 0, 255))
print(f"Кластер {idx + 1}: RGB({r}, {g}, {b}) -> Порядковый номер в палитре")
print("="*40 + "\n")
# 3. Создание сжатого изображения
compressed_pixels = centroids[labels]
compressed_img_np = compressed_pixels.reshape(height, width, channels)
compressed_img_np = np.clip(compressed_img_np, 0, 255).astype(np.uint8)
# Сохраняем просто сжатую картинку
Image.fromarray(compressed_img_np).save(output_path)
print(f"Одиночное сжатое изображение сохранено в: {output_path}")
# --- СБОРКА КОЛЛАЖА КАК В МЕТОДИЧКЕ (Рисунок 12) ---
# Правая часть: оригинал сверху, сжатая снизу
right_side = np.vstack([original_uint8, compressed_img_np])
# Левая часть: вертикальные полосы кластеров
palette_height = height * 2
palette_width = width
palette_np = np.zeros((palette_height, palette_width, 3), dtype=np.uint8)
# Считаем ширину одной полосы цвета
bar_width = palette_width // k
for c in range(k):
start_x = c * bar_width
# Последняя полоса забирает остаток пикселей из-за округления
end_x = (c + 1) * bar_width if c < k - 1 else palette_width
# Закрашиваем полосу цветом текущего центроида
r, g, b = int(np.clip(centroids[c][0], 0, 255)), int(np.clip(centroids[c][1], 0, 255)), int(np.clip(centroids[c][2], 0, 255))
palette_np[:, start_x:end_x, :] = [r, g, b]
# Соединяем левую палитру и правую стопку картинок по горизонтали
final_report_image = np.hstack([palette_np, right_side])
# Сохраняем готовый коллаж для отчета
report_output_path = "report_result.jpg"
Image.fromarray(final_report_image).save(report_output_path)
print(f"Готовый коллаж для вставки в отчет сохранен в: {report_output_path}")
if __name__ == "__main__":
user_input = input("Введите желаемое количество цветов (кластеров) K: ")
try:
k_value = int(user_input)
if k_value <= 0:
print("Ошибка: Количество кластеров должно быть больше нуля!")
else:
# Запускаем (убедись, что файл input.jpg лежит в той же папке)
kmeans_compress("input.jpg", "output_python.jpg", k=k_value)
except ValueError:
print("Ошибка: Вы ввели не число!").