KKingzor commited on
Commit
0722adb
·
verified ·
1 Parent(s): 60ed224

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +248 -196
app.py CHANGED
@@ -5,95 +5,141 @@ import easyocr
5
  import numpy as np
6
  import re
7
  from datetime import datetime
 
8
 
9
- class TableOCRProcessor:
10
  def __init__(self):
11
- # 初始化 EasyOCR,支持中文和英文
12
- self.reader = easyocr.Reader(['ch_tra', 'ch_sim', 'en'], gpu=False)
13
 
14
- def process_table_image(self, image):
15
- """專門處理表格圖片的OCR"""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
16
  try:
17
  # 確保輸入是PIL Image
18
  if isinstance(image, np.ndarray):
19
  image = Image.fromarray(image)
 
 
20
 
21
- # 執行OCR
22
- results = self.reader.readtext(np.array(image))
23
 
24
- # 處理結果並嘗試重建表格結構
25
- all_data = []
26
- for (bbox, text, confidence) in results:
27
- top_left = bbox[0]
28
- bottom_right = bbox[2]
29
- center_y = (top_left[1] + bottom_right[1]) / 2
30
- center_x = (top_left[0] + bottom_right[0]) / 2
31
-
32
- all_data.append({
33
- '識別文字': text.strip(),
34
- '信心度': round(confidence, 3),
35
- '中心X': int(center_x),
36
- '中心Y': int(center_y),
37
- '左上角X': int(top_left[0]),
38
- '左上角Y': int(top_left[1]),
39
- '右下角X': int(bottom_right[0]),
40
- '右下角Y': int(bottom_right[1]),
41
- '寬度': int(bottom_right[0] - top_left[0]),
42
- '高度': int(bottom_right[1] - top_left[1]),
43
- '文字類型': self.classify_text_detailed(text)
44
- })
45
 
46
- # 創建基本DataFrame
47
- df = pd.DataFrame(all_data)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
48
 
49
- if not df.empty:
50
- # 按Y座標排序(從上到下),然後按X座標排序(從左到右)
 
 
51
  df = df.sort_values(['中心Y', '中心X']).reset_index(drop=True)
52
 
53
- # 嘗試重建表格結構
54
- table_df = self.reconstruct_table(df)
 
 
 
55
 
56
- return df, table_df, self.create_detailed_summary(df)
57
  else:
58
- empty_table = pd.DataFrame()
59
- return df, empty_table, "未識別到任何文字"
60
 
61
  except Exception as e:
62
- error_df = pd.DataFrame([{'錯誤': str(e)}])
63
- return error_df, error_df, f"處理過程中發生錯誤: {str(e)}"
 
 
64
 
65
- def classify_text_detailed(self, text):
66
- """詳細分類文字類型"""
67
- # 檢查是否為日期格式 (如: 5月26日)
68
- if re.search(r'\d+月\d+日', text):
69
- return '日期'
70
- # 檢查是否為時間格式 (如: 07-15, 16-23)
71
- elif re.search(r'\d{2}-\d{2}', text):
72
- return '時間範圍'
73
- # 檢查是否為純數字
74
- elif re.match(r'^\d+$', text):
75
- return '純數字'
76
- # 檢查是否包含中文姓名(2-4個中文字符)
77
- elif re.match(r'^[\u4e00-\u9fff]{2,4}$', text):
78
- return '姓名'
79
- # 檢查是否為中文+數字組合
80
- elif re.search(r'[\u4e00-\u9fff]', text) and re.search(r'\d', text):
81
- return '中文+數字'
82
- # 檢查是否為純中文
83
- elif re.search(r'^[\u4e00-\u9fff]+$', text):
84
- return '中文'
85
- # 其他情況
86
- else:
87
- return '其他'
 
 
 
 
 
88
 
89
- def reconstruct_table(self, df):
90
- """嘗試重建表格結構"""
91
  if df.empty:
92
  return pd.DataFrame()
93
 
94
  try:
95
- # 根據Y座標分組,找出可能的行
96
- y_threshold = 20 # Y座標差異閾值
97
  rows = []
98
  current_row = []
