1
$\begingroup$

I have a document scanned on an old scanner. I'd like to smooth the letters to make them easier and more pleasant to read. I am not trying to do OCR, simply to make the document more legible.

Some examples of the text below: enter image description here enter image description here

Note that I want to improve legibility, not necessarily restore the full image. I've had some encouraging initial results using morphological operations.

$\endgroup$
2
  • $\begingroup$ Your example seems to be binary (black or white)? Maintaining «sharp» transitions while suppressing the random fluctuations along borders seems like a really hard problem. Possibly being tuned to the font size and font type at hand. Perhaps ocr and re-rendering is just as productive? $\endgroup$
    – Knut Inge
    Commented Nov 17 at 8:09
  • $\begingroup$ @KnutInge Morphology is designed for this. I'm by no means an expert, but hacking around with it produced some encouraging results. $\endgroup$ Commented Nov 17 at 20:53

2 Answers 2

1
$\begingroup$

I'm sure there's better approaches, quite possibly via trying to approximate the pixel shapes with splines and then rendering these with anti-aliasing, but in a hurry, I'd devise the following algorithm. Basically, it uses the scan's noisiness (probably due to both original optical noisiness, and the jitter of scan position vs. letter position) as dithering.

  1. Segment the lines into individual letters. In most font renderings of Latin scripts, the diaresis (actually, trema), tittles, and punctuation periods are the same shape, so segmentation can simply be a selection of any black pixel, all its directly and transitively neighboring black pixels.
  2. delete all single-pixel segmentations.
  3. find groups of identical and very similar segments. (something like "less than 5% + 1 pixel difference" or similar)
  4. create a "leader" greyscale bitmap for each segment group, by setting the darkness of each of its pixels to the relative amount that the pixel was black all the segments in its group. So, say you have the group of all e shapes of identical size, then a pixel that is black in every instance of e scanned will be black in the leader. If it's black in 90% of cases, it get's a whiteness level of 0.1 (blackness of 0.9, whatever scheme you prefer).
  5. replace all instances of groups with the group leader
  6. (optionally) apply a gamma function that might make the the transition look nicer.

I manually did steps 1. to 5. for the t's in your first line; I just stacked all t as accurately as I could (randomly chose one t as the "base" for stacking), and set their respective opacity such that they contribute 25% to the final pixel:

All 't' superimposed

That t indeed looks a bit nicer (and I only used four t's; you probably have hundreds, if not thousands to average across)!
I could now go back and replace all original t with that "average" t.

If you increase the smarts with which you map segments to shape groups, you'd start asking question like "how likely is this a c or an e shape, considering it came after a pretty-certain h shape?", and then you'd be doing cool OCR stuff already.

$\endgroup$
2
  • $\begingroup$ Interesting. Would you recommend actually augmenting this with OCR? E.g. OCR the text, produce the leader chars that way, then use something like your approach to replace the pixels with them? Integrating OCR and the pixel-detection seems like an interesting problem. $\endgroup$ Commented Nov 17 at 20:57
  • 1
    $\begingroup$ That would be an interesting combination, yes. Note that "replacing repeated near-identical shapes with a leader" is something that is also done in image compression, e.g., in JPEG2000; I think you'll enjoy this work by David Kriesel, who found his large format scanner produced architectural scans with wrong numbers $\endgroup$ Commented Nov 17 at 21:51
0
$\begingroup$

Another method I stumbled upon researching upscaling shaders quite a while back is based on the idea of Signed Distance Functions:

Chris Green. 2007. Improved alpha-tested magnification for vector textures and special effects. In ACM SIGGRAPH 2007 courses (SIGGRAPH '07). Association for Computing Machinery, New York, NY, USA, 9–18. https://doi.org/10.1145/1281500.1281665; available online.

It's a bit specific to 3D environment (specifically: game) texture scaling, but that shouldn't stop you.

Idea is simple: Let's start with the assumption we have a clean bitmap font of the characters used in your scan.

Take your e shape as bitmap font "realization". The e rendered to a white rectangular pixel image. Imagine that we have a function mapping positions in continuous pixel space (so, 2D real-valued input) to a real number (so, 1D output). (assume dense square pixels.)

Note that this function maps arbitrary positions values, not just integer pixel positions.

Define the following: on the exact edge of your pixel image (so, where a black pixel ends, and a white pixel begins), the function takes value 0.

On every position that is in front of a black pixel, the value of the function is the distance to the closest edge. So, a strictly positive real number.

On every position that is in front of a white pixel, the value of the function is the negative distance to the closest edge. So, a strictly negative real number.

OK, that converts a black-and-white bitmap image into a continuous-valued field. Not that useful for a computer.

Here's the trick: just calculate the distance field at the scan resolution, meaning that instead of defining the function for every possible position, you just define it for actual pixel positions. Call this signed distance bitmap

Claim that the signed distance function is just an interpolation of that signed distance field.

In other words, no matter at which resolution you render that letter, you just convert the target pixel coordinate to the original pixel coordinate system, evaluate the value of the signed distance function at that point, meaning that you interpolate the neighboring signed distance bitmap values with a smooth function. Positive-valued positions are black, negative-valued positions are white.

Got a lot of freedom picking that function, and it will influence what your upscaled letter will look like. Use a (truncated) sinc function as interpolator, and you can be lazy and do the interpolation using FFT->zero-padding->IFFT. (if you symmetrically pad, the result should still be real.)

Upsample your, say, 100 dpi scan to 1200 dpi and read the downscaling of that on your screen, using an anti-aliasing downscaler (which hopefully most viewers do).

That sounds a bit stupid, scaling up to scale down to get greyscale edges, right?

You can take this idea one step further: You define some positive threshold for the signed distance, above which all pixels are definitely black, and a negative threshold, below which all pixels are definitely white, and you find a nice S-shaped curve that maps the distance values in between to shades of grey.

That way, single "missing" black pixels (like in the center of the o in not in your picture) become less white, and single "extra" black pixels (like the one beneath the serifs of the n in your not) become less black.

$\endgroup$
3
  • $\begingroup$ //"Idea is simple: Let's start with the assumption we have a clean bitmap font of the characters used in your scan."// - - - - - Wait a minute here, Marcus. Isn't this simply equivalent to doing OCR? $\endgroup$ Commented Nov 17 at 18:53
  • $\begingroup$ yes, that would be that – but it's just an initial assumption to explain the process, @robertbristow-johnson :) the idea is that you can do the same on the "slightly distorted" scan, and still get a more pleasant-to-read upscaled version $\endgroup$ Commented Nov 17 at 18:59
  • $\begingroup$ (that is, without knowing the clean "ground truth") $\endgroup$ Commented Nov 17 at 19:04

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.