import gradio as gr import pandas as pd from PIL import Image import easyocr import numpy as np import re import traceback class FinalOCRProcessor: def __init__(self): self.reader = None self.lang_config = "未初始化" self.initialize_reader() def initialize_reader(self): """安全初始化EasyOCR""" print("正在初始化OCR引擎...") # 嘗試不同的語言配置 configs = [ (['ch_tra', 'en'], "繁體中文+英文"), (['ch_sim', 'en'], "簡體中文+英文"), (['en'], "僅英文") ] for lang_list, description in configs: try: print(f"嘗試配置:{description}") self.reader = easyocr.Reader(lang_list, gpu=False) self.lang_config = description print(f"✓ 成功初始化:{description}") return except Exception as e: print(f"✗ 配置 {description} 失敗:{str(e)}") continue print("❌ 所有配置都失敗了") self.reader = None self.lang_config = "初始化失敗" def process_image(self, image): """處理圖片的主要函數""" if self.reader is None: return self.create_error_result("OCR引擎未正確初始化") if image is None: return self.create_error_result("請上傳圖片") try: # 轉換圖片格式 if isinstance(image, np.ndarray): img_array = image else: img_array = np.array(image) print("開始OCR識別...") # 執行OCR results = self.reader.readtext(img_array) print(f"識別完成,找到 {len(results)} 個文字區塊") if not results: return self.create_empty_result("圖片中未找到任何文字") # 處理OCR結果 data_list = [] for i, (bbox, text, confidence) in enumerate(results): # 計算位置信息 x_coords = [point[0] for point in bbox] y_coords = [point[1] for point in bbox] left = int(min(x_coords)) right = int(max(x_coords)) top = int(min(y_coords)) bottom = int(max(y_coords)) center_x = int((left + right) / 2) center_y = int((top + bottom) / 2) data_list.append({ '序號': i + 1, '識別文字': text.strip(), '信心度': round(float(confidence), 3), '中心X': center_x, '中心Y': center_y, '左': left, '上': top, '右': right, '下': bottom, '寬度': right - left, '高度': bottom - top, '類型': self.get_text_type(text.strip()) }) # 創建DataFrame並排序 df = pd.DataFrame(data_list) df = df.sort_values(['中心Y', '中心X']).reset_index(drop=True) # 重新編號 df['序號'] = range(1, len(df) + 1) # 生成摘要 summary = self.generate_summary(df) # 嘗試重建表格 table_df = self.reconstruct_table(df) return df, table_df, summary except Exception as e: error_msg = f"處理圖片時出錯:{str(e)}" print(error_msg) print(traceback.format_exc()) return self.create_error_result(error_msg) def get_text_type(self, text): """判斷文字類型""" if not text: return "空白" # 日期格式 if re.search(r'\d+月\d+日', text): return "日期" # 時間格式 if re.search(r'\d{1,2}[-:]\d{1,2}', text): return "時間" # 純數字 if re.match(r'^\d+$', text): return "數字" # 中文姓名或詞語 if re.match(r'^[\u4e00-\u9fff]{1,6}$', text): return "中文" # 包含中文的混合內容 if re.search(r'[\u4e00-\u9fff]', text): return "中文混合" # 英文 if re.match(r'^[a-zA-Z\s]+$', text): return "英文" return "其他" def generate_summary(self, df): """生成識別摘要""" if df.empty: return "沒有識別到任何內容" total = len(df) avg_conf = df['信心度'].mean() high_conf = (df['信心度'] >= 0.8).sum() summary = f"""🔍 OCR識別報告 {'='*40} 📊 基本統計 • 引擎配置:{self.lang_config} • 識別區塊:{total} 個 • 平均信心度:{avg_conf:.3f} • 高信心度區塊:{high_conf} 個 ({high_conf/total*100:.1f}%) 📝 文字類型統計 """ # 統計各類型數量 type_counts = df['類型'].value_counts() for text_type, count in type_counts.items(): percentage = count / total * 100 summary += f"• {text_type}:{count} 個 ({percentage:.1f}%)\n" # 品質評估 if avg_conf >= 0.8: quality = "優秀 ✅" elif avg_conf >= 0.6: quality = "良好 ⚠️" else: quality = "待改進 ❌" summary += f"\n🎯 整體品質:{quality}" return summary def reconstruct_table(self, df): """重建表格結構""" if df.empty: return pd.DataFrame() try: # 按Y座標分組形成行 rows = [] sorted_df = df.sort_values('中心Y') current_row = [] last_y = None y_threshold = 25 # Y座標閾值 for _, item in sorted_df.iterrows(): if last_y is None or abs(item['中心Y'] - last_y) <= y_threshold: current_row.append(item) else: if current_row: # 按X座標排序 current_row.sort(key=lambda x: x['中心X']) rows.append(current_row) current_row = [item] last_y = item['中心Y'] # 添加最後一行 if current_row: current_row.sort(key=lambda x: x['中心X']) rows.append(current_row) if not rows: return pd.DataFrame() # 構建表格數據 table_data = [] max_cols = max(len(row) for row in rows) for row_idx, row_items in enumerate(rows): row_dict = {'行號': row_idx + 1} for col_idx, item in enumerate(row_items): col_name = f'第{col_idx + 1}列' row_dict[col_name] = item['識別文字'] # 填充空列 for col_idx in range(len(row_items), max_cols): col_name = f'第{col_idx + 1}列' row_dict[col_name] = "" table_data.append(row_dict) return pd.DataFrame(table_data) except Exception as e: print(f"重建表格失敗:{e}") return pd.DataFrame([{"錯誤": "表格重建失敗"}]) def create_error_result(self, message): """創建錯誤結果""" error_df = pd.DataFrame([{"錯誤信息": message}]) return error_df, error_df, f"❌ {message}" def create_empty_result(self, message): """創建空結果""" empty_df = pd.DataFrame() return empty_df, empty_df, f"ℹ️ {message}" def main(): """主函數,創建Gradio應用""" # 初始化處理器 processor = FinalOCRProcessor() def process_image_wrapper(image, min_confidence): """包裝處理函數""" try: # 處理圖片 df, table_df, summary = processor.process_image(image) # 應用信心度過濾 if not df.empty and '信心度' in df.columns: original_count = len(df) df = df[df['信心度'] >= min_confidence].reset_index(drop=True) filtered_count = len(df) if filtered_count < original_count: summary += f"\n\n🔍 信心度過濾:保留 {filtered_count}/{original_count} 個結果" return summary, df, table_df except Exception as e: error_msg = f"處理失敗:{str(e)}" error_df = pd.DataFrame([{"錯誤": error_msg}]) return error_msg, error_df, error_df # 創建Gradio界面 with gr.Blocks( title="中文OCR識別系統", theme=gr.themes.Default() ) as app: # 標題和說明 gr.HTML("""