99
  last_y = None
@@ -105,204 +151,210 @@ class TableOCRProcessor:
105
  current_row.append(row)
106
  else:
107
  if current_row:
108
- # 按X座標排序當前行
109
  current_row.sort(key=lambda x: x['中心X'])
110
  rows.append(current_row)
111
  current_row = [row]
112
 
113
  last_y = current_y
114
 
115
- # 添加最後一行
116
  if current_row:
117
  current_row.sort(key=lambda x: x['中心X'])
118
  rows.append(current_row)
119
 
120
  # 構建表格
121
- max_cols = max(len(row) for row in rows) if rows else 0
 
 
 
122
  table_data = []
123
 
124
  for i, row in enumerate(rows):
125
- row_data = {'行': i + 1}
126
  for j, cell in enumerate(row):
127
- col_name = f'列{j+1}'
128
- row_data[col_name] = cell['識別文字']
129
 
130
  # 填充空列
131
  for j in range(len(row), max_cols):
132
- col_name = f'列{j+1}'
133
- row_data[col_name] = ''
134
 
135
  table_data.append(row_data)
136
 
137
  return pd.DataFrame(table_data)
138
 
139
  except Exception as e:
140
- print(f"重建表格時發生錯誤: {e}")
141
- return pd.DataFrame()
142
 
143
- def create_detailed_summary(self, df):
144
- """創建詳細的識別結果摘要"""
145
- if df.empty:
146
- return "未識別到任何文字"
147
-
148
- summary = f"""
149
- 📊 識別結果統計:
150
- ━━━━━━━━━━━━━━━━━━━━━━━━━━
151
- 總識別區塊數:{len(df)} 個
152
- • 平均信心度:{df['信心度'].mean():.3f}
153
- 最高信心度:{df['信心度'].max():.3f}
154
- 最低信心度:{df['信心度'].min():.3f}
 
 
 
 
 
 
155
 
156
  📝 文字類型分布:
157
  """
158
-
159
- type_counts = df['文字類型'].value_counts()
160
- for text_type, count in type_counts.items():
161
- percentage = (count / len(df)) * 100
162
- summary += f"• {text_type}: {count} 個 ({percentage:.1f}%)\n"
163
-
164
- # 添加品質評估
165
- high_confidence = (df['信心度'] >= 0.8).sum()
166
- medium_confidence = ((df['信心度'] >= 0.6) & (df['信心度'] < 0.8)).sum()
167
- low_confidence = (df['信心度'] < 0.6).sum()
168
-
169
- summary += f"""
170
- 🎯 識別品質評估:
171
- • 高信心度 (≥0.8): {high_confidence} 個
172
- • 中信心度 (0.6-0.8): {medium_confidence} 個
173
- • 低信心度 (<0.6): {low_confidence} 個
174
-
175
- 💡 處理建議:
176
- """
177
- if low_confidence > 0:
178
- summary += "• 發現低信心度文字,建議檢查原圖品質\n"
179
- if df['信心度'].mean() >= 0.8:
180
- summary += "• 整體識別品質良好✓\n"
181
- else:
182
- summary += "• 建議提高圖片解析度或清晰度\n"
183
 
184
- return summary
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
185
 
186
- def create_advanced_interface():
187
- """創建進階版Gradio界面"""
188
- processor = TableOCRProcessor()
189
 
190
- def advanced_process(image, confidence_threshold):
191
- if image is None:
192
- return "請上傳圖片", pd.DataFrame(), pd.DataFrame()
193
-
194
- df, table_df, summary = processor.process_table_image(image)
195
-
196
- # 根據信心度閾值過濾結果
197
- if not df.empty and 'signal' in df.columns:
198
- filtered_df = df[df['信心度'] >= confidence_threshold]
199
- else:
200
- filtered_df = df
201
-
202
- return summary, filtered_df, table_df
 
 
 
 
 
203
 
204
- # 創建Gradio界面
205
- with gr.Blocks(title="表格OCR識別系統", theme=gr.themes.Glass()) as demo:
206
  gr.HTML("""
207
- <div style="text-align: center; padding: 20px;">
208
- <h1>📋 專業表格OCR識別系統</h1>
209
- <p style="font-size: 18px; color: #666;">
210
- 專為表格和結構化文檔設計的OCR識別工具
211
- </p>
212
  </div>
213
  """)
