Harry Pham commited on
Commit
dcf3b1f
·
1 Parent(s): 24fd13a

update OCR

Browse files
Files changed (1) hide show
  1. app.py +164 -132
app.py CHANGED
@@ -1,150 +1,182 @@
1
- # app.py — Gradio demo
2
- # Đặt ở root project: engineering-drawing-ai/app.py
 
 
3
 
4
- import os, sys, json, tempfile
5
  import gradio as gr
 
6
  import cv2
 
7
  import numpy as np
 
 
8
  from PIL import Image
9
 
10
- # Auto-download weights từ HuggingFace Hub nếu chưa có
11
- CHECKPOINT = "best.pt"
12
- HF_REPO = "phamha/drawing-model-weights" # ← sửa sau
13
-
14
- def ensure_weights():
15
- if not os.path.exists(CHECKPOINT):
16
- print("[INFO] Downloading model weights from HuggingFace...")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
17
  try:
18
- from huggingface_hub import hf_hub_download
19
- hf_hub_download(
20
- repo_id=HF_REPO,
21
- filename="best.pt",
22
- local_dir=".",
23
- local_dir_use_symlinks=False,
24
- )
25
- print("[INFO] Weights downloaded.")
26
- except Exception as e:
27
- print(f"[ERROR] Cannot download weights: {e}")
28
- raise
29
-
30
- ensure_weights()
31
-
32
- # Import pipeline SAU KHI đảm bảo weights tồn tại
33
- sys.path.insert(0, ".")
34
- from src.inference import run_pipeline
35
-
36
-
37
- # ── Xử lý ảnh ──────────────────────────────────────────────
38
- def process(image: Image.Image):
39
- if image is None:
40
- return None, "{}", "Chưa có ảnh."
41
-
42
- tmp_dir = tempfile.mkdtemp()
43
- tmp_path = os.path.join(tmp_dir, "input.jpg")
44
- image.save(tmp_path, quality=95)
45
-
46
- try:
47
- result, vis_path = run_pipeline(
48
- image_path = tmp_path,
49
- output_dir = tmp_dir,
50
- checkpoint = CHECKPOINT,
51
- conf_thresh = 0.3,
52
- )
53
- except Exception as e:
54
- import traceback
55
- return None, "{}", f"Lỗi pipeline:\n{traceback.format_exc()}"
56
-
57
- # Ảnh kết quả
58
- vis_bgr = cv2.imread(vis_path)
59
- vis_rgb = cv2.cvtColor(vis_bgr, cv2.COLOR_BGR2RGB)
60
-
61
- # JSON sạch (bỏ crop_path)
62
- clean_objs = []
63
- for obj in result["objects"]:
64
- clean_objs.append({
65
- "id": obj["id"],
66
- "class": obj["class"],
67
- "confidence": obj["confidence"],
68
- "bbox": obj["bbox"],
69
- "ocr_content": obj["ocr_content"],
70
  })
71
- json_str = json.dumps(
72
- {"image": result["image"], "objects": clean_objs},
73
- ensure_ascii=False, indent=2
74
- )
75
 
76
- # OCR text đẹp
77
- ocr_parts = []
78
- for obj in result["objects"]:
79
- content = obj.get("ocr_content")
80
- if not content:
81
- continue
82
- if isinstance(content, dict): # Table
83
- content = content.get("text", "")
84
- if not content.strip():
85
- continue
86
- sep = "─" * 46
87
- ocr_parts.append(
88
- f"{sep}\n"
89
- f"[{obj['class']} #{obj['id']}] conf={obj['confidence']}\n"
90
- f"{sep}\n{content}"
91
- )
92
- ocr_text = "\n\n".join(ocr_parts) or "Không phát hiện Note / Table."
93
-
94
- return vis_rgb, json_str, ocr_text
95
-
96
-
97
- # ── Gradio UI ───────────────────────────────────────────────
98
- with gr.Blocks(title="Engineering Drawing Analyzer", theme=gr.themes.Soft()) as demo:
99
-
100
- gr.Markdown("""
101
- # 🔧 Engineering Drawing Analyzer
102
- **Tự động phát hiện và trích xuất văn bản từ bản vẽ kỹ thuật**
103
-
104
- Hỗ trợ 3 loại vùng:
105
- - 🟢 **PartDrawing** — vùng bản vẽ chi tiết
106
- - 🟠 **Note** — ghi chú, chú thích
107
- - 🔴 **Table** — bảng dữ liệu kỹ thuật
108
- """)
109
 
110
- with gr.Row():
111
- with gr.Column(scale=1):
112
- inp = gr.Image(type="pil", label="📁 Upload bản vẽ kỹ thuật")
113
- btn = gr.Button("🔍 Detect & OCR", variant="primary", size="lg")
 
 
 
 
 
 
 
114
 
115
- with gr.Column(scale=1):
116
- out_img = gr.Image(label="✅ Kết quả detection")
117
 
 
 
 
 
 
118
  with gr.Row():
