ranbac commited on
Commit
095a128
·
verified ·
1 Parent(s): acd370b

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +132 -151
app.py CHANGED
@@ -1,7 +1,6 @@
1
  import os
2
 
3
  # --- CẤU HÌNH HỆ THỐNG ---
4
- # Tắt MKLDNN để tránh lỗi Segmentation Fault trên Spaces
5
  os.environ["FLAGS_use_mkldnn"] = "0"
6
  os.environ["FLAGS_enable_mkldnn"] = "0"
7
  os.environ["DN_ENABLE_MKLDNN"] = "0"
@@ -15,95 +14,44 @@ from PIL import Image, ImageDraw, ImageFont
15
  import numpy as np
16
  import requests
17
 
18
- # Tắt log thừa của Paddle
19
  logging.getLogger("ppocr").setLevel(logging.WARNING)
20
 
21
- # --- DANH SÁCH NGÔN NGỮ HỖ TRỢ ---
22
- # Model Latin hỗ trợ tất cả các ngôn ngữ này
23
- LATIN_LANGUAGES = [
24
- "vi", "en", "fr", "de", "es", "it", "pt", "id", "ms", "tr", "pl", "cs", "nl"
25
- ]
26
- LANG_CHOICES = [
27
- ("Tiếng Việt (Vietnamese)", "vi"),
28
- ("Tiếng Anh (English)", "en"),
29
- ("Tiếng Trung (Chinese)", "ch"),
30
- ("Tiếng Pháp (French)", "fr"),
31
- ("Tiếng Đức (German)", "de"),
32
- ("Tiếng Tây Ban Nha (Spanish)", "es"),
33
- ("Tiếng Indonesia", "id"),
34
- ("Đa ngôn ngữ Latin (General Latin)", "latin_group")
35
- ]
36
-
37
- # --- QUẢN LÝ MODEL ---
38
- OCR_CACHE = {}
39
-
40
- def get_ocr_model(user_selection):
41
- """
42
- Chiến lược tối ưu:
43
- - Nếu chọn 'ch' -> Load model Trung Quốc (lang='ch')
44
- - Nếu chọn BẤT KỲ ngôn ngữ Latin nào (vi, en, fr...) -> Load model 'en'
45
- (Trong Paddle, 'en' chính là model PP-OCR Latin đa ngôn ngữ)
46
- """
47
- # Xác định 'backend_lang' thực sự cần load
48
- if user_selection == 'ch':
49
- backend_lang = 'ch'
50
- else:
51
- # Tất cả ngôn ngữ Latin dùng chung model 'en' để tiết kiệm RAM
52
- backend_lang = 'en'
53
-
54
- if backend_lang in OCR_CACHE:
55
- return OCR_CACHE[backend_lang]
56
-
57
- print(f"📡 Đang tải Model gốc cho nhóm: {backend_lang} ...")
58
- try:
59
- model = PaddleOCR(
60
- use_angle_cls=True,
61
- lang=backend_lang,
62
- use_textline_orientation=True,
63
- show_log=False
64
- )
65
- OCR_CACHE[backend_lang] = model
66
- print(f"✅ Đã tải xong model {backend_lang}!")
67
- return model
68
- except Exception as e:
69
- print(f"❌ Lỗi khởi tạo model {backend_lang}: {e}")
70
- return None
71
-
72
- # --- QUẢN LÝ FONT CHỮ ---
73
- def check_and_download_font(lang_code):
74
- """
75
- Tự động chọn font:
76
- - ch: SimFang
77
- - Nhóm Latin (vi, en, fr...): Roboto
78
- """
79
- if lang_code == 'ch':
80
- font_name = "simfang.ttf"
81
- url = "https://github.com/StellarCN/scp_zh/raw/master/fonts/SimFang.ttf"
82
- else:
83
- font_name = "roboto.ttf"
84
- url = "https://github.com/googlefonts/roboto/raw/main/src/hinted/Roboto-Regular.ttf"
85
-
86
- font_path = f"./{font_name}"
87
-
88
- # Chỉ tải nếu file chưa tồn tại (hoặc kích thước file = 0)
89
- if not os.path.exists(font_path) or os.path.getsize(font_path) == 0:
90
- print(f"📥 Đang tải font {font_name}...")
91
  try:
 
