OCR hoá đơn tiếng Việt
Trích xuất văn bản từ ảnh, PDF — hỗ trợ đầy đủ dấu tiếng Việt, ngôn ngữ chữ ký, dấu mộc đỏ. Đặc biệt tối ưu cho hoá đơn VAT mẫu Bộ Tài chính, hoá đơn điện tử và biên lai bán hàng.
Hai endpoint chính
| Endpoint | Định dạng đầu vào | Use case |
|---|---|---|
| POST /api/v1/ocr/image | JPG, PNG, HEIC, WebP — tối đa 10 MB | Ảnh chụp hoá đơn từ điện thoại |
| POST /api/v1/ocr/pdf | PDF — tối đa 50 trang, 25 MB | Hoá đơn điện tử PDF từ Misa, Viettel, FPT |
OCR ảnh — chi tiết
Cú pháp cURL cơ bản
bashcurl -X POST "https://zenicloud.io/api/v1/ocr/image?ws=prod" \
-H "Authorization: Bearer $ZENI_TOKEN" \
-H "Content-Type: image/jpeg" \
--data-binary "@hoa_don_dien.jpg"
Response trả về văn bản đã được sắp xếp theo thứ tự đọc (top-down, left-right) cùng bounding box từng dòng:
json{
"text": "CÔNG TY CỔ PHẦN ĐIỆN LỰC TPHCM\nHOÁ ĐƠN GIÁ TRỊ GIA TĂNG\nMẫu số: 01GTKT3/001\nKý hiệu: AA/26E\nSố: 0000123\nNgày 15 tháng 04 năm 2026\nMST: 0301234567\nĐơn vị mua hàng: CTY TNHH ABC\nMST người mua: 0317999888\nTổng cộng tiền thanh toán: 1.250.000 VND",
"language": "vi",
"confidence": 0.96,
"lines": [
{
"text": "CÔNG TY CỔ PHẦN ĐIỆN LỰC TPHCM",
"bbox": [120, 45, 580, 78],
"confidence": 0.98
}
],
"page_count": 1,
"processing_time_ms": 2340,
"cost_usd": 0.0015
}
Ví dụ Python — xử lý ảnh hoá đơn
pythonimport os, requests, re
ZENI = "https://zenicloud.io/api/v1"
TOKEN = os.environ["ZENI_TOKEN"]
def ocr_image(path):
with open(path, "rb") as f:
r = requests.post(
f"{ZENI}/ocr/image?ws=prod",
headers={
"Authorization": f"Bearer {TOKEN}",
"Content-Type": "image/jpeg",
},
data=f.read(),
timeout=60,
)
r.raise_for_status()
return r.json()
result = ocr_image("hoa_don_dien.jpg")
print(result["text"])
Trích xuất trường hoá đơn VAT
Sau khi có text, dùng regex hoặc chain với AI để rút field. Cách tiếp cận khuyến nghị: gọi tiếp ZeniRouter với task_type: "extract_invoice_vn" để nhận về JSON cấu trúc.
pythonimport json
def extract_invoice(text):
"""Chain OCR text vào AI để parse field."""
r = requests.post(
f"{ZENI}/router/complete?ws=prod",
headers={"Authorization": f"Bearer {TOKEN}"},
json={
"messages": [{
"role": "user",
"content": (
"Trích xuất các trường sau từ hoá đơn VN dưới dạng JSON:\n"
"so_hoa_don, ky_hieu, ngay (YYYY-MM-DD), "
"mst_ban, ten_ban, mst_mua, ten_mua, "
"tong_cong_truoc_thue, thue_vat, tong_thanh_toan.\n\n"
f"VĂN BẢN:\n{text}"
)
}],
"task_type": "extract_invoice_vn",
"response_format": "json",
},
timeout=60,
)
return json.loads(r.json()["text"])
ocr = ocr_image("hoa_don_dien.jpg")
fields = extract_invoice(ocr["text"])
print(fields)
# {
# "so_hoa_don": "0000123",
# "ky_hieu": "AA/26E",
# "ngay": "2026-04-15",
# "mst_ban": "0301234567",
# "ten_ban": "CTY CP ĐIỆN LỰC TPHCM",
# "mst_mua": "0317999888",
# "ten_mua": "CTY TNHH ABC",
# "tong_cong_truoc_thue": 1136364,
# "thue_vat": 113636,
# "tong_thanh_toan": 1250000
# }
OCR PDF — Multi-page
PDF nhiều trang sẽ được xử lý song song. Response trả về mảng pages với text từng trang:
bashcurl -X POST "https://zenicloud.io/api/v1/ocr/pdf?ws=prod" \
-H "Authorization: Bearer $ZENI_TOKEN" \
-H "Content-Type: application/pdf" \
--data-binary "@bao_cao_quy.pdf"
json{
"pages": [
{"page": 1, "text": "BÁO CÁO TÀI CHÍNH...", "confidence": 0.97},
{"page": 2, "text": "Bảng cân đối kế toán...", "confidence": 0.95}
],
"total_text": "BÁO CÁO TÀI CHÍNH... Bảng cân đối kế toán...",
"page_count": 12,
"language": "vi",
"processing_time_ms": 8420,
"cost_usd": 0.018
}
Tính năng riêng cho tiếng Việt
- Dấu thanh đầy đủ — phân biệt được "ó" với "ò", "ã" với "ả" trong điều kiện ảnh kém
- Số tiền VND — nhận diện format "1.250.000 VND", "1,250,000đ", "1.250.000 đồng"
- Ngày tháng VN — parse "Ngày 15 tháng 04 năm 2026" hay "15/04/2026"
- MST 10 hoặc 13 chữ số — tự nhận biết và validate checksum
- Dấu mộc đỏ — chữ chìm dưới dấu vẫn được trích xuất
- Chữ ký tay — phát hiện vùng có chữ ký, không cố parse
Xử lý batch — nhiều file một lúc
Khi cần xử lý hàng loạt (ví dụ 1000 hoá đơn cuối tháng), dùng async API:
pythonimport asyncio, aiohttp
async def ocr_one(session, path):
with open(path, "rb") as f:
async with session.post(
f"{ZENI}/ocr/image?ws=prod",
data=f.read(),
headers={"Authorization": f"Bearer {TOKEN}"},
) as r:
return await r.json()
async def ocr_batch(paths, concurrency=8):
sem = asyncio.Semaphore(concurrency)
async with aiohttp.ClientSession() as session:
async def bounded(p):
async with sem:
return await ocr_one(session, p)
return await asyncio.gather(*[bounded(p) for p in paths])
# 100 file song song concurrency 8
files = [f"invoices/{i}.jpg" for i in range(100)]
results = asyncio.run(ocr_batch(files))
print(f"Đã xử lý {len(results)} hoá đơn")
Chi phí OCR
| Loại | Đơn giá | Ghi chú |
|---|---|---|
| OCR ảnh | $1.50 / 1.000 ảnh | ~36.000 VND/1.000 ảnh |
| OCR PDF | $1.50 / 1.000 trang | PDF 10 trang = 10 trang đếm |
| Free tier | 500 ảnh/tháng miễn phí | Reset đầu tháng |
Tối ưu chi phí
Pre-process ảnh xuống 1024px chiều dài tối đa trước khi gửi — chất lượng OCR không giảm rõ rệt nhưng tốc độ và cost tốt hơn 30 phần trăm. Đồng thời nén JPEG 80 phần trăm thay vì PNG.
Lỗi thường gặp
| Mã | Nguyên nhân | Cách sửa |
|---|---|---|
| 413 | File quá lớn | Giảm dưới 10MB ảnh / 25MB PDF |
| 415 | Định dạng không hỗ trợ | Convert sang JPG/PNG/PDF |
| 422 | Ảnh quá mờ, confidence dưới 0.5 | Chụp lại ảnh ánh sáng tốt |
Kết hợp với module khác
- AI Router — parse text OCR thành JSON cấu trúc
- Vector Search — index hoá đơn để tìm kiếm sau này
- Cron — chạy OCR batch hàng đêm cho hoá đơn upload trong ngày