119
  with gr.Column(scale=1):
120
- out_json = gr.Code(
121
- language="json",
122
- label="📋 JSON output",
123
- lines=25,
124
- )
125
  with gr.Column(scale=1):
126
- out_ocr = gr.Textbox(
127
- label="📝 OCR content (Note & Table)",
128
- lines=25,
129
- max_lines=60,
130
- )
131
-
132
- btn.click(
133
- fn = process,
134
- inputs = [inp],
135
- outputs = [out_img, out_json, out_ocr],
136
  )
137
 
138
- gr.Markdown("""
139
- ---
140
- **Model:** RT-DETR-L fine-tuned | **OCR:** EasyOCR (vi+en) + PaddleOCR fallback
141
- **mAP50:** 0.942 | **Dataset:** Engineering drawings (Vietnamese technical)
142
- """)
143
-
144
-
145
  if __name__ == "__main__":
146
- demo.launch(
147
- server_name = "0.0.0.0",
148
- server_port = 7860,
149
- share = False, # đổi True nếu muốn link public tạm
150
- )
 
1
+ import os
2
+ # Tắt các cảnh báo hệ thống của Ultralytics và Paddle trên Hugging Face
3
+ os.environ['YOLO_CONFIG_DIR'] = '/tmp'
4
+ os.environ["PADDLE_PDX_DISABLE_MODEL_SOURCE_CHECK"] = "True"
5
 
 
6
  import gradio as gr
7
+ import torch
8
  import cv2
9
+ import json
10
  import numpy as np
11
+ from ultralytics import RTDETR
12
+ from paddleocr import PaddleOCR
13
  from PIL import Image
14
 
15
+ # --- Fix lỗi PyTorch load weights ---
16
+ _orig_torch_load = torch.load
17
+ def _patched_load(*args, **kwargs):
18
+ kwargs.setdefault("weights_only", False)
19
+ return _orig_torch_load(*args, **kwargs)
20
+ torch.load = _patched_load
21
+
22
+ DEVICE = "cuda" if torch.cuda.is_available() else "cpu"
23
+
24
+ CLASS_NAMES = ["note", "part-drawing", "table"]
25
+ CLASS_DISPLAY = {"note": "Note", "part-drawing": "PartDrawing", "table": "Table"}
26
+ COLORS = {"note": (0,165,255), "part-drawing": (0,200,0), "table": (0,0,220)}
27
+
28
+ _det_model = None
29
+ _paddle_ocr = None
30
+
31
+ def get_det_model():
32
+ global _det_model
33
+ if _det_model is None:
34
+ # Tự động load file best.pt đang có sẵn trong cùng thư mục
35
+ _det_model = RTDETR('best.pt')
36
+ return _det_model
37
+
38
+ def get_ocr_reader():
39
+ global _paddle_ocr
40
+ if _paddle_ocr is None:
41
+ # lang='vi' hỗ trợ cả tiếng Việt, tiếng Anh và số cực tốt
42
+ _paddle_ocr = PaddleOCR(use_angle_cls=False, lang='vi', show_log=False)
43
+ return _paddle_ocr
44
+
45
+ def _group_rows(items):
46
+ """Thuật toán gom cụm các text cùng 1 hàng cho Table"""
47
+ if not items:
48
+ return []
49
+ items = sorted(items, key=lambda x: x["y"])
50
+ y_vals = [it["y"] for it in items]
51
+ if len(y_vals) > 1:
52
+ gaps = [y_vals[i+1]-y_vals[i] for i in range(len(y_vals)-1)]
53
+ thresh = max(8, (sum(gaps)/len(gaps)) * 0.6)
54
+ else:
55
+ thresh = 12
56
+
57
+ rows, cur = [], [items[0]]
58
+ for item in items[1:]:
59
+ if item["y"] - cur[-1]["y"] < thresh:
60
+ cur.append(item)
61
+ else:
62
+ cur.sort(key=lambda x: x["x"])
63
+ rows.append([i["text"] for i in cur])
64
+ cur = [item]
65
+ cur.sort(key=lambda x: x["x"])
66
+ rows.append([i["text"] for i in cur])
67
+ return rows
68
+
69
+ def process_pipeline(input_image):
70
+ if input_image is None:
71
+ return None, "Vui lòng tải ảnh lên.", ""
72
+
73
+ # Chuyển PIL Image sang BGR Numpy cho OpenCV
74
+ img_bgr = cv2.cvtColor(np.array(input_image), cv2.COLOR_RGB2BGR)
75
+ img_draw = img_bgr.copy()
76
+
77
+ # 1. Detection
78
+ model = get_det_model()
79
+ results = model(img_bgr, imgsz=1024, conf=0.3, iou=0.5, device=DEVICE, verbose=False)
80
+
81
+ ocr_reader = get_ocr_reader()
82
+ objects = []
83
+ ocr_display = ""
84
+
85
+ # 2. Xử lý từng bounding box
86
+ for i, box in enumerate(results[0].boxes):
87
+ x1, y1, x2, y2 = map(int, box.xyxy[0].tolist())
88
+ cls_idx = int(box.cls[0])
89
+ conf_val = round(float(box.conf[0]), 4)
90
+
91
+ # Mapping label
92
  try:
