Tại sao đừng bao giờ quăng ảnh gốc cho thư viện OCR?
Khoảng 6 tháng trước, mình nhận một bài toán khá hóc búa: Số hóa toàn bộ kho hóa đơn cũ cho công ty. Ý tưởng đầu tiên là dùng Google Vision API hay AWS Textract để giải quyết nhanh gọn. Tuy nhiên, rào cản về bảo mật dữ liệu và chi phí khiến mình phải quay xe, tìm đến giải pháp mã nguồn mở: Tesseract OCR.
Thực tế không màu hồng như hướng dẫn trên mạng. Nếu chỉ pip install pytesseract rồi đẩy ảnh chụp điện thoại vào, kết quả trả về thường là một đống ký tự rác. OCR (Optical Character Recognition) giống như việc dạy một đứa trẻ tập đọc trong bóng tối. Muốn đọc đúng, bạn phải bật đèn, lau kính và chỉ tận tay từng dòng. Đó là lúc OpenCV vào cuộc với vai trò bộ lọc tiền xử lý cực kỳ quan trọng.
Quy trình dưới đây được mình đúc kết sau khi xử lý hơn 50.000 bản ghi thực tế. Nó giúp tăng độ chính xác từ mức 40% (ảnh thô) lên hơn 90% chỉ với vài bước xử lý ảnh căn bản.
Chuẩn bị “đồ nghề” chuẩn xác
Một sai lầm phổ biến là cài thư viện Python mà quên cài Engine Tesseract vào hệ điều hành. Pytesseract chỉ là một wrapper (lớp bọc) để Python giao tiếp với phần mềm Tesseract chạy bên dưới.
1. Cài đặt Tesseract Engine
Với Ubuntu/Debian, bạn chạy lệnh:
sudo apt update
sudo apt install tesseract-ocr tesseract-ocr-vie
Với Windows, bạn tải bản cài đặt (.exe) từ GitHub của UB Mannheim. Hãy lưu lại đường dẫn cài đặt, thường là C:\Program Files\Tesseract-OCR để cấu hình trong code.
2. Thư viện hỗ trợ
Nên dùng môi trường ảo (venv) để quản lý dự án sạch sẽ:
pip install pytesseract opencv-python numpy Pillow
Tiền xử lý: Chìa khóa để tăng độ chính xác
Kinh nghiệm xương máu của mình: Chất lượng ảnh đầu vào quyết định 80% thành bại. Thay vì quăng thẳng ảnh gốc, hãy thực hiện ba bước “làm sạch” sau bằng OpenCV.
Bước 1: Grayscale và Khử nhiễu
Ảnh màu chứa quá nhiều nhiễu hạt gây xao nhãng. Chuyển về ảnh xám giúp thuật toán tập trung vào độ tương phản giữa chữ và nền.
import cv2
import pytesseract
# Cấu hình đường dẫn cho Windows
# pytesseract.pytesseract.tesseract_cmd = r'C:\Program Files\Tesseract-OCR\tesseract.exe'
def preprocess_basic(image):
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
# Khử nhiễu bằng Median Blur để làm mịn vùng nền
return cv2.medianBlur(gray, 3)
Bước 2: Phân ngưỡng (Thresholding)
Bước này biến ảnh thành đen trắng hoàn toàn (Binary). Mình ưu tiên dùng Otsu’s Thresholding. Nó tự động tính toán ngưỡng tối ưu, đặc biệt hiệu quả khi ảnh hóa đơn bị đổ bóng hoặc ánh sáng không đều.
def apply_threshold(image):
return cv2.threshold(image, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)[1]
Bước 3: Cấu hình tham số PSM và OEM
Tesseract cung cấp các chế độ đọc khác nhau qua tham số PSM (Page Segmentation Mode). Chọn sai PSM là nguyên nhân hàng đầu khiến kết quả bị nhảy dòng hoặc mất chữ.
- PSM 6: Coi ảnh là một khối văn bản thống nhất (rất tốt cho hóa đơn, văn bản hành chính).
- PSM 3: Tự động phân vùng (dùng cho trang báo có nhiều cột).
- OEM 3: Chế độ mặc định, kết hợp engine LSTM hiện đại.
Đoạn code hoàn chỉnh để trích xuất văn bản:
def extract_invoice_text(image_path):
img = cv2.imread(image_path)
processed_img = apply_threshold(preprocess_basic(img))
# Cấu hình: Tiếng Việt + Chế độ khối văn bản
config = r'--oem 3 --psm 6 -l vie'
return pytesseract.image_to_string(processed_img, config=config)
print(extract_invoice_text('sample_invoice.png'))
Kinh nghiệm vận hành thực tế (Scale-up)
Khi đẩy script lên xử lý hàng chục nghìn ảnh mỗi ngày, bạn sẽ gặp những bài toán mà môi trường local không có.
1. Xử lý ảnh bị nghiêng (Deskewing)
Ảnh chụp bằng tay thường bị lệch góc. Chỉ cần nghiêng 5-10 độ, Tesseract sẽ đọc nhầm dòng trên xuống dòng dưới. Mình thường dùng hàm cv2.minAreaRect để tìm góc nghiêng và xoay ảnh về phương ngang trước khi OCR.
2. Đừng tin 100% vào máy (Confidence Score)
Trong môi trường Production, đừng bao giờ lưu thẳng kết quả OCR vào DB mà không kiểm tra. Pytesseract có thể trả về mức độ tự tin (0-100%) cho từng từ. Với các dự án tài chính, mình đặt ngưỡng 80%. Nếu từ nào có confidence thấp hơn, hệ thống sẽ gắn flag để con người kiểm tra lại (Human-in-the-loop).
# Lấy dữ liệu chi tiết để kiểm soát chất lượng
data = pytesseract.image_to_data(processed_img, output_type=pytesseract.Output.DICT)
for i, text in enumerate(data['text']):
conf = int(data['conf'][i])
if conf > 0: # Bỏ qua khoảng trắng
print(f"Word: {text} | Confidence: {conf}%")
3. Tối ưu hiệu năng
Pytesseract khởi tạo một process mới mỗi khi gọi hàm. Nếu chạy vòng lặp 1.000 ảnh, CPU sẽ bị quá tải. Hãy cân nhắc dùng concurrent.futures để xử lý đa luồng hoặc chuyển sang EasyOCR nếu bạn có GPU và cần tốc độ xử lý nhanh hơn.
Lời kết
OCR không phải là phép màu “cắm là chạy”. Thay vì đi tìm một thư viện vạn năng, hãy tập trung vào việc làm sạch dữ liệu đầu vào. Một tấm ảnh đen trắng sắc nét, không nhiễu và nằm ngang sẽ giúp Tesseract đạt hiệu suất tối đa. Nếu dự án yêu cầu độ phức tạp cao hơn như nhận diện chữ viết tay, lúc đó hãy cân nhắc đến các model Deep Learning tự build hoặc giải pháp trả phí.

