File size: 10,000 Bytes
a183ff9
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
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.")