JustForWorld commited on
Commit
4790b3f
·
1 Parent(s): 22b0ab3

revert: to the b814393 and upgrade guidance_scale=3.8 and strength=0.6

Browse files
Files changed (3) hide show
  1. Dockerfile +1 -5
  2. logic.py +119 -95
  3. requirements.txt +9 -7
Dockerfile CHANGED
@@ -8,6 +8,7 @@ FROM nvidia/cuda:12.1.1-devel-ubuntu22.04
8
  # ============================================
9
  ENV PIP_CACHE_DIR=/data/.cache/pip \
10
  HF_HOME=/data/.cache/huggingface \
 
11
  MAX_JOBS=1 \
12
  FORCE_CUDA=1 \
13
  PYTHONUNBUFFERED=1 \
@@ -40,11 +41,6 @@ WORKDIR /app
40
  RUN python3 -m pip install --upgrade pip setuptools wheel
41
  RUN pip install --no-cache-dir torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu121
42
 
43
- # ============================================
44
- # Установка xformers для оптимизации
45
- # ============================================
46
- RUN pip install --no-cache-dir xformers==0.0.22.post7
47
-
48
  # ============================================
49
  # Установка зависимостей Python
50
  # ============================================
 
8
  # ============================================
9
  ENV PIP_CACHE_DIR=/data/.cache/pip \
10
  HF_HOME=/data/.cache/huggingface \
11
+ OMP_NUM_THREADS=4 \
12
  MAX_JOBS=1 \
13
  FORCE_CUDA=1 \
14
  PYTHONUNBUFFERED=1 \
 
41
  RUN python3 -m pip install --upgrade pip setuptools wheel
42
  RUN pip install --no-cache-dir torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu121
43
 
 
 
 
 
 
44
  # ============================================
45
  # Установка зависимостей Python
46
  # ============================================
logic.py CHANGED
@@ -5,148 +5,172 @@ from PIL import Image, ImageDraw
5
  import torch
6
  from loguru import logger
7
  import time
8
-
9
- # Импорты для iopaint (LaMa)
10
- from iopaint.model_manager import ModelManager
11
- from iopaint.schema import InpaintRequest, HDStrategy, LDMSampler
12
-
13
- # Импорты для diffusers (ControlNet)
14
- from diffusers import StableDiffusionControlNetInpaintPipeline, ControlNetModel, UniPCMultistepScheduler
15
 
16
  class WatermarkRemover:
17
  def __init__(self, device=None):
18
  self.device = device or ("cuda" if torch.cuda.is_available() else "cpu")
19
  logger.info(f"Using device: {self.device}")
20
 
 
21
  self.detector = None
22
- self.lama_model = None # <--- Модель из iopaint
23
- self.controlnet_pipe = None
24
- self.controlnet = None
25
-
26
- def _prepare_image_for_diffusion(self, image: Image.Image, mask: Image.Image) -> Image.Image:
27
- image_np = np.array(image.convert("RGB"))
28
- mask_np = np.array(mask.convert("L"))
29
- inpainted_np = cv2.inpaint(image_np, mask_np, inpaintRadius=15, flags=cv2.INPAINT_NS)
30
- return Image.fromarray(inpainted_np)
31
 
 
 
 
32
  def _load_detector(self):
33
  if self.detector is None:
34
  logger.info("Loading YOLOv8 custom model ('best.pt')...")
35
- self.detector = YOLO("best.pt").to(self.device)
36
- try: self.detector.fuse()
37
- except Exception: pass
 
 
 
38
  logger.success("YOLOv8 model loaded successfully.")
39
 
