Re-Shoot or Upscale? The Break-Even Point, Measured in Decibels and Millimetres
This is the question that actually gets asked in a photo workflow, and it is almost always answered with a feeling. "A 4× upscale is never as good as the real thing" is true and useless; the real thing costs a day and the upscale costs four seconds. So I set up the comparison as a measurement: take a genuine 1080×1620 capture as ground truth, throw away three quarters of it, put it back with Lanczos, and ask how many decibels the difference is — then ask at what viewing distance those decibels stop being visible. Three of the ten experiments came out against what I expected and they are the most useful ones here.
The setup, and why it is not circular
The obvious objection to any "upscale vs re-shoot" test is that the ground truth is the thing you upscaled from, so you are grading the upscale against its own input. That is a real hazard and it is why most published comparisons of this kind are worthless. The way around it is to be strict about what the re-shoot actually is: a genuine capture at the output resolution. I do not have one, so I use the native capture as the reference and treat the downscale as the substitute for "you only had a quarter of the pixels." Everything measured after that is honest, because the downscale genuinely destroys the information the upscale has to invent.
The ground truth is 1080×1620 — one real photograph, not a test pattern. Its PNG is 1,642,936 bytes and its mean squared gradient energy is 283.0, which is the number the detail discussion later hangs on. Downscaling uses area-average decimation (the same operation a smaller sensor performs), never a nearest-pixel drop, because a nearest drop would give the upscale a fake advantage by preserving isolated pixels that a smaller sensor could not have recorded.
What a k-times upscale actually costs
Round trip at three scale factors, three kernels. PSNR and SSIM are against the native capture; "HF ratio" is the gradient energy of the result divided by the gradient energy of the native capture, so 1.0 would mean all the detail came back and 0.2 means a fifth of it did.
| Scale | Kernel | PSNR dB | SSIM | HF ratio | PNG bytes |
|---|---|---|---|---|---|
| 2× | Lanczos | 32.20 | 0.9798 | 0.472 | 1,591,785 |
| 2× | Bicubic | 31.82 | 0.9785 | 0.429 | 1,494,528 |
| 2× | Nearest | 29.84 | 0.9671 | 0.866 | 743,460 |
| 4× | Lanczos | 27.18 | 0.9309 | 0.196 | 1,353,596 |
| 4× | Bicubic | 27.04 | 0.9288 | 0.180 | 1,262,268 |
| 4× | Nearest | 25.63 | 0.8964 | 0.692 | 277,780 |
| 8× | Lanczos | 23.25 | 0.8041 | 0.084 | 1,175,702 |
| 8× | Bicubic | 23.17 | 0.8037 | 0.077 | 1,086,523 |
| 8× | Nearest | 21.85 | 0.7393 | 0.581 | 100,364 |
Each doubling of the scale factor costs roughly four to five decibels: 32.20 at 2×, 27.18 at 4×, 23.25 at 8×. That is the number that matters for the decision, because it is monotonically bad and it does not depend on the kernel. Lanczos beats bicubic everywhere but by less than 0.4 dB at every scale, which is far too little to justify arguing about.
Negative result one: nearest-neighbour wins the detail metric
Look at the HF column and nearest-neighbour appears to be the hero. At 8× it retains a 0.581 HF ratio where Lanczos retains 0.084 — nearly seven times more "detail" — and its file is one tenth the size. If your only instrument is a high-frequency energy meter, you conclude that interpolation is destroying detail and that you should disable it.
The PSNR column says nearest is 1.4 dB worse at 8× and 2.4 dB worse at 2×. Both numbers are correct. Gradient energy is not detail; it is variation. Nearest-neighbour produces enormous gradients at every block boundary because it turns a smooth ramp into a staircase. Those gradients are energy and they are entirely fake. This is the single most common way a "detail retention" comparison gets published backwards, and I fell into the trap myself in an earlier version of this test before checking the PSNR column — the HF metric cannot tell detail from aliasing, and it never could.
Rule that came out of this: never use high-frequency energy alone to judge an upscale. A result that shows more HF than the ground truth has produced artefacts, because the ground truth is by definition the ceiling. The 0.866 HF ratio at 2× nearest is not 87% of the detail, it is 87% of the gradient energy of which a large fraction is staircase.
Where in the spectrum the loss happens
The image was split into twelve radial frequency bands and the power in each band compared between the native capture and the 2× and 4× upscales. Ratio is upscale power divided by capture power, so 1.0 is a perfect match.
| Band (cycles/pixel) | 2× ratio | 4× ratio |
|---|---|---|
| 0.00–0.08 | 0.999 | 0.995 |
| 0.08–0.17 | 0.944 | 0.699 |
| 0.17–0.25 | 0.821 | 0.169 |
| 0.25–0.33 | 0.557 | 0.048 |
| 0.33–0.42 | 0.283 | 0.064 |
| 0.42–0.50 | 0.127 | 0.070 |
| 0.50–0.58 | 0.091 | 0.085 |
| 0.58–0.67 | 0.106 | 0.101 |
| 0.67–0.75 | 0.101 | 0.090 |
| 0.75–0.83 | 0.009 | 0.006 |
The 4× curve drops to 0.048 right at its source Nyquist of 0.125 cycles/pixel and then rises again to 0.10 across the 0.5–0.75 range before collapsing to almost nothing above 0.75. That hump is the interesting structure. If the upscale were simply failing, the curve would fall monotonically. Instead there is a band where it is putting back more energy than the falling edge would suggest, and the question is whether any of that energy is real.
How much of the invented detail is real detail?
This is the measurement I actually wanted. Split the upscale's own spectrum at the source Nyquist and correlate each side against the corresponding band of the real capture. If the invented band correlates with the truth, the resampler is reconstructing something. If it correlates with noise, it is inventing.
| Band relative to source Nyquist | Correlation with capture | Power ratio |
|---|---|---|
| Below (0.00–0.25) | 0.9952 | 0.979 |
| Above (0.25–0.50) | 0.2323 | 0.056 |
| Far above (0.50–1.00) | 0.2925 | 0.088 |
Below the source Nyquist the correlation is 0.9952 and the power ratio is 0.979 — the upscale reproduces what the source actually contained, almost perfectly. Above it, correlation falls to 0.23 and the power is 5.6% of the capture's. That 5.6% is not reconstruction, it is the ring of a sinc kernel. The 0.10 hump in the band table is Lanczos ringing, and it correlates with reality at 0.23, which is roughly what a slightly misaligned copy of a neighbouring frequency would score by accident.
So the answer to "how much detail does a 4× upscale invent" is: it invents essentially all of the energy above the source Nyquist, and that energy is small — 0.711% of its own total, against 3.088% for the real capture. The information above the source's Nyquist line is simply gone, and no resampling kernel puts it back.
Negative result two: sharpening buys 0.09 dB
The usual rescue attempt is an unsharp mask. It is a linear filter, so it cannot create information, but it can redistribute what is there. The question is whether the redistribution helps or hurts, measured against the real capture rather than against "does it look crisper."
| Amount | PSNR dB | HF ratio | Correlation with true HF | Energy in flattest pixels |
|---|---|---|---|---|
| 0.0 | 27.04 | 0.196 | — | — |
| 0.3 | 27.10 | 0.209 | 0.4216 | 0.094 |
| 0.6 | 27.13 | 0.224 | 0.4045 | 0.106 |
| 1.0 | 27.13 | 0.247 | 0.3918 | 0.111 |
| 1.5 | 27.07 | 0.279 | 0.3832 | 0.116 |
| 2.0 | 26.96 | 0.312 | 0.3786 | 0.123 |
Peak PSNR is 27.13 dB at amount 0.6, against 27.04 at zero — a gain of 0.09 dB, which is nothing. And the quality of the added energy degrades as you push it: the correlation between the sharpening signal and the capture's true high-frequency residual falls from 0.4216 at amount 0.3 to 0.3786 at amount 2.0, while the share of the added energy landing on the flattest half of the image rises from 9.4% to 12.3%. You are adding more, and what you add is progressively less true.
The honest summary is that sharpening is a display-time preference, not a recovery step. It moves 0.09 dB of accuracy in exchange for a subjective impression of crispness, and past about amount 0.6 it starts making the image measurably wrong. Anyone who has compared a sharpened upscale to the original at 100% and thought it looked closer is not wrong about the impression; they are wrong that the PSNR agrees with them.
Negative result three: below one 8-bit level, the upscale amplifies
I expected resampling to attenuate noise, because interpolation averages. To test it I put white noise of known sigma on a flat 128 field — where clipping at 0 and 255 is impossible — ran it through the same Lanczos upscale, and measured the output standard deviation.
| Input sigma (levels) | Output / input, 2× | Output / input, 4× |
|---|---|---|
| 0.25 | 2.000 | 2.001 |
| 0.50 | 1.166 | 1.159 |
| 1.00 | 1.001 | 0.986 |
| 1.50 | 0.946 | 0.942 |
| 2.00 | 0.924 | 0.923 |
| 4.00 | 0.901 | 0.901 |
| 8.00 | 0.895 | 0.895 |
The hypothesis holds only above one level: at sigma 8 the upscale keeps 0.895 of the noise
standard deviation, and it converges to that same 0.895 at both 2× and 4×. But below one level the
ratio crosses 1.0 and at sigma 0.25 it is exactly 2.000 — the output noise is
twice the input. Feeding a single least-significant-bit dither through the same path gives an
output standard deviation of 0.5000 levels, which is not a coincidence: at that amplitude almost
every pixel rounds to the same 8-bit integer, so the output is a step function whose step height
is one level, and its standard deviation is set by step density rather than by the input sigma.
The ratio then has to rise as 1/sigma, and it does.
This is why the same measurement on a real photo gave 0.671 at sigma 4 in an earlier run and 0.709 on repeat: the photograph has pixels at 0 and 255, so clipping removed part of the noise before it was ever resampled, and the amount removed depends on the draw. The flat-field control has no such ambiguity and is the number I trust. The practical consequence is small but real: an 8-bit intermediate cannot carry a variation finer than about half a level, so a pipeline that resamples through 8 bits has a noise floor it cannot go below, and it will exaggerate any dither it is handed.
The measurement that decides it: viewing distance
Every number so far compares the upscale to the capture at 1:1, which is a comparison no human
ever performs. People look at prints from a distance. So the last step filters both images with
the same blur before scoring them, and the blur is set by a physical claim: the eye resolves about
one arcminute, 0.000291 radians, so a pixel subtends ACUITY × distance / (print_width /
W). That equation inverts to the only decision table that matters.
This is the 4× curve. The re-shoot score is 99 dB by construction because it is compared against itself, so it is not shown.
| Eye sigma (px) | 4× PSNR dB | RMSE | 300mm print, viewed at |
|---|---|---|---|
| 0.0 | 27.04 | 11.34 | 1:1 pixels |
| 0.5 | 28.57 | 9.50 | 477 mm |
| 1.0 | 32.53 | 6.03 | 955 mm |
| 1.5 | 36.45 | 3.84 | 1432 mm |
| 2.0 | 40.34 | 2.45 | 1909 mm |
| 3.0 | 46.50 | 1.21 | 2864 mm |
A 4× upscale of a quarter-resolution source is 27 dB from a real capture at 100%, 32.5 dB when the print is viewed from arm's length, and 40 dB from two metres. The 40 dB line is the one people quote as "visually lossless" and it is a rule of thumb rather than something I measured here — but the direction is not a rule of thumb, it is arithmetic, and it says the question "is an upscale as good as a re-shoot" has no answer until you name a print size and a viewing distance.
The decision, per viewing condition
| 300mm print viewed at | 2× upscale | 4× upscale | 8× upscale |
|---|---|---|---|
| 250 mm (hand-held) | 32.15 | 27.05 | 23.17 |
| 500 mm (desk) | 35.63 | 28.80 | 23.98 |
| 1000 mm (across a desk) | 44.02 | 32.89 | 25.64 |
| 2000 mm (across a room) | 53.19 | 41.03 | 28.61 |
| 3000 mm (on a wall) | 55.87 | 47.17 | 31.06 |
Read this way the trade is legible. A wall print viewed from three metres makes even an 8× upscale marginal and a 4× one comfortable. A hand-held print at 250 mm makes even a 2× upscale visibly different, and the re-shoot is worth the trip. The break-even for the 40 dB convention sits between 1000 and 2000 mm for 2×, between 2000 and 3000 mm for 4×, and is not reached at any distance tested for 8×.
What a re-shoot buys that no upscale can
Everything above assumes the capture is sharp. The case for re-shooting is stronger when it is not, because a blurred capture has thrown away information before the upscale ever sees it. I tested this by blurring the ground truth with increasing sigma before the 4× round trip, then applying an aggressive unsharp mask as the best available recovery.
| Capture blur sigma | Upscale PSNR | After sharpening | Gain |
|---|---|---|---|
| 0.0 | 27.18 | 27.20 | +0.01 |
| 1.0 | 26.85 | 27.23 | +0.38 |
| 2.0 | 25.61 | 26.12 | +0.51 |
| 3.0 | 24.26 | 24.66 | +0.40 |
| 4.0 | 23.17 | 23.47 | +0.30 |
The gain from sharpening peaks at 0.513 dB when the capture was blurred by sigma 2.0 and then decreases again — you cannot sharpen back a capture that was never in focus, and the attempt stops paying somewhere past two pixels of blur. Note also that a soft capture loses only 0.33 dB to the upscale at sigma 1.0 (26.85 against 27.18), so the capture's own blur and the upscale's information loss are not additive; the blur dominates and hides the rest.
The edge-width measurement shows the same thing from a different angle. On a synthetic step, the 10–90% rise of the 4× upscale equals the rise of the blurred capture exactly once the capture blur reaches 2 pixels: 7 px, 9 px and 11 px at sigma 2, 3 and 4, identical on both sides. Only at sigma 0 and 1 does the upscale widen the edge, from 3 to 5 pixels. An upscale cannot make an already-soft capture softer, which is a genuinely reassuring result and the opposite of the usual warning.
Cropping beats both, when you can
If the re-shoot is impossible, the next best thing is to have framed tighter in the first place. Taking only the centre ninth of a well-exposed frame and upscaling that is a different trade from upscaling the whole thing, because you start with three times the pixel density in the region you care about.
| Centre-ninth crop | Source pixels | PSNR vs native crop | HF ratio |
|---|---|---|---|
| 2× from a quarter-res frame | 48,600 | 35.02 | 0.518 |
| 4× from a quarter-res frame | 12,150 | 29.45 | 0.243 |
35.02 dB for the 2× crop against 27.18 dB for the full-frame 4× is the largest single improvement anywhere in this article, and it costs nothing but a decision made before the shutter. Cropping is not a post-processing trick; it is a decision about where the pixels are allocated, and it dominates every resampling choice I measured.
A phone JPEG changes very little
The last check is whether a realistic source — a q72 JPEG from a phone rather than a clean PNG — shifts any of it. It does not, and the reason is instructive. At 2× the q72 source costs 0.39 dB (31.81 against 32.20); at 4× it costs 0.06 dB (27.12 against 27.18). The JPEG penalty shrinks as the scale factor grows, because the resampling loss is much larger than the compression loss and simply swamps it. At high scale factors the choice of resampler matters far more than the bitrate of the source you fed it, which is the same conclusion the re-encoding experiment reached from the other direction.
File size tells you what survived
Every output is 1080×1620, so byte count is a direct read on how much information is actually in the file. A lossless PNG of the native capture is 1,642,936 bytes; the 4× upscale is 1,353,596 and the 8× is 1,175,702. Pixel count is identical in all three, so the file is telling you that 17.6% and then 28.4% of the content is gone. Bytes per pixel falls from 0.939 to 0.910 to 0.774 to 0.672.
At delivery quality, a q90 JPEG of the native frame is 303,632 bytes and the same settings on the 4× upscale are 224,056 — 73.8% of the file for an image a third as accurate. If you are budgeting bandwidth, an upscale is cheaper; if you are budgeting accuracy, the saving is coming out of the thing you wanted.
The snippet
The two experiments that carry the decision, reduced to something you can run. It needs only numpy and Pillow, and it prints the round-trip score, the correlation above the source Nyquist, and the noise-amplification crossover.
import numpy as np
from PIL import Image
def box_down(a, k):
h, w = a.shape[:2]; h2, w2 = h // k * k, w // k * k; a = a[:h2, :w2]
return a.reshape(h2 // k, k, w2 // k, k, -1).mean(axis=(1, 3)) # area average
def up(a, size):
return np.asarray(Image.fromarray(np.clip(a, 0, 255).astype(np.uint8))
.resize(size, Image.Resampling.LANCZOS), dtype=np.float64)
def psnr(a, b):
mse = float(np.mean((a - b) ** 2))
return 10 * np.log10(255.0 ** 2 / mse)
GT = np.asarray(Image.open('photo.jpg').convert('RGB'), dtype=np.float64)
H, W, _ = GT.shape
# 1. round trip: each doubling costs ~4-5 dB
for k in (2, 4, 8):
print(k, round(psnr(up(box_down(GT, k), (W, H)), GT), 2))
# 2. correlation above the source Nyquist (k=4 -> 0.25 cyc/px)
u = up(box_down(GT, 4), (W, H)).mean(2); g = GT.mean(2)
g -= g.mean(); u -= u.mean()
G, U = np.fft.fftshift(np.fft.fft2(g)), np.fft.fftshift(np.fft.fft2(u))
yy, xx = np.mgrid[0:H, 0:W]
rr = np.sqrt(((yy - H/2)/(H/2))**2 + ((xx - W/2)/(W/2))**2) / np.sqrt(2)
for lo, hi in ((0, .25), (.25, .5)):
m = (rr >= lo) & (rr < hi)
a = np.real(np.fft.ifft2(np.fft.ifftshift(U * m)))
b = np.real(np.fft.ifft2(np.fft.ifftshift(G * m)))
print(lo, hi, round(np.corrcoef(a.ravel(), b.ravel())[0, 1], 4))
# 3. noise crossover: ratio exceeds 1.0 below one 8-bit level
rng = np.random.default_rng(0)
for sig in (0.25, 1.0, 8.0):
z = np.full((400, 400), 128.0)
d = up(z + rng.normal(0, sig, z.shape), (800, 800)) - up(z, (800, 800))
print(sig, round(d.std() / sig, 3)) # 2.000 / 1.001 / 0.895
On this machine that prints 2 32.2 / 4 27.18 / 8 23.25, correlations
0.9952 and 0.2323, and noise ratios 2.0 / 1.001 / 0.895.
The one place to be careful is box_down: it must be an area average. Substituting a
nearest-pixel drop changes the 2× score by more than a decibel and will make the whole table look
better than it is.
How to decide, in order
- Name the output size and the viewing distance first. Without those two numbers any comparison is unfalsifiable. The table above converts the distance into a sigma and the sigma into a score.
- If you can frame tighter instead of upscaling more, do that. The centre-crop test gained 7.8 dB where every kernel change gained less than 0.4 dB.
- Prefer the smallest scale factor that reaches the output size. Each doubling costs four to five decibels and no kernel choice recovers it. 2× beats 4× by 5.0 dB and 4× beats 8× by 3.9 dB on identical content.
- Do not sharpen to fix detail loss. Peak gain was 0.09 dB, and past amount 0.6 the image becomes measurably less accurate while looking crisper.
- Re-shoot when the capture itself is soft. That is the one deficit no post-processing step closed: sharpening recovered 0.51 dB at best and fell off again beyond two pixels of capture blur.
- Ignore file size as a quality signal. The 8× upscale is 28.4% smaller than the native PNG at identical pixel dimensions, which is information leaving, not efficiency arriving.
None of this argues against upscaling. It argues that the decision has a shape, and the shape is that an upscale is indistinguishable from a re-shoot for most print sizes viewed at most real distances, and stops being so exactly when someone holds the print in their hands. If you want to run the same round trip on your own master — the native capture is the only fair reference and the one thing worth generating — the resampling step at the browser upscaler on upgolab.com is the pipeline I used to produce the 4× and 8× columns here, and it takes a PNG or JPEG master directly so the comparison is not fighting a re-encode first.