import gradio as gr import os import tempfile import numpy as np from PIL import Image from src.predict import process_single_image import sys sys.path.insert(0, "./src") # 自定义主题 - 炫彩现代化 custom_theme = gr.themes.Default( primary_hue="purple", secondary_hue="pink", neutral_hue="slate", font=[gr.themes.GoogleFont("Poppins"), gr.themes.GoogleFont("Inter"), "Arial", "sans-serif"] ).set( button_primary_background_fill="linear-gradient(45deg, #667eea 0%, #764ba2 100%)", button_primary_background_fill_hover="linear-gradient(45deg, #764ba2 0%, #667eea 100%)", button_primary_text_color="white", button_secondary_background_fill="linear-gradient(45deg, #f093fb 0%, #f5576c 100%)", button_secondary_background_fill_hover="linear-gradient(45deg, #f5576c 0%, #f093fb 100%)", button_secondary_text_color="white" ) def get_example_images(folder_path="ffhq"): """获取示例图片列表""" return sorted([os.path.join(folder_path, f) for f in os.listdir(folder_path) if f.lower().endswith(('.png', '.jpg', '.jpeg'))]) def safe_extract_prob(cls_probs): """安全地从cls_probs中提取概率值""" try: if cls_probs is None: return 0.0 elif isinstance(cls_probs, (list, np.ndarray)) and len(cls_probs) > 0: return float(cls_probs[0]) elif hasattr(cls_probs, '__getitem__'): return float(cls_probs[0]) else: return float(cls_probs) except (TypeError, IndexError, ValueError) as e: print(f"Error extracting probability: {e}") return 0.0 # 创建主界面 with gr.Blocks( title="Loupe - AI图像伪造检测系统", theme=custom_theme, css=""" /* 全局样式 */ body { background: linear-gradient(-45deg, #ee7752, #e73c7e, #23a6d5, #23d5ab); background-size: 400% 400%; animation: gradientBG 15s ease infinite; min-height: 100vh; } @keyframes gradientBG { 0% { background-position: 0% 50%; } 50% { background-position: 100% 50%; } 100% { background-position: 0% 50%; } } /* 主容器样式 */ .gradio-container { background: rgba(255, 255, 255, 0.95); backdrop-filter: blur(10px); border-radius: 20px; box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1); margin: 20px; padding: 20px; } /* 标题样式 */ .title-box { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); padding: 30px; border-radius: 15px; margin-bottom: 30px; box-shadow: 0 15px 35px rgba(102, 126, 234, 0.3); position: relative; overflow: hidden; } .title-box::before { content: ''; position: absolute; top: -50%; left: -50%; width: 200%; height: 200%; background: linear-gradient(45deg, transparent, rgba(255, 255, 255, 0.1), transparent); animation: shine 3s infinite; } @keyframes shine { 0% { transform: translateX(-100%) translateY(-100%) rotate(45deg); } 100% { transform: translateX(100%) translateY(100%) rotate(45deg); } } .title-text { font-weight: 700; font-size: 32px; color: white; margin-bottom: 8px; text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.3); background: linear-gradient(45deg, #fff, #f0f8ff); -webkit-background-clip: text; -webkit-text-fill-color: transparent; background-clip: text; } .subtitle-text { color: rgba(255, 255, 255, 0.9); font-size: 18px; font-weight: 300; text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.2); } /* 输入和结果框样式 */ .input-box, .result-box { background: linear-gradient(145deg, rgba(255, 255, 255, 0.9), rgba(248, 250, 252, 0.9)); padding: 25px; border-radius: 15px; margin-bottom: 20px; border: 1px solid rgba(255, 255, 255, 0.3); box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1); backdrop-filter: blur(10px); transition: all 0.3s ease; } .input-box:hover, .result-box:hover { transform: translateY(-5px); box-shadow: 0 20px 40px rgba(0, 0, 0, 0.15); } .input-title, .result-title { font-weight: 700; background: linear-gradient(45deg, #667eea, #764ba2); -webkit-background-clip: text; -webkit-text-fill-color: transparent; background-clip: text; margin-bottom: 15px; font-size: 20px; text-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); } /* 按钮样式 */ .btn-primary { background: linear-gradient(45deg, #667eea 0%, #764ba2 100%); border: none; border-radius: 25px; padding: 12px 30px; font-weight: 600; text-transform: uppercase; letter-spacing: 1px; box-shadow: 0 10px 20px rgba(102, 126, 234, 0.3); transition: all 0.3s ease; } .btn-primary:hover { transform: translateY(-3px); box-shadow: 0 15px 30px rgba(102, 126, 234, 0.4); background: linear-gradient(45deg, #764ba2 0%, #667eea 100%); } .btn-secondary { background: linear-gradient(45deg, #f093fb 0%, #f5576c 100%); border: none; border-radius: 25px; padding: 10px 25px; font-weight: 600; box-shadow: 0 8px 16px rgba(240, 147, 251, 0.3); transition: all 0.3s ease; } .btn-secondary:hover { transform: translateY(-2px); box-shadow: 0 12px 24px rgba(240, 147, 251, 0.4); } /* 图片上传区域 */ #upload_image { min-height: 350px; border: 3px dashed rgba(102, 126, 234, 0.3); border-radius: 15px; background: linear-gradient(45deg, rgba(102, 126, 234, 0.05), rgba(118, 75, 162, 0.05)); transition: all 0.3s ease; } #upload_image:hover { border-color: rgba(102, 126, 234, 0.6); background: linear-gradient(45deg, rgba(102, 126, 234, 0.1), rgba(118, 75, 162, 0.1)); transform: scale(1.02); } /* 概率显示 */ #probability input { font-weight: bold; background: linear-gradient(45deg, #667eea, #764ba2); -webkit-background-clip: text; -webkit-text-fill-color: transparent; background-clip: text; font-size: 1.2em; } #result_text input { font-size: 1.1em; font-weight: 600; background: linear-gradient(45deg, rgba(102, 126, 234, 0.1), rgba(118, 75, 162, 0.1)); border-radius: 10px; border: 2px solid rgba(102, 126, 234, 0.2); } /* 画廊样式 */ .gallery-item { border-radius: 12px !important; transition: all 0.3s ease; box-shadow: 0 5px 15px rgba(0, 0, 0, 0.1); } .gallery-item:hover { transform: scale(1.05); box-shadow: 0 10px 25px rgba(0, 0, 0, 0.2); } /* 示例按钮 */ .example-btn { margin-top: 15px; width: 100%; background: linear-gradient(45deg, #23a6d5 0%, #23d5ab 100%); border-radius: 20px; font-weight: 600; box-shadow: 0 8px 16px rgba(35, 166, 213, 0.3); transition: all 0.3s ease; } .example-btn:hover { transform: translateY(-2px); box-shadow: 0 12px 24px rgba(35, 166, 213, 0.4); } /* Tab 样式 */ .tab-nav button { border-radius: 15px 15px 0 0; background: linear-gradient(45deg, rgba(102, 126, 234, 0.8), rgba(118, 75, 162, 0.8)); color: white; font-weight: 600; transition: all 0.3s ease; } .tab-nav button:hover { background: linear-gradient(45deg, rgba(118, 75, 162, 0.9), rgba(102, 126, 234, 0.9)); transform: translateY(-2px); } /* 滑块样式 */ .gr-slider input[type="range"] { background: linear-gradient(45deg, #667eea, #764ba2); border-radius: 10px; } /* 手风琴样式 */ .gr-accordion { background: linear-gradient(145deg, rgba(255, 255, 255, 0.8), rgba(248, 250, 252, 0.8)); border-radius: 15px; border: 1px solid rgba(102, 126, 234, 0.2); box-shadow: 0 5px 15px rgba(0, 0, 0, 0.1); } /* 炫彩加载动画 */ @keyframes rainbow { 0% { background-position: 0% 50%; } 50% { background-position: 100% 50%; } 100% { background-position: 0% 50%; } } .processing { background: linear-gradient(-45deg, #ee7752, #e73c7e, #23a6d5, #23d5ab); background-size: 400% 400%; animation: rainbow 2s ease infinite; } /* 响应式设计 */ @media (max-width: 768px) { .title-text { font-size: 24px; } .subtitle-text { font-size: 16px; } .input-box, .result-box { padding: 20px; } } """ ) as demo: # 标题部分 - 炫彩渐变设计 with gr.Column(elem_classes="title-box"): gr.Markdown("""
🔍 Loupe 图像伪造检测系统
✨ 基于深度学习的图像伪造检测与定位技术
""") # 添加装饰性分割线 gr.HTML("""
""") # 主界面组件 with gr.Row(equal_height=True): with gr.Column(scale=1, min_width=300): # 输入图像区域 - 炫彩设计 with gr.Column(elem_classes="input-box"): gr.Markdown("""
🎨 输入图像
""") with gr.Tabs(): with gr.Tab("📤 上传图片", id="upload_tab"): image_input = gr.Image(type="pil", label="", elem_id="upload_image") upload_button = gr.Button("🚀 开始检测", variant="primary", size="lg", elem_classes="btn-primary") with gr.Tab("🖼️ 示例图片", id="example_tab"): example_images = get_example_images() example_gallery = gr.Gallery( value=example_images, label="", columns=4, rows=None, height="auto", object_fit="contain", allow_preview=True, selected_index=None ) # 添加炫彩检测按钮 example_button = gr.Button( "✨ 检测选中的示例图片", variant="primary", elem_classes="example-btn" ) # 隐藏组件用于存储选中索引 selected_index = gr.Number(visible=False) with gr.Accordion("⚙️ 高级设置", open=False): threshold = gr.Slider(0, 1, value=0.5, step=0.01, label="🎯 检测敏感度") gr.HTML("""
💡 调整数值可改变检测的严格程度
""") with gr.Column(scale=1.5, min_width=500): # 检测结果区域 - 炫彩设计 with gr.Column(elem_classes="result-box"): gr.Markdown("""
🎯 检测结果
""") with gr.Tabs(): with gr.Tab("🔍 检测效果", id="result_tab"): output_image = gr.Image(label="伪造区域标记", interactive=False) with gr.Tab("⚖️ 对比视图", id="compare_tab"): with gr.Row(): original_display = gr.Image(label="原始图像", interactive=False) processed_display = gr.Image(label="检测结果", interactive=False) with gr.Group(): with gr.Row(): fake_prob = gr.Number(label="🎲 伪造概率", precision=2, elem_id="probability") result_text = gr.Textbox(label="📝 检测结论", interactive=False, elem_id="result_text") with gr.Row(): save_button = gr.Button("💾 保存结果", variant="secondary", elem_classes="btn-secondary") clear_button = gr.Button("🧹 清除", variant="secondary", elem_classes="btn-secondary") # 关于部分 - 炫彩设计 with gr.Accordion("🌟 关于系统", open=False): gr.HTML("""

