PandaArtStation commited on
Commit
de4e465
·
verified ·
1 Parent(s): cbc09c4

Update models.py

Browse files
Files changed (1) hide show
  1. models.py +195 -174
models.py CHANGED
@@ -6,24 +6,29 @@ from diffusers import (
6
  PNDMScheduler,
7
  EulerDiscreteScheduler
8
  )
9
- from PIL import Image, ImageDraw, ImageFilter, ImageEnhance
10
  import numpy as np
11
- from typing import List, Optional, Union, Tuple
12
- import os
13
 
14
  class InteriorDesignerPro:
15
  def __init__(self):
16
- """Инициализация моделей для дизайна интерьеров"""
17
  self.device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
18
  self.model_name = "stabilityai/stable-diffusion-2-1"
19
 
20
  # Определяем мощность GPU
21
- self.is_powerful_gpu = self._check_gpu_capability()
22
-
23
- print(f"🚀 Инициализация InteriorDesignerPro на {self.device}")
24
- print(f"💪 Мощный GPU: {'Да' if self.is_powerful_gpu else 'Нет'}")
25
-
26
- # Основная модель для img2img
 
 
 
 
 
 
27
  self.pipe = StableDiffusionImg2ImgPipeline.from_pretrained(
28
  self.model_name,
29
  torch_dtype=torch.float16 if self.device.type == "cuda" else torch.float32,
@@ -31,9 +36,8 @@ class InteriorDesignerPro:
31
  requires_safety_checker=False
32
  ).to(self.device)
33
 
34
- # Модель для inpainting с обработкой ошибок
35
  try:
36
- print("🎨 Загрузка модели для удаления объектов...")
37
  self.inpaint_pipe = StableDiffusionInpaintPipeline.from_pretrained(
38
  "runwayml/stable-diffusion-inpainting",
39
  torch_dtype=torch.float16 if self.device.type == "cuda" else torch.float32,
@@ -42,69 +46,64 @@ class InteriorDesignerPro:
42
  local_files_only=False,
43
  resume_download=True
44
  ).to(self.device)
45
- print("✅ Модель inpainting загружена успешно")
46
  except Exception as e:
47
- print(f"⚠️ Не удалось загрузить модель inpainting: {e}")
48
- print("📌 Используем основную модель как альтернативу")
49
- # Используем основную модель для inpainting
50
- self.inpaint_pipe = self.pipe
51
-
52
  # Оптимизация памяти
53
  if self.device.type == "cuda":
54
  self.pipe.enable_attention_slicing()
55
  if hasattr(self.pipe, 'enable_xformers_memory_efficient_attention'):
56
  try:
57
  self.pipe.enable_xformers_memory_efficient_attention()
 
 
58
  print("✅ xFormers оптимизация включена")
59
  except:
60
- pass
61
 
62
  # Настройка планировщиков
63
  self.schedulers = {
64
- "DDIM": DDIMScheduler,
65
- "PNDM": PNDMScheduler,
66
- "Euler": EulerDiscreteScheduler
67
  }
68
 
69
- # Установка дефолтного планировщика
70
- self.pipe.scheduler = DDIMScheduler.from_config(self.pipe.scheduler.config)
71
 
72
- def _check_gpu_capability(self):
73
- """Проверка мощности GPU"""
74
- if self.device.type != "cuda":
75
- return False
76
-
77
- # Проверяем доступную память
78
- try:
79
- mem_gb = torch.cuda.get_device_properties(0).total_memory / 1024**3
80
- return mem_gb >= 12 # 12GB+ считаем мощным GPU
81
- except:
82
- return False
83
-
84
- def apply_style_pro(self, image: Image.Image, style_name: str, room_type: str,
85
  strength: float = 0.75, quality: str = "balanced") -> Image.Image:
86
  """Применение стиля к изображению"""
87
  from design_styles import DESIGN_STYLES
88
 
89
- if style_name not in DESIGN_STYLES:
90
- raise ValueError(f"Неизвестный стиль: {style_name}")
 
 
 
 
 
 
 
 
 
 
 
91
 
92
- style = DESIGN_STYLES[style_name]
93
-
94
  # Настройки качества
95
  quality_settings = {
96
- "fast": {"steps": 20, "guidance": 7.5},
97
- "balanced": {"steps": 35, "guidance": 8.5},
98
- "ultra": {"steps": 50, "guidance": 10.0}
99
  }
100
 
101
  settings = quality_settings.get(quality, quality_settings["balanced"])
 