92
  r = requests.get(url, allow_redirects=True)
93
  with open(font_path, 'wb') as f:
94
  f.write(r.content)
95
- print(f"✅ Font {font_name} sẵn sàng.")
96
- except Exception as e:
97
- print(f"⚠️ Lỗi tải font: {e}")
98
- return None
99
  return font_path
100
 
101
- # --- HÀM VẼ KHUNG & TEXT ---
 
 
102
  def universal_draw(image, raw_data, font_path):
103
  if image is None: return image
 
 
104
  if isinstance(image, np.ndarray):
105
  image = Image.fromarray(image)
106
 
 
107
  canvas = image.copy()
108
  draw = ImageDraw.Draw(canvas)
109
 
@@ -113,9 +61,7 @@ def universal_draw(image, raw_data, font_path):
113
  except:
114
  font = ImageFont.load_default()
115
 
116
- items_to_draw = []
117
-
118
- # Hàm chuẩn hóa box
119
  def parse_box(b):
120
  try:
121
  if hasattr(b, 'tolist'): b = b.tolist()
@@ -125,114 +71,149 @@ def universal_draw(image, raw_data, font_path):
125
  return None
126
  except: return None
127
 
128
- # Logic trích xuất box từ kết quả Paddle
129
- if isinstance(raw_data, list) and len(raw_data) > 0:
130
- # Trường hợp chuẩn (List of Lists)
131
- for line in raw_data:
132
- if isinstance(line, list):
133
- for res in line:
134
- if isinstance(res, list) and len(res) == 2:
135
- box = parse_box(res[0])
136
- txt = res[1][0]
137
- if box and txt: items_to_draw.append((box, txt))
138
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
139
  # Vẽ
140
  for box, txt in items_to_draw:
141
  try:
 
142
  draw.polygon(box, outline="red", width=3)
 
143
  txt_x, txt_y = box[0]
144
- # Vẽ nền đỏ cho chữ
145
  if hasattr(draw, "textbbox"):
146
  text_bbox = draw.textbbox((txt_x, txt_y), txt, font=font, anchor="lb")
147
  draw.rectangle(text_bbox, fill="red")
148
  draw.text((txt_x, txt_y), txt, fill="white", font=font, anchor="lb")
149
  else:
150
- draw.text((txt_x, txt_y - 20), txt, fill="white", font=font)
151
  except: continue
152
 
153
  return canvas
154
 
155
- # --- XỬ LÝ TEXT ---
156
- def extract_clean_text(raw_data):
157
- texts = []
158
- if isinstance(raw_data, list):
159
- for line in raw_data:
160
- if isinstance(line, list):
161
- for res in line:
162
- if isinstance(res, list) and len(res) == 2:
163
- texts.append(res[1][0])
164
- return "\n".join(texts)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
165
 
166
  # --- MAIN PREDICT ---
167
- def predict(image, lang_selection):
168
- if image is None: return None, "Vui lòng upload ảnh.", "No Data"
169
 
170
  try:
171
- # 1. Lấy Model (Cache)
172
- ocr = get_ocr_model(lang_selection)
173
- if ocr is None:
174
- return image, "Lỗi khởi tạo Model. Vui lòng kiểm tra Log.", "Init Error"
175
-
176
- # 2. Lấy Font
177
- font_path = check_and_download_font(lang_selection)
178
-
179
- # 3. Chuẩn bị ảnh
180
- original_pil = image.copy()
181
  image_np = np.array(image)
182
 
183
- # 4. Chạy OCR
184
- # cls=True để tự động xoay chiều văn bản
185
- raw_result = ocr.ocr(image_np, cls=True)
186
 
