DEV Community

yue xing
yue xing

Posted on

Feathering or shrinking the matte didn't fix the halo in my blurred video background

For a course project I'm building a page that swaps video backgrounds, so I first checked what an existing tool does with background blur. There was a dark ring around the person. I blamed the matte edge and spent an evening feathering and shrinking it. On the one 478×850 clip I tested, neither changed the halo, and feathering only looked better because of how I counted pixels. Changing the order of the steps removed it.

The ring comes from blurring the person too

I used ImgIng (https://imging.ai/) for the exports, on an M4 MacBook in Chromium 149, which had no GPU acceleration to offer. The matting feature is labelled Beta and meant for Chrome or Edge on a computer, not phones. The clip is an 8-second Wikimedia Commons video of one person before a light curtain. She wears a headscarf, so I checked the scarf, head and hands, not hair. The blur slider runs from 4 to 40 and starts at 18 px. In this blur-the-original-background mode the ring appears because the frame being blurred still has the person in it, so her dark clothes and skin get smeared outward.

Head of the blurred-background export, 2x zoom, blur strength 18 on the left and 40 on the right

Look at the band just outside the headscarf: at 18 it is a dark ring hugging the edge, at 40 it is wider and fainter. Right at the edge this clip's background came out about 60-odd grey levels (0–255) darker than the empty curtain blurred the same way. Video source: Sayvhior / Wikimedia Commons, CC BY-SA 4.0 (signing by JudithNG12); this comparison is shared under CC BY-SA 4.0.

How I measured the ring

To try other matte edges I needed the matte, so I exported the clip with a transparent background. That option only exports WebM (the MP4 choice greys out), and its alpha channel became my matte. Then I wrote the simplest version in Python with OpenCV: Gaussian blur on the whole frame at sigma 18, person pasted back through the matte. For a reference I rebuilt the empty curtain: for each pixel, the median of the frames where she wasn't covering it. That got back 62.5% of it. ΔL is my result's brightness minus the same composite built on that empty curtain, so negative means darker. Distance is measured from the solid part of each matte (alpha above 0.5), only pixels with alpha below 0.02 count, and I averaged the 4–16 px rings over frames 36, 60, 100 and 156.

A hard matte edge left the halo unchanged

soft2 = cv2.GaussianBlur(alpha, (0, 0), 2)
soft5 = cv2.GaussianBlur(alpha, (0, 0), 5)
core = (alpha > 0.5).astype(np.uint8)
tight = cv2.erode(core, np.ones((5, 5), np.uint8)).astype(np.float32)

bg_blur = cv2.GaussianBlur(frame, (0, 0), 18)
for name, m in {"orig": alpha, "soft2": soft2, "soft5": soft5, "tight": tight}.items():
    out = frame * m[..., None] + bg_blur * (1 - m[..., None])
Enter fullscreen mode Exit fullscreen mode
Matte edge Semi-transparent band ΔL at 4–16 px
Original about 11 px −15.4
Feathered, sigma 2 about 15 px −16.6
Feathered, sigma 5 about 23 px −8.7
Shrunk 2 px, hard edge 0 px −15.3

Motion blur on the hands inflates the band widths. Even so, going from an 11 px soft edge to no soft edge at all moved the halo by 0.1 grey levels. The code shows why: the dark pixels leaking outward come from the person inside bg_blur, and the matte only decides which pixels the sharp person covers afterwards, so shrinking it can't take her out of the blur. I still don't get why sigma 2 came out slightly worse than the original.

The sigma 5 number fooled me at first

I was pleased with −8.7 for about ten minutes, until I checked which pixels were left in the 4–16 px rings. With a 23 px soft edge, much of that zone has alpha above 0.02, so it drops out of the stats and the pixels left over sit further out, where the halo is fainter anyway. On screen the ring is still there. My guess is that a feather that wide hides part of the halo inside a blurry outline.

Filling the background first removed it

What worked was taking her out before blurring. I filled her area from the surrounding background with OpenCV's Telea inpainting, blurred that, then pasted the sharp person on top. This is my own reproduction of a common compositing order, not a tool feature.

Bar chart of brightness difference 4–16 px outside the edge for four matte edges, comparing blur-the-original against fill-then-blur

Compare the red bars with the near-zero blue ones, then find the blue bar that drops: the hard edge with the fill area taken straight from the matte. Data: my measurements on this clip, same four frames.

Matte edge Fill area = matte Fill area = matte + 4 px
Original +1.3 +0.5
Feathered, sigma 2 +0.7 +0.4
Feathered, sigma 5 +0.2 −0.1
Shrunk 2 px, hard edge −14.1 +0.3

That −14.1 is the same mistake from the other side. When the matte is tighter than the real person, her outer 2 px never get filled, they get blurred as background, and the halo comes back. Growing the fill area 4 px past the matte fixed every row. The matte edge matters, just not where I was looking. It decides which pixels get filled.

What I would do and what I haven't checked

When I see this ring now, I leave the matte settings alone and check whether the blurred layer still contains the person. Compositing myself, I blur a background with the person filled in, grown a few pixels past the matte. To check a result I compare the 4–16 px outside the edge against a clean frame of the background. This is one clip, one person, a light curtain and dark clothes. With a light shirt on a dark wall I'd expect a bright ring instead, but that's a guess, since I had no such footage.

Top comments (0)