214
 
215
  with gr.Row():
216
- with gr.Column(scale=2):
217
  image_input = gr.Image(
218
- label="📤 上傳表格圖片",
219
  type="pil",
220
- height=500
221
  )
222
 
223
- with gr.Row():
224
- confidence_slider = gr.Slider(
225
- minimum=0.0,
226
- maximum=1.0,
227
- value=0.5,
228
- step=0.1,
229
- label="🎯 信心度值",
230
- info="只顯示信心度高於此值的結果"
231
- )
232
-
233
- process_btn = gr.Button(
234
- "🚀 開始識別",
235
- variant="primary",
236
- size="lg"
237
- )
238
 
239
- with gr.Column(scale=3):
240
- summary_output = gr.Textbox(
241
- label="📊 識別統計報告",
242
- lines=15,
243
- max_lines=20
244
  )
245
 
246
- with gr.Tabs():
247
- with gr.TabItem("📝 詳細識別結果"):
248
- detailed_output = gr.Dataframe(
249
- label="完整OCR結果",
250
  interactive=True,
251
  wrap=True,
252
- height=400
253
  )
254
 
255
- with gr.TabItem("📋 重建表格"):
256
- table_output = gr.Dataframe(
257
- label="重建的表格結構",
258
  interactive=True,
259
  wrap=True,
260
- height=400
261
  )
262
 
263
- # 使用說明
264
- with gr.Accordion("📖 使用說明", open=False):
265
  gr.Markdown("""
266
- ### 🔧 功能說明
267
-
268
- **詳細識別結果**:
269
- - 顯示每個識別到的文字區塊的完整信息
270
- - 包含位置座標、信心度、文字類型等
271
-
272
- **重建表格**:
273
- - 嘗試根據文字位置重建原始表格結構
274
- - 按行列組織數據,便於進一步處理
275
 
276
- **識別統計報告**:
277
- - 提供識別品質評估和改進建議
278
- - 統計不同類型文字的分布情況
 
 
279
 
280
- ### 💡 使用技巧
281
- - 上傳清晰、對比度高的圖片效果更好
282
- - 調整信心度閾值可以過濾低質量結果
283
- - 可以複製表數據到Excel等工具中使用
 
284
  """)
285
 
286
  # 綁定事件
287
  process_btn.click(
288
- fn=advanced_process,
289
- inputs=[image_input, confidence_slider],
290
- outputs=[summary_output, detailed_output, table_output]
291
  )
292
 
293
  image_input.change(
294
- fn=advanced_process,
295
- inputs=[image_input, confidence_slider],
296
- outputs=[summary_output, detailed_output, table_output]
297
  )
298
 
299
  return demo
300
 
301
- # 創建並啟動應用
302
  if __name__ == "__main__":
303
- demo = create_advanced_interface()
304
  demo.launch(
305
  server_name="0.0.0.0",
306
  server_port=7860,
307
- share=True
 
308
  )
 
5
  import numpy as np
6
  import re
7
  from datetime import datetime
8
+ import traceback
9
 
10
+ class StableOCRProcessor:
11
  def __init__(self):
12
+ self.reader = None
13
+ self.initialize_reader()
14
 
15
+ def initialize_reader(self):
16
+ """安全初始化EasyOCR"""
17
+ try:
18
+ # 首先嘗試繁體中文+英文
19
+ self.reader = easyocr.Reader(['ch_tra', 'en'], gpu=False)
20
+ self.lang_config = "繁體中文 + 英文"
21
+ print("成功初始化:繁體中文 + 英文")
22
+ except Exception as e1:
23
+ try:
24
+ # 如果失敗,嘗試簡體中文+英文
25
+ self.reader = easyocr.Reader(['ch_sim', 'en'], gpu=False)
26
+ self.lang_config = "簡體中文 + 英文"
27
+ print("成功初始化:簡體中文 + 英文")
28
+ except Exception as e2:
29
+ try:
30
+ # 如果還是失敗,只使用英文
31
+ self.reader = easyocr.Reader(['en'], gpu=False)
32
+ self.lang_config = "僅英文"
33
+ print("成功初始化:僅英文")
34
+ except Exception as e3:
35
+ print(f"初始化失敗:{e3}")
36
+ self.reader = None
37
+ self.lang_config = "初始化失敗"
38
+
39
+ def process_image_safely(self, image):
40
+ """安全處理圖片"""
41
+ if self.reader is None:
42
+ return self.create_error_response("OCR引擎初始化失敗")
43
+
44
  try:
45
  # 確保輸入是PIL Image
46
  if isinstance(image, np.ndarray):
47
  image = Image.fromarray(image)
48
+ elif image is None:
49
+ return self.create_error_response("請上傳有效的圖片")
50
 
51
+ # 轉換為numpy array
52
+ img_array = np.array(image)
53
 
54
+ # 執行OCR
55
+ print("開始執行OCR...")
56
+ results = self.reader.readtext(img_array)
57
+ print(f"OCR完成,識別到 {len(results)} 個文字區塊")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
58
 
59
+ # 處理結果
60
+ processed_data = []
61
+ for i, (bbox, text, confidence) in enumerate(results):
62
+ try:
63
+ top_left = bbox[0]
64
+ bottom_right = bbox[2]
65
+ center_y = (top_left[1] + bottom_right[1]) / 2
66
+ center_x = (top_left[0] + bottom_right[0]) / 2
67
+
68
+ processed_data.append({
69
+ '序號': i + 1,
70
+ '識別文字': text.strip(),
71
+ '信心度': round(float(confidence), 3),
72
+ '中心X': int(center_x),
73
+ '中心Y': int(center_y),
74
+ '左上角X': int(top_left[0]),
75
+ '左上角Y': int(top_left[1]),
76
+ '右下角X': int(bottom_right[0]),
77
+ '右下角Y': int(bottom_right[1]),
78
+ '文字類型': self.classify_text(text.strip())
79
+ })
80
+ except Exception as e:
81
+ print(f"處理第{i+1}個結果時出錯:{e}")
82
+ continue
83
 
84
+ # 創建DataFrame
85
+ if processed_data:
86
+ df = pd.DataFrame(processed_data)
87
+ # 按位置排序
88
  df = df.sort_values(['中心Y', '中心X']).reset_index(drop=True)
89
 
90
+ # 嘗試重建表格
91
+ table_df = self.rebuild_table(df)
92
+
93
+ # 創建摘要
94
+ summary = self.create_summary(df)
95
 
96
+ return df, table_df, summary
97
  else:
98
+ return self.create_empty_response("未識別到任何文字")
 
99
 
100
  except Exception as e:
101
+ error_msg = f"處理圖片時發生錯誤:{str(e)}"
102
+ print(error_msg)
103
+ print(traceback.format_exc())
104
+ return self.create_error_response(error_msg)
105
 
106
+ def classify_text(self, text):
107
+ """分類文字類型"""
108
+ if not text:
109
+ return '空白'
110
+
111
+ try:
112
+ # 日期格式 (如: 5月26日)
113
+ if re.search(r'\d+月\d+日', text):
114
+ return '日期'
115
+ # 時間範圍 (如: 07-15)
116
+ elif re.search(r'\d{2}-\d{2}', text):
117
+ return '時間'
118
+ # 純數字
119
+ elif re.match(r'^\d+$', text):
120
+ return '數字'
121
+ # 中文姓名或地名
122
+ elif re.match(r'^[\u4e00-\u9fff]{2,4}$', text):
123
+ return '中文名稱'
124
+ # 包含中文
125
+ elif re.search(r'[\u4e00-\u9fff]', text):
126
+ return '中文內容'
127
+ # 英文字母
128
+ elif re.match(r'^[a-zA-Z]+$', text):
129
+ return '英文'
130
+ else:
131
+ return '其他'
132
+ except Exception:
133
+ return '未知'
134
 
135
+ def rebuild_table(self, df):
136
+ """重建表格結構"""
137
  if df.empty:
138
  return pd.DataFrame()
139
 
140
  try:
141
+ # Y座標分組
142
+ y_threshold = 30
143
  rows = []
144
  current_row = []
145
  last_y = None
 
151
  current_row.append(row)
152
  else:
153
  if current_row:
 
154
  current_row.sort(key=lambda x: x['中心X'])
155
  rows.append(current_row)
156
  current_row = [row]
157
 
158
  last_y = current_y
159
 
 
160
  if current_row:
161
  current_row.sort(key=lambda x: x['中心X'])
162
  rows.append(current_row)
163
 
164
  # 構建表格
165
+ if not rows:
166
+ return pd.DataFrame()
167
+
168
+ max_cols = max(len(row) for row in rows)
169
  table_data = []
170
 
171
  for i, row in enumerate(rows):
172
+ row_data = {'行': i + 1}
173
  for j, cell in enumerate(row):
174
+ row_data[f'列{j+1}'] = cell['識別文字']
 
175
 
176
  # 填充空列
177
  for j in range(len(row), max_cols):
178
+ row_data[f'列{j+1}'] = ''
 
179
 
180
  table_data.append(row_data)
181
 
182
  return pd.DataFrame(table_data)
183
 
184
  except Exception as e:
185
+ print(f"重建表格失敗:{e}")
186
+ return pd.DataFrame([{'錯誤': '表格重建失敗'}])
187
 
188
+ def create_summary(self, df):
189
+ """創建摘要報告"""
190
+ try:
191
+ if df.empty:
192
+ return "未識別到任何內容"
193
+
194
+ avg_confidence = df['信心度'].mean()
195
+ high_conf = (df['信心度'] >= 0.8).sum()
196
+ total_count = len(df)
197
+
198
+ summary = f"""
199
+ 🔍 OCR識別報告
200
+ ═══════════════════════════════════
201
+ 📊 基本統計:
202
+ • 語言配置:{self.lang_config}
203
+ • 識別區塊:{total_count} 個
204
+ • 平均信心度:{avg_confidence:.3f}
205
+ • 高信心度區塊:{high_conf} 個 ({high_conf/total_count*100:.1f}%)
206
 
207
  📝 文字類型分布:
208
  """
209
+ type_counts = df['文字類型'].value_counts()
210
+ for text_type, count in type_counts.items():
211
+ percentage = (count / total_count) * 100
212
+ summary += f"• {text_type}:{count} ({percentage:.1f}%)\n"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
213
 
214
+ # 品質評估
215
+ if avg_confidence >= 0.8:
216
+ summary += "\n✅ 識別品質:優秀"
217
+ elif avg_confidence >= 0.6:
218
+ summary += "\n⚠️ 識別品質:良好"
219
+ else:
220
+ summary += "\n❌ 識別品質:需改進"
221
+
222
+ return summary
223
+
224
+ except Exception as e:
225
+ return f"生成報告時發生錯誤:{str(e)}"
226
+
227
+ def create_error_response(self, error_msg):
228
+ """創建錯誤響應"""
229
+ error_df = pd.DataFrame([{'錯誤': error_msg}])
230
+ return error_df, error_df, f"❌ 錯誤:{error_msg}"
231
+
232
+ def create_empty_response(self, msg):
233
+ """創建空響應"""
234
+ empty_df = pd.DataFrame()
235
+ return empty_df, empty_df, f"ℹ️ {msg}"
236
 
237
+ def create_stable_interface():
238
+ """創建穩定的Gradio界面"""
239
+ processor = StableOCRProcessor()
240
 
241
+ def process_with_error_handling(image, min_confidence):
242
+ """帶錯誤處理的處理函數"""
243
+ try:
244
+ df, table_df, summary = processor.process_image_safely(image)
245
+
246
+ # 應用信心度過濾
247
+ if not df.empty and '信心度' in df.columns:
248
+ filtered_df = df[df['信心度'] >= min_confidence].reset_index(drop=True)
249
+ if len(filtered_df) != len(df):
250
+ summary += f"\n\n🔍 已過濾低信心度結果:{len(df) - len(filtered_df)} 個"
251
+ df = filtered_df
252
+
253
+ return summary, df, table_df
254
+
255
+ except Exception as e:
256
+ error_msg = f"處��失敗:{str(e)}"
257
+ error_df = pd.DataFrame([{'錯誤': error_msg}])
258
+ return error_msg, error_df, error_df
259
 