102
 
103
- # Генерация промпта
104
- prompt = f"{room_type}, {style['prompt']}"
105
- negative_prompt = style.get('negative', '')
106
-
107
- # Применение стиля
108
  result = self.pipe(
109
  prompt=prompt,
110
  negative_prompt=negative_prompt,
@@ -115,182 +114,197 @@ class InteriorDesignerPro:
115
  ).images[0]
116
 
117
  return result
118
-
119
- def remove_objects(self, image: Image.Image, mask: Image.Image,
120
- inpaint_prompt: str = None) -> Image.Image:
121
- """Удаление объектов с изображения"""
122
- if inpaint_prompt is None:
123
- inpaint_prompt = "empty room, clean walls, seamless texture"
124
-
125
- # Проверяем тип модели
126
- if hasattr(self.inpaint_pipe, 'components') and 'vae' in self.inpaint_pipe.components:
127
- # Это настоящая inpainting модель
128
- result = self.inpaint_pipe(
129
- prompt=inpaint_prompt,
130
- negative_prompt="objects, furniture, people",
131
- image=image,
132
- mask_image=mask,
133
- strength=0.99,
134
- num_inference_steps=50,
135
- guidance_scale=7.5
136
- ).images[0]
137
- else:
138
- # Fallback на img2img с маской
139
- # Создаем композитное изображение
140
- masked_image = Image.composite(
141
- Image.new('RGB', image.size, (255, 255, 255)),
142
- image,
143
- mask.convert('L').point(lambda x: 0 if x > 128 else 255)
144
- )
145
-
146
- result = self.pipe(
147
- prompt=inpaint_prompt,
148
- negative_prompt="objects, furniture, people",
149
- image=masked_image,
150
- strength=0.95,
151
- num_inference_steps=50,
152
- guidance_scale=7.5
153
- ).images[0]
154
-
155
- return result
156
-
157
- def create_variations(self, image: Image.Image, num_variations: int = 4) -> List[Image.Image]:
158
- """Создание вариаций дизайна"""
159
  variations = []
160
 
161
  prompts = [
162
- "modern interior design, professional photo",
163
- "cozy interior design, warm lighting",
164
- "luxury interior design, high-end materials",
165
- "minimalist interior design, clean space"
166
  ]
167
 
168
  for i in range(min(num_variations, len(prompts))):
169
- variation = self.pipe(
170
  prompt=prompts[i],
171
  image=image,
172
- strength=0.5 + (i * 0.1),
173
- num_inference_steps=30,
174
  guidance_scale=7.5
175
  ).images[0]
176
- variations.append(variation)
177
 
178
  return variations
179
-
180
  def create_hdr_lighting(self, image: Image.Image, intensity: float = 0.3) -> Image.Image:
181
  """Улучшение освещения (HDR эффект)"""
182
  # Конвертируем в numpy
183
- img_array = np.array(image).astype(np.float32)
184
 
185
  # Создаем HDR эффект
186
- # Увеличиваем контраст в светлых областях
187
- bright_mask = img_array > 200
188
- img_array[bright_mask] = img_array[bright_mask] * (1 + intensity)
189
-
190
- # Усиливаем тени
191
- dark_mask = img_array < 50
192
- img_array[dark_mask] = img_array[dark_mask] * (1 - intensity * 0.5)
193
 
194
- # Нормализация
195
- img_array = np.clip(img_array, 0, 255).astype(np.uint8)
 
196
 
197
- # Применяем дополнительные фильтры
198
- enhanced = Image.fromarray(img_array)
199
- enhanced = enhanced.filter(ImageFilter.UnsharpMask(radius=1, percent=150, threshold=3))
 
 
 
 
200
 
201
- # Настройка яркости и контраста
202
- enhancer = ImageEnhance.Contrast(enhanced)
203
- enhanced = enhancer.enhance(1.1)
204
 
205
- return enhanced
206
-
207
  def enhance_details(self, image: Image.Image) -> Image.Image:
208
  """Улучшение деталей изображения"""
209
  # Увеличиваем резкость
210
- enhanced = image.filter(ImageFilter.SHARPEN)
211
- enhanced = enhanced.filter(ImageFilter.UnsharpMask(radius=2, percent=200, threshold=3))
212
 
213
- # Улучшаем цвета
214
- color_enhancer = ImageEnhance.Color(enhanced)
215
- enhanced = color_enhancer.enhance(1.1)
216
 
