I wanted to know whether the 3 in cv2.medianBlur(img, 3) is safe to keep as a constant before OCR, so I swept the kernel on one scanned book page. On the page at its original size, a 3×3 median was the best thing I tried: Tesseract went from 11 character errors to 2. On the same page scaled to half width, the same 3×3 median took it from 4 errors to 28. The filter did not change. The letters got smaller.

The page is page 8 of H. G. Wells's The Time Machine (William Heinemann, 1895), a clean single-column print scan from Wikimedia Commons at 1516 px wide. The reference text is the validated English Wikisource transcription of that page, 1,002 characters after normalising. "Errors" below means character edit distance against that text, so 11 errors is 98.9% and 90 errors is 91.0%. Everything ran once on an Apple M4 Mac with Tesseract 5.5.3 and OpenCV. One page and one run each, so read these as the shape of a curve, not as accuracy to expect on your documents.

The full size page liked a small median

The sweep runs on the untouched page, no added noise or rotation. The m0 line is the unfiltered run, done just before the loop with tesseract page.jpg m0 -l eng.

$ for k in 3 5 7 9; do python3 -c "import cv2;cv2.imwrite(\"m.png\",cv2.medianBlur(cv2.imread(\"page.jpg\",0),$k))"; tesseract m.png m$k -l eng 2>/dev/null; python3 cer.py truth.txt m$k.txt; done m0.txt: 编辑距离 11 / 1002 字符,准确率 98.9% m3.txt: 编辑距离 2 / 1002 字符,准确率 99.8% m5.txt: 编辑距离 6 / 1002 字符,准确率 99.4% m7.txt: 编辑距离 14 / 1002 字符,准确率 98.6% m9.txt: 编辑距离 90 / 1002 字符,准确率 91.0% 

The scoring script prints its labels in Chinese: 编辑距离 is edit distance, 字符 is characters, 准确率 is accuracy. The curve has a clear bottom at 3, is roughly back to baseline by 7, and falls off at 9. Otsu plus a 5×5 closing was far worse at 716 errors, since closing on black-on-white text erases thin strokes.

Half the size and the same kernel hurts

Then I resized the page to 758 px wide to imitate a smaller font or a lower-resolution scan and ran the same filters.

Median kernel Full size, 1516 px Half size, 758 px
none 11 4
3×3 2 28
5×5 6 142
7×7 14 765

At half size there is no kernel that helps. The 5×5 that was harmless at full size drops accuracy to 85.8%, and Otsu plus a 3×3 closing produced an empty output file. The first row bothers me most. Unfiltered, the half-size page had 4 errors and the full-size page had 11. I don't have an explanation for that, and with one page I'm not going to invent one. It does mean the safe default at half size was no filter at all.

My guess at why the best kernel moves with scale

Everything in this section is my inference. I never measured a stroke in pixels on this page, so treat it as a hypothesis to test. The idea starts from scale, not from the filter. Shrinking the page to half width while keeping a 3×3 window should behave roughly like running a 6×6 window on the full-size page, because the window now covers twice as much of each letter. If that holds, the half-size column is just the full-size curve shifted toward bigger kernels. The numbers fit that loosely: half-size 3×3 sits at 28, between full-size 7×7 at 14 and 9×9 at 90. A median is a local majority vote, and a hairline that fills only a small share of the window loses that vote. At full size a 3×3 probably only outvotes paper grain and speckle, which would fit 11 dropping to 2. A Chinese page from the 1986 reissue of the official Simplified Characters Table points the same way. With the macOS built-in recognizer, a 3×3 median moved it from 5 to 4 errors out of 557 characters, and 9×9 went to 178. From 7×7 on, thin horizontal strokes, dots, small radicals and enumeration commas went first.

Recognizers disagree about preprocessing too

What sits after the filter matters too. The macOS built-in recognizer barely reacted to tilts from 0 to 5° and got slightly worse after Otsu in every condition I tried, while Tesseract needed deskewing and denoising. The third recognizer was ImgIng https://imging.ai/ , which runs PP-OCR models inside a desktop browser, on the same Mac. It was the most tilt sensitive of the three. At 3° its line order got scrambled and the Chinese page lost two lines. At 5° it lost about half of the Chinese page, and the interface still showed the job as complete with no warning. After denoising and deskewing, it came back to 2 errors. I didn't sweep kernels through ImgIng.

Pick the kernel per resolution

The kernel shouldn't be a constant. Before a batch goes through OCR, transcribe one representative page by hand, or take an existing transcription, and run the loop above at the resolutions the batch actually contains. For mixed input sizes my plan, untested beyond this page, is to bucket by text line height in pixels and keep a kernel per bucket, with "no filter" as an option. The cheap check is to halve your test page and rerun the sweep. If the best kernel moves, you have my problem.

Sources: page 8 of H. G. Wells, The Time Machine (William Heinemann, London, 1895), scan by University of Illinois via HathiTrust, Wikimedia Commons, public domain. Reference text from English Wikisource, CC BY-SA 4.0. Chinese page from the National Language Commission's 1986 reissue of the Simplified Characters Table, Internet Archive, Public Domain Mark 1.0. The half-size and tilted versions are my own.