40
- def _load_lama_model(self):
41
- # Инициализируем LaMa через iopaint
42
- if self.lama_model is None:
43
- logger.info("Initializing LaMa model via iopaint...")
44
- self.lama_model = ModelManager(name="lama", device=self.device)
45
- logger.success("LaMa model initialized successfully.")
46
-
47
- def _load_controlnet_model(self):
48
- if self.controlnet_pipe is None:
49
- logger.info("Loading ControlNet-Inpaint model...")
50
- self.controlnet = ControlNetModel.from_pretrained(
51
- "lllyasviel/control_v11p_sd15_inpaint",
52
- torch_dtype=torch.float16 if self.device == "cuda" else torch.float32
53
- )
54
- self.controlnet_pipe = StableDiffusionControlNetInpaintPipeline.from_pretrained(
55
- "runwayml/stable-diffusion-v1-5", controlnet=self.controlnet,
56
  torch_dtype=torch.float16 if self.device == "cuda" else torch.float32,
57
- safety_checker=None
58
  ).to(self.device)
59
- self.controlnet_pipe.scheduler = UniPCMultepScheduler.from_config(self.controlnet_pipe.scheduler.config)
60
- try: self.controlnet_pipe.enable_xformers_memory_efficient_attention()
61
- except Exception: pass
62
- logger.success("ControlNet-Inpaint model loaded successfully.")
 
63
 
 
 
 
64
  def _get_mask_yolo(self, image: Image.Image) -> Image.Image:
65
- self._load_detector()
 
66
  img_np = np.array(image.convert("RGB"))
67
  results = self.detector.predict(img_np, conf=0.25, imgsz=864, device=self.device)
68
  mask = Image.new("L", image.size, 0)
 
69
  if results and len(results[0].boxes) > 0:
70
  draw = ImageDraw.Draw(mask)
71
  boxes = results[0].boxes.xyxy.cpu().numpy()
72
- for bbox in boxes: draw.rectangle(list(bbox), fill=255)
 
 
 
 
73
  return mask
74
 
 
 
 
75
  def _inpaint_image(self, image: Image.Image, mask: Image.Image) -> Image.Image:
76
- # ... код до ЭТАПА 1 без изменений
77
- prompt = "ultra realistic photo of architecture, high detail, 4k, professional photography"
78
- negative_prompt = "blurry, distorted, deformed, low quality, noise, grain, text, logo"
 
 
 
 
 
 
 
 
 
 
79
  orig_w, orig_h = image.size
80
  mask_np = np.array(mask)
81
  ys, xs = np.where(mask_np > 0)
82
- if xs.size == 0 or ys.size == 0: return image
 
 
 
 
83
  pad = max(48, int(min(orig_w, orig_h) * 0.03))
84
- x_min, y_min = max(int(xs.min()) - pad, 0), max(int(ys.min()) - pad, 0)
85
- x_max, y_max = min(int(xs.max()) + pad, orig_w), min(int(ys.max()) + pad, orig_h)
 
 
 
86
  crop_box = (x_min, y_min, x_max, y_max)
87
- original_crop = image.crop(crop_box)
88
- mask_crop = mask.crop(crop_box)
89
-
90
- # --- ЭТАП 1: Быстрая очистка с помощью iopaint (LaMa) ---
91
- logger.info("Step 1: Pre-cleaning with iopaint/LaMa to create a clean reference...")
92
- self._load_lama_model()
93
-
94
- # Конвертируем PIL в NumPy
95
- original_crop_np = np.array(original_crop)
96
- mask_crop_np = np.array(mask_crop.convert("L"))
97
-
98
- # Создаем конфиг для iopaint
99
- config = InpaintRequest(hd_strategy=HDStrategy.ORIGINAL)
100
-
101
- # Вызываем модель. Важно: iopaint возвращает BGR!
102
- cleaned_bgr_np = self.lama_model(original_crop_np, mask_crop_np, config)
103
-
104
- # Конвертируем BGR -> RGB
105
- cleaned_rgb_np = cv2.cvtColor(cleaned_bgr_np, cv2.COLOR_BGR2RGB)
106
-
107
- cleaned_reference_crop = Image.fromarray(cleaned_rgb_np)
108
- logger.success("Clean reference created.")
109
-
110
- if self.device == "cuda": torch.cuda.empty_cache()
111
-
112
- # --- ЭТАП 2: Качественная реставрация с помощью ControlNet (без изменений) ---
113
- logger.info("Step 2: High-quality restoration with ControlNet-Inpaint...")
114
- self._load_controlnet_model()
115
- canvas_crop = self._prepare_image_for_diffusion(original_crop, mask_crop)
116
- crop_w, crop_h = original_crop.size
117
- new_w, new_h = int(np.ceil(crop_w / 8) * 8), int(np.ceil(crop_h / 8) * 8)
118
- resized_canvas = canvas_crop.resize((new_w, new_h), resample=Image.LANCZOS)
119
- resized_mask = mask_crop.resize((new_w, new_h), resample=Image.NEAREST)
120
- resized_cleaned_reference = cleaned_reference_crop.resize((new_w, new_h), resample=Image.LANCZOS)
121
  with torch.inference_mode():
