Spaces:
Running
on
Zero
Running
on
Zero
Update utils.py
Browse files
utils.py
CHANGED
@@ -1,52 +1,208 @@
|
|
1 |
import numpy as np
|
2 |
-
from PIL import Image, ImageDraw, ImageFont
|
3 |
import cv2
|
4 |
-
from typing import List, Tuple, Optional
|
5 |
from sklearn.cluster import KMeans
|
6 |
-
import
|
|
|
7 |
|
8 |
class ImageProcessor:
|
9 |
-
"""
|
10 |
|
11 |
@staticmethod
|
12 |
-
def
|
13 |
-
"""
|
14 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
15 |
width = max(before.width, after.width)
|
16 |
height = max(before.height, after.height)
|
17 |
|
18 |
-
|
19 |
-
|
20 |
-
|
21 |
-
# Создаем объединенное изображение
|
22 |
-
combined = Image.new('RGB', (width * 2 + 10, height + 60), color='white')
|
23 |
|
24 |
# Вставляем изображения
|
25 |
-
|
26 |
-
|
27 |
|
28 |
# Добавляем подписи
|
29 |
-
draw = ImageDraw.Draw(
|
30 |
try:
|
31 |
font = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf", 30)
|
32 |
except:
|
33 |
font = None
|
|
|
|
|
|
|
34 |
|
35 |
-
|
36 |
-
draw.text((width//2 - 50, 10), "ДО", fill='black', font=font, anchor="mt")
|
37 |
-
draw.text((width + width//2 + 5, 10), "ПОСЛЕ", fill='black', font=font, anchor="mt")
|
38 |
-
|
39 |
-
# Разделительная линия
|
40 |
-
draw.line([(width + 5, 50), (width + 5, height + 50)], fill='gray', width=2)
|
41 |
-
|
42 |
-
return combined
|
43 |
|
44 |
@staticmethod
|
45 |
-
def create_grid(images
|
46 |
-
|
47 |
-
"""Создание сетки из изображений"""
|
48 |
if not images:
|
49 |
return None
|
50 |
-
|
51 |
n = len(images)
|
52 |
-
rows = (n + cols - 1) // cols
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
import numpy as np
|
2 |
+
from PIL import Image, ImageDraw, ImageFont, ImageFilter, ImageEnhance
|
3 |
import cv2
|
|
|
4 |
from sklearn.cluster import KMeans
|
5 |
+
import io
|
6 |
+
import base64
|
7 |
|
8 |
class ImageProcessor:
|
9 |
+
"""Класс для обработки изображений"""
|
10 |
|
11 |
@staticmethod
|
12 |
+
def resize_to_standard(image, max_size=1024):
|
13 |
+
"""Изменяет размер изображения до стандартного"""
|
14 |
+
ratio = min(max_size / image.width, max_size / image.height)
|
15 |
+
if ratio < 1:
|
16 |
+
new_size = (int(image.width * ratio), int(image.height * ratio))
|
17 |
+
return image.resize(new_size, Image.Resampling.LANCZOS)
|
18 |
+
return image
|
19 |
+
|
20 |
+
@staticmethod
|
21 |
+
def create_before_after(before, after):
|
22 |
+
"""Создает сравнение до/после"""
|
23 |
+
# Убедимся что изображения одного размера
|
24 |
width = max(before.width, after.width)
|
25 |
height = max(before.height, after.height)
|
26 |
|
27 |
+
# Создаем новое изображение
|
28 |
+
comparison = Image.new('RGB', (width * 2 + 20, height), (255, 255, 255))
|
|
|
|
|
|
|
29 |
|
30 |
# Вставляем изображения
|
31 |
+
comparison.paste(before, (0, 0))
|
32 |
+
comparison.paste(after, (width + 20, 0))
|
33 |
|
34 |
# Добавляем подписи
|
35 |
+
draw = ImageDraw.Draw(comparison)
|
36 |
try:
|
37 |
font = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf", 30)
|
38 |
except:
|
39 |
font = None
|
40 |
+
|
41 |
+
draw.text((width // 2 - 50, height - 50), "ДО", fill=(255, 255, 255), font=font, stroke_width=2, stroke_fill=(0, 0, 0))
|
42 |
+
draw.text((width + 20 + width // 2 - 70, height - 50), "ПОСЛЕ", fill=(255, 255, 255), font=font, stroke_width=2, stroke_fill=(0, 0, 0))
|
43 |
|
44 |
+
return comparison
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
45 |
|
46 |
@staticmethod
|
47 |
+
def create_grid(images, titles=None, cols=2):
|
48 |
+
"""Создает сетку из изображений"""
|
|
|
49 |
if not images:
|
50 |
return None
|
51 |
+
|
52 |
n = len(images)
|
53 |
+
rows = (n + cols - 1) // cols
|
54 |
+
|
55 |
+
# Размер одного изображения
|
56 |
+
img_width = images[0].width
|
57 |
+
img_height = images[0].height
|
58 |
+
|
59 |
+
# Создаем сетку
|
60 |
+
grid_width = img_width * cols + 10 * (cols - 1)
|
61 |
+
grid_height = img_height * rows + 10 * (rows - 1) + (50 if titles else 0)
|
62 |
+
|
63 |
+
grid = Image.new('RGB', (grid_width, grid_height), (255, 255, 255))
|
64 |
+
draw = ImageDraw.Draw(grid)
|
65 |
+
|
66 |
+
try:
|
67 |
+
font = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf", 20)
|
68 |
+
except:
|
69 |
+
font = None
|
70 |
+
|
71 |
+
# Размещаем изображения
|
72 |
+
for i, img in enumerate(images):
|
73 |
+
row = i // cols
|
74 |
+
col = i % cols
|
75 |
+
x = col * (img_width + 10)
|
76 |
+
y = row * (img_height + 10) + (50 if titles else 0)
|
77 |
+
|
78 |
+
grid.paste(img, (x, y))
|
79 |
+
|
80 |
+
# Добавляем заголовок
|
81 |
+
if titles and i < len(titles):
|
82 |
+
draw.text((x + img_width // 2 - 50, y - 40), titles[i], fill=(0, 0, 0), font=font)
|
83 |
+
|
84 |
+
return grid
|
85 |
+
|
86 |
+
@staticmethod
|
87 |
+
def detect_room_type(image):
|
88 |
+
"""Определяет тип комнаты по изображению"""
|
89 |
+
# Упрощенная логика определения типа комнаты
|
90 |
+
# В реальности здесь может быть ML модель
|
91 |
+
|
92 |
+
# Конвертируем в numpy array
|
93 |
+
img_array = np.array(image)
|
94 |
+
|
95 |
+
# Анализируем цвета и формы
|
96 |
+
# Это упрощенная эвристика
|
97 |
+
avg_color = img_array.mean(axis=(0, 1))
|
98 |
+
|
99 |
+
# Простая логика на основе преобладающих цветов
|
100 |
+
if avg_color[2] > avg_color[0] * 1.2: # Больше синего
|
101 |
+
return "Ванная"
|
102 |
+
elif avg_color[1] > avg_color[0] * 1.1: # Больше зеленого
|
103 |
+
return "Детская"
|
104 |
+
elif np.std(img_array) < 50: # Низкая контрастность
|
105 |
+
return "Спальня"
|
106 |
+
else:
|
107 |
+
return "Гостиная"
|
108 |
+
|
109 |
+
@staticmethod
|
110 |
+
def apply_vignette(image, intensity=0.3):
|
111 |
+
"""Применяет эффект виньетки"""
|
112 |
+
# Создаем маску виньетки
|
113 |
+
width, height = image.size
|
114 |
+
|
115 |
+
# Создаем радиальный градиент
|
116 |
+
x = np.linspace(-1, 1, width)
|
117 |
+
y = np.linspace(-1, 1, height)
|
118 |
+
X, Y = np.meshgrid(x, y)
|
119 |
+
radius = np.sqrt(X**2 + Y**2)
|
120 |
+
|
121 |
+
# Создаем маску
|
122 |
+
vignette = 1 - (radius * intensity)
|
123 |
+
vignette = np.clip(vignette, 0, 1)
|
124 |
+
vignette = (vignette * 255).astype(np.uint8)
|
125 |
+
|
126 |
+
# Применяем к изображению
|
127 |
+
vignette_img = Image.fromarray(vignette, mode='L')
|
128 |
+
|
129 |
+
# Накладываем
|
130 |
+
result = Image.new('RGB', image.size)
|
131 |
+
result.paste(image, (0, 0))
|
132 |
+
result.paste((0, 0, 0), (0, 0), vignette_img)
|
133 |
+
|
134 |
+
return Image.blend(image, result, intensity)
|
135 |
+
|
136 |
+
class ColorPalette:
|
137 |
+
"""Класс для работы с цветовыми палитрами"""
|
138 |
+
|
139 |
+
@staticmethod
|
140 |
+
def extract_colors(image, n_colors=5):
|
141 |
+
"""Извлекает основные цвета из изображения"""
|
142 |
+
# Уменьшаем изображение для ускорения
|
143 |
+
small_image = image.resize((150, 150), Image.Resampling.LANCZOS)
|
144 |
+
|
145 |
+
# Конвертируем в массив
|
146 |
+
img_array = np.array(small_image)
|
147 |
+
img_array = img_array.reshape(-1, 3)
|
148 |
+
|
149 |
+
# Кластеризация цветов
|
150 |
+
kmeans = KMeans(n_clusters=n_colors, random_state=42, n_init=10)
|
151 |
+
kmeans.fit(img_array)
|
152 |
+
|
153 |
+
# Получаем центры кластеров (основные цвета)
|
154 |
+
colors = kmeans.cluster_centers_.astype(int)
|
155 |
+
|
156 |
+
# Создаем изображение палитры
|
157 |
+
palette_height = 100
|
158 |
+
palette_width = 500
|
159 |
+
color_block_width = palette_width // n_colors
|
160 |
+
|
161 |
+
palette_img = Image.new('RGB', (palette_width, palette_height))
|
162 |
+
draw = ImageDraw.Draw(palette_img)
|
163 |
+
|
164 |
+
for i, color in enumerate(colors):
|
165 |
+
x_start = i * color_block_width
|
166 |
+
x_end = (i + 1) * color_block_width
|
167 |
+
color_tuple = tuple(color)
|
168 |
+
draw.rectangle([x_start, 0, x_end, palette_height], fill=color_tuple)
|
169 |
+
|
170 |
+
return palette_img, colors.tolist()
|
171 |
+
|
172 |
+
@staticmethod
|
173 |
+
def suggest_palette(style_name):
|
174 |
+
"""Предлагает цветовую палитру для стиля"""
|
175 |
+
palettes = {
|
176 |
+
"Современный минимализм": ["#FFFFFF", "#F5F5F5", "#E0E0E0", "#9E9E9E", "#424242"],
|
177 |
+
"Скандинавский": ["#FFFFFF", "#F8F8F8", "#E8DCC6", "#A8A8A8", "#4A4A4A"],
|
178 |
+
"Индустриальный": ["#2C2C2C", "#4A4A4A", "#7A7A7A", "#B8860B", "#8B4513"],
|
179 |
+
"Бохо": ["#CD853F", "#DEB887", "#D2691E", "#8B4513", "#A0522D"],
|
180 |
+
"Японский": ["#F5DEB3", "#D2B48C", "#BC8F8F", "#8B7355", "#4B4B4B"],
|
181 |
+
"Ар-деко": ["#FFD700", "#B8860B", "#2F4F4F", "#000000", "#8B0000"],
|
182 |
+
"Прованс": ["#E6E6FA", "#DDA0DD", "#D8BFD8", "#9370DB", "#8B7D6B"],
|
183 |
+
"Хай-тек": ["#C0C0C0", "#808080", "#4169E1", "#000000", "#FFFFFF"]
|
184 |
+
}
|
185 |
+
|
186 |
+
return palettes.get(style_name, ["#FFFFFF", "#E0E0E0", "#808080", "#404040", "#000000"])
|
187 |
+
|
188 |
+
@staticmethod
|
189 |
+
def apply_color_filter(image, color_rgb, intensity=0.3):
|
190 |
+
"""Применяет цветовой фильтр к изображению"""
|
191 |
+
# Создаем цветной слой
|
192 |
+
color_layer = Image.new('RGB', image.size, color_rgb)
|
193 |
+
|
194 |
+
# Смешиваем с оригиналом
|
195 |
+
return Image.blend(image, color_layer, intensity)
|
196 |
+
|
197 |
+
@staticmethod
|
198 |
+
def adjust_brightness_contrast(image, brightness=1.0, contrast=1.0):
|
199 |
+
"""Настройка яркости и контраста"""
|
200 |
+
# Яркость
|
201 |
+
enhancer = ImageEnhance.Brightness(image)
|
202 |
+
image = enhancer.enhance(brightness)
|
203 |
+
|
204 |
+
# Контраст
|
205 |
+
enhancer = ImageEnhance.Contrast(image)
|
206 |
+
image = enhancer.enhance(contrast)
|
207 |
+
|
208 |
+
return image
|