import os import csv import base64 from datetime import datetime from pathlib import Path import gradio as gr import google.generativeai as genai from PIL import Image import resend import tempfile class GeminiImageAnalyzer: def __init__(self, api_key): """初始化Gemini客戶端""" self.api_key = api_key # 正確的配置方式 genai.configure(api_key=api_key) # 創建模型實例 self.model = genai.GenerativeModel('gemini-2.0-flash-exp') def validate_image(self, image_path): """驗證圖片格式和存在性""" if not os.path.exists(image_path): raise FileNotFoundError(f"圖片檔案不存在: {image_path}") file_extension = Path(image_path).suffix.lower() if file_extension not in ['.jpg', '.jpeg', '.png', '.gif', '.webp']: raise ValueError("支援 .jpg, .jpeg, .png, .gif, .webp 格式的圖片") return True def analyze_single_image(self, image_path, prompt="請詳細描述這張圖片的內容,並提取圖片中的文字,使用繁體中文"): """分析單張圖片""" try: # 驗證圖片 self.validate_image(image_path) # 直接使用PIL打開圖片 image = Image.open(image_path) # 調用Gemini API的正確方式 response = self.model.generate_content([prompt, image]) return response.text except Exception as e: return f"分析失敗: {str(e)}" def analyze_multiple_images(self, image_paths, comparison_prompt=None): """分析多張圖片並進行比較""" try: if not image_paths or len(image_paths) == 0: return "❌ 請至少上傳一張圖片" # 如果只有一張圖片,使用單圖片分析 if len(image_paths) == 1: return self.analyze_single_image( image_paths[0], comparison_prompt or "請詳細描述這張圖片的內容,並提取圖片中的文字,使用繁體中文" ) # 多圖片分析 images = [] image_info = [] for i, image_path in enumerate(image_paths): # 驗證圖片 self.validate_image(image_path) # 打開圖片 image = Image.open(image_path) images.append(image) image_info.append(f"圖片 {i+1}: {os.path.basename(image_path)}") # 構建比較提示詞 if not comparison_prompt: comparison_prompt = f""" 請詳細分析和比較這 {len(images)} 張圖片。請從以下方面進行分析: 1. **個別圖片描述**: - 分別描述每張圖片的主要內容 - 識別每張圖片中的文字、物品、人物等 2. **相似之處**: - 找出圖片之間的共同點 - 相同的元素、主題、風格等 3. **差異比較**: - 指出圖片之間的明顯差異 - 不同的顏色、構圖、內容、角度等 4. **整體總結**: - 這些圖片可能的關聯性 - 整體給人的印象或傳達的訊息 請使用繁體中文回答,條理清晰地組織你的分析。 """ # 準備內容列表 content_parts = [comparison_prompt] content_parts.extend(images) # 調用API進行比較分析 response = self.model.generate_content(content_parts) return response.text except Exception as e: return f"多圖片分析失敗: {str(e)}" def batch_analyze_images(self, image_paths, individual_prompt=None): """批量分析圖片(每張圖片單獨分析)""" try: if not image_paths or len(image_paths) == 0: return "❌ 請至少上傳一張圖片" results = {} individual_prompt = individual_prompt or "請詳細描述這張圖片的內容,並提取圖片中的文字,使用繁體中文" for i, image_path in enumerate(image_paths): image_name = os.path.basename(image_path) print(f"正在分析圖片 {i+1}/{len(image_paths)}: {image_name}") result = self.analyze_single_image(image_path, individual_prompt) results[f"圖片 {i+1} ({image_name})"] = result # 格式化輸出 formatted_result = "📊 **批量圖片分析結果**\n\n" for image_key, analysis in results.items(): formatted_result += f"## {image_key}\n\n{analysis}\n\n---\n\n" return formatted_result except Exception as e: return f"批量分析失敗: {str(e)}" class EmailSender: def __init__(self, api_key): """初始化Resend郵件客戶端""" resend.api_key = api_key def send_analysis_email(self, to_email, analysis_result, image_count=1, analysis_type="單圖片分析"): """發送分析結果郵件""" try: # 構建HTML郵件內容 html_content = f"""

