ภาพรวม
POST/ocr
ส่งไฟล์เอกสาร 1 ไฟล์ต่อ request แบบ multipart/form-data
ส่วนประกอบของ request
| ส่วน | ค่า | หมายเหตุ |
|---|---|---|
| Method | POST | |
| URL | http://<host>:5000/ocr | |
Header Content-Type | multipart/form-data; boundary=... |
HTTP library ส่วนใหญ่ตั้งให้อัตโนมัติเมื่อแนบไฟล์ ไม่ต้องกำหนดเอง |
| Authorization | ไม่ต้องส่ง |
Form fields
| ชื่อ field | ชนิด | จำเป็น | คำอธิบาย |
|---|---|---|---|
file | binary (ไฟล์) | ใช่ | ไฟล์เอกสาร ขนาดไม่เกิน 100 MB ชื่อไฟล์ไม่มีผลต่อการประมวลผลและไม่ถูกบันทึก part นี้ต้องระบุ Content-Type ด้วย (ดูตารางด้านล่าง) |
ชนิดไฟล์ที่รองรับ
| ชนิดไฟล์ | Content-Type ของ part | วิธีที่ระบบตรวจชนิดไฟล์ | การประมวลผล |
|---|---|---|---|
ค่าใดก็ได้ (แนะนำ application/pdf) | จากเนื้อไฟล์ (ขึ้นต้นด้วย %PDF-) |
แปลงแต่ละหน้าเป็นภาพ 200 dpi แล้ว OCR ทุกหน้า สูงสุด 30 หน้า | |
| DOCX | ค่าใดก็ได้ | จากเนื้อไฟล์ (ไฟล์ zip) | OCR เฉพาะภาพที่แทรกในเนื้อเอกสาร ตามลำดับที่ปรากฏ โดยแต่ละภาพนับเป็น 1 หน้า ข้อความที่พิมพ์ใน Word ไม่ถูก OCR เพราะอ่านจากไฟล์ได้โดยตรงอยู่แล้ว |
| ภาพ (PNG, JPEG, TIFF, BMP, WebP) | ต้องเป็น image/* | จาก Content-Type ของ part | OCR 1 หน้า และหมุนภาพที่เอียงหรือกลับหัวให้ตรงอัตโนมัติ |
ถ้าส่งไฟล์ภาพด้วย Content-Type application/octet-stream จะได้ 415
ให้ระบุ image/png, image/jpeg หรือชนิดที่ตรงกับไฟล์
การนำผลไปใช้
Response มีข้อมูลหลายระดับ ตั้งแต่หัวเอกสารที่แยกเป็น field ไปจนถึงพิกัดของแต่ละคำ ให้เลือกใช้ส่วนที่ตรงกับงาน ไม่จำเป็นต้องใช้ทุกส่วน
| ต้องการ | ใช้ field | วิธีใช้ |
|---|---|---|
| ข้อมูลหัวหนังสือ (ที่, วันที่, เรื่อง, เรียน, ผู้ลงนาม) | documents |
1 รายการต่อ 1 เอกสารในไฟล์ แต่ละรายการบอกหน้าที่เอกสารเริ่ม และมี key ตาม fields |
| เนื้อความของหนังสือ | pages[].structure.blocks |
เลือกเฉพาะ block ที่ type เป็น paragraph และ list_item
(ไม่รวมหัวเอกสาร ตรารับ ลายเซ็น และเลขหน้า) |
| ตาราง เช่น บัญชีรายชื่อแนบท้าย | pages[].structure.blocks |
เลือก block ที่ type เป็น table แล้วใช้ rows (array 2 มิติ)
หรือใช้ cells ถ้าต้องการโครงสร้างการ merge |
| แสดงผลให้คนอ่าน | markdown |
Markdown ของทั้งไฟล์ ตารางเป็น HTML <table> เพื่อรองรับ merged cells |
| ค้นหาข้อความ หรือทำ full-text index | pages[].text |
ข้อความดิบของทั้งหน้าเรียงตามบรรทัด ไม่มีโครงสร้าง |
| ไฮไลต์ตำแหน่งบนภาพ หรือตรวจความมั่นใจ | pages[].lines |
แต่ละบรรทัดมีกรอบ (box) และ confidence ส่วน words ใช้ระบุตำแหน่งระดับคำ |
ขั้นตอนที่แนะนำ
- ตรวจ HTTP status ถ้าไม่ใช่ 200 ให้อ่านสาเหตุจาก
detail(ดู Error) - ตรวจว่าประมวลผลครบทุกหน้า โดยเทียบ
total_pagesกับจำนวนรายการในpagesถ้าไม่เท่ากัน แสดงว่าไฟล์เกิน 30 หน้าและหน้าที่เกินถูกข้าม - วนอ่าน
pagesแล้วข้ามหน้าที่มีerrorหน้าที่อ่านได้จะมีstructureเสมอ - ใช้
documentsเป็นตัวแบ่งเอกสาร: PDF หนึ่งไฟล์มักรวมหลายเอกสาร เช่น บันทึกข้อความ + คำสั่ง + บัญชีแนบท้าย เนื้อหาของแต่ละเอกสารคือหน้าตั้งแต่หน้าที่เอกสารนั้นเริ่ม จนถึงก่อนหน้าที่เอกสารถัดไปเริ่ม - ถ้าไม่ต้องการภาพ ให้ทิ้ง
pages[].imageทันทีหลังรับ response ภาพนี้มีขนาด 200–400 KB ต่อหน้า
ค่าทั้งหมดเป็นข้อความตามที่ OCR อ่านได้ วรรณยุกต์อาจตกหรือเกิน และเว้นวรรคอาจหายบ้าง
ก่อนนำค่าไปเทียบตรงตัว (เช่น ค้นฐานข้อมูลด้วย ref_no) ควร normalize ก่อน หรือให้คนตรวจทาน
ใช้ confidence ใน lines ช่วยคัดบรรทัดที่ควรตรวจ
Response ระดับไฟล์
สำเร็จได้ HTTP 200 body เป็น JSON object ซ้อนกัน 3 ชั้น: ไฟล์ → หน้า → โครงสร้าง
ระดับไฟล์
- kind
- total_pages
- documents
- markdown
- pages[] →
ต่อหน้า
- name
- text · lines · dropped
- rotation · timing
- image
- structure →
structure (ใช้ส่วนนี้เป็นหลัก)
- fields
- blocks
- markdown
key ระดับไฟล์:
| field | ชนิด | คำอธิบาย |
|---|---|---|
kind | string | ชนิดไฟล์ที่ระบบตรวจได้: "pdf", "docx" หรือ "image" |
total_pages | integer | จำนวนหน้าทั้งหมดในไฟล์ (DOCX คือจำนวนภาพ)
ถ้ามากกว่าจำนวนรายการใน pages แสดงว่ามีหน้าที่ถูกข้ามเพราะเกิน 30 หน้า |
pages | array of object | ผลของแต่ละหน้าตามลำดับ ดู pages[] |
documents | array of object | 1 รายการต่อทุกหน้าที่เป็นหน้าแรกของเอกสาร
ประกอบด้วย page (string ชื่อหน้า เช่น "หน้า 2") และ key ทั้งหมดของ fields ในหน้านั้น
เป็น [] ถ้าไม่พบหัวเอกสารเลย |
markdown | string | Markdown ของทั้งไฟล์ แต่ละหน้าคั่นด้วย comment
<!-- หน้า n --> ไม่รวมเลขหน้าและข้อความบอกต่อหน้า |
ตัวอย่าง response
ตัวอย่างจาก PDF 3 หน้า: หน้า 1 เป็นบันทึกข้อความ หน้า 2 เป็นบัญชีแนบท้ายที่มีตาราง
และหน้า 3 เป็นหน้าที่อ่านไม่ได้ (ใส่ไว้เพื่อให้เห็นรูปแบบ error ปกติจะเกิดกับภาพใน DOCX)
ข้อมูลในตัวอย่างสมมติขึ้น และตัดบางส่วนให้สั้นลง: image ตัดเหลือ ...
และ lines แสดงเพียงบรรทัดเดียว
{
"kind": "pdf",
"total_pages": 3,
"documents": [
{
"page": "หน้า 1",
"doc_type": "บันทึกข้อความ",
"department": "องค์การบริหารส่วนจังหวัดตัวอย่าง กองการเจ้าหน้าที่",
"ref_no": "ตย ๕๑๐๑๑/๑๒๓",
"date": "๕ พฤศจิกายน ๒๕๖๙",
"subject": "ขออนุมัติแต่งตั้งคณะทำงาน",
"to": "นายกองค์การบริหารส่วนจังหวัดตัวอย่าง",
"signers": [
{ "name": "นายสมชาย ใจดี", "position": "ผู้อำนวยการกองการเจ้าหน้าที่" }
]
}
],
"markdown": "<!-- หน้า 1 -->\n\n# บันทึกข้อความ\n\n**ส่วนราชการ** องค์การบริหารส่วนจังหวัดตัวอย่าง ...",
"pages": [
{
"name": "หน้า 1",
"image": "data:image/jpeg;base64,...",
"text": "บันทึกข้อความ\nส่วนราชการ องค์การบริหารส่วนจังหวัดตัวอย่าง กองการเจ้าหน้าที่\n...",
"lines": [
{
"text": "บันทึกข้อความ",
"confidence": 0.9871,
"row": 0,
"box": [[612.0, 180.5], [1050.2, 180.5], [1050.2, 248.0], [612.0, 248.0]],
"words": [
{ "text": "บันทึกข้อความ", "confidence": 0.9871,
"box": [[612.0, 180.5], [1050.2, 180.5], [1050.2, 248.0], [612.0, 248.0]] }
]
}
],
"dropped": [
{ "text": "ส", "confidence": 0.41, "row": 21,
"box": [[880.0, 1702.0], [1010.0, 1702.0], [1010.0, 1790.0], [880.0, 1790.0]],
"reason": "too_short" }
],
"rotation": 0,
"text_angle": 0.12,
"timing": { "det_ms": 142.3, "rec_ms": 268.9, "n_lines": 24 },
"structure": {
"fields": {
"doc_type": "บันทึกข้อความ",
"department": "องค์การบริหารส่วนจังหวัดตัวอย่าง กองการเจ้าหน้าที่",
"ref_no": "ตย ๕๑๐๑๑/๑๒๓",
"date": "๕ พฤศจิกายน ๒๕๖๙",
"subject": "ขออนุมัติแต่งตั้งคณะทำงาน",
"to": "นายกองค์การบริหารส่วนจังหวัดตัวอย่าง",
"signers": [
{ "name": "นายสมชาย ใจดี", "position": "ผู้อำนวยการกองการเจ้าหน้าที่" }
]
},
"blocks": [
{ "type": "stamp", "text": "เลขรับ: 1343\nวันที่: 6 พ.ย. 69" },
{ "type": "title", "text": "บันทึกข้อความ" },
{ "type": "field", "key": "department", "text": "ส่วนราชการ องค์การบริหารส่วนจังหวัดตัวอย่าง กองการเจ้าหน้าที่" },
{ "type": "field", "key": "ref_no", "text": "ที่ ตย ๕๑๐๑๑/๑๒๓ วันที่ ๕ พฤศจิกายน ๒๕๖๙" },
{ "type": "field", "key": "subject", "text": "เรื่อง ขออนุมัติแต่งตั้งคณะทำงาน" },
{ "type": "field", "key": "to", "text": "เรียน นายกองค์การบริหารส่วนจังหวัดตัวอย่าง" },
{ "type": "paragraph", "text": "ด้วยกองการเจ้าหน้าที่มีความจำเป็นต้องแต่งตั้งคณะทำงาน\nเพื่อดำเนินการปรับปรุงทะเบียนประวัติ ดังนี้" },
{ "type": "list_item", "text": "๑) นายสมศักดิ์ ตัวอย่าง ประธานคณะทำงาน" },
{ "type": "list_item", "text": "๒) นางสาวสมหญิง ตัวอย่าง คณะทำงาน" },
{ "type": "paragraph", "text": "จึงเรียนมาเพื่อโปรดพิจารณาอนุมัติ" },
{ "type": "signature", "text": "(นายสมชาย ใจดี)", "name": "นายสมชาย ใจดี", "position": "ผู้อำนวยการกองการเจ้าหน้าที่" },
{ "type": "note", "text": "- อนุมัติ" }
],
"markdown": "# บันทึกข้อความ\n\n..."
}
},
{
"name": "หน้า 2",
"image": "data:image/jpeg;base64,...",
"text": "บัญชีรายชื่อคณะทำงาน\nที่ ชื่อ - สกุล ตำแหน่ง\n...",
"lines": [],
"dropped": [],
"rotation": 0,
"text_angle": -0.05,
"timing": { "det_ms": 120.7, "rec_ms": 190.4, "n_lines": 15 },
"structure": {
"fields": {},
"blocks": [
{ "type": "caption", "text": "บัญชีรายชื่อคณะทำงาน" },
{
"type": "table",
"kind": "ruled",
"n_rows": 4,
"n_cols": 4,
"rows": [
["ที่", "ชื่อ - สกุล", "ตำแหน่ง", ""],
["", "", "ประเภท", "ระดับ"],
["๑", "นายสมศักดิ์ ตัวอย่าง", "อำนวยการ", "ต้น"],
["๒", "นางสาวสมหญิง ตัวอย่าง", "วิชาการ", "ชำนาญการ"]
],
"cells": [
{ "row": 0, "col": 0, "rowspan": 2, "colspan": 1, "text": "ที่" },
{ "row": 0, "col": 1, "rowspan": 2, "colspan": 1, "text": "ชื่อ - สกุล" },
{ "row": 0, "col": 2, "rowspan": 1, "colspan": 2, "text": "ตำแหน่ง" },
{ "row": 1, "col": 2, "rowspan": 1, "colspan": 1, "text": "ประเภท" },
{ "row": 1, "col": 3, "rowspan": 1, "colspan": 1, "text": "ระดับ" },
{ "row": 2, "col": 0, "rowspan": 1, "colspan": 1, "text": "๑" },
{ "row": 2, "col": 1, "rowspan": 1, "colspan": 1, "text": "นายสมศักดิ์ ตัวอย่าง" },
{ "row": 2, "col": 2, "rowspan": 1, "colspan": 1, "text": "อำนวยการ" },
{ "row": 2, "col": 3, "rowspan": 1, "colspan": 1, "text": "ต้น" },
{ "row": 3, "col": 0, "rowspan": 1, "colspan": 1, "text": "๒" },
{ "row": 3, "col": 1, "rowspan": 1, "colspan": 1, "text": "นางสาวสมหญิง ตัวอย่าง" },
{ "row": 3, "col": 2, "rowspan": 1, "colspan": 1, "text": "วิชาการ" },
{ "row": 3, "col": 3, "rowspan": 1, "colspan": 1, "text": "ชำนาญการ" }
],
"box": [[150.0, 420.0], [2200.0, 420.0], [2200.0, 1100.0], [150.0, 1100.0]]
},
{ "type": "page_number", "text": "-๒-" }
],
"markdown": "**บัญชีรายชื่อคณะทำงาน**\n\n<table>...</table>"
}
},
{
"name": "หน้า 3",
"error": "ถอดรหัสภาพไม่ได้ (เช่น EMF/WMF ไม่รองรับ)"
}
]
}
จุดที่ควรสังเกตในตัวอย่าง:
- ค่าใน
fieldsไม่มีคำนำหน้า เช่น"subject": "ขออนุมัติ…"แต่textของ blockfieldมีคำนำหน้า เช่น"เรื่อง ขออนุมัติ…" paragraphแรกมี\nตรงจุดที่เอกสารขึ้นบรรทัดใหม่- หน้า 2 ไม่ได้ขึ้นต้นเอกสารใหม่ จึงมี
fieldsเป็น{}และไม่อยู่ในdocuments - ในตาราง "ที่" และ "ชื่อ - สกุล" คลุม 2 แถว (
rowspan: 2) ส่วน "ตำแหน่ง" คลุม 2 คอลัมน์ (colspan: 2) ในrowsช่องที่ถูกคลุมจึงเป็น"" - หน้า 3 มีเพียง
nameและerror
pages[]: ผลแต่ละหน้า
แต่ละหน้ามี 2 แบบ: หน้าที่อ่านได้มี field ครบตามตาราง ส่วนหน้าที่อ่านไม่ได้มีเพียง name และ error
ใช้การมีอยู่ของ structure หรือ error แยกสองกรณีนี้
| field | ชนิด | คำอธิบาย |
|---|---|---|
name | string | ชื่อหน้า: PDF เป็น "หน้า 1", DOCX เป็น "ภาพ 1 (image1.png)", ไฟล์ภาพเป็น "ภาพ" |
error | string | มีเฉพาะหน้าที่อ่านไม่ได้ เช่น ภาพใน DOCX เป็น EMF/WMF หรือภาพใหญ่เกิน หน้าแบบนี้ไม่มี field อื่น และ request ยังได้ 200 |
structure | object | ผลการแยกโครงสร้างของหน้า ซึ่งเป็นส่วนหลักที่ควรใช้ ดู structure |
text | string | ข้อความทั้งหน้าเรียงตามลำดับอ่าน แต่ละบรรทัดคั่นด้วย \n ไม่มีการแยกโครงสร้าง |
lines | array of object | บรรทัดที่อ่านได้พร้อมตำแหน่งและความมั่นใจ ดู lines |
dropped | array of object | บริเวณที่ตรวจพบแต่ถูกคัดออกจากผล ดู dropped |
rotation | integer | องศาที่ระบบหมุนภาพให้ตรงก่อน OCR: 0, 90, 180 หรือ 270 |
text_angle | number | มุมเอียงของข้อความบนภาพต้นฉบับ (องศา) |
timing | object | เวลาที่ใช้ประมวลผลหน้านี้ ดู timing |
image | string (data URL) | ภาพหน้าที่ใช้ OCR ในรูป data:image/jpeg;base64,...
พิกัด box ทุกตัวในหน้านี้อ้างอิงภาพนี้ (สำหรับ PDF คือภาพที่ render ที่ 200 dpi) |
structure
blocks: เนื้อหาตามประเภท
ทุก block มี type ทุก block ยกเว้นตารางมี text และบาง type มี key เพิ่มตามตาราง
การขึ้นบรรทัดใหม่ในเอกสารเก็บเป็น \n ระบบไม่ต่อบรรทัดให้ เพราะเอกสารราชการตัดบรรทัดทั้งที่เว้นวรรคและกลางคำ
ฝั่งผู้ใช้เลือกแทน \n ด้วย "" หรือ " " ตามงานได้
| type | คืออะไร | key เพิ่มเติม |
|---|---|---|
title | ชื่อเอกสาร เช่น บันทึกข้อความ, คำสั่ง…, ประกาศ… | |
field | บรรทัดหัวเอกสาร: ส่วนราชการ, ที่…วันที่, เรื่อง, เรียน, อ้างถึง, สิ่งที่ส่งมาด้วย, สั่ง ณ วันที่
โดย text รวมคำนำหน้า (เช่น "เรื่อง") ไว้ด้วย |
key (string): ชื่อ key เดียวกับใน fields เช่น "subject" |
heading | หัวข้อในเนื้อหา เช่น "๒. คณะทำงาน… ประกอบด้วย", "มีหน้าที่" | |
caption | ชื่อตารางที่อยู่เหนือตาราง เช่น "บัญชีรายละเอียด…" | |
paragraph | เนื้อหาหลักของเอกสาร คือข้อความเนื้อความที่ตามหลังหัวเอกสารหรือหัวข้อ | |
list_item | ข้อย่อยที่ขึ้นต้นด้วยเลขข้อหรือสัญลักษณ์ เช่น ๑), ๒.๑, (๓), -
โดย text รวมเลขข้อไว้ด้วย | |
table | ตาราง | ดู ตาราง (ไม่มี text) |
signature | ผู้ลงนาม โดยรวมบรรทัดตำแหน่งที่อยู่ใต้ชื่อไว้แล้ว
text คือบรรทัดชื่อในวงเล็บตามต้นฉบับ |
name (string): ชื่อที่ไม่มีวงเล็บposition (string): ตำแหน่ง ถ้าไม่พบจะเป็นค่าว่าง |
stamp | ตรารับหนังสือเหนือชื่อเอกสาร (เลขรับ, วันที่รับ, เวลารับ) | |
note | ข้อความเกษียณหรือสั่งการท้ายบันทึกข้อความ (หลังลายเซ็น) | |
page_number | เลขหน้า เช่น "-๒-" | |
continuation | ข้อความบอกต่อหน้าถัดไป เช่น "/๔. คณะทำงาน.." |
ลำดับ block ทั่วไปของบันทึกข้อความคือ
stamp → title → field (ส่วนราชการ, ที่/วันที่, เรื่อง, เรียน)
→ paragraph / list_item → signature → note
fields: หัวเอกสาร
มีค่าเฉพาะหน้าที่พบชื่อเอกสารที่ส่วนบนของหน้า หน้าอื่นเป็น {}
key ที่หาไม่พบจะไม่อยู่ใน object (ไม่ได้เป็น null) ให้ตรวจการมีอยู่ของ key ก่อนใช้
ค่าทุกตัวเป็นข้อความตามที่ OCR อ่านได้ เช่น วันที่ยังเป็นเลขไทยและปี พ.ศ. ระบบไม่แปลงรูปแบบให้
ค่าจะไม่มีคำนำหน้า เช่น "เรื่อง" หรือ "เรียน" ซึ่งต่างจาก text ของ block field
| key | ชนิด | มาจาก | ตัวอย่างค่า |
|---|---|---|---|
doc_type | string | ชื่อเอกสาร | บันทึกข้อความ · คำสั่งองค์การบริหารส่วนจังหวัดตัวอย่าง |
department | string | ส่วนราชการ | องค์การบริหารส่วนจังหวัดตัวอย่าง กองการเจ้าหน้าที่ |
ref_no | string | ที่ | ตย ๕๑๐๑๑/๑๒๓ |
date | string | วันที่ (บรรทัดเดียวกับ "ที่") | ๕ พฤศจิกายน ๒๕๖๙ |
subject | string | เรื่อง (รวมบรรทัดที่ขึ้นใหม่แล้ว) | ขออนุมัติแต่งตั้งคณะทำงาน |
to | string | เรียน | หัวหน้าส่วนราชการในสังกัดทุกส่วนราชการ |
reference | string | อ้างถึง | หนังสือ… ลงวันที่… |
enclosures | string | สิ่งที่ส่งมาด้วย | สำเนาคำสั่ง… จำนวน ๑ ฉบับ |
signed_date | string | สั่ง ณ วันที่ / ประกาศ ณ วันที่ | ๕ พฤศจิกายน พ.ศ. ๒๕๖๙ |
signers | array of object | ผู้ลงนามในหน้านั้น | แต่ละรายการมี name (string) และ position (string) |
ตาราง
block ที่มี type เป็น "table" มี key ดังนี้
| key | ชนิด | คำอธิบาย |
|---|---|---|
kind | string | "ruled" คือตารางที่มีเส้นตาราง
"list" คือรายชื่อที่จัดเป็นคอลัมน์แต่ไม่มีเส้น (เช่น ชื่อ | ตำแหน่ง | หน้าที่ ในคำสั่งแต่งตั้ง) |
n_rows | integer | จำนวนแถว (นับรวมแถวหัวตาราง) |
n_cols | integer | จำนวนคอลัมน์ |
rows | array of array of string | ข้อความของตารางในรูป 2 มิติ ขนาด n_rows × n_cols
เซลล์ที่ merge กันจะมีข้อความอยู่ที่ช่องบนซ้าย ช่องที่ถูกคลุมเป็น ""
ข้อความหลายบรรทัดในเซลล์เดียวกันคั่นด้วยช่องว่าง เหมาะกับการแปลงเป็น CSV หรือนำเข้า spreadsheet |
cells | array of object | รายการเซลล์จริง ใช้เมื่อต้องการโครงสร้างการ merge ที่แน่นอน
แต่ละเซลล์มี row, col (integer, เริ่มที่ 0), rowspan, colspan (integer, อย่างน้อย 1)
และ text (string) |
box | array of [x, y] | กรอบของตารางบนภาพ image ของหน้า เป็น 4 จุด |
ตัวอย่าง: ตารางที่มี merged cells
| ที่ | ชื่อ - สกุล | ตำแหน่ง | |
| ประเภท | ระดับ | ||
| ๑ | นายสมศักดิ์ ตัวอย่าง | อำนวยการ | ต้น |
"rows": [
["ที่", "ชื่อ - สกุล", "ตำแหน่ง", ""],
["", "", "ประเภท", "ระดับ"],
["๑", "นายสมศักดิ์ ตัวอย่าง", "อำนวยการ", "ต้น"]
],
"cells": [
{ "row": 0, "col": 0, "rowspan": 2, "colspan": 1, "text": "ที่" },
{ "row": 0, "col": 2, "rowspan": 1, "colspan": 2, "text": "ตำแหน่ง" },
…
]
หัวตารางหลายชั้น
ตัวอย่างเช่น หัวตารางที่มี "อัตราเงินเดือนก่อนเลื่อน" คลุม 3 คอลัมน์ และแถวล่างแยกเป็น "ประเภท", "ขั้น", "เงินเดือน"
ใน cells จะมีเซลล์ row 0, col 5, colspan 3 สำหรับหัวชั้นบน และเซลล์ row 1 ที่คอลัมน์ 5, 6, 7 สำหรับหัวชั้นล่าง
ส่วน rows[0] จะเป็น "อัตราเงินเดือนก่อนเลื่อน", "", "" ที่คอลัมน์ 5–7
ถ้าต้องการชื่อคอลัมน์ชั้นเดียว เช่น "อัตราเงินเดือนก่อนเลื่อน / ขั้น" ให้ระบบปลายทางรวมเองจาก cells
ตารางที่ต่อกันหลายหน้าจะได้เป็นหลายตาราง หน้าละหนึ่งตาราง
lines, words, dropped, timing
ข้อมูลระดับบรรทัดและคำจากโมเดล OCR โดยตรง ใช้สำหรับไฮไลต์ตำแหน่งบนภาพหรือตรวจคุณภาพ
พิกัดทุกตัวหน่วยเป็นพิกเซลของ image ในหน้านั้น
box คือ 4 จุด [[x, y], …] เรียงจากมุมบนซ้าย บนขวา ล่างขวา ล่างซ้าย
lines[]
| field | ชนิด | คำอธิบาย |
|---|---|---|
text | string | ข้อความในบรรทัด |
confidence | number 0–1 | ความมั่นใจของโมเดล บรรทัดที่ต่ำกว่า 0.9 ควรตรวจทาน |
row | integer | ลำดับแถวบนหน้า ส่วนที่อยู่ในแนวเดียวกัน (เช่น "ที่…" กับ "วันที่…") จะได้ row เดียวกัน |
box | array of [x, y] | กรอบของบรรทัด |
words | array of object | คำในบรรทัด แต่ละคำมี text, confidence และ box |
dropped[]
บริเวณที่ตรวจพบว่าเป็นข้อความแต่ถูกคัดออก ส่วนใหญ่คือตราประทับ ลายเซ็น หรือลายมือ
มี text, confidence, row, box เหมือน lines และมี reason เพิ่ม
| reason | ความหมาย |
|---|---|
low_confidence | ความมั่นใจต่ำกว่า 0.8 |
too_short | สั้นกว่า 2 ตัวอักษร |
invalid_thai | ลำดับอักษรไทยที่เป็นไปไม่ได้ เช่น สระหรือวรรณยุกต์ลอย |
empty | อ่านแล้วไม่ได้ข้อความ |
timing
| field | ชนิด | คำอธิบาย |
|---|---|---|
det_ms | number | เวลาหาตำแหน่งบรรทัด (มิลลิวินาที) |
rec_ms | number | เวลาอ่านข้อความ (มิลลิวินาที) |
n_lines | integer | จำนวนบรรทัดที่ส่งให้โมเดลอ่าน (รวมบรรทัดที่ถูกคัดออก) |
Error
ทุก error ตอบกลับเป็น JSON object ที่มี key detail (string ภาษาไทยบอกสาเหตุ)
ยกเว้น 422 ซึ่ง detail เป็น array รายละเอียดตามรูปแบบ validation ของ FastAPI
{ "detail": "รองรับเฉพาะไฟล์ภาพ, PDF และ DOCX" }{ "detail": [ { "type": "missing", "loc": ["body", "file"], "msg": "Field required", "input": null } ] }| Status | เมื่อไร | ควรทำอย่างไร |
|---|---|---|
400 | อ่านไฟล์ไม่ได้: PDF/DOCX/ภาพเสีย, PDF ติดรหัสผ่าน, DOCX ไม่มีภาพในเนื้อเอกสาร | ไม่ต้องส่งซ้ำ ให้แจ้งผู้ใช้ตรวจไฟล์ |
413 | ไฟล์ใหญ่เกิน 100 MB | แบ่งไฟล์หรือลดความละเอียดก่อนส่ง |
415 | ไม่ใช่ PDF หรือ DOCX และ Content-Type ไม่ใช่ image/* | ตรวจชนิดไฟล์และ Content-Type ของ part |
422 | ไม่ได้ส่ง field file หรือชื่อ field ไม่ตรง | แก้ request ให้ส่งไฟล์ใน field file |
500 | ข้อผิดพลาดภายใน service | ส่งซ้ำได้ ถ้ายังเกิดซ้ำให้แจ้งผู้ดูแล |
ใน DOCX ถ้ามีบางภาพที่อ่านไม่ได้ (เช่น EMF/WMF) request ยังสำเร็จด้วย 200
โดยหน้านั้นใน pages จะมี error แทนผลลัพธ์
GET/health
ใช้ตรวจว่า service พร้อมรับงาน (โหลดโมเดลเสร็จแล้ว) เหมาะกับ health check ของ load balancer หรือ container ไม่ต้องส่ง parameter
| field | ชนิด | คำอธิบาย |
|---|---|---|
status | string | "ok" เมื่อพร้อมใช้งาน |
backend | string | engine ที่ใช้รันโมเดล: "openvino" หรือ "onnxruntime" |
{ "status": "ok", "backend": "openvino" }ข้อจำกัด
- ไฟล์ไม่เกิน 100 MB และประมวลผลสูงสุด 30 หน้า ต่อ request หน้าที่เกินจะถูกข้าม
- อ่านเอกสารพิมพ์ภาษาไทยและอังกฤษได้ดี แต่ลายมือ ตัวอักษรตกแต่ง และภาพถ่ายทั่วไปจะอ่านไม่ได้หรือไม่แม่น
- OCR อาจอ่านวรรณยุกต์ตกหรือเกิน (เช่น ที่ → ที) และอาจตกเว้นวรรค
- การแยกโครงสร้าง (fields, blocks, ตาราง) ปรับจากหนังสือราชการรูปแบบ อบจ. เอกสารรูปแบบอื่นจึงอาจได้
paragraphมากกว่า - ผลของเอกสารหลายหน้าแยกตามหน้า ตารางแนบท้ายที่ต่อหลายหน้าจึงได้เป็นหลายตาราง