217
- # Увеличиваем четкость
218
- sharpness_enhancer = ImageEnhance.Sharpness(enhanced)
219
- enhanced = sharpness_enhancer.enhance(1.2)
 
 
 
 
 
 
 
220
 
221
- return enhanced
222
-
223
  def change_element(self, image: Image.Image, element: str, value: str,
224
- strength: float = 0.7) -> Image.Image:
225
- """Изменение отдельного элемента интерьера"""
226
  from design_styles import ROOM_ELEMENTS
227
 
228
  if element not in ROOM_ELEMENTS:
229
- raise ValueError(f"Неизвестный элемент: {element}")
230
 
231
  element_info = ROOM_ELEMENTS[element]
 
232
 
233
- # Создаем промпт для изменения
234
- prompt = f"interior design, {element_info['prompt_add']}, {value}"
235
- negative_prompt = "blurry, distorted"
236
 
237
- # Применяем изменения
238
  result = self.pipe(
239
  prompt=prompt,
240
- negative_prompt=negative_prompt,
241
  image=image,
242
  strength=strength,
243
- num_inference_steps=40,
244
  guidance_scale=8.0
245
  ).images[0]
246
 
247
  return result
248
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
249
  def create_style_comparison(self, image: Image.Image, styles: List[str],
250
  room_type: str = "living room") -> Image.Image:
251
- """Создание сравнения стилей"""
252
  styled_images = []
253
 
 
254
  for style in styles:
255
- try:
256
- styled = self.apply_style_pro(
257
- image, style, room_type,
258
- strength=0.75, quality="fast"
259
- )
260
- styled_images.append((styled, style))
261
- except Exception as e:
262
- print(f"Ошибка при применении стиля {style}: {e}")
263
-
264
- if not styled_images:
265
- return image
266
 
267
- # Используем метод из процессора
268
- if hasattr(self, '_create_comparison_grid'):
269
- return self._create_comparison_grid(styled_images)
270
- else:
271
- # Простая сетка
272
- return self._simple_grid(styled_images)
273
-
274
- def _simple_grid(self, images_with_titles: List[Tuple[Image.Image, str]]) -> Image.Image:
275
- """Создание простой сетки изображений"""
276
- if not images_with_titles:
277
  return None
278
 
279
- images = [img for img, _ in images_with_titles]
280
- titles = [title for _, title in images_with_titles]
 
 
281
 
282
- # Размеры
283
  img_width, img_height = images[0].size
284
- cols = min(3, len(images))
285
- rows = (len(images) + cols - 1) // cols
286
 
287
- # Создаем сетку
288
- grid_width = img_width * cols
289
- grid_height = img_height * rows
290
  grid = Image.new('RGB', (grid_width, grid_height), 'white')
291
 
292
  # Размещаем изображения
293
- for idx, (img, title) in enumerate(images_with_titles):
294
  row = idx // cols
295
  col = idx % cols
296
  x = col * img_width
@@ -300,25 +314,32 @@ class InteriorDesignerPro:
300
  return grid
301
 
302
 
303
- # Дополнительный класс для удаления объектов
304
  class ObjectRemover:
 
305
  def __init__(self, device):
306
  self.device = device
307
 
308
- def generate_mask_from_text(self, image: Image.Image, text: str, precision: float) -> Image.Image:
 
309
  """Генерация маски на основе текстового описания"""
310
  # Простая реализация - создаем маску в центре
311
  width, height = image.size
312
  mask = Image.new('L', (width, height), 0)
 
 
 
313
  draw = ImageDraw.Draw(mask)
314
 
315
  # Размер области зависит от precision
316
- margin = int(min(width, height) * (1 - precision) / 2)
 
317
 
318
- # Рисуем белую область в центре
319
- draw.ellipse([margin, margin, width-margin, height-margin], fill=255)
 
 
320
 
321
- # Размываем для плавных краев
322
  mask = mask.filter(ImageFilter.GaussianBlur(radius=20))
323
 
324
  return mask
 
6
  PNDMScheduler,
7
  EulerDiscreteScheduler
8
  )
9
+ from PIL import Image, ImageFilter, ImageEnhance
10
  import numpy as np
11
+ from typing import List, Union, Optional
12
+ import cv2
13
 
14
  class InteriorDesignerPro:
15
  def __init__(self):
 
16
  self.device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
17
  self.model_name = "stabilityai/stable-diffusion-2-1"
18
 
19
  # Определяем мощность GPU