122
- result = self.controlnet_pipe(
123
- prompt=prompt, negative_prompt=negative_prompt, image=resized_canvas,
124
- mask_image=resized_mask, control_image=resized_cleaned_reference,
125
- num_inference_steps=25, guidance_scale=7.5,
126
- controlnet_conditioning_scale=1.0, strength=1.0
 
 
 
127
  ).images[0]
128
- logger.success("High-quality restoration complete.")
129
-
130
- if result.size != original_crop.size: result_resized = result.resize(original_crop.size, resample=Image.LANCZOS)
131
- else: result_resized = result
 
 
132
  base = image.copy()
133
- base.paste(result_resized, (x_min, y_min), mask=mask_crop)
134
- if self.device == "cuda": torch.cuda.empty_cache()
 
 
 
 
 
135
  return base
136
 
 
 
 
137
  def run(self, image: Image.Image) -> Image.Image:
138
  start_time = time.time()
139
  logger.info("Starting watermark removal...")
 
140
  mask_image = self._get_mask_yolo(image)
141
  mask_np = np.array(mask_image)
142
- if not np.any(mask_np): return image
143
- logger.info("Post-processing mask...")
 
 
 
 
144
  kernel = np.ones((15, 15), np.uint8)
145
  closed_mask = cv2.morphologyEx(mask_np, cv2.MORPH_CLOSE, kernel)
146
  final_kernel = np.ones((7, 7), np.uint8)
147
  processed_mask_np = cv2.dilate(closed_mask, final_kernel, iterations=1)
148
  processed_mask_pil = Image.fromarray(processed_mask_np)
149
  logger.success("Mask processed.")
 
150
  result_img = self._inpaint_image(image, processed_mask_pil)
151
  end_time = time.time()
152
  logger.success(f"Watermark removal completed in {end_time - start_time:.2f}s.")
 
5
  import torch
6
  from loguru import logger
7
  import time
8
+ from diffusers import AutoPipelineForInpainting
 
 
 
 
 
 
9
 
10
  class WatermarkRemover:
11
  def __init__(self, device=None):
12
  self.device = device or ("cuda" if torch.cuda.is_available() else "cpu")
13
  logger.info(f"Using device: {self.device}")
14
 
15
+ # Lazy-loaded models
16
  self.detector = None
17
+ self.inpainting_pipe = None
 
 
 
 
 
 
 
 
18
 
19
+ # ======================================================
20
+ # Lazy-load YOLO
21
+ # ======================================================
22
  def _load_detector(self):
23
  if self.detector is None:
24
  logger.info("Loading YOLOv8 custom model ('best.pt')...")
25
+ self.detector = YOLO("best.pt")
26
+ self.detector.to(self.device)
27
+ try:
28
+ self.detector.fuse()
29
+ except Exception:
30
+ pass
31
  logger.success("YOLOv8 model loaded successfully.")
32
 
