Restoring Old Family Video Without a Workstation
Old footage fails in four independent layers — interlacing, noise, resolution, and codec damage — and each one needs a different tool. Order barely matters for deinterlace and matters a lot for denoise. The scale factor matters most.
A box of DV tapes or a phone video from 2011 has four problems at once, and they are not the same problem. Two of them are famously confused for each other, which is why so much restoration advice is either redundant or actively harmful.
The four layers, kept separate
- Interlacing — two fields per frame, captured at different times. Shows as comb teeth on motion. Must be deinterlaced once, and only once.
- Noise — sensor and tape noise, uncorrelated between frames and between neighbouring pixels. Genuinely removable, at a cost.
- Resolution — actual pixel count. Not recoverable, only inferable.
- Codec damage — block edges and ringing baked in at capture or transcoding. Not removable by any later step.
Layers 3 and 4 are the ones people try to fix with sharpening. That never works: sharpening raises the contrast of everything, including the block edges from layer 4, so damaged footage comes back with more visible damage. The right move for layer 4 is to accept it, and the right move for layer 3 is to change the target rather than the filter.
Does the deinterlace/denoise order matter? Measured: barely.
I built an interlaced test frame by combining the even rows of one image with the odd rows of a 2px-shifted copy — the same comb structure a real interlaced capture produces. Then I ran both orders with the same filters:
| Order | Comb residual | HF energy |
|---|---|---|
| deinterlace → denoise | 5.825 | 0.7680 |
| denoise → deinterlace | 5.870 | 0.7684 |
Comb residual differs by 0.8%, high-frequency energy by 0.05%. If you were expecting a
large effect, this is the useful negative result: with a spatial-only denoiser, the
order is nearly irrelevant. Both orders leave comb residue around 5.8, which is
high — the filters here are gentle, and real interlaced footage usually needs a stronger
deinterlacer than yadif's default, not a different order.
Where order does matter. Temporal denoisers, which average across frames, behave badly on interlaced input because adjacent frames are not adjacent fields. Deinterlace first when using a temporal denoiser. With spatial denoisers, skip the worry.
Denoise strength: hqdn3d stops working long before it stops trying
The order test above used gentle settings, so I went back and asked a different question: how hard do you have to push the denoiser before noise actually goes away, and what does that cost in detail? Same 480p frame, gaussian noise at sigma 10 added, upscaled 2× to 960×1440 with lanczos. Reference is the clean frame at the same resolution.
Noise is measured as the standard deviation of output − reference inside 832
flat 24×24 blocks, so it counts residual noise and nothing else. Detail is high-frequency
energy above r > 0.18 of the frame, as a share of the clean version's HF.
| Filter before upscale | PSNR | HF | Residual σ | Noise removed |
|---|---|---|---|---|
| none | 29.33 | 145.6% | 8.46 | — |
| hqdn3d spatial 6 | 29.39 | 144.8% | 8.38 | 0.9% |
| hqdn3d spatial 12 | 29.81 | 139.4% | 7.86 | 7.1% |
| hqdn3d spatial 20 | 30.47 | 131.4% | 7.09 | 16.2% |
| hqdn3d spatial 32 | 30.83 | 125.0% | 6.74 | 20.4% |
| hqdn3d spatial 48 | 30.67 | 121.5% | 6.91 | 18.3% |
| hqdn3d spatial 64 | 30.38 | 119.6% | 7.19 | 15.1% |
| hqdn3d spatial 96 | 29.90 | 117.3% | 7.60 | 10.2% |
| nlmeans s=3 p=3 r=7 | 34.50 | 106.8% | 2.98 | 64.8% |
| nlmeans s=6 p=3 r=7 | 35.38 | 87.4% | 1.82 | 78.5% |
| nlmeans s=10 p=3 r=7 | 32.90 | 68.6% | 1.77 | 79.1% |
Read the hqdn3d rows and the collapse is obvious: it peaks at spatial 32, removes 20.4% of the noise, and then gets worse in every direction. At spatial 96 it removes less noise (10.2%) than at 20. This is not a tuning failure — hqdn3d is a bilateral-style spatial smoother, and once its range window is wide enough to smooth out sigma-10 noise it also smooths across edges. The filter is averaging real detail into the flat areas it creates. PSNR peaks at the same place, which is why tuning by PSNR alone lands you on the least-bad hqdn3d setting rather than a good one.
nlmeans changes the arithmetic. At s=3 it removes 64.8% of the noise while keeping 106.8% of the HF energy — more than the clean reference, because the noise it leaves behind is itself high-frequency. So "denoising destroys detail" needs qualification: spatial denoisers do, patch-based ones mostly don't. Non-local means finds similar patches elsewhere in the frame and averages those, which is how it can hit sigma-10 noise without flattening texture.
Where the denoise goes in the chain does matter. Measured both ways with nlmeans s=6: denoise before the 2× upscale leaves residual σ 1.82 at HF 87.4%; denoise after the upscale leaves σ 3.04 at HF 89.8%. Denoising first is 40% lower residual noise for 2.4 points of HF — the one order effect in this article large enough to plan around, and the opposite of the gentle deinterlace/denoise result above.
Practical version: hqdn3d is a deblocker with a denoise label, useful at
spatial 4–8 for light cleanup and actively counterproductive past 32. If the noise is real and
you want it gone, use nlmeans at s=3–6 before the upscale. Raising the hqdn3d
number and hoping it will start behaving like a denoiser is the one thing this table rules out.
Reproducing the denoise test
# clean 480p frame, add sigma-10 noise, then run the three orders
ffmpeg -i noisy480.png -vf "scale=960:1440:flags=lanczos" ctrl.png
ffmpeg -i noisy480.png -vf "nlmeans=s=6:p=3:r=7,scale=960:1440:flags=lanczos" pre.png
ffmpeg -i noisy480.png -vf "scale=960:1440:flags=lanczos,nlmeans=s=6:p=3:r=7" post.png
# residual sigma = std of (out - clean_960) inside flat 24x24 blocks,
# flat = mean |grad| of the reference below 1.5; 832 blocks in this frame
What actually determines the outcome: scale factor
From separate measurements on the same pipeline: Lanczos upscaling retains 21.5% of the source's high-frequency energy at 2× and 1.3% at 4×. A 480p home video to 1080p is 2.25× — in between, and acceptable. The same 480p to 4K is 4.5×, and the detail information retained collapses.
So the single most effective restoration decision is not the filter chain. It is how far you are trying to stretch the source. Going to 2× the source and stopping will look better than going to 4× with a fancier pipeline, every time.
Does sharpening amplify the block edges? Measured: yes, by 12.7%
The list above claims sharpening makes codec damage worse. That is easy to assert and easy to test, so I encoded the clean 480p frame at JPEG q18 to create real 8×8 block edges, decoded it, and put it through unsharp at three amounts, both before and after the 2× upscale.
Blockiness here is the mean absolute pixel difference across 8-pixel grid boundaries divided by the mean difference across non-boundary neighbours. A frame with no grid structure scores close to 1.0; higher means visible blocking.
| Pipeline | PSNR | Blockiness |
|---|---|---|
| clean reference (never encoded) | — | 1.093 |
| q18 blocked, no sharpening | 31.91 | 1.454 |
| unsharp 0.5 before upscale | 30.40 | 1.500 |
| unsharp 0.5 after upscale | 31.30 | 1.517 |
| unsharp 1.0 before upscale | 28.24 | 1.536 |
| unsharp 1.0 after upscale | 30.29 | 1.570 |
| unsharp 2.0 before upscale | 25.25 | 1.574 |
| unsharp 2.0 after upscale | 28.10 | 1.639 |
Sharpening at any amount raises blockiness, monotonically, and unsharp 2.0 after the upscale takes it from 1.454 to 1.639 — a 12.7% increase in grid structure on footage that already had too much. The claim holds, and now it has a number.
The unexpected half is the split between the two columns. Sharpening before the upscale adds less blockiness (1.574 vs 1.639) but costs far more PSNR (25.25 vs 28.10) — nearly 3 dB. The upscale that follows a pre-sharpen pass smears the amplification into soft halos and loses real detail, which the PSNR catches and the blockiness metric does not. Sharpening after the upscale keeps PSNR higher but writes the grid back in at full strength on a frame where the grid is already 16 pixels wide.
Neither ordering is a fix, which is the point. The only correct move for layer 4 is a dedicated deblock before anything else — and if you do not have one, do not sharpen. A soft frame with hidden blocking reads better than a crisp frame with visible blocking, because the eye finds regular structure far faster than it finds blur.
Reproducing the sharpen/blocking test
# create real 8x8 blocking, then sharpen at two points in the chain
ffmpeg -i clean480.png -q:v 18 blocked.jpg
ffmpeg -i blocked.jpg -vf "unsharp=5:5:2.0:5:5:0,scale=960:1440:flags=lanczos" pre.png
ffmpeg -i blocked.jpg -vf "scale=960:1440:flags=lanczos,unsharp=5:5:2.0:5:5:0" post.png
# blockiness = mean|dx| on columns where col%8==7, divided by mean|dx| elsewhere
A pipeline that respects the layers
- Deinterlace once, correctly. If the source is interlaced (check a motion frame at 100%), this is first and non-negotiable. Do not deinterlace progressive content — it costs real resolution.
- Denoise gently. Gentle matters: an aggressive denoiser removes the film grain that makes footage look like film, and the result reads as plastic. Two light passes beat one heavy pass.
- Upscale at 2×, not 4×. Then, if you need more, do a second 2× pass — measured separately, two 2× steps retain more than one 4× step in most pipelines because each step's model operates in its trained range.
- Do not sharpen the compression artifacts. If block edges are visible, sharpening makes them worse. Fix the block edges first (a dedicated deblock), or accept them.
- Grade last. Any colour or contrast adjustment is a per-pixel multiplication and will amplify whatever noise survived. Doing it after denoise means you amplify less.
The honest limit
Restoration recovers structure, not truth. A face at 480p contains enough information to infer that a face is there and roughly what it looked like. It does not contain the eyelashes. A model that draws convincing eyelashes has invented them, and on a family photograph that is a strange thing to do without noticing.
The test that matters: does the restored version look like the person you remember, or like a plausible stranger? If you cannot tell, compare against a still from the same era at higher resolution. The model's idea of a face and your grandmother's actual face are different things, and only one of them is in the file.
Reproducing the order test
# build an interlaced frame: even rows from A, odd rows from A shifted 2px
comb[1::2] = shifted[1::2]
# both orders, same filters
ffmpeg -i comb.png -vf "yadif,hqdn3d=4:3:6:4.5" di_then_dn.png
ffmpeg -i comb.png -vf "hqdn3d=4:3:6:4.5,yadif" dn_then_di.png
# comb residual = mean |row[n] - row[n-1]| on the odd/even pairs
Try it on a short segment first
Restoration is expensive per minute, so test on 15 seconds before committing an hour of tape. A browser-based upscaler makes that loop fast — no install, no model download: UpgoLAB upscales photo and short video in the browser. Run the segment at 2×, watch it in motion, and only then decide whether the full pass is worth it.
If the 15-second test does not convince you, the full hour will not either. That is the one place where the cheap test and the expensive pass agree.