Spaces:
Running
Running
Download ocr-training/scripts/quality_check.py from phucsd/cham-ocr-studio: direct link, hf CLI and curl.
- Browser
- Download file 10 kB
-
https://huggingface.co/spaces/phucsd/cham-ocr-studio/resolve/main/ocr-training/scripts/quality_check.py
- Command line
-
hf download hf://spaces/phucsd/cham-ocr-studio/ocr-training/scripts/quality_check.py
-
curl -L -o quality_check.py https://huggingface.co/spaces/phucsd/cham-ocr-studio/resolve/main/ocr-training/scripts/quality_check.py
10 kB
| import os | |
| import sys | |
| import yaml | |
| from PIL import Image | |
| # Configure UTF-8 stdout to support printing Cham characters to the terminal on Windows | |
| if sys.stdout and sys.stdout.encoding != 'utf-8': | |
| try: | |
| sys.stdout.reconfigure(encoding='utf-8') | |
| except AttributeError: | |
| pass | |
| def run_integrity_check(model_dir): | |
| """ | |
| Kiểm tra sự tồn tại và tính hợp lệ vật lý của các file checkpoint hoặc inference. | |
| """ | |
| print("🔍 1. Kiểm tra tính toàn vẹn của tệp tin mô hình...") | |
| if not os.path.exists(model_dir): | |
| print(f"❌ Lỗi: Thư mục chứa mô hình không tồn tại tại: {model_dir}") | |
| return False | |
| found_files = os.listdir(model_dir) | |
| # Kiểm tra xem đây có phải là thư mục mô hình inference không | |
| if "inference.pdiparams" in found_files: | |
| print("✅ Tìm thấy bộ mô hình Inference ('inference')") | |
| file_path = os.path.join(model_dir, "inference.pdiparams") | |
| sz_mb = os.path.getsize(file_path) / (1024 * 1024) | |
| print(f" - inference.pdiparams ({sz_mb:.2f} MB)") | |
| return True | |
| required_extensions = [".pdparams", ".pdopt", ".states"] | |
| prefixes = ["best_accuracy", "latest"] | |
| passed = True | |
| for prefix in prefixes: | |
| has_prefix = any(f.startswith(prefix) for f in found_files) | |
| if has_prefix: | |
| print(f"✅ Tìm thấy bộ checkpoint loại: '{prefix}'") | |
| # Kiểm tra xem có đủ các file cần thiết không | |
| for ext in required_extensions: | |
| expected_file = f"{prefix}{ext}" | |
| file_path = os.path.join(model_dir, expected_file) | |
| if expected_file not in found_files: | |
| print(f"⚠️ Thiếu tệp: {expected_file}") | |
| passed = False | |
| else: | |
| sz_mb = os.path.getsize(file_path) / (1024 * 1024) | |
| if sz_mb == 0: | |
| print(f"❌ Tệp tin bị rỗng (0 bytes): {expected_file}") | |
| passed = False | |
| else: | |
| print(f" - {expected_file} ({sz_mb:.2f} MB)") | |
| break | |
| else: | |
| print("❌ Không tìm thấy tệp checkpoint hoặc inference hợp lệ.") | |
| passed = False | |
| return passed | |
| def calculate_cer(predicted, target): | |
| """Tính toán Levenshtein Distance để tìm Character Error Rate (CER)""" | |
| # Mảng 2D cho Levenshtein distance | |
| d = [[0] * (len(target) + 1) for _ in range(len(predicted) + 1)] | |
| for i in range(len(predicted) + 1): | |
| d[i][0] = i | |
| for j in range(len(target) + 1): | |
| d[0][j] = j | |
| for i in range(1, len(predicted) + 1): | |
| for j in range(1, len(target) + 1): | |
| if predicted[i - 1] == target[j - 1]: | |
| cost = 0 | |
| else: | |
| cost = 1 | |
| d[i][j] = min( | |
| d[i - 1][j] + 1, # Deletion | |
| d[i][j - 1] + 1, # Insertion | |
| d[i - 1][j - 1] + cost # Substitution | |
| ) | |
| if not target: | |
| return 0.0 if not predicted else 1.0 | |
| return d[len(predicted)][len(target)] / len(target) | |
| def run_inference_qa(model_dir, dict_path, data_dir, num_test_samples=20): | |
| """ | |
| Tải mô hình và chạy nhận diện thử nghiệm để đánh giá chất lượng (Accuracy, CER). | |
| """ | |
| print("\n🔮 2. Khởi chạy kiểm tra độ chính xác nhận dạng mô hình...") | |
| # 1. Kiểm tra tập dữ liệu validation | |
| val_label_path = os.path.join(data_dir, "val_label.txt") | |
| if not os.path.exists(val_label_path): | |
| print(f"❌ Không tìm thấy nhãn tập Val: {val_label_path}") | |
| return None | |
| # 2. Đọc các mẫu kiểm thử từ file nhãn | |
| test_samples = [] | |
| with open(val_label_path, 'r', encoding='utf-8') as f: | |
| for line in f: | |
| parts = line.strip().split('\t') | |
| if len(parts) >= 2: | |
| test_samples.append((parts[0], parts[1])) | |
| if not test_samples: | |
| print("❌ Tập nhãn Val rỗng, không có dữ liệu để chạy thử.") | |
| return None | |
| # Lấy ngẫu nhiên các mẫu để chạy QA nhanh | |
| import random | |
| random.seed(42) # Cố định ngẫu nhiên | |
| if len(test_samples) > num_test_samples: | |
| test_samples = random.sample(test_samples, num_test_samples) | |
| # 3. Khởi tạo PaddleOCR Recognition engine sử dụng TextRecognizer cấp thấp để nạp checkpoint hoặc inference trực tiếp | |
| try: | |
| import sys | |
| PROJECT_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) | |
| paddleocr_dir = os.path.join(PROJECT_ROOT, "PaddleOCR") | |
| if paddleocr_dir not in sys.path: | |
| sys.path.insert(0, paddleocr_dir) | |
| from tools.infer.predict_rec import TextRecognizer | |
| import tools.infer.utility as utility | |
| best_model_prefix = None | |
| if "inference.pdiparams" in os.listdir(model_dir): | |
| best_model_prefix = model_dir | |
| else: | |
| for f in os.listdir(model_dir): | |
| if f.startswith("best_accuracy") and f.endswith(".pdparams"): | |
| best_model_prefix = os.path.join(model_dir, "best_accuracy") | |
| break | |
| elif f.startswith("latest") and f.endswith(".pdparams"): | |
| best_model_prefix = os.path.join(model_dir, "latest") | |
| break | |
| if not best_model_prefix: | |
| print("❌ Không tìm thấy tiền tố file pdparams để load.") | |
| return None | |
| print(f"🔄 Đang nạp mô hình từ: {best_model_prefix}") | |
| # Backup sys.argv và xóa tạm thời để tránh argparse lỗi đối số chưa định nghĩa | |
| sys_argv_backup = sys.argv | |
| sys.argv = [sys.argv[0]] | |
| args = utility.parse_args() | |
| sys.argv = sys_argv_backup | |
| args.rec_model_dir = best_model_prefix | |
| args.rec_char_dict_path = dict_path | |
| # Cấu hình sử dụng CPU hoặc GPU bằng paddle | |
| import paddle | |
| args.use_gpu = paddle.is_compiled_with_cuda() | |
| args.rec_image_shape = "3, 48, 320" | |
| args.use_space_char = True | |
| ocr = TextRecognizer(args) | |
| except Exception as e: | |
| print(f"❌ Không thể tải bộ thư viện PaddleOCR hoặc trọng số mô hình: {e}") | |
| import traceback | |
| traceback.print_exc() | |
| return None | |
| # 4. Chạy kiểm thử từng ảnh | |
| total_cer = 0.0 | |
| correct_predictions = 0 | |
| print("\n--- Báo cáo Chi tiết Nhận dạng ---") | |
| for i, (rel_img_path, ground_truth) in enumerate(test_samples): | |
| img_path = os.path.join(data_dir, rel_img_path) | |
| if not os.path.exists(img_path): | |
| continue | |
| try: | |
| # Chạy nhận diện bằng cv2.imread + TextRecognizer | |
| import cv2 | |
| img = cv2.imread(img_path) | |
| if img is not None: | |
| rec_res, elapse = ocr([img]) | |
| if rec_res and rec_res[0]: | |
| pred_text, confidence = rec_res[0] | |
| else: | |
| pred_text, confidence = "", 0.0 | |
| else: | |
| pred_text, confidence = "", 0.0 | |
| cer = calculate_cer(pred_text, ground_truth) | |
| total_cer += cer | |
| is_correct = (pred_text == ground_truth) | |
| if is_correct: | |
| correct_predictions += 1 | |
| status_symbol = "✅ Đúng" if is_correct else f"❌ Sai (CER: {cer * 100:.1f}%)" | |
| print(f"[{i+1:02d}] Ảnh: {os.path.basename(rel_img_path)}") | |
| print(f" Nhãn gốc: '{ground_truth}'") | |
| print(f" Nhận diện: '{pred_text}' (Độ tin cậy: {confidence:.2f})") | |
| print(f" Kết quả: {status_symbol}") | |
| except Exception as e: | |
| print(f"⚠️ Lỗi khi chạy ảnh {rel_img_path}: {e}") | |
| # 5. Tổng kết độ chính xác | |
| avg_cer = total_cer / len(test_samples) | |
| word_accuracy = correct_predictions / len(test_samples) | |
| print("\n--- KẾT QUẢ ĐÁNH GIÁ TRỰC QUAN ---") | |
| print(f"📈 Word-level Accuracy (Độ chính xác từ): {word_accuracy * 100:.2f}%") | |
| print(f"📉 Average Character Error Rate (CER trung bình): {avg_cer * 100:.2f}%") | |
| # Tiêu chuẩn thông qua QA: CER < 5% | |
| qa_status = "PASS" if avg_cer < 0.05 else "WARNING" if avg_cer < 0.15 else "FAIL" | |
| print(f"🏆 Trạng thái chất lượng mô hình: {qa_status.upper()}") | |
| return { | |
| "word_accuracy": word_accuracy, | |
| "avg_cer": avg_cer, | |
| "qa_status": qa_status | |
| } | |
| if __name__ == '__main__': | |
| # Đường dẫn kiểm thử mặc định | |
| PROJECT_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) | |
| default_model = os.path.join(PROJECT_ROOT, "data", "output", "rec_cham", "rec_ppocr_v4") # Hoặc đường dẫn đã giải nén | |
| default_dict = os.path.join(PROJECT_ROOT, "data", "cham_dict.txt") | |
| default_data = os.path.join(PROJECT_ROOT, "data", "cham_synthetic_images") | |
| if len(sys.argv) > 1: | |
| default_model = sys.argv[1] | |
| if len(sys.argv) > 2: | |
| default_dict = sys.argv[2] | |
| if len(sys.argv) > 3: | |
| default_data = sys.argv[3] | |
| print("=== Khởi chạy Kiểm tra Chất lượng Mô hình ===") | |
| integrity_ok = run_integrity_check(default_model) | |
| if integrity_ok: | |
| run_inference_qa(default_model, default_dict, default_data) | |
| else: | |
| print("\n❌ Thất bại: Không thể thực hiện kiểm tra độ chính xác do tệp tin checkpoints thiếu hoặc lỗi.") | |