33
+ # ======================================================
34
+ # Lazy-load Stable Diffusion
35
+ # ======================================================
36
+ def _load_inpainting_model(self):
37
+ if self.inpainting_pipe is None:
38
+ logger.info("Loading Stable Diffusion 1.5 Inpainting from Hugging Face Hub...")
39
+ # Возвращаемся к скачиванию из интернета
40
+ self.inpainting_pipe = AutoPipelineForInpainting.from_pretrained(
41
+ "runwayml/stable-diffusion-inpainting",
 
 
 
 
 
 
 
42
  torch_dtype=torch.float16 if self.device == "cuda" else torch.float32,
43
+ safety_checker=None,
44
  ).to(self.device)
45
+ try:
46
+ self.inpainting_pipe.enable_attention_slicing()
47
+ except Exception:
48
+ pass
49
+ logger.success("Stable Diffusion 1.5 Inpainting model loaded successfully.")
50
 
51
+ # ======================================================
52
+ # Mask generation via YOLO
53
+ # ======================================================
54
  def _get_mask_yolo(self, image: Image.Image) -> Image.Image:
55
+ self._load_detector() # ensure YOLO loaded
56
+
57
  img_np = np.array(image.convert("RGB"))
58
  results = self.detector.predict(img_np, conf=0.25, imgsz=864, device=self.device)
59
  mask = Image.new("L", image.size, 0)
60
+
61
  if results and len(results[0].boxes) > 0:
62
  draw = ImageDraw.Draw(mask)
63
  boxes = results[0].boxes.xyxy.cpu().numpy()
64
+ logger.info(f"YOLO found {len(boxes)} watermark box(es).")
65
+ for bbox in boxes:
66
+ draw.rectangle(list(bbox), fill=255)
67
+ else:
68
+ logger.warning("No watermark detected.")
69
  return mask
70
 
71
+ # ======================================================
72
+ # Partial inpainting
73
+ # ======================================================
74
  def _inpaint_image(self, image: Image.Image, mask: Image.Image) -> Image.Image:
75
+ self._load_inpainting_model() # ensure pipeline loaded
76
+
77
+ prompt = (
78
+ "ultra realistic photo of interior or exterior architecture, "
79
+ "natural lighting, clean surface, consistent material texture, realistic color balance"
80
+ )
81
+ negative_prompt = (
82
+ "text, logo, watermark, signature, fake object, furniture, table, person, "
83
+ "painting, mirror artifact, blurry, distorted, deformed, low quality, noise, grain"
84
+ )
85
+
86
+ logger.info("Running partial Stable Diffusion inpainting...")
87
+
88
  orig_w, orig_h = image.size
89
  mask_np = np.array(mask)
90
  ys, xs = np.where(mask_np > 0)
91
+
92
+ if xs.size == 0 or ys.size == 0:
93
+ logger.info("Mask empty — skipping inpainting.")
94
+ return image
95
+
96
  pad = max(48, int(min(orig_w, orig_h) * 0.03))
97
+ x_min = max(int(xs.min()) - pad, 0)
98
+ x_max = min(int(xs.max()) + pad, orig_w)
99
+ y_min = max(int(ys.min()) - pad, 0)
100
+ y_max = min(int(ys.max()) + pad, orig_h)
101
+
102
  crop_box = (x_min, y_min, x_max, y_max)
103
+ crop_img = image.crop(crop_box)
104
+ crop_mask = mask.crop(crop_box)
105
+
106
+ crop_w, crop_h = crop_img.size
107
+ max_side = 1024
108
+ scale = 1.0
109
+ if max(crop_w, crop_h) > max_side:
110
+ scale = max_side / max(crop_w, crop_h)
111
+
112
+ new_w = int(np.ceil((crop_w * scale) / 8) * 8)
113
+ new_h = int(np.ceil((crop_h * scale) / 8) * 8)
114
+
115
+ if (new_w, new_h) != (crop_w, crop_h):
116
+ resized_img = crop_img.resize((new_w, new_h), resample=Image.LANCZOS)
117
+ resized_mask = crop_mask.resize((new_w, new_h), resample=Image.LANCZOS)
118
+ else:
119
+ resized_img, resized_mask = crop_img, crop_mask
120
+
121
+ resized_mask = resized_mask.convert("L")
122
+ mask_thr = np.array(resized_mask)
123
+ mask_thr = (mask_thr > 127).astype(np.uint8) * 255
124
+ resized_mask = Image.fromarray(mask_thr, mode="L")
125
+
 
 
 
 
 
 
 
 
 
 
 
