PandaArtStation commited on
Commit
23dafae
·
verified ·
1 Parent(s): b03ac2c

Update models.py

Browse files
Files changed (1) hide show
  1. models.py +155 -241
models.py CHANGED
@@ -3,312 +3,226 @@ from diffusers import (
3
  StableDiffusionImg2ImgPipeline,
4
  StableDiffusionInpaintPipeline,
5
  DDIMScheduler,
6
- EulerDiscreteScheduler,
7
- PNDMScheduler
8
  )
9
  from PIL import Image, ImageFilter, ImageEnhance
10
  import numpy as np
11
- from typing import List, Optional, Tuple
12
 
13
  class InteriorDesignerPro:
14
  def __init__(self):
15
- """Инициализация AI дизайнера"""
16
- self.device = torch.device("cuda") # ТОЛЬКО GPU!
17
- self.model_name = "stabilityai/stable-diffusion-2-1"
18
-
19
- # Определяем мощность GPU
20
  gpu_name = torch.cuda.get_device_name(0)
21
- self.is_powerful_gpu = any(x in gpu_name.lower() for x in ['h100', 'a100', 'h200', 'a6000'])
22
-
23
- print(f"🚀 Initializing on GPU: {gpu_name}")
24
- print(f"💪 Powerful GPU detected: {self.is_powerful_gpu}")
25
-
26
- # Основная модель для дизайна
27
  self.pipe = StableDiffusionImg2ImgPipeline.from_pretrained(
28
- self.model_name,
29
  torch_dtype=torch.float16,
30
  safety_checker=None,
31
- requires_safety_checker=False
 
32
  ).to(self.device)
33
-
34
- # Модель для inpainting (удаление объектов)
 
 
 
35
  try:
36
  self.inpaint_pipe = StableDiffusionInpaintPipeline.from_pretrained(
37
- "runwayml/stable-diffusion-inpainting",
38
  torch_dtype=torch.float16,
39
  safety_checker=None,
40
  requires_safety_checker=False,
41
  local_files_only=False,
42
  resume_download=True
43
  ).to(self.device)
44
- print("Inpainting model loaded")
45
  except Exception as e:
46
- print(f"⚠️ Failed to load inpainting model: {e}")
47
  print("Using img2img as fallback for object removal")
48
  self.inpaint_pipe = None
49
-
50
- # Оптимизация памяти
51
- self.pipe.enable_attention_slicing()
52
- if hasattr(self.pipe, 'enable_xformers_memory_efficient_attention'):
53
- try:
54
- self.pipe.enable_xformers_memory_efficient_attention()
55
- print("✅ xFormers optimization enabled")
56
- except:
57
- pass
58
-
59
- # Настройка планировщиков
60
- self.schedulers = {
61
- "fast": EulerDiscreteScheduler.from_config(self.pipe.scheduler.config),
62
- "balanced": DDIMScheduler.from_config(self.pipe.scheduler.config),
63
- "quality": PNDMScheduler.from_config(self.pipe.scheduler.config)
64
- }
65
-
66
- def apply_style_pro(self, image: Image.Image, style: str, room_type: str,
67
- strength: float = 0.75, quality: str = "balanced") -> Image.Image:
68
- """Применение стиля к интерьеру"""
69
- from design_styles import DESIGN_STYLES, get_detailed_prompt
70
-
71
- # Подготовка изображения
72
- image = self._prepare_image(image)
73
-
74
- # Получаем промпт для стиля
75
- style_info = DESIGN_STYLES.get(style, DESIGN_STYLES["Современный минимализм"])
76
- prompt = get_detailed_prompt(style, room_type)
77
- negative_prompt = style_info.get("negative", "")
78
-
79
- # Выбираем планировщик
80
- self.pipe.scheduler = self.schedulers.get(quality, self.schedulers["balanced"])
81
-
82
- # Параметры генерации
83
- params = {
84
  "fast": {"steps": 20, "guidance": 7.5},
85
  "balanced": {"steps": 35, "guidance": 8.5},
86
  "ultra": {"steps": 50, "guidance": 10}
87
  }
88
-
89
- settings = params.get(quality, params["balanced"])
90
-
 
 
 
 
91
  # Генерация
92
  result = self.pipe(
93
- prompt=prompt,
94
- negative_prompt=negative_prompt,
95
  image=image,
96
  strength=strength,
97
  num_inference_steps=settings["steps"],
98
  guidance_scale=settings["guidance"]
99
  ).images[0]
