Working_time / app.py
KKingzor's picture
Update app.py
dd516dc verified
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("""
<div style="text-align: center; padding: 20px; background: #f0f2f6; border-radius: 10px; margin-bottom: 20px;">
<h1 style="color: #1f2937; margin-bottom: 10px;">🔍 中文OCR識別系統</h1>
<p style="color: #6b7280; font-size: 16px;">
支持中文、英文、數字識別,自動重建表格結構
</p>
</div>
""")
# 主要操作區域
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
)