20
+ if self.device.type == "cuda":
21
+ gpu_name = torch.cuda.get_device_name(0).lower()
22
+ self.is_powerful_gpu = any(x in gpu_name for x in ["a100", "v100", "h100", "h200", "rtx 40", "rtx 30"])
23
+ else:
24
+ self.is_powerful_gpu = False
25
+
26
+ print(f"🚀 Инициализация на {self.device}")
27
+ if self.device.type == "cuda":
28
+ print(f"🎮 GPU: {torch.cuda.get_device_name(0)}")
29
+ print(f"💪 Мощный GPU: {'Да' if self.is_powerful_gpu else 'Нет'}")
30
+
31
+ # Основная модель
32
  self.pipe = StableDiffusionImg2ImgPipeline.from_pretrained(
33
  self.model_name,
34
  torch_dtype=torch.float16 if self.device.type == "cuda" else torch.float32,
 
36
  requires_safety_checker=False
37
  ).to(self.device)
38
 
39
+ # Модель для inpainting (удаление объектов)
40
  try:
 
41
  self.inpaint_pipe = StableDiffusionInpaintPipeline.from_pretrained(
42
  "runwayml/stable-diffusion-inpainting",
43
  torch_dtype=torch.float16 if self.device.type == "cuda" else torch.float32,
 
46
  local_files_only=False,
47
  resume_download=True
48
  ).to(self.device)
49
+ print("✅ Inpainting модель загружена")
50
  except Exception as e:
51
+ print(f"⚠️ Не удалось загрузить inpainting модель: {e}")
52
+ print("Используем основную модель для inpainting")
53
+ self.inpaint_pipe = None
54
+
 
55
  # Оптимизация памяти
56
  if self.device.type == "cuda":
57
  self.pipe.enable_attention_slicing()
58
  if hasattr(self.pipe, 'enable_xformers_memory_efficient_attention'):
59
  try:
60
  self.pipe.enable_xformers_memory_efficient_attention()
61
+ if self.inpaint_pipe:
62
+ self.inpaint_pipe.enable_xformers_memory_efficient_attention()
63
  print("✅ xFormers оптимизация включена")
64
  except:
65
+ print("⚠️ xFormers недоступен")
66
 
67
  # Настройка планировщиков
68
  self.schedulers = {
69
+ "ddim": DDIMScheduler.from_config(self.pipe.scheduler.config),
70
+ "pndm": PNDMScheduler.from_config(self.pipe.scheduler.config),
71
+ "euler": EulerDiscreteScheduler.from_config(self.pipe.scheduler.config)
72
  }
73
 
74
+ # Удаление объектов
75
+ self.object_remover = ObjectRemover(self.device)
76
 
77
+ def apply_style_pro(self, image: Image.Image, style: str, room_type: str,
 
 
 
 
 
 
 
 
 
 
 
 
78
  strength: float = 0.75, quality: str = "balanced") -> Image.Image:
79
  """Применение стиля к изображению"""
80
  from design_styles import DESIGN_STYLES
81
 
82
+ if style not in DESIGN_STYLES:
83
+ style = "Современный минимализм"
84
+
85
+ style_info = DESIGN_STYLES[style]
86
+ base_prompt = style_info.get("prompt", "")
87
+ negative_prompt = style_info.get("negative", "")
88
+
89
+ # Добавляем специфику комнаты
90
+ if "room_specific" in style_info and room_type in style_info["room_specific"]:
91
+ room_prompt = style_info["room_specific"][room_type]
92
+ prompt = f"{room_prompt}, {base_prompt}, interior design, professional photo"
93
+ else:
94
+ prompt = f"{room_type} {base_prompt}, interior design, professional photo"
95
 
 
 
96
  # Настройки качества
97
  quality_settings = {
98
+ "fast": {"steps": 20, "guidance": 7.5, "scheduler": "euler"},
99
+ "balanced": {"steps": 35, "guidance": 8.5, "scheduler": "ddim"},
100
+ "ultra": {"steps": 50, "guidance": 10, "scheduler": "ddim"}
101
  }
102
 
103
  settings = quality_settings.get(quality, quality_settings["balanced"])
104
+ self.pipe.scheduler = self.schedulers[settings["scheduler"]]
105
 
106
+ # Генерация
 
 
 
 
107
  result = self.pipe(
108
  prompt=prompt,
109
  negative_prompt=negative_prompt,
 
114
  ).images[0]
115
 
116
  return result
