"""
def render_interactive_polygons(img_pil, zones, prob_masks):
buff = BytesIO()
img_pil.save(buff, format="JPEG")
img_b64 = base64.b64encode(buff.getvalue()).decode("utf-8")
width, height = img_pil.size
def san(name):
return name.lower().replace(" ", "-").replace("/", "-")
svg_polygons = ""
menu_items = ""
valid_zones = []
for zone in zones:
z_name = zone.name.lower()
mask = prob_masks.get(z_name)
if mask is None or not mask.any():
continue
valid_zones.append(z_name)
zc = san(z_name)
# ── Redimensionar máscara de 512×512 para o espaço da imagem exibida ──
mask_u8 = cv2.resize(
mask.astype(np.uint8) * 255,
(width, height), # dimensões reais da imagem no SVG
interpolation=cv2.INTER_NEAREST
)
cnts, _ = cv2.findContours(mask_u8, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
for cnt in cnts:
approx = cv2.approxPolyDP(cnt, 0.002 * cv2.arcLength(cnt, True), True)
pts = " ".join(f"{pt[0][0]},{pt[0][1]}" for pt in approx)
svg_polygons += f''
for name in sorted(set(valid_zones)):
zc = san(name)
hi = f"document.querySelectorAll('.poly-{zc}').forEach(p=>{{p.style.fill='rgba(239,68,68,.7)';p.style.stroke='rgba(255,255,255,1)';p.style.strokeWidth='3';}});this.style.backgroundColor='#3b82f6';this.style.color='white';"
ho = f"document.querySelectorAll('.poly-{zc}').forEach(p=>{{p.style.fill='rgba(239,68,68,.15)';p.style.stroke='rgba(255,255,255,.2)';p.style.strokeWidth='1';}});this.style.backgroundColor='#1e293b';this.style.color='#cbd5e1';"
menu_items += f'
{name}
'
components.html(f"""
Anatomia Afetada
{menu_items}
""", height=500)
# ══════════════════════════════════════════════════════════════════════
# MAIN
# ══════════════════════════════════════════════════════════════════════
def main():
inject_custom_css()
# Carregar todos os recursos em cache
swin, clip_df40 = get_all_models()
w_swin, w_df40, w_z, bias, threshold_default = load_fusion_weights()
swin_tf, clip_tf = get_transforms()
st.markdown(
'
🔍 Deepfake Face Detector
',
unsafe_allow_html=True)
st.markdown("
Deteção de criação sintética e manipulação em rostos humanos.
", unsafe_allow_html=True)
with st.expander("Como funciona a plataforma", expanded=False):
st.markdown("""
O sistema analisa a imagem em três fases para determinar se foi gerada ou manipulada por Inteligência Artificial:
1. **Análise de Superfície:** Procura artefactos microscópicos e falhas de textura invisíveis ao olho humano.
2. **Coerência Biométrica:** Verifica se os traços faciais e a iluminação são consistentes, detetando trocas de rosto, edições.
3. **Localização de Anomalias:** Isola e mapeia graficamente as áreas específicas onde a manipulação ocorreu.
INPUT: Imagem RGB da cara (A cores) OUTPUT: Classificação → Explicação Visual (Explicador e Segmentador) → Relatório do Gemini
SwinV2 & CLIP DF-40
Analisam texturas microscópicas e traços faciais para calcular a probabilidade de a imagem ser falsa.
2. Mapeamento
CLIP Surgery & BiSeNet
Funcionam como um raio-X, isolando e destacando as zonas exatas do rosto que sofreram manipulação.
3. Relatório
Gemini (Google)
Lê as anomalias detetadas nos passos anteriores e gera uma explicação consoante seja falsa ou real.
""", unsafe_allow_html=True)
col_input, col_result = st.columns([1, 1.2], gap="large")
img_bgr = None
raw_img_bgr = None
# ── Coluna de Input ───────────────────────────────────────────────
with col_input:
st.markdown("#### Origem da Imagem")
if escolha_input == "Sua Imagem":
up = st.file_uploader("Arraste o ficheiro", type=["jpg", "png", "jpeg"],
label_visibility="collapsed")
if up:
raw_img_bgr = cv2.imdecode(np.frombuffer(up.read(), np.uint8), cv2.IMREAD_COLOR)
else:
nome_base = "false" if "Falso" in escolha_input else "real"
for ext in [".png", ".jpg", ".jpeg"]:
p = ROOT_DIR / "exemplos" / f"{nome_base}{ext}"
if p.exists():
raw_img_bgr = cv2.imread(str(p))
break
analisar = False
if raw_img_bgr is not None:
with st.spinner("A detetar rosto na imagem..."):
cropped, status = extract_main_face(raw_img_bgr)
if cropped is None:
st.error(status)
else:
img_bgr = cropped
st.image(cv2.cvtColor(img_bgr, cv2.COLOR_BGR2RGB),
caption="Área de análise isolada", width=350)
label_btn = "Verificar Autenticidade" if escolha_input == "Sua Imagem" \
else f"Analisar Exemplo ({nome_base.upper()})"
analisar = st.button(label_btn, use_container_width=True)
# ── Coluna de Resultados ─────────────────────────────────────────
with col_result:
st.markdown("#### 📝 Resultados da Análise")
if analisar and img_bgr is not None:
# Limpar estado de análise anterior
for key in ["contrastive_hm", "per_text_hm", "prompt_list", "reg_scores"]:
st.session_state.pop(key, None)
img_rgb = cv2.cvtColor(img_bgr, cv2.COLOR_BGR2RGB)
img_hires = cv2.resize(img_rgb, (512, 512))
img_pil = Image.fromarray(img_rgb)
raw_rgb = cv2.cvtColor(raw_img_bgr, cv2.COLOR_BGR2RGB)
raw_pil = Image.fromarray(raw_rgb)
# Preparar tensores
t_swin = swin_tf(raw_pil).unsqueeze(0).to("cpu")
t_clip = (clip_tf(img_pil).unsqueeze(0).to("cpu")
.type(next(clip_df40.parameters()).dtype))
# Inferência paralela dos três especialistas
with st.spinner("A verificar autenticidade..."):
with concurrent.futures.ThreadPoolExecutor(max_workers=3) as ex:
f_swin = ex.submit(lambda t: float(torch.softmax(swin(t), dim=1)[0, 1].item()), t_swin)
f_clip = ex.submit(lambda t: float(torch.softmax(clip_df40(t), dim=1)[0, 1].item()), t_clip)
f_surgery = ex.submit(generate_heatmap, img_hires)
prob_swin = f_swin.result()
prob_clip_df40 = f_clip.result()
contrastive_hm, per_text_hm, scores, prompts, _ = f_surgery.result()
# Z-score espacial
masks = get_region_masks(img_hires) # # 512×512 — mesma resolução do heatmap
contrast_map = np.clip(
per_text_hm.get("AI face manipulation", np.zeros((512, 512))) -
per_text_hm.get("real human face", np.zeros((512, 512))),
0, 1
)
reg_scores = score_regions_manipulation(img_hires, contrast_map, masks, scores)
contrasts = [d["contrast"] for d in reg_scores.values()]
z_anomaly = ((max(contrasts) - np.mean(contrasts)) / (np.std(contrasts) + 1e-6)
if len(contrasts) > 1 else 0.0)
# Fusão LR + High-Confidence Override
logit = prob_swin * w_swin + prob_clip_df40 * w_df40 + z_anomaly * w_z + bias
prob_final = float(1.0 / (1.0 + np.exp(-logit)))
if max(prob_swin, prob_clip_df40) > 0.85:
prob_final = max(prob_final, max(prob_swin, prob_clip_df40))
is_fake = prob_final > threshold
# Guardar no session_state para o painel técnico
st.session_state.update({
"contrastive_hm": contrastive_hm,
"per_text_hm": per_text_hm,
"prompt_list": prompts,
"reg_scores": reg_scores,
})
render_confidence_bar(prob_final, threshold)
if is_fake:
zones = extract_artifact_zones(img_hires, contrastive_hm, masks, reg_scores)
if zones:
prob_masks = segment_zones_with_probability(contrastive_hm, zones, prob_threshold=0.40)
render_interactive_polygons(Image.fromarray(img_rgb), zones, prob_masks)
else:
st.image(img_rgb, width=250, caption="Nenhuma anomalia detetada.")
# ── Relatório ────────────────────────
if analisar and img_bgr is not None and "is_fake" in dir() and not is_fake:
st.markdown("",
unsafe_allow_html=True)
st.markdown("### 📋 Relatório")
with st.spinner("A gerar relatório ..."):
orchestrator = ForensicVLMOrchestrator(mode="api")
stream = orchestrator.generate_real_justification(img_rgb, prob_final)
ESTILO_REAL = ("background-color:#0f172a;padding:25px;border-radius:8px;"
"border-left:4px solid #22c55e;font-size:15px;color:#f8fafc;"
"line-height:1.7;box-shadow:0 4px 6px rgba(0,0,0,.2);margin-bottom:2rem;")
box_real = st.empty()
if isinstance(stream, str):
box_real.markdown(f"
{stream}
", unsafe_allow_html=True)
else:
acumulado = ""
for chunk in stream:
if chunk:
acumulado += chunk
box_real.markdown(f"
{acumulado} ▌
",
unsafe_allow_html=True)
box_real.markdown(f"
{acumulado}
", unsafe_allow_html=True)
# ── Relatório ) ──────────
if analisar and img_bgr is not None and "is_fake" in dir() and is_fake and "zones" in dir() and zones:
st.markdown("", unsafe_allow_html=True)
st.markdown("### 📋 Relatório ")
with st.spinner("A gerar relatório ..."):
orchestrator = ForensicVLMOrchestrator(mode="api")
global_bbox = (
min(z.bbox[0] for z in zones), min(z.bbox[1] for z in zones),
max(z.bbox[2] for z in zones), max(z.bbox[3] for z in zones),
)
stream = orchestrator.generate_justification(
img_rgb=img_hires,
prob_final=prob_final,
prob_swin=prob_swin,
prob_clip=prob_clip_df40,
zone_name=", ".join(z.name for z in zones),
bbox=global_bbox,
)
ESTILO = ("background-color:#0f172a;padding:25px;border-radius:8px;"
"border-left:4px solid #FF0000;font-size:15px;color:#f8fafc;"
"line-height:1.7;box-shadow:0 4px 6px rgba(0,0,0,.2);margin-bottom:2rem;")
box = st.empty()
if isinstance(stream, str):
box.markdown(f"
{stream}
", unsafe_allow_html=True)
else:
acumulado = ""
for chunk in stream:
if chunk:
acumulado += chunk
box.markdown(f"
{acumulado} ▌
", unsafe_allow_html=True)
box.markdown(f"
{acumulado}
", unsafe_allow_html=True)
# ── Painel Técnico: Matemática Contrastiva ────────────────────────
if analisar and is_fake and st.session_state.get("contrastive_hm") is not None:
with st.expander("Visão Detalhada: Como a Anomalia é Isolada", expanded=False):
st.markdown("### Processo de Subtração Visual")
st.markdown("O sistema analisa a imagem através de duas 'lentes' diferentes: uma programada para detetar sinais de manipulação gerada por IA e outra para reconhecer padrões orgânicos de um rosto humano natural. Ao subtrair a componente natural, o ruído visual desaparece, destacando apenas as áreas manipuladas.")
st.markdown(" ", unsafe_allow_html=True)
top_prompt = "AI face manipulation"
real_prompt = "real human face"
c1, cm, c2, ce, c3 = st.columns([1.5, .3, 1.5, .3, 1.5], vertical_alignment="center")
with c1:
st.markdown(render_heat_card(
visualize_heatmap(st.session_state["per_text_hm"][top_prompt]),
"Padrão Sintético", "Lente de Manipulação", "#ef4444"), unsafe_allow_html=True)
with cm:
st.markdown("
-
", unsafe_allow_html=True)
with c2:
if real_prompt in st.session_state["per_text_hm"]:
st.markdown(render_heat_card(
visualize_heatmap(st.session_state["per_text_hm"][real_prompt]),
"Padrão Orgânico", "Lente Natural", "#22c55e"), unsafe_allow_html=True)
with ce:
st.markdown("
=
", unsafe_allow_html=True)
with c3:
st.markdown(render_heat_card(
visualize_heatmap(st.session_state["contrastive_hm"]),
"Resultado Final", "Anomalia Destacada", "#3b82f6"), unsafe_allow_html=True)
if __name__ == "__main__":
main()