100
-
101
- return result
102
-
103
- def remove_objects(self, image: Image.Image, mask: Image.Image,
104
- inpaint_prompt: str = None) -> Image.Image:
105
- """Удаление объектов с изображения"""
106
- if self.inpaint_pipe is None:
107
- # Fallback метод через img2img
108
- return self._remove_objects_fallback(image, mask)
109
-
110
- # Подготовка
111
- image = self._prepare_image(image, size=(512, 512))
112
- mask = self._prepare_mask(mask, size=(512, 512))
113
-
114
- # Промпт для заполнения
115
- if not inpaint_prompt:
116
- inpaint_prompt = "empty room interior, clean walls, seamless texture, high quality"
117
-
118
- # Inpainting
119
- result = self.inpaint_pipe(
120
- prompt=inpaint_prompt,
121
- negative_prompt="furniture, objects, people, clutter",
122
- image=image,
123
- mask_image=mask,
124
- num_inference_steps=50,
125
- guidance_scale=7.5,
126
- strength=0.99
127
- ).images[0]
128
-
129
  return result
130
-
131
- def _remove_objects_fallback(self, image: Image.Image, mask: Image.Image) -> Image.Image:
132
- """Альтернативный метод удаления объектов"""
133
- # Размываем область под маской
134
- import cv2
135
-
136
- img_np = np.array(image)
137
- mask_np = np.array(mask.convert('L'))
138
-
139
- # Расширяем маску
140
- kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (15, 15))
141
- mask_np = cv2.dilate(mask_np, kernel, iterations=2)
142
-
143
- # Inpaint через OpenCV
144
- result_np = cv2.inpaint(img_np, mask_np, 7, cv2.INPAINT_TELEA)
145
- result = Image.fromarray(result_np)
146
-
147
- # Улучшаем через img2img
148
- enhanced = self.pipe(
149
- prompt="clean empty interior space, seamless walls",
150
- negative_prompt="objects, furniture",
151
- image=result,
152
- strength=0.3,
153
- num_inference_steps=20,
154
- guidance_scale=5
155
- ).images[0]
156
-
157
- return enhanced
158
-
159
- def create_variations(self, image: Image.Image, num_variations: int = 4,
160
- variation_strength: float = 0.3) -> List[Image.Image]:
161
  """Создание вариаций дизайна"""
162
  variations = []
163
- base_prompt = "interior design, professional photo, high quality"
164
-
165
  for i in range(num_variations):
166
- # Немного меняем seed для разнообразия
167
- generator = torch.Generator(device=self.device).manual_seed(42 + i)
168
-
169
- result = self.pipe(
170
- prompt=base_prompt,
171
  image=image,
172
- strength=variation_strength,
173
  num_inference_steps=30,
174
- guidance_scale=7.5,
175
- generator=generator
176
  ).images[0]
177
-
178
- variations.append(result)
179
-
180
  return variations
181
-
182
- def create_hdr_lighting(self, image: Image.Image, intensity: float = 0.3) -> Image.Image:
183
- """Создание HDR освещения"""
184
- # Конвертируем в numpy
185
- img_array = np.array(image).astype(np.float32) / 255.0
186
-
187
- # Создаем несколько экспозиций
188
- exposures = [
189
- img_array * 0.5, # Недоэкспонированная
190
- img_array, # Нормальная
191
- np.clip(img_array * 1.5, 0, 1) # Переэкспонированная
192
- ]
193
-
194
- # Простой tone mapping
195
- hdr = np.mean(exposures, axis=0)
196
 
197
- # Увеличиваем контраст
198
- hdr = (hdr - 0.5) * (1 + intensity) + 0.5
199
- hdr = np.clip(hdr, 0, 1)
200
-
201
- # Обратно в PIL
202
- return Image.fromarray((hdr * 255).astype(np.uint8))
203
-
204
- def enhance_details(self, image: Image.Image) -> Image.Image:
 
 
 
 
 
 
 
 
 
 
 
 
 
205
  """Улучшение деталей изображения"""
206
  # Увеличиваем резкость
207
  enhancer = ImageEnhance.Sharpness(image)
208
- image = enhancer.enhance(1.5)
209
-
210
  # Немного увеличиваем контраст
