BƯỚC 01
Chuẩn bị và cách chạy
Bài 4 ghép nhận dạng và xử lý chuỗi. Ví dụ chỉ dùng phiếu giả lập có bố cục cố định, không chứa dữ liệu cá nhân. Ta đọc ORDER 2027 và TOTAL 125000, rồi tạo hai trường order_id, total_vnd.
OCR trả về chữ. Trích xuất dữ liệu mới quyết định chữ nào là mã đơn, chữ nào là số tiền. Một quy tắc đúng cho phiếu này không tự dùng được cho mọi hóa đơn.
Mở PowerShell trong một thư mục riêng. Tạo môi trường rồi cài thư viện bằng các lệnh dưới. Không cần GPU.
py -m venv .venv
.\.venv\Scripts\python.exe -m pip install opencv-python numpy pytesseractÝ nghĩa đoạn mã
Lệnh đầu tạo môi trường riêng. Lệnh sau cài thư viện vào đúng môi trường đó. Không cần kích hoạt môi trường hay đổi chính sách PowerShell.
BƯỚC 02
Tạo phiếu và lưu văn bản thô
Tạo ảnh hai dòng có đáp án tự biết. Đọc toàn khối bằng PSM 6. Khi dùng ảnh thật, cần xử lý xoay hoặc phối cảnh trước nếu trang bị nghiêng nhiều, vì biểu thức tìm chữ không thể sửa ảnh mất nét.
from pathlib import Path
import shutil
import cv2
import numpy as np
import pytesseract
engine = shutil.which("tesseract")
if engine is None:
candidate = Path(r"C:\Program Files\Tesseract-OCR\tesseract.exe")
if candidate.is_file():
engine = str(candidate)
if engine is None:
raise RuntimeError("Install Tesseract or set its executable path first")
pytesseract.pytesseract.tesseract_cmd = engine
import re
import json
image = np.full((280, 1000, 3), 255, dtype=np.uint8)
for text, y in [("ORDER 2027", 110), ("TOTAL 125000", 215)]:
cv2.putText(image, text, (35, y), cv2.FONT_HERSHEY_SIMPLEX,
2.0, (0, 0, 0), 3, cv2.LINE_AA)
cv2.imwrite("receipt_demo.png", image)
rgb = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
raw = pytesseract.image_to_string(rgb, lang="eng", config="--psm 6")
Path("receipt_raw.txt").write_text(raw, encoding="utf-8")
print(repr(raw))BƯỚC 03
Chỉ lấy trường khớp đúng quy tắc
Với phiếu mẫu, mỗi trường phải nằm trọn trên một dòng. ORDER đi với 4 chữ số, TOTAL đi với từ 1 đến 9 chữ số và chưa dùng dấu phân cách hàng nghìn. Dùng fullmatch để tránh nhận nhầm SUBTOTAL thành TOTAL.
Nếu không có đúng một kết quả cho mỗi trường, trả needs_review và để cả hai trường rỗng. Quy tắc không tự sửa O thành 0 hay đoán chữ còn thiếu. Phiếu thật cần xử lý rõ tiền tệ, dấu thập phân và nhiều loại tổng tiền.
def parse_receipt(text):
orders, totals = [], []
for line in text.upper().splitlines():
line = " ".join(line.split())
order = re.fullmatch(r"ORDER\s+([0-9]{4})", line)
total = re.fullmatch(r"TOTAL\s+([0-9]{1,9})", line)
if order:
orders.append(order.group(1))
if total:
totals.append(int(total.group(1)))
valid = len(orders) == 1 and len(totals) == 1
return {"status": "parsed" if valid else "needs_review",
"order_id": orders[0] if valid else None,
"total_vnd": totals[0] if valid else None}
result = parse_receipt(raw)
Path("receipt_fields.json").write_text(
json.dumps(result, ensure_ascii=False, indent=2), encoding="utf-8")
print(result)BƯỚC 04
Thử cả đầu vào đúng lẫn trường hợp phải xem lại
Kiểm tra bộ trích xuất riêng, chưa cần phụ thuộc OCR. Một dòng không phải TOTAL, hai dòng TOTAL hoặc ký tự không phải số đều phải được đánh dấu xem lại.
assert parse_receipt("ORDER 2027\nTOTAL 125000")["total_vnd"] == 125000
assert parse_receipt("ORDER 2027\nSUBTOTAL 125000")["status"] == "needs_review"
assert parse_receipt("ORDER 2027\nTOTAL 125000\nTOTAL 90000")["status"] == "needs_review"
assert parse_receipt("ORDER 2027\nTOTAL 12O000")["status"] == "needs_review"
print("Parser checks passed")Ý nghĩa đoạn mã
Các assert bảo vệ bốn hành vi rõ ràng. Chúng không khẳng định OCR đúng trên mọi ảnh. Chất lượng nhận dạng và chất lượng trích xuất cần được kiểm tra riêng.
BƯỚC 05
Chạy và tự kiểm tra
Mục tiêu của ảnh mẫu là order_id="2027", total_vnd=125000. Nếu JSON ghi needs_review, mở receipt_raw.txt và receipt_demo.png để tìm chỗ khác biệt. Không thay giá trị rỗng bằng một số tiền đoán.
Bước tiếp theo là thêm nhiều ảnh có đáp án, đo tỉ lệ đúng từng trường và số trường cần người xem lại. Đó là cách mở rộng từ một ví dụ chạy được sang một quy trình đáng tin cậy.
.\.venv\Scripts\python.exe ocr_receipt_fields.pyÝ nghĩa đoạn mã
Chạy tệp đã ghép bằng Python trong .venv. Kết quả được in ra cửa sổ lệnh để đối chiếu.
ĐẾN LƯỢT BẠN
Thử một thay đổi nhỏ
- Thêm dòng SUBTOTAL 100000 trước TOTAL và kiểm tra chương trình vẫn lấy đúng tổng.
- Tiếp theo tạo hai dòng TOTAL để bảo đảm hệ thống yêu cầu xem lại.
- Ghi một quy tắc mới cho ngày YYYY-MM-DD, kiểm tra cả ngày không tồn tại trước khi sử dụng.




