Skip to content

Preprocessing

Bodhi42 edited this page Nov 1, 2026 · 5 revisions

🌐 English · Русский

Straighten a difficult page

Install 0.5.1 and get the sample files.

Use deskew for a small tilt, orientation for a quarter-turn, and dewarping for bent text lines. These change the pixels before recognition; they do not repair spelling afterward.

Your input Choose Default
Scan leans a few degrees deskew=True On
Whole image is rotated 90°, 180° or 270° orientation=True Off
Text curves near a book spine Settings(unwarp=True) Off

Start with the correction matching the visible problem; flat, upright scans usually need only the defaults.

A tilted scan before and after correction

The supplied tilted.jpg leans a few degrees. The notebook uses the same deskew routine directly, then displays both images:

import numpy as np
from PIL import Image
from occular.deskew import deskew_image

image = np.array(Image.open("tilted.jpg").convert("RGB"))
straight, angle = deskew_image(image)
print("Applied correction:", angle, "degrees")

Actual output:

Applied correction: 5.8 degrees

Normal OCR already applies this correction; this low-level call makes its pixel effect visible. Open the run to compare before and after.

Open the real Jupyter run (all code visible)

Straighten a tilted scan: all notebook code and actual output

Download notebook · tilted.jpg

The scan leans a few degrees

You usually don't need to do anything. Small-angle correction, called deskew, is already on.

from occular import ocr

text = ocr("tilted-scan.png")

The photo is sideways or upside down

Turn on page orientation:

from occular import ocr

text = ocr("sideways-photo.jpg", orientation=True)

A sideways page turned upright

For sideways.png, the notebook calls the orientation model and shows its correction:

import numpy as np
from PIL import Image
from occular.orientation import OrientationDetector

image = np.array(Image.open("sideways.png").convert("RGB"))
upright, angle, score = OrientationDetector().correct(image)
print("Page rotation:", angle, "degrees")
print("Confidence:", round(score, 3))

Actual output:

Page rotation: 90 degrees
Confidence: 0.964

The 90° page rotation is a different task from the 5.8° deskew above. Open the run to see the page upright.

Open the real Jupyter run (all code visible)

Turn a sideways page upright: all notebook code and actual output

Download notebook · sideways.png

Occular tries to turn the page upright before reading it. If it isn't confident about the direction, it leaves the page as it is.

The page curves near a book spine

Try dewarping. It attempts to flatten the page before reading:

from occular import OCRPipeline, Settings

reader = OCRPipeline(Settings(unwarp=True))
text = reader.get_text("book-photo.jpg")
print(text)

This recognizes both the original and flattened candidates and compares a text-length/confidence heuristic. It keeps the flattened candidate only if its score is sufficiently higher. That is not measured reading accuracy: it takes longer and can still choose incorrectly. Test it on your actual curved pages.

A curved page before and after UVDoc

This supplied input is synthetic: straight text was bent along a smooth curve. The following direct call exposes the actual flattened candidate, using the same default UVDoc model as unwarp=True:

import cv2
from occular.unwarp_uvdoc import UVDocUnwarper

image = cv2.imread("curved.png")
flat = UVDocUnwarper(model_id="Shivin11/occular-uvdoc").unwarp(image)
print("Input and candidate dimensions:", image.shape, flat.shape)

The notebook uses ordinary Matplotlib to display the upper part of each image. The title and text baselines become nearly horizontal; no output pixels were manually retouched:

Actual UVDoc candidate compared with the synthetic curved input

A visible flattening is not proof of correct text. Run the normal guarded pipeline too; deskew=False isolates dewarping in this comparison:

from occular import OCRPipeline, Settings

original = OCRPipeline(Settings(deskew=False, reading_order=False))
guarded = OCRPipeline(Settings(deskew=False, reading_order=False, unwarp=True))
plain = original.process_image("curved.png")
chosen = guarded.process_image("curved.png")
original_score = sum(len(x["text"]) * x["confidence"] for x in plain)
guarded_score = sum(len(x["text"]) * x["confidence"] for x in chosen)
print("Original score:", round(original_score, 2))
print("Guarded score:", round(guarded_score, 2))
print("ORIGINAL — first 3 lines")
for line in plain[:3]:
    print(line["text"])
print("GUARDED — first 3 lines")
for line in chosen[:3]:
    print(line["text"])

Actual output:

Original score: 248.95
Guarded score: 433.13
ORIGINAL — first 3 lines
DOCUMENT PROCESSING NOTES
scanned page may lean edges
skew corrects that small anale
GUARDED — first 3 lines
DOCUMENT PROCESSING NOTES
A scanned page may lean a few degrees.
Deskew corrects that small angle.

The guarded score increased from 248.95 to 433.13, so the default comparison selected its flattened candidate on this input. Its first lines also read more accurately against the supplied source. These scores measure the release's heuristic, not accuracy or calibrated confidence; this synthetic example does not establish performance on real book photos.

Open the real Jupyter run (all code visible)

Actual executed notebook with all code visible

Notebook · curved.png · Run these examples

For PDFs: in version 0.5.1, orientation and dewarping work through the image path. Render PDF pages to images first →

More control: disable deskew or always dewarp

Use ocr("scan.png", deskew=False) to disable the default deskew handling. This also disables its per-line orientation handling.

OCRPipeline(Settings(unwarp="always")) skips the original-versus-flattened comparison. Use it only after checking that it helps your images; flat scans can get worse.

Geometry and dewarping details →

Clone this wiki locally