Dev.to WebDev πŸ›  Dev πŸ‘ 0 πŸ“– 4 min read

A 38 MB upscaler lost to nearest neighbour on text, and the ghost strokes show why

Of the weight files this run pulled down, EDSR_x4.pb is the heavyweight at 38,573,255 bytes. ESPCN_x4.pb next to it is 100,323 bytes, so the big one is 384 times its size. On the text sample at 4x the big one came back w

Of the weight files this run pulled down, EDSR_x4.pb is the heavyweight at 38,573,255 bytes. ESPCN_x4.pb next to it is 100,323 bytes, so the big one is 384 times its size. On the text sample at 4x the big one came back with PSNR 18.74 dB and the small one with 19.75. Plain nearest neighbour, which is not a model at all, scored 19.27. The largest file in the folder finished last in the set, under the crudest method available, for a reason you can see rather than guess at.

Scope first, because none of this travels far. The numbers come out of a loop: a clean 1024x1024 original goes down to 256 with Pillow 11.3.0 LANCZOS, every method puts it back up to 1024, and the result is compared against the original it was made from. That downscale is artificial damage. A real blurry photo also carries missed focus, hand shake, grain and a few rounds of JPEG, so these figures do not predict what these methods would do to one of those. Three samples, two scales, one run per cell, one Apple-silicon Mac, desktop Chromium. The classic CNN side is OpenCV 5.0.0 dnn_superres with the official ESPCN, FSRCNN, EDSR and LapSRN weights; SSIM comes from scikit-image 0.24.0. Portrait and landscape samples are CC0 images from Wikimedia Commons by William Stitt and Bonnie Moreland; the text sample is my own.

The 100 KB model beat the 38 MB one on text

method weight file text 4x PSNR SSIM
nearest none 19.27 0.8176
bicubic none 19.85 0.8232
Lanczos none 19.97 0.8177
ESPCN x4 100,323 B 19.75 0.8293
FSRCNN x4 41,661 B 19.77 0.8186
LapSRN x4 2,712,798 B 19.92 0.8376
EDSR x4 38,573,255 B 18.74 0.8018
ImgIng fast preset runs in the browser 19.68 0.9048

Every row above is measured in this run; nothing in it is quoted from a vendor page. The last row is the only GAN-tier number here. It comes from one preset of ImgIng https://imging.ai/ , the fast one, whose panel names Real-ESRGAN General x4v3. Four other presets exist on that page and none of them were run, so read that row as one tier and nothing wider.

Those ghost strokes have a name and it is ringing

The lower half of the text sample is a dark signboard: a 76 px Chinese headline on top, then a band carrying a 34 px orange subhead and 46 px Latin lettering. Crop that lower band out of the 4x results and EDSR is sharp in a wrong way. Every Latin letter in HUIMIN HARDWARE carries one faint extra edge along its right side, and the Chinese strokes come out doubled, a stroke and then a lighter copy of it running alongside. On the very same crop bicubic and Lanczos fail in the opposite direction: horizontals and hooks round off into blobs, and nothing doubles anywhere.

The textbook name for the first failure is ringing, sometimes ghosting. At a high-contrast step the response overshoots on one side and undershoots on the other, and when the step happens to be a dark stroke on a light plate, that overshoot reads as a second stroke. Why this particular pretrained file overshoots this hard on lettering I do not know. I can read it off the picture and off the 18.74, not out of the weights. What matters is that blur and ringing are different failures, and only the first gets better when you sharpen afterwards.

Where to look is the biggest glyph in the frame

A whole-image score says something went wrong without saying what, and a crop of the small body text says nothing at all. In the body-text region of this sample the seven non-GAN methods land between 20.41 and 21.05 dB, all of it inside 0.64 dB, because at 4x from a 256 px original there is very little left in an 11 px glyph to argue over. The differences live in the large high-contrast lettering.

So crop that band and enlarge it with nearest neighbour, so the enlargement adds no smoothing of its own:

from PIL import Image

box = (36, 862, 1000, 932)          # same band in every file
for tag in ("gt", "lanczos", "edsr", "fast"):
    im = Image.open(f"x4_{tag}.png").crop(box)
    w, h = im.size
    im.resize((int(w * 1.8), int(h * 1.8)), Image.NEAREST).save(f"crop_{tag}.png")

Those lines are the simplified version of the crop; the numbers in the table came from the full script. Stack the four files and the ringing is unmistakable at the right-hand edge of every letter.

EDSR is fine everywhere else in this run

The same weight file wins three columns elsewhere. Landscape at 4x: 31.72 dB and 0.8978 SSIM, best in the set. The eye-and-lash region of the portrait at 4x: 33.04 dB, also the highest of the eight. At 2x it takes the portrait at 36.26 and the landscape at 36.69, both top of the table. One weight file, one text sample, one scale is not a verdict on open-source super resolution, and it is not a verdict on EDSR either.

Which makes the usable rule narrower than "big models ring". Photographic texture and lettering are two different jobs, and a model that reconstructs tree crowns beautifully can put a ghost next to every letter in the same frame. If text is what you are enlarging, the check costs about a minute: one original, one 4x result per candidate, one 70 pixel band of your largest glyphs, side by side at nearest-neighbour zoom. Whether a newer EDSR weight behaves differently here I have no idea, since this run used the official pretrained x4 file and only that one.

πŸ“° Read the original article on Dev.to WebDev

Originally published by Dev.to WebDev. Aggregated on AIWithGhost for educational purposes β€” full credit and traffic to the original publisher.