260
+ # 創建界面
261
+ with gr.Blocks(title="穩定版中文OCR系統", theme=gr.themes.Soft()) as demo:
262
  gr.HTML("""
263
+ <div style="text-align: center; padding: 20px; background: linear-gradient(90deg, #667eea 0%, #764ba2 100%); color: white; border-radius: 10px; margin-bottom: 20px;">
264
+ <h1>🔍 穩定版中文OCR識別系統</h1>
265
+ <p>支持中文表格和文檔的OCR識別,自動容錯處理</p>
 
 
266
  </div>
267
  """)
268
 
269
  with gr.Row():
270
+ with gr.Column(scale=1):
271
  image_input = gr.Image(
272
+ label="📤 上傳圖片",
273
  type="pil",
274
+ height=400
275
  )
276
 
277
+ min_confidence = gr.Slider(
278
+ minimum=0.0,
279
+ maximum=1.0,
280
+ value=0.3,
281
+ step=0.1,
282
+ label="🎯 最低信心度",
283
+ info="過濾掉信心度低於此的結果"
284
+ )
285
+
286
+ process_btn = gr.Button(
287
+ "🚀 開始識別",
288
+ variant="primary",
289
+ size="lg"
290
+ )
 
291
 
292
+ with gr.Column(scale=1):
293
+ summary_text = gr.Textbox(
294
+ label="📊 識別報告",
295
+ lines=12,
296
+ max_lines=15
297
  )
298
 
299
+ with gr.Row():
300
+ with gr.Column():
301
+ gr.Markdown("### 📋 詳細識別結果")
302
+ detail_table = gr.Dataframe(
303
  interactive=True,
304
  wrap=True,
305
+ height=300
306
  )
307
 
308
+ with gr.Column():
309
+ gr.Markdown("### 🔄 重建表格")
310
+ rebuilt_table = gr.Dataframe(
311
  interactive=True,
312
  wrap=True,
313
+ height=300
314
  )
315
 
316
+ # 說明區域
317
+ with gr.Accordion("💡 使用說明", open=False):
318
  gr.Markdown("""
319
+ ### 系統特色
320
+ - ✅ **智能容錯**:自動處理各種錯誤情況
321
+ - 🔧 **多語言支持**:自動選擇最佳語言配置
322
+ - 📊 **表格重建**:智能重建原始表格結構
323
+ - 🎯 **品質控制**:可調整信心度閾值
 
 
 
 
324
 
325
+ ### 使用技巧
326
+ 1. **圖片要求**:清晰、對比度高的圖片效果最佳
327
+ 2. **信心度調整**:降低閾值可獲得更多結果,提高閾值可���得更準確結果
328
+ 3. **表格處理**:系統會自動識別行列結構並重建表格
329
+ 4. **結果導出**:可以複製表格數據到其他應用程序
330
 
331
+ ### 支持內容
332
+ - 中文文字(繁體/簡體)
333
+ - 英文字母和數字
334
+ - 日期和時間
335
+ - 表格和結構化數據
336
  """)
337
 
338
  # 綁定事件
339
  process_btn.click(
340
+ fn=process_with_error_handling,
341
+ inputs=[image_input, min_confidence],
342
+ outputs=[summary_text, detail_table, rebuilt_table]
343
  )
344
 
345
  image_input.change(
346
+ fn=process_with_error_handling,
347
+ inputs=[image_input, min_confidence],
348
+ outputs=[summary_text, detail_table, rebuilt_table]
349
  )
350
 
351
  return demo
352
 
 
353
  if __name__ == "__main__":
354
+ demo = create_stable_interface()
355
  demo.launch(
356
  server_name="0.0.0.0",
357
  server_port=7860,
358
+ share=True,
359
+ show_error=True
360
  )