Spaces:
Sleeping
Sleeping
| import gradio as gr | |
| import cv2 | |
| import numpy as np | |
| from PIL import Image | |
| from groq import Groq | |
| import base64 | |
| import io | |
| import csv | |
| import pandas as pd | |
| from docx import Document | |
| import tempfile | |
| import os | |
| from datetime import datetime | |
| from linebot import LineBotApi | |
| from linebot.models import TextSendMessage | |
| # LINE Bot 設定 | |
| LINE_CHANNEL_ACCESS_TOKEN = 'YrPDXLY2jRrcfIyw4sDWC9Hk0AhTa37gAHR5Sof/8RPzqFdNEQhhJg+qv1Wt7qSINh508AANgQ9BjbouYnOnvTBrROcuYvC5tcr5tBcgNXNvspjV1UZPEvD+O7Pf93LLlk4MxbQIbn+tB7ZIvjx36wdB04t89/1O/w1cDnyilFU=' | |
| def send_line_message(user_id, message, line_token=None): | |
| """ | |
| Send a message to a specific LINE user | |
| :param user_id: LINE user ID to send message to | |
| :param message: Text message to send | |
| :param line_token: Optional custom LINE token | |
| """ | |
| try: | |
| # 使用自訂 token 或預設 token | |
| token = line_token if line_token else LINE_CHANNEL_ACCESS_TOKEN | |
| if not token.strip(): | |
| return False, "LINE Token 不能為空" | |
| if not user_id.strip(): | |
| return False, "User ID 不能為空" | |
| # Initialize LineBotApi with the Channel Access Token | |
| line_bot_api = LineBotApi(token) | |
| # Create a TextSendMessage | |
| text_message = TextSendMessage(text=message) | |
| # Send the message | |
| line_bot_api.push_message(user_id, text_message) | |
| print(f"Message successfully sent to {user_id}") | |
| return True, "訊息發送成功!" | |
| except Exception as e: | |
| error_msg = f"發送訊息時發生錯誤: {str(e)}" | |
| print(error_msg) | |
| return False, error_msg | |
| def preview_image(image, flip_horizontal=False): | |
| """預覽圖片,顯示翻轉效果""" | |
| if image is None: | |
| return None | |
| # 直接顯示原圖,不進行任何翻轉 | |
| # 因為攝像頭拍照後的圖片已經是正確方向 | |
| if flip_horizontal: | |
| # 只有用戶勾選時才翻轉 | |
| return image.transpose(Image.FLIP_LEFT_RIGHT) | |
| else: | |
| # 預設顯示原圖(正確方向) | |
| return image | |
| def encode_image(image): | |
| """將 PIL Image 轉換為 base64 編碼""" | |
| buffered = io.BytesIO() | |
| image.save(buffered, format="JPEG") | |
| return base64.b64encode(buffered.getvalue()).decode("utf-8") | |
| def process_ocr_and_send_line(image, api_key, prompt, flip_horizontal, | |
| send_to_line, line_token, line_user_id): | |
| """使用 Groq API 進行 OCR 文字辨識並可選擇發送到 LINE""" | |
| if image is None: | |
| return "請先拍照或上傳圖片", None, None, "未執行" | |
| if not api_key.strip(): | |
| return "請輸入有效的 Groq API Key", None, None, "未執行" | |
| if not prompt.strip(): | |
| prompt = "請幫我辨識拍照的文字和內容進行OCR辨識" | |
| try: | |
| # 只有在用戶明確要求時才翻轉圖片 | |
| processed_image = image | |
| if flip_horizontal: | |
| processed_image = image.transpose(Image.FLIP_LEFT_RIGHT) | |
| print("已手動翻轉圖片") | |
| else: | |
| print("使用原始圖片方向") | |
| # 將圖片轉換為 base64 | |
| base64_image = encode_image(processed_image) | |
| image_content = { | |
| "type": "image_url", | |
| "image_url": {"url": f"data:image/jpeg;base64,{base64_image}"} | |
| } | |
| # 初始化 Groq 客戶端 | |
| client = Groq(api_key=api_key) | |
| # 發送 API 請求 | |
| completion = client.chat.completions.create( | |
| model="meta-llama/llama-4-scout-17b-16e-instruct", | |
| messages=[{ | |
| "role": "user", | |
| "content": [ | |
| {"type": "text", "text": prompt}, | |
| image_content | |
| ] | |
| }], | |
| temperature=1, | |
| max_completion_tokens=512, | |
| top_p=1, | |
| stream=False, | |
| stop=None, | |
| ) | |
| # 獲取辨識結果 | |
| content = completion.choices[0].message.content | |
| # 生成輸出檔案 | |
| csv_file = create_csv_output(content) | |
| docx_file = create_docx_output(content) | |
| # 準備 LINE 發送狀態 | |
| line_status = "未執行" | |
| # 如果用戶選擇發送到 LINE | |
| if send_to_line: | |
| if not line_user_id.strip(): | |
| line_status = "錯誤:請輸入 LINE User ID" | |
| else: | |
| # 準備要發送的訊息 | |
| timestamp = datetime.now().strftime('%Y-%m-%d %H:%M:%S') | |
| line_message = f"""📸 OCR 辨識結果 | |
| 🕒 辨識時間: {timestamp} | |
| 📝 辨識內容: | |
| {content} | |
| --- | |
| 由 OCR 文字辨識系統自動發送""" | |
| # 發送到 LINE | |
| success, message = send_line_message(line_user_id, line_message, line_token) | |
| if success: | |
| line_status = f"✅ {message}" | |
| else: | |
| line_status = f"❌ {message}" | |
| return content, csv_file, docx_file, line_status | |
| except Exception as e: | |
| error_msg = f"OCR 辨識發生錯誤: {str(e)}" | |
| return error_msg, None, None, "未執行" | |
| def create_csv_output(content): | |
| """創建 CSV 輸出檔案""" | |
| try: | |
| # 創建臨時檔案 | |
| temp_file = tempfile.NamedTemporaryFile(mode='w', delete=False, suffix='.csv', encoding='utf-8-sig') | |
| # 將辨識結果寫入 CSV | |
| writer = csv.writer(temp_file) | |
| writer.writerow(['時間戳記', 'OCR辨識結果']) | |
| writer.writerow([datetime.now().strftime('%Y-%m-%d %H:%M:%S'), content]) | |
| # 如果內容包含多行,每行作為一個記錄 | |
| lines = content.split('\n') | |
| if len(lines) > 1: | |
| writer.writerow([]) # 空行分隔 | |
| writer.writerow(['行號', '內容']) | |
| for i, line in enumerate(lines, 1): | |
| if line.strip(): | |
| writer.writerow([i, line.strip()]) | |
| temp_file.close() | |
| return temp_file.name | |
| except Exception as e: | |
| print(f"創建 CSV 檔案時發生錯誤: {e}") | |
| return None | |
| def create_docx_output(content): | |
| """創建 DOCX 輸出檔案""" | |
| try: | |
| # 創建臨時檔案 | |
| temp_file = tempfile.NamedTemporaryFile(delete=False, suffix='.docx') | |
| temp_file.close() | |
| # 創建 Word 文件 | |
| doc = Document() | |
| doc.add_heading('OCR 辨識結果', 0) | |
| # 添加時間戳記 | |
| doc.add_heading('辨識時間', level=1) | |
| doc.add_paragraph(datetime.now().strftime('%Y年%m月%d日 %H:%M:%S')) | |
| # 添加辨識內容 | |
| doc.add_heading('辨識內容', level=1) | |
| doc.add_paragraph(content) | |
| # 如果內容包含多行,分段顯示 | |
| lines = content.split('\n') | |
| if len(lines) > 3: | |
| doc.add_heading('分行內容', level=1) | |
| for i, line in enumerate(lines, 1): | |
| if line.strip(): | |
| doc.add_paragraph(f"{i}. {line.strip()}") | |
| doc.save(temp_file.name) | |
| return temp_file.name | |
| except Exception as e: | |
| print(f"創建 DOCX 檔案時發生錯誤: {e}") | |
| return None | |
| def clear_inputs(): | |
| """清除輸入內容""" | |
| return None, "", "", "", "", False, "未執行" | |
| def test_line_message(line_token, line_user_id): | |
| """測試 LINE 訊息發送""" | |
| if not line_user_id.strip(): | |
| return "❌ 請輸入 LINE User ID" | |
| test_message = f"""🔔 測試訊息 | |
| 這是來自 OCR 文字辨識系統的測試訊息。 | |
| 🕒 測試時間: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')} | |
| 如果您收到此訊息,表示 LINE Bot 設定成功!""" | |
| success, message = send_line_message(line_user_id, test_message, line_token) | |
| if success: | |
| return f"✅ {message}" | |
| else: | |
| return f"❌ {message}" | |
| # 創建 Gradio 介面 | |
| with gr.Blocks(title="OCR + LINE Bot 整合系統", theme=gr.themes.Soft()) as demo: | |
| gr.Markdown("# 📸📱 OCR + LINE Bot 整合系統") | |
| gr.Markdown("使用攝像頭拍照或上傳圖片,透過 AI 進行文字辨識,並可選擇將結果發送到 LINE") | |
| with gr.Row(): | |
| with gr.Column(scale=1): | |
| # 輸入區域 | |
| gr.Markdown("### 📝 輸入設定") | |
| # 圖片輸入 | |
| image_input = gr.Image( | |
| sources=['webcam', 'upload'], | |
| type='pil', | |
| label="拍照或上傳圖片" | |
| ) | |
| # API Key 輸入 | |
| api_key_input = gr.Textbox( | |
| label="Groq API Key", | |
| placeholder="請輸入您的 Groq API Key", | |
| type="password", | |
| value="" | |
| ) | |
| # 提示詞輸入 | |
| prompt_input = gr.Textbox( | |
| label="辨識提示詞", | |
| placeholder="輸入您想要的辨識提示...", | |
| value="請幫我辨識拍照的文字和內容進行OCR辨識,請盡可能詳細和準確地提取所有可見的文字內容。", | |
| lines=3 | |
| ) | |
| # 鏡像修正選項 | |
| flip_checkbox = gr.Checkbox( | |
| label="🔄 手動翻轉圖片(如果文字方向不對才勾選)", | |
| value=False, | |
| info="通常攝像頭拍照後圖片方向是正確的,只在文字顛倒時才勾選此選項" | |
| ) | |
| # 圖片預覽 | |
| preview_image_output = gr.Image( | |
| label="圖片預覽(將要辨識的圖片)", | |
| type='pil', | |
| interactive=False | |
| ) | |
| # LINE Bot 設定 | |
| gr.Markdown("### 📱 LINE Bot 設定") | |
| send_to_line_checkbox = gr.Checkbox( | |
| label="📤 將辨識結果發送到 LINE", | |
| value=False, | |
| info="勾選後會將 OCR 結果自動發送到指定的 LINE 用戶" | |
| ) | |
| line_token_input = gr.Textbox( | |
| label="LINE Channel Access Token(選填)", | |
| placeholder="留空則使用預設 Token", | |
| type="password", | |
| value="" | |
| ) | |
| line_user_id_input = gr.Textbox( | |
| label="LINE User ID", | |
| placeholder="請輸入要發送訊息的 LINE User ID", | |
| value="U377f923cd08097b0a01116f8e942650b" | |
| ) | |
| # 操作按鈕 | |
| with gr.Row(): | |
| process_btn = gr.Button("🔍 開始辨識", variant="primary") | |
| test_line_btn = gr.Button("📱 測試 LINE", variant="secondary") | |
| clear_btn = gr.Button("🗑️ 清除", variant="secondary") | |
| with gr.Column(scale=1): | |
| # 輸出區域 | |
| gr.Markdown("### 📊 辨識結果") | |
| # 文字結果輸出 | |
| text_output = gr.Textbox( | |
| label="辨識結果", | |
| placeholder="辨識結果將顯示在這裡...", | |
| lines=8, | |
| max_lines=12 | |
| ) | |
| # LINE 發送狀態 | |
| line_status_output = gr.Textbox( | |
| label="LINE 發送狀態", | |
| placeholder="LINE 發送狀態將顯示在這裡...", | |
| lines=2, | |
| interactive=False | |
| ) | |
| # 檔案下載 | |
| gr.Markdown("### 📁 下載檔案") | |
| with gr.Row(): | |
| csv_download = gr.File( | |
| label="下載 CSV 檔案", | |
| visible=True | |
| ) | |
| docx_download = gr.File( | |
| label="下載 DOCX 檔案", | |
| visible=True | |
| ) | |
| # 使用說明 | |
| with gr.Accordion("📖 使用說明", open=False): | |
| gr.Markdown(""" | |
| ### 使用步驟: | |
| 1. **獲取 Groq API Key**:前往 [Groq官網](https://groq.com) 註冊並取得 API Key | |
| 2. **設定 LINE Bot**(選用): | |
| - 輸入您的 LINE User ID | |
| - 可選擇輸入自訂的 LINE Channel Access Token | |
| - 點擊「測試 LINE」確認設定正確 | |
| 3. **拍照或上傳**:使用攝像頭拍照或上傳包含文字的圖片 | |
| 4. **檢查方向**:查看圖片預覽中的文字方向是否正確 | |
| 5. **必要時翻轉**:只有當文字看起來是顛倒的時候才勾選「手動翻轉圖片」 | |
| 6. **輸入 API Key**:在上方欄位輸入您的 Groq API Key | |
| 7. **選擇發送方式**:勾選「將辨識結果發送到 LINE」如果您想要自動發送結果 | |
| 8. **開始辨識**:點擊「開始辨識」按鈕 | |
| 9. **下載結果**:辨識完成後可下載 CSV 和 DOCX 格式的結果檔案 | |
| ### 支援功能: | |
| - 📷 即時攝像頭拍照 | |
| - 🔄 攝像頭鏡像修正(解決左右相反問題) | |
| - 📤 圖片檔案上傳 | |
| - 🤖 AI 文字辨識 | |
| - 📱 LINE Bot 訊息發送 | |
| - 📄 CSV 格式輸出 | |
| - 📝 Word 文檔輸出 | |
| - 🔧 自訂提示詞 | |
| ### LINE User ID 取得方式: | |
| 1. 在 LINE 中加入您的 LINE Bot 為好友 | |
| 2. 傳送任意訊息給 Bot | |
| 3. 在 Bot 後台查看 User ID | |
| 4. 或使用 LINE 開發者工具取得 | |
| """) | |
| # 事件綁定 | |
| process_btn.click( | |
| fn=process_ocr_and_send_line, | |
| inputs=[image_input, api_key_input, prompt_input, flip_checkbox, | |
| send_to_line_checkbox, line_token_input, line_user_id_input], | |
| outputs=[text_output, csv_download, docx_download, line_status_output] | |
| ) | |
| test_line_btn.click( | |
| fn=test_line_message, | |
| inputs=[line_token_input, line_user_id_input], | |
| outputs=line_status_output | |
| ) | |
| clear_btn.click( | |
| fn=clear_inputs, | |
| outputs=[image_input, text_output, api_key_input, prompt_input, | |
| line_token_input, send_to_line_checkbox, line_status_output] | |
| ) | |
| # 圖片預覽更新事件 | |
| image_input.change( | |
| fn=preview_image, | |
| inputs=[image_input, flip_checkbox], | |
| outputs=preview_image_output | |
| ) | |
| flip_checkbox.change( | |
| fn=preview_image, | |
| inputs=[image_input, flip_checkbox], | |
| outputs=preview_image_output | |
| ) | |
| # 啟動應用程式 | |
| if __name__ == "__main__": | |
| demo.launch( | |
| share=True, | |
| server_name="0.0.0.0", | |
| server_port=None, # 讓 Gradio 自動尋找可用端口 | |
| show_error=True | |
| ) |