Bối cảnh dự án
VAON xây dựng một microservice OCR để trích xuất text và mã định danh (serial, IMEI, part number, model number) từ ảnh chụp nhãn sản phẩm. Yêu cầu khó nhất không phải độ chính xác trên ảnh in đẹp, mà là chạy được trên ảnh chụp thực tế bằng CPU. Kết quả kiểm chứng: tiếng Anh confidence 0.997, tiếng Nhật 0.999, đúng 100%, trên bộ model 132MB không cần GPU.
Thách thức
Ảnh đầu vào là ảnh chụp thực tế, không phải bản scan sạch
Nhãn được chụp bằng camera trong điều kiện vận hành thật: góc nghiêng, mờ, ánh sáng không đều, nền lộn xộn. OCR đạt điểm cao trên ảnh in chuẩn có thể hỏng hoàn toàn ở nhóm ảnh này. Tiêu chí đánh giá vì vậy phải là ảnh chụp, không phải ảnh render.
OCR mặc định đọc sai tiếng Việt có dấu: mất dấu hoàn toàn
Với chuỗi Sản phẩm Việt Nam, engine mặc định trả về Sn phm Vit Nam. Dấu bị bỏ sạch, không phải sai lệch nhẹ. Trong khi đó tiếng Anh, tiếng Trung và tiếng Nhật không gặp vấn đề này. Đây là lỗi riêng của một ngôn ngữ, cần xử lý riêng chứ không thể chỉnh tham số chung.
Phải chạy CPU-only để kiểm soát chi phí hạ tầng
Ràng buộc đặt ra từ đầu: không phụ thuộc GPU. Điều này loại bỏ phần lớn model OCR nặng và buộc phải chọn theo trục kích thước model, không chỉ theo trục độ chính xác. Mọi phương án đều được đo trên cùng một điều kiện CPU.
Mã định danh nằm rải rác trên nhãn, đọc được text chưa đủ
Một nhãn chứa nhiều cụm số. Việc đọc đúng ký tự chưa giải quyết được bài toán: cần biết cụm nào là serial, cụm nào là part number, và ghép đúng nhãn với giá trị đứng cạnh nó. Đây là bài toán cấu trúc, không phải bài toán nhận dạng ký tự.
Giải pháp của VAON
Tách thành hai endpoint OCR độc lập hoàn toàn thay vì gộp chung một pipeline. Client chọn engine theo nhu cầu ngôn ngữ, không bị ép qua một chuỗi xử lý duy nhất.
Chọn PP-OCRv6_medium làm engine mặc định vì hỗ trợ sẵn tiếng Trung, Anh, Nhật và hệ Latin trong cùng một model 132MB, đã kiểm chứng bằng test thật, không cần huấn luyện thêm.
Thêm VietOCR làm engine phụ chuyên tiếng Việt có dấu, bật/tắt qua flag VIETOCR_ENABLED và mặc định tắt. Lý do chọn cách này thay vì thay engine mặc định: VietOCR nặng khoảng 623MB, gấp gần 5 lần engine chính, nên chỉ nên trả chi phí đó khi thực sự có nhu cầu tiếng Việt.
Viết lớp trích xuất mã định danh dùng chung cho cả hai nhánh, theo hướng rule-based: regex label-matching, ghép không gian theo lân cận gần nhất, và Luhn checksum cho IMEI. Chọn rule thay vì huấn luyện model riêng để kết quả minh bạch và audit được từng bước.
Tách khoá điều phối đồng thời riêng cho mỗi engine (asyncio.Semaphore kết hợp threading.Lock), tránh hai engine tranh chấp tài nguyên của nhau khi cùng chịu tải.
Siết đầu vào ở tầng middleware: giới hạn dung lượng upload, chặn decompression-bomb bằng trần 25 megapixel, ghim pillow==12.3.0 để vá CVE-2023-4863, và xác thực SHA256 cho file weight tải về.
Giá trị mang lại
Đúng 100% trên tiếng Anh và tiếng Nhật, confidence 0.997-0.999
Chuỗi S/N: ABC123456 đạt confidence 0.997. Chuỗi tiếng Nhật 日本語のテスト製品番号123 đạt 0.999. Cả hai đọc đúng hoàn toàn bằng engine mặc định, không cần cấu hình riêng theo ngôn ngữ.
Tiếng Việt có dấu: từ sai hoàn toàn thành đúng 100%
Cùng một chuỗi mà engine mặc định trả về Sn phm Vit Nam, VietOCR đọc đúng toàn bộ với confidence 0.913. Vấn đề được khoanh vùng đúng một ngôn ngữ và xử lý bằng một engine bật theo nhu cầu, thay vì đánh đổi trên toàn hệ thống.
132MB, không cần GPU
Bộ model mặc định gồm 59MB detection và 73MB recognition. Toàn bộ chạy trên CPU. Trong phiên demo ghi hình, một lượt đọc mã hoàn tất ở mức 1.800ms. Chi phí hạ tầng vì vậy nằm ở nhóm máy phổ thông, không phải nhóm có GPU.
Trích xuất mã định danh minh bạch, audit được
Vì dùng rule thay vì model, mỗi mã trích ra đều truy được về quy tắc đã khớp: nhãn nào, vị trí nào, có qua Luhn checksum hay không. Khi kết quả sai, nguyên nhân xác định được ngay thay vì phải huấn luyện lại.
54 test tự động và một danh sách giới hạn được công bố
Service có 42 unit test và 12 integration test chạy trên model thật, tách thành hai quy trình riêng. Trong quá trình phát triển, một route mới bị phát hiện chưa áp giới hạn dung lượng upload; lỗi được vá trước khi release. Upload 11MB hiện trả về 413 đúng như thiết kế.