Last reviewed September 1, 2026

How do I extract tables from bad scans?

Direct answer

Fix the image first, then constrain the output. Most table failures on scanned documents are input failures: a 150 DPI fax, a page photographed at an angle, a heavily compressed JPEG, or a black-and-white threshold that ate the thin table rules. Re-scan or re-render at 300 DPI in grayscale, deskew, and only then ask software for the table — and ask for a typed array of rows rather than "the table", because a typed array is something your code can count and add up.

01

Diagnose the input before you blame the parser

Open the file and answer three questions. Is there a text layer, or is this an image pretending to be a PDF? What is the effective resolution of the page in pixels per inch? Is the page straight? Those three answers explain the large majority of "the table came out garbled" tickets.

A PDF can contain real text objects, a scanned image, or both — the PDF format has no obligation to store anything as text. If selecting text in a viewer selects nothing, no amount of parser tuning helps: you are doing image recognition, and image quality is now your main lever.

  • Resolution: 300 DPI for printed text is the comfortable target; 200 DPI is workable; below 150 DPI, thin digits start losing strokes.
  • Skew: a page rotated by two degrees breaks row alignment across a wide table. Deskew before recognition.
  • Compression: repeated JPEG saves smear digit edges. Prefer PNG or TIFF for archived scans.
  • Binarization: aggressive black-and-white conversion deletes light table rules and faint dot-matrix print. Grayscale preserves more.
  • Photos: a phone photo adds perspective distortion and uneven lighting on top of everything else. Ask senders for a scan or a native PDF.
02

Why a wall of text is worse than a typed array

Raw OCR output for a table is a stream of words with coordinates. Reconstructing rows and columns from that stream is a second, separate problem — table structure recognition — and it is where homegrown pipelines usually stall, because the rules that work for ruled tables fail on tables held together by whitespace alone.

Declaring the shape you want inverts the problem. Instead of "give me the table and I will figure out the columns", you say: this document contains zero or more rows, each row has a date, a description, an amount, and a running balance, and amounts are numbers. Now every row you get back is checkable — the count, the arithmetic, and the types are all things a test can assert.

03

The failures that survive a good scan

Even with a clean 300 DPI page, four table problems recur. Merged cells that span two columns, so a value lands under the wrong header. Multi-line descriptions that look like extra rows. Columns with no header at all, where the only clue is position. And footers — "carried forward", "subtotal", "continued" — that are not data but read exactly like data.

Handle these with structure, not hope. Give the array a header-aware description, add a boolean or enum field that lets a summary row be labelled as such, and reconcile the rows you got against a control total printed on the document. If the rows do not add up to the stated total, you have found the error without reading a single cell.

04

When looking at the page beats reading its text

There are two ways to get a table out of a scan. The common path converts the image to text or markdown first, then maps that text into fields. The alternative sends the page image itself to a vision-capable model, so layout, alignment, and rule lines stay visible during extraction.

The text-first path is usually cheaper and easier to cache. The image path earns its cost on dense multi-column layouts, forms where position carries meaning, and pages where the rules are the only thing separating columns. In Dokyumi, that choice is stored on the schema as its OCR mode: standard runs OCR and then maps fields; vision sends the page to a vision model directly. It is a per-schema property, not a per-request flag.

Checklist

Scan-quality triage: run this before touching parser settings