211
- enhancer = ImageEnhance.Contrast(image)
212
- image = enhancer.enhance(1.1)
213
-
214
- # Применяем unsharp mask
215
- image = image.filter(ImageFilter.UnsharpMask(radius=1, percent=150, threshold=3))
216
-
217
- return image
218
-
219
- def change_element(self, image: Image.Image, element: str, value: str,
220
- strength: float = 0.5) -> Image.Image:
221
  """Изменение отдельного элемента интерьера"""
222
  from design_styles import ROOM_ELEMENTS
223
-
224
  element_info = ROOM_ELEMENTS.get(element, {})
225
  prompt_add = element_info.get("prompt_add", element.lower())
226
-
227
  prompt = f"interior with {value} {prompt_add}, professional photo"
228
  negative = f"old {element}, damaged, ugly"
229
-
230
  result = self.pipe(
231
  prompt=prompt,
232
  negative_prompt=negative,
233
  image=image,
234
  strength=strength,
235
- num_inference_steps=30,
236
- guidance_scale=8
237
  ).images[0]
238
-
239
  return result
240
-
241
- def create_style_comparison(self, image: Image.Image, styles: List[str]) -> Image.Image:
242
- """Создание сравнения нескольких стилей"""
243
- styled_images = []
244
 
 
 
 
 
 
 
 
245
  for style in styles:
246
- styled = self.apply_style_pro(image, style, "living room", strength=0.75, quality="fast")
247
- styled_images.append(styled)
248
-
249
- # Создаем сетку через метод который добавим динамически
250
- return self._create_comparison_grid(styled_images, styles)
251
-
252
- def _prepare_image(self, image: Image.Image, size: tuple = (768, 768)) -> Image.Image:
253
- """Подготовка изображения"""
254
- # Конвертируем в RGB
255
- if image.mode != 'RGB':
256
- image = image.convert('RGB')
257
-
258
- # Изменяем размер сохраняя пропорции
259
- image.thumbnail(size, Image.Resampling.LANCZOS)
260
-
261
- # Дополняем до нужного размера
262
- new_image = Image.new('RGB', size, (255, 255, 255))
263
- paste_x = (size[0] - image.width) // 2
264
- paste_y = (size[1] - image.height) // 2
265
- new_image.paste(image, (paste_x, paste_y))
266
-
267
- return new_image
268
-
269
- def _prepare_mask(self, mask: Image.Image, size: tuple = (512, 512)) -> Image.Image:
270
- """Подготовка маски"""
271
- # Конвертируем в L (grayscale)
272
- if mask.mode != 'L':
273
- mask = mask.convert('L')
274
-
275
- # Изменяем размер
276
- mask = mask.resize(size, Image.Resampling.LANCZOS)
277
-
278
- # Усиливаем контраст (четкие границы)
279
- mask = mask.point(lambda x: 255 if x > 128 else 0)
280
-
281
- return mask
282
 
283
- # Класс для удаления объектов
284
  class ObjectRemover:
285
- def __init__(self):
286
- self.device = torch.device("cuda") # ТОЛЬКО GPU!
287
-
288
- def generate_mask_from_text(self, image: Image.Image, text_description: str,
289
- precision: float = 0.3) -> Image.Image:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
290
  """Генерация маски на основе текстового описания"""
291
- # Простая реализация - создаем маску в центре
292
- # В реальности здесь должен быть CLIP/SAM для поиска объектов
293
-
294
  width, height = image.size
295
  mask = Image.new('L', (width, height), 0)
296
-
297
- # Создаем примерную область в центре
298
- from PIL import ImageDraw
 
 
 
 
299
  draw = ImageDraw.Draw(mask)
300
-
301
- # Размер области зависит от precision
302
- margin_x = int(width * (0.5 - precision))
303
- margin_y = int(height * (0.5 - precision))
304
-
305
- # Рисуем белую область
306
- draw.rectangle(
307
- [margin_x, margin_y, width - margin_x, height - margin_y],
308
- fill=255
309
- )
310
-
311
- # Размываем края для плавного перехода
312
- mask = mask.filter(ImageFilter.GaussianBlur(radius=20))
313
-
314
  return mask
 
3
  StableDiffusionImg2ImgPipeline,
4
  StableDiffusionInpaintPipeline,
5
  DDIMScheduler,
6
+ PNDMScheduler,
7
+ EulerDiscreteScheduler
8
  )
9
  from PIL import Image, ImageFilter, ImageEnhance
10
  import numpy as np
11
+ import cv2
12
 