187
- if not raw_result or raw_result[0] is None:
188
- return image, "Không tìm thấy văn bản nào.", str(raw_result)
189
-
190
- # 5. Xử kết quả hình học (Unwarping nếu )
191
- # (Đơn giản hóa: Vẽ trực tiếp lên ảnh gốc vì ta đã tắt unwarping để tăng tốc)
192
- annotated_image = universal_draw(original_pil, raw_result, font_path)
193
-
194
- # 6. Xuất Text
195
- final_text = extract_clean_text(raw_result)
196
-
197
- debug_info = f"Language Mode: {lang_selection}\nModel Loaded: {'Chinese' if lang_selection == 'ch' else 'Latin (En/Vi/Fr...)'}\nFont: {font_path}\nRaw Data Sample:\n{str(raw_result)[:500]}..."
198
-
199
- return annotated_image, final_text, debug_info
 
 
 
 
 
 
 
 
 
 
 
 
 
200
 
201
  except Exception as e:
202
  import traceback
203
- return image, f"Lỗi xử lý: {str(e)}", traceback.format_exc()
204
 
205
  # --- GIAO DIỆN ---
206
- with gr.Blocks(title="Universal Latin & Chinese OCR") as iface:
207
- gr.Markdown("## 🌐 Universal OCR (Hỗ trợ Tiếng Việt, Anh, Trung, Pháp, Đức...)")
208
- gr.Markdown("Sử dụng model **Latin PP-OCR** đa ngôn ngữ và **Chinese PP-OCR**.")
209
 
210
  with gr.Row():
211
  with gr.Column():
212
- input_img = gr.Image(type="pil", label="Ảnh đầu vào")
213
-
214
- lang_dropdown = gr.Dropdown(
215
- choices=LANG_CHOICES,
216
- value="vi",
217
- label="Chọn Ngôn Ngữ",
218
- info="Nhóm Latin (Vi, En, Fr...) dùng chung 1 model siêu nhẹ."
219
- )
220
-
221
- submit_btn = gr.Button("🚀 CHẠY NHẬN DIỆN", variant="primary")
222
 
223
  with gr.Column():
224
  with gr.Tabs():
225
- with gr.TabItem("🖼️ Kết quả"):
226
- output_img = gr.Image(type="pil", label="Ảnh đã vẽ khung")
227
  with gr.TabItem("📝 Văn bản"):
228
- # Đã xóa show_copy_button=True để sửa lỗi
229
- output_txt = gr.Textbox(label="Nội dung", lines=15, interactive=True)
230
  with gr.TabItem("🐞 Debug"):
231
- output_debug = gr.Textbox(label="Log hệ thống", lines=10)
232
 
233
  submit_btn.click(
234
  fn=predict,
235
- inputs=[input_img, lang_dropdown],
236
  outputs=[output_img, output_txt, output_debug]
237
  )
238
 
 
1
  import os
2
 
3
  # --- CẤU HÌNH HỆ THỐNG ---
 
4
  os.environ["FLAGS_use_mkldnn"] = "0"
5
  os.environ["FLAGS_enable_mkldnn"] = "0"
6
  os.environ["DN_ENABLE_MKLDNN"] = "0"
 
14
  import numpy as np
15
  import requests
16
 
17
+ # Tắt log thừa
18
  logging.getLogger("ppocr").setLevel(logging.WARNING)
19
 
20
+ print("Đang khởi tạo PaddleOCR (Coordinate Sync Mode)...")
21
+
22
+ try:
23
+ ocr = PaddleOCR(use_textline_orientation=True, use_doc_orientation_classify=False,
24
+ use_doc_unwarping=False, lang='ch')
25
+ except Exception as e:
26
+ print(f"Lỗi khởi tạo: {e}. Chuyển về chế độ mặc định.")
27
+ ocr = PaddleOCR(lang='ch')
28
+
29
+ print("Model đã sẵn sàng!")
30
+
31
+ # --- TẢI FONT ---
32
+ def check_and_download_font():
33
+ font_path = "./simfang.ttf"
34
+ if not os.path.exists(font_path):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
35
  try:
36
+ url = "https://github.com/StellarCN/scp_zh/raw/master/fonts/SimFang.ttf"
37
  r = requests.get(url, allow_redirects=True)
38
  with open(font_path, 'wb') as f:
39
  f.write(r.content)
40
+ except:
41
+ return None
 
 
42
  return font_path
43
 
