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("""