13
  class InteriorDesignerPro:
14
  def __init__(self):
15
+ self.device = torch.device("cuda")
16
+ self.model_name = "RealVisXL V4.0"
17
+
18
+ # Проверка GPU
 
19
  gpu_name = torch.cuda.get_device_name(0)
20
+ self.is_powerful_gpu = any(gpu in gpu_name for gpu in ['A100', 'H100', 'RTX 4090', 'RTX 3090'])
21
+
22
+ # Основная модель - RealVis V4 для фотореалистичных интерьеров
23
+ print(f"Loading {self.model_name} on {gpu_name}...")
 
 
24
  self.pipe = StableDiffusionImg2ImgPipeline.from_pretrained(
25
+ "SG161222/RealVisXL_V4.0",
26
  torch_dtype=torch.float16,
27
  safety_checker=None,
28
+ requires_safety_checker=False,
29
+ local_files_only=False
30
  ).to(self.device)
31
+
32
+ # Настройка scheduler для лучшего качества
33
+ self.pipe.scheduler = EulerDiscreteScheduler.from_config(self.pipe.scheduler.config)
34
+
35
+ # Inpainting модель для удаления объектов
36
  try:
37
  self.inpaint_pipe = StableDiffusionInpaintPipeline.from_pretrained(
38
+ "stabilityai/stable-diffusion-2-inpainting",
39
  torch_dtype=torch.float16,
40
  safety_checker=None,
41
  requires_safety_checker=False,
42
  local_files_only=False,
43
  resume_download=True
44
  ).to(self.device)
45
+ print("Inpainting model loaded successfully")
46
  except Exception as e:
47
+ print(f"Warning: Could not load inpainting model: {e}")
48
  print("Using img2img as fallback for object removal")
49
  self.inpaint_pipe = None
50
+
51
+ def apply_style_pro(self, image, style_name, room_type, strength=0.75, quality="balanced"):
52
+ """Применение стиля к изображению с учетом качества"""
53
+ from design_styles import DESIGN_STYLES
54
+
55
+ style = DESIGN_STYLES.get(style_name, DESIGN_STYLES["Современный минимализм"])
56
+
57
+ # Настройки качества
58
+ quality_settings = {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
59
  "fast": {"steps": 20, "guidance": 7.5},
60
  "balanced": {"steps": 35, "guidance": 8.5},
61
  "ultra": {"steps": 50, "guidance": 10}
62
  }
63
+
64
+ settings = quality_settings.get(quality, quality_settings["balanced"])
65
+
66
+ # Генерация промпта с учетом комнаты
67
+ room_specific = style.get("room_specific", {}).get(room_type, "")
68
+ full_prompt = f"{style['prompt']}, {room_specific}, {room_type} interior design, professional photo, high quality, 8k"
69
+
70
  # Генерация
71
  result = self.pipe(
72
+ prompt=full_prompt,
73
+ negative_prompt=style.get("negative", "low quality, blurry"),
74
  image=image,
75
  strength=strength,
76
  num_inference_steps=settings["steps"],
77
  guidance_scale=settings["guidance"]
78
  ).images[0]
79
+
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
80
  return result
81
+
82
+ def create_variations(self, image, num_variations=4):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
83
  """Создание вариаций дизайна"""
84
  variations = []
85
+ base_seed = torch.randint(0, 1000000, (1,)).item()
86
+
87
  for i in range(num_variations):
88
+ torch.manual_seed(base_seed + i)
89
+
90
+ var = self.pipe(
91
+ prompt="interior design variation, same style, different details",
 
92
  image=image,
93
+ strength=0.4 + (i * 0.05),
94
  num_inference_steps=30,
95
+ guidance_scale=7.5
 
96
  ).images[0]
97
+
98
+ variations.append(var)
99
+
100
  return variations
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
101
 
102
+ def create_hdr_lighting(self, image, intensity=0.3):
103
+ """Улучшение освещения в стиле HDR"""
104
+ # Конвертируем в numpy
105
+ img_array = np.array(image)
106
+
107
+ # Применяем CLAHE для улучшения контраста
108
+ lab = cv2.cvtColor(img_array, cv2.COLOR_RGB2LAB)
109
+ l, a, b = cv2.split(lab)
110
+
111
+ clahe = cv2.createCLAHE(clipLimit=3.0, tileGridSize=(8,8))
112
+ l_clahe = clahe.apply(l)
113
+
114
+ enhanced_lab = cv2.merge([l_clahe, a, b])
115
+ enhanced_rgb = cv2.cvtColor(enhanced_lab, cv2.COLOR_LAB2RGB)
116
+
117
+ # Смешиваем с оригиналом
118
+ result = cv2.addWeighted(img_array, 1-intensity, enhanced_rgb, intensity, 0)
119
+
120
+ return Image.fromarray(result)
121
+
122
+ def enhance_details(self, image):
123
  """Улучшение деталей изображения"""
