edge-generator / app.py
Daniel251's picture
Update app.py
d30d9ff verified
Raw
History Blame Contribute Delete
23 kB
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)