117
+
118
+ def create_variations(self, image: Image.Image, num_variations: int = 4,
119
+ strength: float = 0.5) -> List[Image.Image]:
120
+ """Создание вариаций изображения"""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
121
  variations = []
122
 
123
  prompts = [
124
+ "modern interior design, bright and airy",
125
+ "cozy warm interior, soft lighting",
126
+ "minimalist clean space, neutral colors",
127
+ "luxury elegant interior, premium materials"
128
  ]
129
 
130
  for i in range(min(num_variations, len(prompts))):
131
+ var = self.pipe(
132
  prompt=prompts[i],
133
  image=image,
134
+ strength=strength,
135
+ num_inference_steps=25,
136
  guidance_scale=7.5
137
  ).images[0]
138
+ variations.append(var)
139
 
140
  return variations
141
+
142
  def create_hdr_lighting(self, image: Image.Image, intensity: float = 0.3) -> Image.Image:
143
  """Улучшение освещения (HDR эффект)"""
144
  # Конвертируем в numpy
145
+ img_array = np.array(image)
146
 
147
  # Создаем HDR эффект
148
+ # Светлая версия
149
+ light = cv2.convertScaleAbs(img_array, alpha=1.2, beta=30)
150
+ # Темная версия
151
+ dark = cv2.convertScaleAbs(img_array, alpha=0.8, beta=-30)
 
 
 
152
 
153
+ # Комбинируем
154
+ hdr = cv2.addWeighted(img_array, 1-intensity, light, intensity/2, 0)
155
+ hdr = cv2.addWeighted(hdr, 1, dark, intensity/2, 0)
156
 
157
+ # Улучшаем контраст
158
+ lab = cv2.cvtColor(hdr, cv2.COLOR_RGB2LAB)
159
+ l, a, b = cv2.split(lab)
160
+ clahe = cv2.createCLAHE(clipLimit=3.0, tileGridSize=(8,8))
161
+ l = clahe.apply(l)
162
+ enhanced = cv2.merge([l, a, b])
163
+ result = cv2.cvtColor(enhanced, cv2.COLOR_LAB2RGB)
164
 
165
+ return Image.fromarray(result)
 
 
166
 
 
 
167
  def enhance_details(self, image: Image.Image) -> Image.Image:
168
  """Улучшение деталей изображения"""
169
  # Увеличиваем резкость
170
+ enhancer = ImageEnhance.Sharpness(image)
171
+ sharp = enhancer.enhance(1.5)
172
 
173
+ # Улучшаем детали
174
+ detail_filter = ImageFilter.DETAIL
175
+ detailed = sharp.filter(detail_filter)
176
 
177
+ # Легкий unsharp mask
178
+ blurred = detailed.filter(ImageFilter.GaussianBlur(radius=1))
179
+ img_array = np.array(detailed)
180
+ blur_array = np.array(blurred)
181
+
182
+ # Применяем маску
183
+ sharpened = img_array + 0.5 * (img_array - blur_array)
184
+ sharpened = np.clip(sharpened, 0, 255).astype(np.uint8)
185
+
186
+ return Image.fromarray(sharpened)
187
 
 
 
188
  def change_element(self, image: Image.Image, element: str, value: str,
189
+ strength: float = 0.5) -> Image.Image:
190
+ """Изменение конкретного элемента интерьера"""
191
  from design_styles import ROOM_ELEMENTS
192
 
193
  if element not in ROOM_ELEMENTS:
194
+ return image
195
 
196
  element_info = ROOM_ELEMENTS[element]
197
+ prompt_add = element_info.get("prompt_add", "")
198
 
199
+ prompt = f"{element} {value}, {prompt_add}, interior design"
200
+ negative = "bad quality, distorted"
 
201
 
 
202
  result = self.pipe(
203
  prompt=prompt,
204
+ negative_prompt=negative,
205
  image=image,
206
  strength=strength,
207
+ num_inference_steps=30,
208
  guidance_scale=8.0
209
  ).images[0]
210
 
211
  return result