44
+ FONT_PATH = check_and_download_font()
45
+
46
+ # --- HÀM VẼ ĐA NĂNG ---
47
  def universal_draw(image, raw_data, font_path):
48
  if image is None: return image
49
+
50
+ # Đảm bảo image là PIL
51
  if isinstance(image, np.ndarray):
52
  image = Image.fromarray(image)
53
 
54
+ # Copy để vẽ
55
  canvas = image.copy()
56
  draw = ImageDraw.Draw(canvas)
57
 
 
61
  except:
62
  font = ImageFont.load_default()
63
 
64
+ # Hàm parse box
 
 
65
  def parse_box(b):
66
  try:
67
  if hasattr(b, 'tolist'): b = b.tolist()
 
71
  return None
72
  except: return None
73
 
74
+ items_to_draw = []
75
+
76
+ # Logic tìm box/text
77
+ # Ưu tiên cấu trúc PaddleX: rec_texts + dt_polys
78
+ processed = False
79
+ if isinstance(raw_data, list) and len(raw_data) > 0 and isinstance(raw_data[0], dict):
80
+ data_dict = raw_data[0]
81
+ texts = data_dict.get('rec_texts')
82
+ boxes = data_dict.get('dt_polys', data_dict.get('rec_polys', data_dict.get('dt_boxes')))
83
+
84
+ if texts and boxes and isinstance(texts, list) and isinstance(boxes, list):
85
+ for i in range(min(len(texts), len(boxes))):
86
+ txt = texts[i]
87
+ box = parse_box(boxes[i])
88
+ if box and txt: items_to_draw.append((box, txt))
89
+ processed = True
90
+
91
+ # Fallback Logic
92
+ if not processed:
93
+ def hunt(data):
94
+ if isinstance(data, dict):
95
+ box = None; text = None
96
+ for k in ['points', 'box', 'dt_boxes', 'poly']:
97
+ if k in data: box = parse_box(data[k]); break
98
+ for k in ['transcription', 'text', 'rec_text', 'label']:
99
+ if k in data: text = data[k]; break
100
+ if box and text: items_to_draw.append((box, text)); return
101
+ for v in data.values(): hunt(v)
102
+ elif isinstance(data, (list, tuple)):
103
+ if len(data) == 2 and isinstance(data[0], list) and len(data[0]) == 4:
104
+ box = parse_box(data[0])
105
+ txt_obj = data[1]
106
+ text = txt_obj[0] if isinstance(txt_obj, (list, tuple)) else txt_obj
107
+ if box and isinstance(text, str): items_to_draw.append((box, text)); return
108
+ for item in data: hunt(item)
109
+ hunt(raw_data)
110
+
111
  # Vẽ
112
  for box, txt in items_to_draw:
113
  try:
114
+ # Vẽ khung đỏ
115
  draw.polygon(box, outline="red", width=3)
116
+ # Vẽ chữ
117
  txt_x, txt_y = box[0]
 
118
  if hasattr(draw, "textbbox"):
119
  text_bbox = draw.textbbox((txt_x, txt_y), txt, font=font, anchor="lb")
120
  draw.rectangle(text_bbox, fill="red")
121
  draw.text((txt_x, txt_y), txt, fill="white", font=font, anchor="lb")
122
  else:
123
+ draw.text((txt_x, txt_y - font_size), txt, fill="white", font=font)
124
  except: continue
125
 
126
  return canvas
127
 
128
+ # --- HÀM XỬ LÝ TEXT ---
129
+ def deep_extract_text(data):
130
+ found_texts = []
131
+ if isinstance(data, str):
132
+ if len(data.strip()) > 0: return [data]
133
+ return []
134
+ if isinstance(data, (list, tuple)):
135
+ for item in data: found_texts.extend(deep_extract_text(item))
136
+ elif isinstance(data, dict):
137
+ for val in data.values(): found_texts.extend(deep_extract_text(val))
138
+ elif hasattr(data, '__dict__'): found_texts.extend(deep_extract_text(data.__dict__))
139
+ return found_texts
140
+
141
+ def clean_text_result(text_list):
142
+ cleaned = []
143
+ block_list = ['min', 'max', 'general', 'header', 'footer', 'structure']
144
+ for t in text_list:
145
+ t = t.strip()
146
+ if len(t) < 2 and not any(u'\u4e00' <= c <= u'\u9fff' for c in t): continue
147
+ if t.lower().endswith(('.ttf', '.json', '.pdparams', '.yml', '.log')): continue
148
+ if t.lower() in block_list: continue
149
+ if not re.search(r'[\w\u4e00-\u9fff]', t): continue
150
+ cleaned.append(t)
151
+ return cleaned
152
 