Seven checks, in the order that pays. Each one is measurable on the file you already have, and the first four are free to fix at the source.

  1. 1

    Is there a text layer?

    Try selecting text in a viewer, or run pdftotext. Nothing selectable means image-only: this is an OCR job, not a parsing job.

  2. 2

    What is the page resolution?

    Divide the image pixel width by the page width in inches. Under 150 DPI, re-scan rather than tune. Faxes commonly arrive near 200 x 100 DPI.

  3. 3

    Is the page skewed or rotated?

    Straighten to within about half a degree. Skew is the single biggest cause of columns bleeding into each other.

  4. 4

    Is it grayscale or hard black-and-white?

    Re-scan in grayscale where you can. Thresholded scans lose thin rules, faint stamps, and light dot-matrix print.

  5. 5

    Has it been through JPEG more than once?

    Look for blocky halos around digits. Ask the sender for the original, or a PDF/PNG/TIFF export instead.

  6. 6

    Are the table rules visible at 100% zoom?

    If you cannot see the column separators, position is the only signal left — that is the case for sending the image to a vision model rather than text.

  7. 7

    Does the document print a control total?

    If it does, you can verify the extraction automatically. If it does not, decide now who verifies the rows and how.

If a page fails three or more of these, fixing the intake is cheaper than any amount of extraction tuning: ask the sender for a native PDF, raise the scanner setting once, and every future document gets better.

Copy-pasteable artifact

A row-level schema that makes bad rows visible

The point of this schema is not that it extracts more; it is that it makes failure detectable. Rows are typed, summary rows are labelled instead of silently mixed into the data, and the document’s own stated totals are captured so your code can reconcile against them.

statement-rows.schema.json
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "title": "Scanned statement — row extraction",
  "type": "object",
  "required": ["rows"],
  "properties": {
    "stated_row_count": { "type": ["integer", "null"], "minimum": 0 },
    "stated_total": { "type": ["number", "null"] },
    "rows": {
      "type": "array",
      "items": {
        "type": "object",
        "required": ["row_type", "description", "amount"],
        "properties": {
          "row_type": { "enum": ["item", "subtotal", "carried_forward", "total"] },
          "page": { "type": ["integer", "null"], "minimum": 1 },
          "date": { "type": ["string", "null"], "format": "date" },
          "description": { "type": "string", "minLength": 1 },
          "amount": { "type": "number" },
          "balance": { "type": ["number", "null"] }
        }
      }
    }
  }
}

Terminology bridge

This is called table structure recognition, and the cleanup step is image preprocessing

Finding the cells and their relationships in an image is table structure recognition; the deskew, denoise and resolution work in front of it is preprocessing. Vendors bundle both inside "OCR", which is why two products can both claim to "extract tables" and behave completely differently on your scans. When you evaluate one, test it on your worst pages, not the sample invoice in its documentation.

  • table extraction
  • table structure recognition
  • image preprocessing
  • deskew
  • binarization

Follow-up questions

Can I just increase the DPI of a file I already have?+
Upscaling an existing image does not recover detail that was never captured — it only makes the same pixels bigger. Re-scan the paper, or ask for the native file. Re-rendering a text-layer PDF at higher DPI is different, and that does help.
What resolution do faxes arrive at?+
Group 3 fax defines standard and fine vertical resolutions with a horizontal resolution around 200 dpi, so a fax is a low-resolution bilevel image by design. Treat fax as the worst case, and prefer any other transport when you can influence the sender.
Does Dokyumi charge more for a page that needs vision mode?+
No. One credit covers a document of up to 5 pages, and a document consumes one more credit per additional 5 pages. Self-serve plans stop at 50 pages per document. The OCR mode changes how the page is read, not how it is billed.

Evidence notes

Sources and limitations

Sources used

Limitations

  • No preprocessing recovers information the scan never captured. Below roughly 150 DPI, some digits are genuinely ambiguous to any reader, human or machine.
  • Reconciliation catches rows that do not add up. It cannot detect a row that was dropped and whose absence still balances — control totals and row counts printed on the document are what close that gap.
  • Dokyumi has published no table-extraction accuracy benchmark. Test on your own worst pages before committing a workflow to it.

Published and last reviewed September 1, 2026. Product behavior can change; the linked API, pricing, and security pages are the controlling public references.

Try it on the scan that keeps failing

Define a row schema, send the page, and see which rows come back typed. The free plan includes 25 credits a month and 2 schemas, with no card required, which is enough to test this on your own documents before deciding anything.