import gradio as gr import numpy as np from PIL import Image, ImageFilter, ImageDraw, ImageEnhance, ImageOps import cv2 import rembg import tempfile import os import logging logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__) class ImageProcessor: def __init__(self): logger.info("🔧 Inicializando processador...") try: self.session = rembg.new_session("u2net") logger.info("✅ Modelo U2Net carregado com sucesso!") except Exception as e: logger.error(f"❌ Erro ao carregar modelo: {e}") self.session = None def remove_background(self, img): """Remove fundo da imagem""" logger.info(f"🔄 REMOVER FUNDO - Entrada: {img.size}, modo: {img.mode}") try: if self.session: logger.info("🔄 Chamando rembg.remove()...") result = rembg.remove(img, session=self.session) logger.info(f"✅ Fundo removido! Resultado: {result.size}, modo: {result.mode}") return result else: logger.warning("⚠️ Sessão não disponível, retornando imagem original") return img.convert('RGBA') except Exception as e: logger.error(f"❌ Erro na remoção de fundo: {e}") return img.convert('RGBA') def create_smooth_mask(self, alpha_channel): """Cria máscara suavizada""" logger.info("🔄 CRIAR MÁSCARA SUAVIZADA") logger.info(f"📊 Alpha shape: {alpha_channel.shape}, dtype: {alpha_channel.dtype}") # Converte para binário mask = (alpha_channel > 128).astype(np.uint8) * 255 logger.info(f"📊 Máscara binária - Unique values: {np.unique(mask)}") # Preenche buracos contours, hierarchy = cv2.findContours(mask, cv2.RETR_CCOMP, cv2.CHAIN_APPROX_SIMPLE) logger.info(f"📊 Número de contornos encontrados: {len(contours)}") if hierarchy is not None: for i, contour in enumerate(contours): if hierarchy[0][i][3] == -1: # Contorno externo cv2.drawContours(mask, [contour], 0, 255, -1) # Suaviza bordas mask = cv2.GaussianBlur(mask, (5, 5), 0) mask = cv2.morphologyEx(mask, cv2.MORPH_CLOSE, np.ones((3,3), np.uint8)) logger.info(f"✅ Máscara criada! Shape final: {mask.shape}") return mask def add_border(self, img, mask, size, color_hex): """Adiciona borda ao objeto usando dilatação da máscara""" logger.info(f"🔄 ADICIONAR BORDA - size={size}, color_hex={color_hex}") if size == 0: logger.info("⏭️ Borda desativada (tamanho = 0)") return img try: # Garante que temos uma string de cor válida if not color_hex or not isinstance(color_hex, str): color_hex = "#FF0000" logger.warning(f"⚠️ Cor inválida, usando padrão: {color_hex}") logger.info(f"🎨 Cor original recebida: {color_hex}") # Converte cor hex para RGB hex_color = color_hex.lstrip('#') logger.info(f"🎨 Hex após strip: {hex_color}") if len(hex_color) != 6: hex_color = "FF0000" logger.warning(f"⚠️ Formato de cor inválido, usando vermelho") r = int(hex_color[0:2], 16) g = int(hex_color[2:4], 16) b = int(hex_color[4:6], 16) border_color = (r, g, b, 255) logger.info(f"🎨 Cor da borda RGB: {border_color}") # Garante que a máscara está no formato correto logger.info(f"📊 Máscara dtype antes: {mask.dtype}") if mask.dtype != np.uint8: mask = mask.astype(np.uint8) # Cria kernel para dilatação baseado no tamanho da borda kernel_size = max(3, size * 2) kernel = np.ones((kernel_size, kernel_size), np.uint8) logger.info(f"📏 Kernel size: {kernel_size}x{kernel_size}") dilated_mask = cv2.dilate(mask, kernel, iterations=1) logger.info(f"📊 Máscara dilatada - valores únicos: {np.unique(dilated_mask)}") # Cria máscara apenas da borda border_mask = cv2.subtract(dilated_mask, mask) border_mask = cv2.GaussianBlur(border_mask, (3, 3), 0) border_pixels = np.sum(border_mask > 0) logger.info(f"📏 Pixels de borda criados: {border_pixels}") # Cria imagem base com a borda img_array = np.array(img).copy() height, width = img_array.shape[:2] logger.info(f"📊 Imagem array shape: {img_array.shape}") # Cria camada só com a borda border_only = np.zeros((height, width, 4), dtype=np.uint8) border_only[:, :, 0] = r border_only[:, :, 1] = g border_only[:, :, 2] = b border_only[:, :, 3] = border_mask # Log da camada de borda logger.info(f"📊 Border_only - R min/max: {border_only[:,:,0].min()}/{border_only[:,:,0].max()}") logger.info(f"📊 Border_only - G min/max: {border_only[:,:,1].min()}/{border_only[:,:,1].max()}") logger.info(f"📊 Border_only - B min/max: {border_only[:,:,2].min()}/{border_only[:,:,2].max()}") # Combina: borda primeiro, depois objeto result_array = border_only.copy() # Sobrepõe a imagem original onde ela tem alpha > 0 alpha_img = img_array[:, :, 3].astype(float) / 255.0 alpha_border = border_only[:, :, 3].astype(float) / 255.0 logger.info(f"📊 Alpha imagem - min/max: {alpha_img.min():.2f}/{alpha_img.max():.2f}") logger.info(f"📊 Alpha borda - min/max: {alpha_border.min():.2f}/{alpha_border.max():.2f}") for c in range(3): # RGB result_array[:, :, c] = ( img_array[:, :, c] * alpha_img + border_only[:, :, c] * alpha_border * (1 - alpha_img) ).astype(np.uint8) # Canal alpha combinado result_array[:, :, 3] = np.maximum(img_array[:, :, 3], border_only[:, :, 3]) # Verifica resultado logger.info(f"📊 Result array - R min/max: {result_array[:,:,0].min()}/{result_array[:,:,0].max()}") logger.info(f"📊 Result array - cor média: {result_array[:,:,:3].mean(axis=(0,1))}") result = Image.fromarray(result_array, 'RGBA') logger.info(f"✅ Borda adicionada! Result: {result.size}, modo: {result.mode}") return result except Exception as e: logger.error(f"❌ Erro ao adicionar borda: {e}") import traceback logger.error(traceback.format_exc()) return img def apply_background(self, foreground, bg_type, bg_color_hex, bg_image): """Aplica fundo à imagem""" logger.info(f"🔄 APLICAR FUNDO - Tipo: '{bg_type}', Cor: '{bg_color_hex}'") logger.info(f"📊 Foreground: {foreground.size}, modo: {foreground.mode}") try: if bg_type == "transparent": logger.info("✅ Mantendo fundo transparente") return foreground elif bg_type == "color": # Valida e corrige cor se necessário logger.info(f"🎨 Processando fundo colorido - Valor recebido: {bg_color_hex}") if not bg_color_hex or not isinstance(bg_color_hex, str): bg_color_hex = "#FFFFFF" logger.warning(f"⚠️ Cor de fundo inválida, usando branco") logger.info(f"🎨 Cor após validação: {bg_color_hex}") # Converte hex para RGB hex_color = bg_color_hex.lstrip('#') logger.info(f"🎨 Hex após strip: {hex_color}") if len(hex_color) != 6: hex_color = "FFFFFF" logger.warning("⚠️ Formato de cor inválido, usando branco") r = int(hex_color[0:2], 16) g = int(hex_color[2:4], 16) b = int(hex_color[4:6], 16) logger.info(f"🎨 RGB do fundo: ({r}, {g}, {b})") # Cria fundo sólido bg = Image.new('RGBA', foreground.size, (r, g, b, 255)) logger.info(f"📊 Background criado: {bg.size}, cor: {bg.getpixel((0,0))}") # Combina usando alpha composite result = Image.alpha_composite(bg, foreground) logger.info(f"✅ Fundo colorido aplicado! Result: {result.size}, modo: {result.mode}") # Verifica cor resultante sample_pixel = result.getpixel((10, 10)) logger.info(f"📊 Pixel de amostra (10,10): {sample_pixel}") return result elif bg_type == "image": if bg_image is None: logger.warning("⚠️ Nenhuma imagem de fundo fornecida, mantendo transparente") return foreground logger.info(f"🖼️ Aplicando imagem de fundo {bg_image.size}, modo: {bg_image.mode}") # Converte e redimensiona fundo bg = bg_image.convert('RGBA') logger.info(f"📊 Background convertido: {bg.size}, modo: {bg.mode}") if bg.size != foreground.size: logger.info(f"📐 Redimensionando fundo de {bg.size} para {foreground.size}") bg = bg.resize(foreground.size, Image.Resampling.LANCZOS) # Combina result = Image.alpha_composite(bg, foreground) logger.info("✅ Imagem de fundo aplicada!") return result else: logger.warning(f"⚠️ Tipo de fundo desconhecido: '{bg_type}', mantendo transparente") return foreground except Exception as e: logger.error(f"❌ Erro ao aplicar fundo: {e}") import traceback logger.error(traceback.format_exc()) return foreground def process(self, img, border_size, border_color, bg_type, bg_img, bg_color, effect, smooth): """Processa a imagem completa""" logger.info("="*60) logger.info("🚀 INICIANDO PROCESSAMENTO COMPLETO") logger.info("="*60) # Log de todos os parâmetros recebidos logger.info(f"📋 PARÂMETROS RECEBIDOS:") logger.info(f" - img: {'None' if img is None else f'{img.size}, {img.mode}'}") logger.info(f" - border_size: {border_size}") logger.info(f" - border_color: {border_color}") logger.info(f" - bg_type: {bg_type}") logger.info(f" - bg_img: {'None' if bg_img is None else f'{bg_img.size}, {bg_img.mode}'}") logger.info(f" - bg_color: {bg_color}") logger.info(f" - smooth: {smooth}") if img is None: logger.warning("⚠️ Nenhuma imagem fornecida") return None, None, "⚠️ Por favor, faça upload de uma imagem", None, None try: logger.info(f"📸 Imagem recebida: {img.size}, modo: {img.mode}") # Passo 1: Remove fundo logger.info("="*30) logger.info("1️⃣ REMOVENDO FUNDO") logger.info("="*30) no_bg = self.remove_background(img) logger.info(f"📊 No BG result: {no_bg.size}, modo: {no_bg.mode}") # Passo 2: Extrai e processa máscara logger.info("="*30) logger.info("2️⃣ PROCESSANDO MÁSCARA") logger.info("="*30) if no_bg.mode == 'RGBA': alpha = np.array(no_bg.split()[3]) logger.info(f"📊 Alpha channel shape: {alpha.shape}, unique: {np.unique(alpha)}") else: logger.warning("⚠️ Imagem sem canal alpha, criando máscara completa") alpha = np.ones((no_bg.height, no_bg.width), dtype=np.uint8) * 255 # CORREÇÃO CRÍTICA: Verifica se smooth é realmente booleano logger.info(f"🔄 Smooth value: {smooth}, type: {type(smooth)}") if smooth: logger.info("🔧 Criando máscara suavizada") mask = self.create_smooth_mask(alpha) else: logger.info("🔧 Criando máscara básica (sem suavização)") mask = (alpha > 128).astype(np.uint8) * 255 logger.info(f"📊 Máscara final - shape: {mask.shape}, unique: {np.unique(mask)}") # Passo 3: Aplica máscara refinada à imagem original logger.info("="*30) logger.info("3️⃣ APLICANDO MÁSCARA REFINADA") logger.info("="*30) logger.info("🔄 Convertendo imagem para RGBA...") result_rgba = img.convert('RGBA') logger.info(f"📊 Imagem convertida: {result_rgba.size}, modo: {result_rgba.mode}") result_array = np.array(result_rgba) logger.info(f"📊 Array original shape: {result_array.shape}") result_array[:, :, 3] = mask logger.info(f"📊 Array após aplicar máscara - Alpha unique: {np.unique(result_array[:,:,3])}") result = Image.fromarray(result_array, 'RGBA') logger.info(f"✅ Máscara aplicada! Result: {result.size}, modo: {result.mode}") # Passo 4: Adiciona borda logger.info("="*30) logger.info("4️⃣ ADICIONANDO BORDA") logger.info("="*30) logger.info(f"🎨 Usando cor da borda: {border_color}") bordered = self.add_border(result, mask, border_size, border_color) logger.info(f"📊 Bordered result: {bordered.size}, modo: {bordered.mode}") # Passo 5: Aplica fundo logger.info("="*30) logger.info("5️⃣ APLICANDO FUNDO") logger.info("="*30) logger.info(f"🎭 Tipo de fundo: {bg_type}, Cor: {bg_color}") final = self.apply_background(bordered, bg_type, bg_color, bg_img) logger.info(f"📊 Final result: {final.size}, modo: {final.mode}") # Passo 6: Salva arquivos logger.info("="*30) logger.info("6️⃣ SALVANDO ARQUIVOS") logger.info("="*30) logger.info("💾 Salvando arquivos...") final_path = os.path.join(tempfile.gettempdir(), f"final_{os.getpid()}.png") nobg_path = os.path.join(tempfile.gettempdir(), f"nobg_{os.getpid()}.png") final.save(final_path, "PNG") bordered.save(nobg_path, "PNG") logger.info(f"✅ Arquivos salvos: {final_path}, {nobg_path}") # Mostra informações mais detalhadas if bg_type == "color": bg_info = f"{bg_type} ({bg_color})" elif bg_type == "image" and bg_img is not None: bg_info = f"{bg_type} ({bg_img.size[0]}x{bg_img.size[1]})" else: bg_info = bg_type status = f"✅ SUCESSO!\n" status += f"📏 Tamanho: {final.size[0]}x{final.size[1]}px\n" status += f"🖍️ Borda: {border_size}px - Cor: {border_color}\n" status += f"🎭 Fundo: {bg_info}\n" status += f"✨ Suavização: {'Ativa' if smooth else 'Desativada'}" logger.info("="*60) logger.info("✅ PROCESSAMENTO CONCLUÍDO COM SUCESSO!") logger.info("="*60) return final, bordered, status, final_path, nobg_path except Exception as e: logger.error("="*60) logger.error(f"❌ ERRO CRÍTICO NO PROCESSAMENTO: {e}") logger.error("="*60) import traceback logger.error(traceback.format_exc()) error_msg = f"❌ Erro no processamento:\n{str(e)}\n\nVerifique os logs para detalhes." return None, None, error_msg, None, None # Inicializa processador globalmente logger.info("🌟 Iniciando aplicação...") processor = ImageProcessor() # Interface Gradio with gr.Blocks(theme=gr.themes.Soft(), title="🎨 Magic Background Remover PRO") as demo: gr.Markdown(""" # 🎨 Magic Background Remover PRO ### Remoção automática de fundo com IA + Bordas personalizadas **Como usar:** 1. 📤 Faça upload de uma imagem 2. 🖍️ Ajuste a borda (tamanho e cor) 3. 🎭 Escolha o tipo de fundo 4. 📥 Baixe o resultado! """) with gr.Row(): with gr.Column(scale=1): gr.Markdown("### ⚙️ Controles") img_in = gr.Image(label="📤 Upload da Imagem", type="pil", height=300) with gr.Group(): gr.Markdown("**🖍️ Configurações da Borda**") border_size = gr.Slider(0, 50, 10, step=1, label="Espessura (px)") border_color = gr.ColorPicker("#FF0000", label="Cor da Borda") smooth = gr.Checkbox(True, label="✨ Suavizar bordas (recomendado)") with gr.Group(): gr.Markdown("**🎭 Configurações do Fundo**") bg_type = gr.Radio( choices=["transparent", "color", "image"], label="Tipo de Fundo", value="transparent", info="Selecione o tipo de fundo" ) bg_color = gr.ColorPicker("#FFFFFF", label="Cor do Fundo", visible=False) bg_img = gr.Image(label="Imagem de Fundo", type="pil", visible=False) def toggle_bg_controls(bg_type): """Controla visibilidade dos controles de fundo""" logger.info(f"🔄 TOGGLE BG CONTROLS - bg_type: {bg_type}") visible_color = (bg_type == "color") visible_img = (bg_type == "image") return ( gr.ColorPicker(visible=visible_color), gr.Image(visible=visible_img) ) bg_type.change( fn=toggle_bg_controls, inputs=[bg_type], outputs=[bg_color, bg_img] ) process_btn = gr.Button("🚀 PROCESSAR IMAGEM", variant="primary", size="lg") with gr.Column(scale=1): gr.Markdown("### 🖼️ Resultados") img_out = gr.Image(label="Imagem Final", height=350) img_nobg = gr.Image(label="Apenas sem Fundo (com borda)", height=250) with gr.Row(): dl_final = gr.File(label="📥 Download - Imagem Final", visible=False) dl_nobg = gr.File(label="📥 Download - Sem Fundo", visible=False) status = gr.Textbox( label="📊 Status do Processamento", lines=6, value="✅ Sistema pronto!\n\n📝 Aguardando upload de imagem..." ) gr.Markdown(""" --- ### 💡 Dicas para melhores resultados: - Use imagens com bom contraste entre objeto e fundo - Para retratos: bordas de 8-15px funcionam bem - Para produtos: bordas de 3-8px são ideais - Ative a suavização para bordas mais naturais **🤖 Processamento com IA U2Net - Qualidade profissional!** """) # Conecta eventos inputs = [img_in, border_size, border_color, bg_type, bg_img, bg_color, gr.State(""), smooth] outputs = [img_out, img_nobg, status, dl_final, dl_nobg] # Função wrapper para log extra def process_with_logs(*args): logger.info("🔄 EVENTO DISPARADO - Iniciando processamento via interface") logger.info(f"📋 Args recebidos: {len(args)}") for i, arg in enumerate(args): if i == 0: # img logger.info(f" Arg {i} (img): {'None' if arg is None else f'{arg.size}, {arg.mode}'}") elif i == 3: # bg_type logger.info(f" Arg {i} (bg_type): {arg}") elif i == 4: # bg_img logger.info(f" Arg {i} (bg_img): {'None' if arg is None else f'{arg.size}, {arg.mode}'}") elif i == 5: # bg_color logger.info(f" Arg {i} (bg_color): {arg}") elif i == 7: # smooth logger.info(f" Arg {i} (smooth): {arg}, type: {type(arg)}") result = processor.process(*args) logger.info(f"✅ Processamento via interface concluído") return result # Botão principal process_btn.click( fn=process_with_logs, inputs=inputs, outputs=outputs, show_progress=True ) # Reprocessa quando imagem mudar img_in.change( fn=process_with_logs, inputs=inputs, outputs=outputs, show_progress=True ) # Reprocessa ao mudar tamanho da borda border_size.change( fn=process_with_logs, inputs=inputs, outputs=outputs, show_progress=True ) # Reprocessa ao mudar COR da borda border_color.change( fn=process_with_logs, inputs=inputs, outputs=outputs, show_progress=True ) # Reprocessa ao mudar suavização smooth.change( fn=process_with_logs, inputs=inputs, outputs=outputs, show_progress=True ) # Reprocessa ao mudar tipo de fundo bg_type.change( fn=process_with_logs, inputs=inputs, outputs=outputs, show_progress=True ) # Reprocessa ao mudar COR do fundo bg_color.change( fn=process_with_logs, inputs=inputs, outputs=outputs, show_progress=True ) # Reprocessa ao mudar IMAGEM de fundo bg_img.change( fn=process_with_logs, inputs=inputs, outputs=outputs, show_progress=True ) logger.info("✅ Interface Gradio criada com sucesso!") demo.launch(debug=True)