OCRmyPDF --deskew not working? Check the tilt
2.2%
of words found by --deskew at 10 degrees
Try it on your PDF, $0.006 a page
--deskew fixed 3 degrees, not 7
Every one of these runs exited 0. At 10 degrees, 11 of 12 pages came back with no text.
Check the angle it used:
ocrmypdf -v 1 --deskew in.pdf out.pdf 2>&1 | grep "Deskew angle"
9 Deskew angle: 0.000
10 Deskew angle: 0.000
11 Deskew angle: 0.000
12 Deskew angle: 0.000
Pages 1 to 8 printed no angle line, only
[tesseract] Empty page!!. Both mean the page was not
turned.
Why: Tesseract sees no text lines
OCRmyPDF asks Tesseract for the angle with --psm 2. On a
page tilted 10 degrees, Tesseract found nothing:
tesseract page.png stdout --psm 2
Empty page!!
OCRmyPDF then uses an angle of 0. At 3 degrees, Tesseract printed
Deskew angle: 0.0442, in radians: about 2.5 degrees.
Fix: straighten the page first
My script tries every angle from -45 to 45 degrees, turns each page, and saves a new PDF. It needs PyMuPDF, Pillow and NumPy.
python straighten.py in.pdf straight.pdf
ocrmypdf --rotate-pages --deskew straight.pdf out.pdf
straighten.py, 33 lines
import io
import sys
import numpy as np
import pymupdf
from PIL import Image
def sharpness(ink, angle):
rows = np.asarray(ink.rotate(float(angle))).sum(axis=1, dtype=float)
return ((rows[1:] - rows[:-1]) ** 2).sum()
def find_angle(gray):
small = gray.resize((gray.width // 4, gray.height // 4))
ink = small.point(lambda v: 255 if v < 128 else 0)
rough = max(range(-45, 46), key=lambda a: sharpness(ink, a))
fine = np.arange(rough - 1, rough + 1.25, 0.25)
return float(max(fine, key=lambda a: sharpness(ink, a)))
src, dst = pymupdf.open(sys.argv[1]), pymupdf.open()
for number, page in enumerate(src, 1):
pix = page.get_pixmap(dpi=300, colorspace=pymupdf.csGRAY)
gray = Image.frombytes("L", (pix.width, pix.height), pix.samples)
angle = find_angle(gray)
print(f"page {number}: turned {angle:+.2f} degrees")
gray = gray.rotate(angle, Image.BICUBIC, expand=True, fillcolor=255)
jpeg = io.BytesIO()
gray.save(jpeg, "JPEG", quality=90, dpi=(300, 300))
new = dst.new_page(width=gray.width * 0.24, height=gray.height * 0.24)
new.insert_image(new.rect, stream=jpeg.getvalue())
dst.save(sys.argv[2])
| Tilt | Straighten first |
|---|---|
| 5 degrees | 94.7% |
| 7 degrees | 94.7% |
| 10 degrees | 92.4% |
| 15 degrees | 91.1% |
| 30 degrees | 81.6% |
| 45 degrees | 80.4% |
--deskew found 0.1% or less from 15 degrees up. Tilted the other way, straightening stayed within 1 point.
5.4 to 13.0 s the whole fix, 12 pages
3.0 to 5.8 s --deskew alone
Sideways pages need --rotate-pages
| Page | --deskew | --rotate-pages |
|---|---|---|
| Turned 90 degrees | 88.9% | 94.3% |
| Upside down | 0.3% | 94.4% |
| Turned 270 degrees | 0.5% | 94.2% |
Sideways and tilted too? Use both flags. On pages turned 93 degrees,
--rotate-pages alone found 61.0%, and with
--deskew 93.6%.
My script first gave 91.5% on those pages.
Want text, not a PDF? Try our Actor
PDF OCR to Markdown, our Apify Actor, turns sideways pages upright and straightens tilts up to 5 degrees by itself.
| Pages | Actor |
|---|---|
| 0 to 5 degrees | 94.0% to 94.4% |
| 7 degrees | 83.7% |
| 10 degrees | 10.9% |
| 10 degrees, straightened first | 92.9% |
| Sideways or upside down | 94.4% |
- It gives text or Markdown, not a searchable PDF. Files upload to your Apify run, and go to no LLM or other API.
- The Apify free plan lets you try 20 pages a run and 50 a month.
Questions people ask
My scans have a typed stamp. Which flags?
Use --force-ocr --deskew. On my stamped scans it found 94.3% of the words, and the typed line on 10 of 12 pages. --skip-text --deskew skipped every page, and --redo-ocr refuses --deskew.
Does the script keep color?
No. It saves gray JPEG pages at 300 dpi, quality 90. Keep your original file.
What does --rotate-pages-threshold do?
OCRmyPDF turns a page only if Tesseract is this sure. The default is 14. My pages turned 90 degrees scored 18.31 to 33.37, so all 12 turned.
Method, limits and sources
How I tested it
I took 12 printed book pages, 4 from each of 3 Wikisource scans, and turned each by a known angle: 0 to 45 degrees both ways, then 90, 180 and 270.
Each PDF ran through OCRmyPDF at its default settings, one run at a time. Words found counts the true words in any order, read with pdftotext, against the proofread Wikisource text.
The Actor is build 0.1.35's own page code, run on this Mac. Scripts and results are in research/bench/ocrmypdf-deskew-2026-11-02/.
What I did not test
- Real scanner tilt. Mine is a clean turn of a flat page.
-
--clean: it needs unpaper, which I did not install. - Color pages, photos and forms.
- Windows, Linux and Docker.
Versions and sources
- OCRmyPDF 17.13.0 (MPL 2.0), Tesseract 5.5.3, Poppler pdftotext, PyMuPDF 1.28.2, Pillow 13.0.0 and NumPy 2.5.4. One Mac.
- OCRmyPDF documentation
- Tesseract on rotation and deskewing
- We make the Apify Actor. The other tools are open source, and we do not make them.