212
+
213
+ def remove_objects(self, image: Image.Image, mask: Image.Image,
214
+ inpaint_prompt: str = None) -> Image.Image:
215
+ """Удаление объектов с изображения"""
216
+ if self.inpaint_pipe is None:
217
+ # Fallback на обычную модель
218
+ return self.remove_objects_fallback(image, mask, inpaint_prompt)
219
+
220
+ if inpaint_prompt is None:
221
+ inpaint_prompt = "empty room, clean space, seamless background"
222
+
223
+ # Убеждаемся что маска в правильном формате
224
+ if mask.mode != "L":
225
+ mask = mask.convert("L")
226
+
227
+ # Inpainting
228
+ result = self.inpaint_pipe(
229
+ prompt=inpaint_prompt,
230
+ negative_prompt="furniture, objects, people",
231
+ image=image,
232
+ mask_image=mask,
233
+ strength=0.99,
234
+ num_inference_steps=50,
235
+ guidance_scale=7.5
236
+ ).images[0]
237
+
238
+ return result
239
+
240
+ def remove_objects_fallback(self, image: Image.Image, mask: Image.Image,
241
+ inpaint_prompt: str = None) -> Image.Image:
242
+ """Альтернативный метод удаления объектов"""
243
+ # Используем OpenCV inpainting
244
+ img_array = np.array(image)
245
+ mask_array = np.array(mask.convert("L"))
246
+
247
+ # Расширяем маску для лучшего результата
248
+ kernel = np.ones((5,5), np.uint8)
249
+ mask_array = cv2.dilate(mask_array, kernel, iterations=2)
250
+
251
+ # Inpainting
252
+ result = cv2.inpaint(img_array, mask_array, 3, cv2.INPAINT_TELEA)
253
+
254
+ # Постобработка через img2img для улучшения
255
+ result_img = Image.fromarray(result)
256
+
257
+ if inpaint_prompt is None:
258
+ inpaint_prompt = "clean empty space, seamless texture"
259
+
260
+ enhanced = self.pipe(
261
+ prompt=inpaint_prompt,
262
+ image=result_img,
263
+ strength=0.3,
264
+ num_inference_steps=20,
265
+ guidance_scale=7.5
266
+ ).images[0]
267
+
268
+ return enhanced
269
+
270
  def create_style_comparison(self, image: Image.Image, styles: List[str],
271
  room_type: str = "living room") -> Image.Image:
272
+ """Создание сравнения нескольких стилей"""
273
  styled_images = []
274
 
275
+ # Генерируем для каждого стиля
276
  for style in styles:
277
+ styled = self.apply_style_pro(
278
+ image, style, room_type,
279
+ strength=0.75,
280
+ quality="fast" # Быстрый режим для множественной генерации
281
+ )
282
+ styled_images.append(styled)
 
 
 
 
 
283
 
284
+ # Создаем сетку
285
+ return self._create_comparison_grid(styled_images, styles)
286
+
287
+ def _create_comparison_grid(self, images: List[Image.Image],
288
+ titles: List[str]) -> Image.Image:
289
+ """Создание сетки из изображений"""
290
+ if not images:
 
 
 
291
  return None
292
 
293
+ # Определяем размер сетки
294
+ n = len(images)
295
+ cols = min(3, n)
296
+ rows = (n + cols - 1) // cols
297
 
298
+ # Размер одного изображения
299
  img_width, img_height = images[0].size
 
 
300
 
301
+ # Создаем холст
302
+ grid_width = cols * img_width
303
+ grid_height = rows * img_height
304
  grid = Image.new('RGB', (grid_width, grid_height), 'white')
305
 
306
  # Размещаем изображения
307
+ for idx, (img, title) in enumerate(zip(images, titles)):
308
  row = idx // cols
309
  col = idx % cols
310
  x = col * img_width
 
314
  return grid
315
 
316
 
 
317
  class ObjectRemover:
318
+ """Класс для удаления объектов"""
319
  def __init__(self, device):
320
  self.device = device
321
 
322
+ def generate_mask_from_text(self, image: Image.Image, text: str,
323
+ precision: float = 0.3) -> Image.Image:
324
  """Генерация маски на основе текстового описания"""
325
  # Простая реализация - создаем маску в центре
326
  width, height = image.size
327
  mask = Image.new('L', (width, height), 0)
328
+
329
+ # Создаем примерную область в центре
330
+ from PIL import ImageDraw
331
  draw = ImageDraw.Draw(mask)
332
 
333
  # Размер области зависит от precision
334
+ margin_x = int(width * (0.5 - precision))
335
+ margin_y = int(height * (0.5 - precision))
336
 
337
+ draw.ellipse(
338
+ [margin_x, margin_y, width - margin_x, height - margin_y],
339
+ fill=255
340
+ )
341
 
342
+ # Размываем маску
343
  mask = mask.filter(ImageFilter.GaussianBlur(radius=20))
344
 
345
  return mask