153
  # --- MAIN PREDICT ---
154
+ def predict(image):
155
+ if image is None: return None, "Chưa ảnh.", "No Data"
156
 
157
  try:
158
+ # Chuẩn bị ảnh đầu vào
159
+ original_pil = image.copy() if isinstance(image, Image.Image) else Image.fromarray(image).copy()
 
 
 
 
 
 
 
 
160
  image_np = np.array(image)
161
 
162
+ # 1. OCR
163
+ raw_result = ocr.ocr(image_np)
 
164
 
165
+ # 2. XỬ ẢNH ĐỂ VẼ (KEY FIX: Lấy ảnh từ Preprocessor nếu có)
166
+ target_image_for_drawing = original_pil
167
+
168
+ # Kiểm tra xem Paddle chỉnh sửa ảnh không (dựa vào key 'doc_preprocessor_res')
169
+ if isinstance(raw_result, list) and len(raw_result) > 0 and isinstance(raw_result[0], dict):
170
+ if 'doc_preprocessor_res' in raw_result[0]:
171
+ proc_res = raw_result[0]['doc_preprocessor_res']
172
+ # Nếu ảnh đầu ra đã chỉnh sửa (output_img)
173
+ if 'output_img' in proc_res:
174
+ print("Phát hiện ảnh đã qua xử lý hình học. Đang đồng bộ tọa độ...")
175
+ numpy_img = proc_res['output_img']
176
+ target_image_for_drawing = Image.fromarray(numpy_img)
177
+
178
+ # 3. Vẽ lên ảnh ĐÚNG (Target Image)
179
+ annotated_image = universal_draw(target_image_for_drawing, raw_result, FONT_PATH)
180
+
181
+ # 4. Xử lý Text
182
+ all_texts = deep_extract_text(raw_result)
183
+ final_texts = clean_text_result(all_texts)
184
+ text_output = "\n".join(final_texts) if final_texts else "Không tìm thấy văn bản."
185
+
186
+ # Debug Info
187
+ debug_str = str(raw_result)[:1000]
188
+ debug_info = f"Used Image Source: {'Preprocessed' if target_image_for_drawing != original_pil else 'Original'}\nData Preview:\n{debug_str}..."
189
+
190
+ return annotated_image, text_output, debug_info
191
 
192
  except Exception as e:
193
  import traceback
194
+ return image, f"Lỗi: {str(e)}", traceback.format_exc()
195
 
196
  # --- GIAO DIỆN ---
197
+ with gr.Blocks(title="PaddleOCR Perfect Overlay") as iface:
198
+ gr.Markdown("## PaddleOCR Chinese - High Precision Overlay")
 
199
 
200
  with gr.Row():
201
  with gr.Column():
202
+ input_img = gr.Image(type="pil", label="Input Image")
203
+ submit_btn = gr.Button("RUN OCR", variant="primary")
 
 
 
 
 
 
 
 
204
 
205
  with gr.Column():
206
  with gr.Tabs():
207
+ with gr.TabItem("🖼️ Kết quả Khớp Tọa Độ"):
208
+ output_img = gr.Image(type="pil", label="Overlay Result")
209
  with gr.TabItem("📝 Văn bản"):
210
+ output_txt = gr.Textbox(label="Text Content", lines=15)
 
211
  with gr.TabItem("🐞 Debug"):
212
+ output_debug = gr.Textbox(label="Debug Info", lines=15)
213
 
214
  submit_btn.click(
215
  fn=predict,
216
+ inputs=input_img,
217
  outputs=[output_img, output_txt, output_debug]
218
  )
219