🔍 中文OCR識別系統

支持中文、英文、數字識別,自動重建表格結構

""") # 主要操作區域 with gr.Row(): # 左側:圖片上傳和控制 with gr.Column(scale=1): image_input = gr.Image( label="📤 上傳圖片", type="pil" ) confidence_slider = gr.Slider( minimum=0.0, maximum=1.0, value=0.3, step=0.1, label="🎯 最低信心度閾值" ) process_button = gr.Button( "🚀 開始識別", variant="primary" ) # 右側:摘要報告 with gr.Column(scale=1): summary_output = gr.Textbox( label="📊 識別報告", lines=15, max_lines=20 ) # 結果展示區域 with gr.Row(): with gr.Column(): gr.Markdown("### 📋 詳細識別結果") detail_output = gr.Dataframe( label="所有識別的文字內容", interactive=True ) with gr.Column(): gr.Markdown("### 🔄 重建表格") table_output = gr.Dataframe( label="按表格結構重新組織的數據", interactive=True ) # 使用說明 with gr.Accordion("📖 使用說明", open=False): gr.Markdown(""" ### 🚀 快速開始 1. **上傳圖片**:點擊上傳包含文字的圖片 2. **調整參數**:設置最低信心度(建議0.3-0.7) 3. **開始識別**:點擊按鈕或直接上傳圖片自動處理 4. **查看結果**:在兩個表格中查看識別結果 ### 📊 結果說明 - **詳細識別結果**:每個文字區塊的完整信息 - **重建表格**:嘗試還原原始表格的行列結構 - **識別報告**:統計信息和品質評估 ### 💡 優化建議 - 使用清晰、高對比度的圖片 - 確保文字大小適中(不要太小) - 避免圖片傾斜或變形 - 調整信心度閾值獲得最佳結果 """) # 綁定事件 process_button.click( fn=process_image_wrapper, inputs=[image_input, confidence_slider], outputs=[summary_output, detail_output, table_output] ) # 圖片上傳時自動處理 image_input.change( fn=process_image_wrapper, inputs=[image_input, confidence_slider], outputs=[summary_output, detail_output, table_output] ) return app if __name__ == "__main__": app = main() app.launch( server_name="0.0.0.0", server_port=7860, share=True )