Every "remove text from video" tool reports success. Almost none of them tell you how they removed it, and the difference matters: cropping the caption band and painting a blurred box over it both count as success in the UI, and both will ruin a video you were about to publish.
I have been on the wrong end of this. A tool returned a clean-looking preview, I queued forty clips, and the output had a soft grey smear following every shot. The letters were gone. So was the texture behind them.
So here is the check I run now. It takes about a minute per clip and it does not depend on trusting the vendor.
Three things get sold under the same word
Before the check, it helps to know what you are checking for:
| Approach | What it does | What it costs you |
|---|---|---|
| Crop | Cuts the bottom of the frame off | Resolution, composition, aspect ratio |
| Cover | Blur or solid box over the text area | An obvious smudge that tracks the shot |
| Reconstruct | Rebuilds the background behind the letters, frame by frame | Compute time |
Only the third one survives a client review. And the third one is the only one where the region behind the text keeps its texture.
That last property is the whole basis of the test below.
The test: measure texture where the text used to be
If a tool reconstructed the background, the pixels behind the letters should look statistically like the rest of the frame - similar variance, similar high-frequency energy.
If a tool covered the area, that region becomes unnaturally smooth. A blur collapses high-frequency detail. A solid box removes it entirely.
So: crop the caption band, crop a reference band of the same size from a clean part of the same frame, and compare.
from PIL import Image, ImageFilter, ImageStat
def hf_energy(img):
"""High-frequency energy = how much fine detail survives."""
edges = img.convert("L").filter(ImageFilter.FIND_EDGES)
return ImageStat.Stat(edges).stddev[0]
def caption_band_score(frame_path, band, reference):
"""band / reference: (left, top, right, bottom) tuples."""
im = Image.open(frame_path)
b = hf_energy(im.crop(band))
r = hf_energy(im.crop(reference))
return round(b / r, 3) if r else None
# caption band roughly where the burned-in text sat
print(caption_band_score(
"frame_0142.png",
band=(0, 880, 1920, 1010),
reference=(0, 300, 1920, 430), # same height, clean part of the shot
))
Read the ratio, not the absolute number:
- ~0.8 to 1.2 - the region behaves like the rest of the frame. Reconstruction, or the area was genuinely empty.
- 0.3 to 0.6 - softened. Probably a blur, or a reconstruction that lost detail on a hard case.
- below 0.2 - flat. That is a box, or a crop that moved the band.
Two caveats, because this is a heuristic and not a spec:
- The reference band has to be plausible. If you compare a caption band sitting over a sky against a reference band full of foliage, the ratio is meaningless. Pick a reference from the same depth of field and similar subject matter.
- Film grain inflates both numbers. Compare within one clip, never across clips.
Sample more than one frame
A single frame lies. Most tools handle a static wide shot perfectly and fall apart the moment the background moves.
Pull frames at the four worst moments you can find:
ffmpeg -i input.mp4 -vf "select='eq(n\,30)+eq(n\,240)+eq(n\,900)+eq(n\,1500)'" -vsync 0 frame_%04d.png
Then run the score on each. If one frame is at 0.9 and another is at 0.15, you have a tool that works until the background gets complicated - which is the same as a tool that does not work, because you will not be watching every frame of every clip.
The four moments worth sampling:
- text over a face, or over hair - the classic failure
- text over a moving background, where the area behind the letters changes every frame
- karaoke-style captions that appear word by word, so the mask changes constantly
- text with a drop shadow or outline, which covers more pixels than the glyphs themselves
The temporal version, if you do not want to write code
Scrub the clip at 2x and watch only the caption band.
A reconstructed region changes with the shot. A covered region does not: the smear stays put while the background slides underneath it. That mismatch is what your eye picks up as "wrong" long before you can name it.
You can also just zoom to 400% on a single frame. Reconstruction leaves grain and texture. A blur leaves a smooth gradient. A box leaves a hard edge you can trace with a cursor.
What to do when the score fails
Three options, in order of how much work they are:
Re-export from the project file. If you still have the Premiere / Resolve / CapCut project, delete the caption layer and re-export. This is always faster than repairing a bad removal, and it is the option people forget because they assume the file is gone.
Try a different removal route. If the tool smeared on hair, it will smear on every close-up in the series. Test the replacement on the same four frames before you queue anything.
Re-shoot or re-cut. Occasionally the honest answer. A caption band that sits over a moving subject with fine detail is genuinely hard, and no amount of post-processing fixes a mask that has nothing to reconstruct from.
The part worth internalising
The failure mode is not "the tool did not remove the text". The failure mode is "the tool removed the text and left a mark you will not notice until a client notices it".
Measuring texture in the caption band catches that, because it tests the property that actually matters - whether the background survived - rather than the property the tool reports on, which is whether the glyphs are gone.
If you want a reference implementation to score against, CleanVideoText is the one I use for the reconstruction route; its free tier at https://cleanvideotext.com/free-video-text-remover/ is enough to run the four-frame test before committing to a series. And if you are new to removing subtitles that are burned into the frames, start with one clip, not forty.
Top comments (0)