126
  with torch.inference_mode():
127
+ result = self.inpainting_pipe(
128
+ prompt=prompt,
129
+ negative_prompt=negative_prompt,
130
+ image=resized_img,
131
+ mask_image=resized_mask,
132
+ num_inference_steps=27,
133
+ guidance_scale=3.8,
134
+ strength=0.6,
135
  ).images[0]
136
+
137
+ if result.size != crop_img.size:
138
+ result_resized = result.resize(crop_img.size, resample=Image.LANCZOS)
139
+ else:
140
+ result_resized = result
141
+
142
  base = image.copy()
143
+ paste_mask = crop_mask.convert("L")
144
+ paste_mask = Image.fromarray((np.array(paste_mask) > 127).astype(np.uint8) * 255, mode="L")
145
+ base.paste(result_resized, (x_min, y_min), mask=paste_mask)
146
+
147
+ if self.device == "cuda":
148
+ torch.cuda.empty_cache()
149
+
150
  return base
151
 
152
+ # ======================================================
153
+ # Main process
154
+ # ======================================================
155
  def run(self, image: Image.Image) -> Image.Image:
156
  start_time = time.time()
157
  logger.info("Starting watermark removal...")
158
+
159
  mask_image = self._get_mask_yolo(image)
160
  mask_np = np.array(mask_image)
161
+
162
+ if not np.any(mask_np):
163
+ logger.info("No watermark found. Returning original image.")
164
+ return image
165
+
166
+ logger.info("Post-processing mask (morphology)...")
167
  kernel = np.ones((15, 15), np.uint8)
168
  closed_mask = cv2.morphologyEx(mask_np, cv2.MORPH_CLOSE, kernel)
169
  final_kernel = np.ones((7, 7), np.uint8)
170
  processed_mask_np = cv2.dilate(closed_mask, final_kernel, iterations=1)
171
  processed_mask_pil = Image.fromarray(processed_mask_np)
172
  logger.success("Mask processed.")
173
+
174
  result_img = self._inpaint_image(image, processed_mask_pil)
175
  end_time = time.time()
176
  logger.success(f"Watermark removal completed in {end_time - start_time:.2f}s.")
requirements.txt CHANGED
@@ -14,20 +14,22 @@ ultralytics==8.2.2
14
  # ===============================
15
  # Утилиты
16
  # ===============================
17
- numpy==1.26.4
18
  loguru==0.7.2
19
  opencv-python-headless==4.9.0.80
20
  Pillow==9.5.0
21
  einops
22
 
23
  # ===============================
24
- # --- Инпеинтинг ---
25
  # ===============================
26
- # Устанавливаем iopaint с "extras", чтобы он подтянул все AI-модели (LaMa, SD и т.д.)
27
- # Это также автоматически установит ПРАВИЛЬНЫЕ версии diffusers, transformers и т.д.
28
- iopaint[models]==1.2.1
 
 
29
 
30
  # ===============================
31
- # Оптимизация
32
  # ===============================
33
- xformers==0.0.22.post7
 
14
  # ===============================
15
  # Утилиты
16
  # ===============================
17
+ numpy
18
  loguru==0.7.2
19
  opencv-python-headless==4.9.0.80
20
  Pillow==9.5.0
21
  einops
22
 
23
  # ===============================
24
+ # --- Hugging Face Ecosystem ---
25
  # ===============================
26
+ # Совместимые версии для SD2 Inpainting и torch 2.3+/CUDA 12.1
27
+ diffusers==0.21.4
28
+ transformers==4.30.2
29
+ accelerate==0.21.0
30
+ huggingface-hub==0.21.4
31
 
32
  # ===============================
33
+ # Прочее (иногда нужно diffusers)
34
  # ===============================
35
+ safetensors