✨ Loupe 伪造图像检测系统

🚀 技术
基于深度学习的图像伪造检测与定位
⭐ 特点
高精度、实时处理、可解释性强
📱 版本
v2.0.0 炫彩版
👥 开发者
EVOL Lab (jyc, xxw)
💡 系统介绍
本系统可检测多种图像篡改痕迹,包括复制-移动、拼接、擦除等操作。采用最新的深度学习算法,提供高精度的检测结果和直观的可视化分析。
""") # 页脚 - 炫彩设计 gr.HTML("""
✨ 感谢使用 Loupe 图像伪造检测系统 ✨
© 2025 EVOL Lab | 让AI守护图像真实性 🛡️
🌟 科技点亮未来,智能守护真实 🌟
""") def process_image(image, threshold_value): """处理上传的图像""" if image is None: return { output_image: None, fake_prob: 0.0, result_text: "❌ 请上传有效图像" } with tempfile.NamedTemporaryFile(suffix=".png", delete=False) as tmp_file: image_path = tmp_file.name image.save(image_path) try: processed_img, cls_probs = process_single_image(image_path) prob = safe_extract_prob(cls_probs) # 根据概率生成炫彩结论 if prob > threshold_value + 0.2: conclusion = "🚨 高度疑似伪造" emoji = "🔴" elif prob > threshold_value: conclusion = "⚠️ 可能伪造" emoji = "🟡" else: conclusion = "✅ 未检测到伪造" emoji = "🟢" return { output_image: processed_img, original_display: image, processed_display: processed_img, fake_prob: prob, result_text: f"{emoji} {conclusion} (概率: {prob:.2f})" } except Exception as e: print(f"Error in processing: {e}") return { output_image: None, original_display: image, processed_display: None, fake_prob: 0.0, result_text: f"❌ 处理错误: {str(e)}" } finally: if os.path.exists(image_path): os.unlink(image_path) def process_example(example_data, selected_idx, threshold_value): """处理示例图像""" if not example_data or selected_idx is None: return { image_input: None, output_image: None, original_display: None, processed_display: None, fake_prob: 0.0, result_text: "⚠️ 请先选择示例图片", threshold: threshold_value } try: selected_idx = int(selected_idx) image_info = example_data[selected_idx] # 处理不同的数据格式 if isinstance(image_info, (tuple, list)): image_path = image_info[0] # (path, caption)格式 elif isinstance(image_info, dict): image_path = image_info.get("name", image_info.get("path")) else: image_path = image_info print(f"Processing selected image (index {selected_idx}): {image_path}") # 调试日志 # 处理图像 processed_img, cls_probs = process_single_image(image_path) prob = safe_extract_prob(cls_probs) original_img = Image.open(image_path) # 根据概率生成炫彩结论 if prob > threshold_value + 0.2: conclusion = "🚨 高度疑似伪造" emoji = "🔴" elif prob > threshold_value: conclusion = "⚠️ 可能伪造" emoji = "🟡" else: conclusion = "✅ 未检测到伪造" emoji = "🟢" return { image_input: original_img, output_image: processed_img, original_display: original_img, processed_display: processed_img, fake_prob: prob, result_text: f"{emoji} {conclusion} (概率: {prob:.2f})", threshold: threshold_value } except Exception as e: print(f"Error in processing example: {e}") return { image_input: None, output_image: None, original_display: None, processed_display: None, fake_prob: 0.0, result_text: f"❌ 示例处理错误: {str(e)}", threshold: threshold_value } def clear_all(): """清除所有输入输出""" return { image_input: None, output_image: None, original_display: None, processed_display: None, fake_prob: 0.0, result_text: "🧹 已清除所有数据" } def update_selected_index(evt: gr.SelectData): """更新选中的图片索引""" return evt.index # 交互逻辑 upload_button.click( process_image, [image_input, threshold], [output_image, original_display, processed_display, fake_prob, result_text] ) # 示例图片选择事件 example_gallery.select( update_selected_index, None, selected_index ) # 示例图片检测按钮点击事件 example_button.click( process_example, [example_gallery, selected_index, threshold], [image_input, output_image, original_display, processed_display, fake_prob, result_text, threshold] ) save_button.click( lambda img: (img.save("result.jpg"), "💾 结果已保存为 result.jpg")[1] if img else "❌ 没有图像可保存", [output_image], None, api_name="save_result" ) clear_button.click( clear_all, [], [image_input, output_image, original_display, processed_display, fake_prob, result_text] ) if __name__ == "__main__": demo.launch( server_name="0.0.0.0", server_port=7864, favicon_path="./favicon.ico" if os.path.exists("./favicon.ico") else None )