124
  # Увеличиваем резкость
125
  enhancer = ImageEnhance.Sharpness(image)
126
+ sharp = enhancer.enhance(1.5)
127
+
128
  # Немного увеличиваем контраст
129
+ enhancer = ImageEnhance.Contrast(sharp)
130
+ contrast = enhancer.enhance(1.1)
131
+
132
+ return contrast
133
+
134
+ def change_element(self, image, element, value, strength=0.7):
 
 
 
 
135
  """Изменение отдельного элемента интерьера"""
136
  from design_styles import ROOM_ELEMENTS
137
+
138
  element_info = ROOM_ELEMENTS.get(element, {})
139
  prompt_add = element_info.get("prompt_add", element.lower())
140
+
141
  prompt = f"interior with {value} {prompt_add}, professional photo"
142
  negative = f"old {element}, damaged, ugly"
143
+
144
  result = self.pipe(
145
  prompt=prompt,
146
  negative_prompt=negative,
147
  image=image,
148
  strength=strength,
149
+ num_inference_steps=40,
150
+ guidance_scale=8.0
151
  ).images[0]
152
+
153
  return result
 
 
 
 
154
 
155
+ def create_style_comparison(self, image, styles, quality="fast"):
156
+ """Создание сравнения стилей"""
157
+ results = []
158
+
159
+ # Настройки для быстрой генерации
160
+ steps = 20 if quality == "fast" else 35
161
+
162
  for style in styles:
163
+ styled = self.apply_style_pro(
164
+ image,
165
+ style,
166
+ "living room", # default
167
+ strength=0.75,
168
+ quality=quality
169
+ )
170
+ results.append((style, styled))
171
+
172
+ return results
173
+
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
174
 
 
175
  class ObjectRemover:
176
+ """Класс для удаления объектов"""
177
+
178
+ def __init__(self, inpaint_pipe):
179
+ self.pipe = inpaint_pipe
180
+ self.device = torch.device("cuda")
181
+
182
+ def remove_objects(self, image, mask):
183
+ """Удаление объектов с изображения"""
184
+ if self.pipe is None:
185
+ # Fallback на простое заполнение
186
+ return self.simple_inpaint(image, mask)
187
+
188
+ # Используем inpainting pipeline
189
+ result = self.pipe(
190
+ prompt="empty room interior, clean wall, seamless texture",
191
+ negative_prompt="furniture, objects, people, clutter",
192
+ image=image,
193
+ mask_image=mask,
194
+ strength=0.99,
195
+ num_inference_steps=50,
196
+ guidance_scale=7.5
197
+ ).images[0]
198
+
199
+ return result
200
+
201
+ def simple_inpaint(self, image, mask):
202
+ """Простое заполнение через OpenCV"""
203
+ img_array = np.array(image)
204
+ mask_array = np.array(mask.convert('L'))
205
+
206
+ # Инпейнтинг через OpenCV
207
+ result = cv2.inpaint(img_array, mask_array, 3, cv2.INPAINT_TELEA)
208
+
209
+ return Image.fromarray(result)
210
+
211
+ def generate_mask_from_text(self, image, text_description, precision=0.3):
212
  """Генерация маски на основе текстового описания"""
213
+ # Простая маска в центре (заглушка)
214
+ # В реальности тут должен быть CLIP или SAM
 
215
  width, height = image.size
216
  mask = Image.new('L', (width, height), 0)
217
+
218
+ # Создаем маску в центре
219
+ center_x, center_y = width // 2, height // 2
220
+ radius = int(min(width, height) * precision)
221
+
222
+ # Рисуем круг
223
+ import ImageDraw
224
  draw = ImageDraw.Draw(mask)
225
+ draw.ellipse([center_x - radius, center_y - radius,
226
+ center_x + radius, center_y + radius], fill=255)
227
+
 
 
 
 
 
 
 
 
 
 
 
228
  return mask