🤖 Gemini AI 圖片分析結果

智能圖片分析與比較系統

📋 分析類型:{analysis_type} | 📸 圖片數量:{image_count} 張

🔍 分析結果

{analysis_result}
🕐 產生時間:{datetime.now().strftime("%Y年%m月%d日 %H:%M:%S")}
🚀 Powered by Google Gemini 2.0 & Resend
""" params = { "from": "Gemini AI ", "to": [to_email], "subject": f"🤖 AI圖片分析結果 - {analysis_type} ({image_count}張圖片)", "html": html_content } email = resend.Emails.send(params) return f"郵件發送成功!郵件ID: {email.get('id', 'N/A')}" except Exception as e: return f"郵件發送失敗: {str(e)}" def analyze_images_and_send_email(images, analysis_mode, custom_prompt, recipient_email, gemini_key, resend_key): """分析圖片並發送郵件""" if not images or len(images) == 0: return "❌ 請至少上傳一張圖片", "", "" if not recipient_email: return "❌ 請輸入收件人信箱", "", "" if not gemini_key: return "❌ 請輸入Gemini API Key", "", "" if not resend_key: return "❌ 請輸入Resend API Key", "", "" try: # 初始化服務 analyzer = GeminiImageAnalyzer(gemini_key) email_sender = EmailSender(resend_key) # 獲取圖片路徑列表 image_paths = images if isinstance(images, list) else [images] image_count = len(image_paths) # 根據分析模式選擇分析方法 if analysis_mode == "比較分析": analysis_result = analyzer.analyze_multiple_images(image_paths, custom_prompt) analysis_type = "多圖片比較分析" elif analysis_mode == "批量分析": analysis_result = analyzer.batch_analyze_images(image_paths, custom_prompt) analysis_type = "批量個別分析" else: # 單張分析 if len(image_paths) > 1: # 如果上傳多張但選擇單張分析,只分析第一張 analysis_result = analyzer.analyze_single_image(image_paths[0], custom_prompt) analysis_type = "單圖片分析(僅分析第一張)" image_count = 1 else: analysis_result = analyzer.analyze_single_image(image_paths[0], custom_prompt) analysis_type = "單圖片分析" if analysis_result.startswith(("分析失敗", "多圖片分析失敗", "批量分析失敗", "❌")): return f"❌ {analysis_result}", analysis_result, "" # 發送郵件 email_result = email_sender.send_analysis_email( recipient_email, analysis_result, image_count, analysis_type ) status = f"✅ 處理完成!\n📊 分析模式:{analysis_type}\n📸 處理圖片:{image_count} 張\n📧 {email_result}" return status, analysis_result, email_result except Exception as e: error_msg = f"❌ 處理失敗: {str(e)}" return error_msg, "", "" def save_results_to_csv(images, analysis_mode, custom_prompt, analysis_result): """儲存結果到CSV""" try: timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") temp_dir = tempfile.gettempdir() csv_filename = os.path.join(temp_dir, f"multi_image_analysis_{timestamp}.csv") with open(csv_filename, 'w', newline='', encoding='utf-8-sig') as csvfile: fieldnames = ['時間戳記', '分析模式', '圖片數量', '圖片列表', '自訂提示', '分析結果'] writer = csv.DictWriter(csvfile, fieldnames=fieldnames) writer.writeheader() # 處理圖片列表 if images: image_list = [os.path.basename(img) if isinstance(img, str) else f"圖片_{i+1}" for i, img in enumerate(images)] image_names = "; ".join(image_list) image_count = len(images) else: image_names = "無" image_count = 0 writer.writerow({ '時間戳記': datetime.now().strftime("%Y-%m-%d %H:%M:%S"), '分析模式': analysis_mode, '圖片數量': image_count, '圖片列表': image_names, '自訂提示': custom_prompt or "預設提示", '分析結果': analysis_result }) return csv_filename except Exception as e: return f"儲存失敗: {str(e)}" # 建立Gradio介面 def create_interface(): with gr.Blocks( title="🤖 Gemini 多圖片分析比較助手", theme=gr.themes.Soft(), css=""" .gradio-container { max-width: 1400px !important; margin: auto !important; } .main-header { text-align: center; margin-bottom: 2rem; padding: 1.5rem; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; border-radius: 15px; box-shadow: 0 4px 15px rgba(0,0,0,0.2); } .api-warning { background: linear-gradient(135deg, #fff3cd 0%, #ffeaa7 100%); border: 1px solid #ffeaa7; border-radius: 10px; padding: 15px; margin: 15px 0; color: #856404; box-shadow: 0 2px 8px rgba(0,0,0,0.1); } .feature-highlight { background: linear-gradient(135deg, #e3f2fd 0%, #bbdefb 100%); border: 2px solid #2196f3; border-radius: 10px; padding: 15px; margin: 15px 0; color: #1976d2; } """ ) as demo: gr.HTML("""

🤖 Gemini 多圖片分析比較助手

支援單張分析、多圖比較、批量處理 - 一站式AI圖片分析解決方案

🚀 部署於 Hugging Face Spaces | 🔥 支援最新 Gemini 2.0

""") # 新功能亮點 gr.HTML("""

🎯 新增功能亮點:

""") # API 安全提醒 gr.HTML("""
⚠️ 重要提醒:
• 請勿在公共環境中分享您的 API 密鑰
• 建議在本地環境或私人部署中使用敏感功能
• 此應用程式不會儲存您的 API 密鑰
• 多圖片分析會消耗更多 API 配額,請注意使用量
""") with gr.Row(): with gr.Column(scale=1): gr.Markdown("### 📁 圖片上傳") images_input = gr.File( label="選擇圖片 (支援多張上傳)", file_count="multiple", file_types=["image"], height=200 ) # 顯示上傳的圖片預覽 images_gallery = gr.Gallery( label="圖片預覽", show_label=True, elem_id="gallery", columns=3, rows=2, height=300, object_fit="contain" ) gr.Markdown("### 🎯 分析模式") analysis_mode = gr.Radio( choices=["單張分析", "比較分析", "批量分析"], value="比較分析", label="選擇分析模式", info=""" • 單張分析:分析單張圖片(如上傳多張僅分析第一張) • 比較分析:比較多張圖片的差異和相似點(推薦) • 批量分析:對每張圖片進行單獨分析 """ ) gr.Markdown("### ✏️ 分析設定") prompt_input = gr.Textbox( label="自訂提示詞 (可選)", placeholder="留空將使用智能預設提示詞...", lines=3, info="不同分析模式會自動使用相應的最佳化提示詞" ) recipient_email = gr.Textbox( label="📧 收件人信箱", placeholder="your-email@example.com" ) with gr.Column(scale=1): gr.Markdown("### 🔑 API 設定") with gr.Accordion("如何獲取 API Keys", open=False): gr.Markdown(""" **Gemini API Key:** 1. 前往 [Google AI Studio](https://aistudio.google.com/app/apikey) 2. 創建新的 API Key 3. 複製 API Key(格式:AIza...) **Resend API Key:** 1. 前往 [Resend](https://resend.com/api-keys) 2. 註冊帳號並創建 API Key 3. 複製 API Key(格式:re_...) **注意事項:** - 多圖片分析會消耗更多 API 配額 - 建議先用少量圖片測試 - 大型圖片會增加處理時間 """) gemini_key = gr.Textbox( label="Gemini API Key", placeholder="AIza...", type="password" ) resend_key = gr.Textbox( label="Resend API Key", placeholder="re_...", type="password" ) gr.Markdown("### 🚀 執行操作") analyze_btn = gr.Button( "🔍 開始AI分析並發送郵件", variant="primary", size="lg" ) gr.Markdown("### 📊 處理結果") with gr.Row(): status_output = gr.Textbox( label="📈 處理狀態", lines=4, interactive=False ) with gr.Row(): with gr.Column(): analysis_output = gr.Textbox( label="🤖 AI分析結果", lines=15, interactive=False ) with gr.Column(): email_output = gr.Textbox( label="📧 郵件發送結果", lines=15, interactive=False ) with gr.Row(): download_btn = gr.Button("💾 下載詳細CSV結果", variant="secondary") csv_file = gr.File(label="CSV檔案", visible=False) # 事件綁定 def update_gallery(files): if files: return [file.name for file in files] return [] images_input.change( fn=update_gallery, inputs=[images_input], outputs=[images_gallery] ) analyze_btn.click( fn=analyze_images_and_send_email, inputs=[images_input, analysis_mode, prompt_input, recipient_email, gemini_key, resend_key], outputs=[status_output, analysis_output, email_output] ) def download_csv(images, analysis_mode, custom_prompt, analysis_result): if analysis_result and not analysis_result.startswith("❌"): filename = save_results_to_csv(images, analysis_mode, custom_prompt, analysis_result) if not filename.startswith("儲存失敗"): return gr.update(value=filename, visible=True) return gr.update(visible=False) download_btn.click( fn=download_csv, inputs=[images_input, analysis_mode, prompt_input, analysis_output], outputs=csv_file ) # 使用說明 with gr.Accordion("📝 詳細使用說明", open=False): gr.Markdown(""" ### 🎯 分析模式說明 **1. 單張分析** - 適用:單張圖片的詳細分析 - 特點:深入分析圖片內容、文字識別 - 建議:需要詳細了解單張圖片內容時使用 **2. 比較分析(推薦)** - 適用:2-10張圖片的對比分析 - 特點:找出圖片間的差異、相似點、關聯性 - 建議:需要對比多張圖片時使用,最適合2-5張圖片 **3. 批量分析** - 適用:多張圖片需要分別分析 - 特點:每張圖片獨立分析,生成個別報告 - 建議:需要處理大量圖片且每張都需單獨報告 ### 📝 使用步驟 1. **上傳圖片**:支援 JPG、PNG、GIF、WebP 格式 2. **選擇模式**:根據需求選擇分析模式 3. **設定提示**:可自訂提示詞或使用預設 4. **輸入信箱**:設定接收結果的郵箱 5. **配置API**:輸入必要的API密鑰 6. **開始分析**:點擊按鈕開始處理 7. **查看結果**:在介面查看結果或等待郵件 ### ⚡ 效能建議 - **圖片數量**:建議一次處理2-10張圖片 - **圖片大小**:單張圖片建議不超過10MB - **處理時間**:多圖片分析需要更長時間,請耐心等待 - **API配額**:注意API使用量,避免超出限制 ### 🛠️ 故障排除 - 如果分析失敗,檢查API密鑰是否正確 - 確保圖片格式支援且未損壞 - 網路不穩定時可能需要重試 - 大量圖片處理時間較長,請耐心等待 """) # 提示詞範例 with gr.Accordion("🎯 提示詞範例", open=False): gr.Markdown("### 📸 單張分析提示詞") gr.Examples( examples=[ ["請詳細描述這張圖片,包括主要物件、顏色、構圖和可能的背景故事"], ["提取並分析圖片中的所有文字內容,並解釋其含義"], ["分析圖片的藝術風格、攝影技法和視覺效果"], ["識別圖片中的品牌、標誌和商業元素"], ["描述圖片中人物的情緒、動作和互動關係"] ], inputs=prompt_input, label="點擊使用單張分析範例" ) gr.Markdown("### 🔄 比較分析提示詞") gr.Examples( examples=[ ["比較這些圖片的相似點和差異,分析它們的關聯性"], ["分析這些圖片在構圖、色彩和風格上的異同"], ["比較圖片中產品或物件的特徵差異"], ["分析這些圖片的時間順序和變化過程"], ["比較不同角度或場景下同一主題的表現差異"] ], inputs=prompt_input, label="點擊使用比較分析範例" ) # 版本資訊 gr.HTML("""

🚀 Powered by Google Gemini 2.0 & Resend
📍 Deployed on Hugging Face Spaces
🔥 多圖片比較分析版本 2.0 | Made with ❤️ using Gradio
⭐ 支援最多10張圖片同時分析比較

""") return demo # 主程式入口 if __name__ == "__main__": # 建立並啟動介面 demo = create_interface() # Hugging Face Spaces 部署設定 demo.launch( share=False, show_error=True, show_api=False, quiet=False )