93
+ cls_raw = CLASS_NAMES[cls_idx]
94
+ cls_show = CLASS_DISPLAY[cls_raw]
95
+ except:
96
+ cls_raw = model.names[cls_idx].lower()
97
+ cls_show = cls_raw.capitalize()
98
+
99
+ pad = 5
100
+ crop = img_bgr[max(0, y1-pad):min(img_bgr.shape[0], y2+pad),
101
+ max(0, x1-pad):min(img_bgr.shape[1], x2+pad)]
102
+
103
+ ocr_content = ""
104
+ # Thực hiện OCR
105
+ if cls_raw in ["note", "table"]:
106
+ # PaddleOCR chạy trực tiếp trên ảnh màu gốc
107
+ ocr_res = ocr_reader.ocr(crop, cls=False)
108
+
109
+ if ocr_res and ocr_res[0]:
110
+ if cls_raw == "note":
111
+ lines = [line[1][0] for line in ocr_res[0]]
112
+ ocr_content = "\n".join(lines)
113
+ ocr_display += f"### Note {i+1}:\n{ocr_content}\n\n"
114
+
115
+ elif cls_raw == "table":
116
+ items = []
117
+ for line in ocr_res[0]:
118
+ box_pts = line[0]
119
+ text = line[1][0]
120
+ center_x = (box_pts[0][0] + box_pts[1][0]) / 2
121
+ center_y = (box_pts[0][1] + box_pts[2][1]) / 2
122
+ items.append({"text": text, "x": center_x, "y": center_y})
123
+
124
+ rows = _group_rows(items)
125
+ ocr_content = "\n".join(" | ".join(r) for r in rows)
126
+ ocr_display += f"### Table {i+1}:\n```text\n{ocr_content}\n```\n\n"
127
+
128
+ # Lưu dữ liệu JSON
129
+ objects.append({
130
+ "id": i+1,
131
+ "class": cls_show,
132
+ "confidence": conf_val,
133
+ "bbox": {"x1": x1, "y1": y1, "x2": x2, "y2": y2},
134
+ "ocr_content": ocr_content if ocr_content else None
 
 
 
 
 
 
 
 
 
 
135
  })
 
 
 
 
136
 
137
+ # Vẽ hình
138
+ color = COLORS.get(cls_raw, (0, 255, 0))
139
+ cv2.rectangle(img_draw, (x1, y1), (x2, y2), color, 3)
140
+ label = f"{cls_show} {conf_val:.2f}"
141
+ (tw, th), _ = cv2.getTextSize(label, cv2.FONT_HERSHEY_SIMPLEX, 0.7, 2)
142
+ cv2.rectangle(img_draw, (x1, y1 - th - 10), (x1 + tw + 8, y1), color, -1)
143
+ cv2.putText(img_draw, label, (x1 + 4, y1 - 4), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (255, 255, 255), 2)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
144
 
145
+ # Convert ảnh kết quả trả về Gradio
146
+ final_img = Image.fromarray(cv2.cvtColor(img_draw, cv2.COLOR_BGR2RGB))
147
+
148
+ json_result = {
149
+ "image": "uploaded_image.jpg",
150
+ "objects": objects
151
+ }
152
+ json_str = json.dumps(json_result, ensure_ascii=False, indent=2)
153
+
154
+ if not ocr_display:
155
+ ocr_display = "Không tìm thấy text nào trong các vùng Note/Table."
156
 
157
+ return final_img, json_str, ocr_display
 
158
 
159
+ # --- GIAO DIỆN GRADIO ---
160
+ with gr.Blocks(title="Engineering Drawing OCR") as demo:
161
+ gr.Markdown("# 🏭 Trích xuất dữ liệu Bản vẽ Kỹ thuật (Detection & OCR)")
162
+ gr.Markdown("**Mô hình Detection:** RT-DETR | **Mô hình OCR:** PaddleOCR")
163
+
164
  with gr.Row():
165
  with gr.Column(scale=1):
166
+ img_input = gr.Image(type="pil", label="Tải ảnh bản vẽ lên")
167
+ btn_submit = gr.Button("🔍 Phân tích", variant="primary")
 
 
 
168
  with gr.Column(scale=1):
169
+ img_output = gr.Image(type="pil", label="Kết quả Detection")
170
+
171
+ with gr.Row():
172
+ json_output = gr.Code(language="json", label="Dữ liệu JSON")
173
+ ocr_output = gr.Markdown(label="Kết quả OCR")
174
+
175
+ btn_submit.click(
176
+ fn=process_pipeline,
177
+ inputs=[img_input],
178
+ outputs=[img_output, json_output, ocr_output]
179
  )
180
 
 
 
 
 
 
 
 
181
  if __name__ == "__main__":
182
+ demo.launch()