mirror of
https://github.com/wiltodelta/remove-ai-watermarks.git
synced 2026-07-05 16:07:49 +02:00
fix(visible): inpaint mid-tone Gemini sparkle instead of a dark diamond
The free `visible` path over-subtracted a faint Gemini sparkle on a mid-tone background into a darker-than-background brown diamond instead of removing it (2026-06-18 prod NPS report, "the watermark was not removed, just its color changed"). The existing over-subtraction guard only tripped when reverse-alpha drove a footprint pixel fully negative (the issue #30 dark-background black-pit case); on a mid-tone background the over-subtraction darkens the core well below the background without any pixel crossing zero, so the gate missed it and shipped the dark mark. Add a second over-subtraction signal to `_reverse_alpha_oversubtracts`: predict the reverse-alpha output at the bright core, (core - a*logo)/(1-a), and route to the footprint inpaint when it lands more than `_OVERSUB_DARK_MARGIN` (25) gray levels below the local background ring. Calibrated wide: clean removals predict within ~12 of background (demo_banana ~-1), the prod regression ~-40, the issue #30 dark case ~-82. Corpus-validated on the 479 detected Gemini images: 10 switch reverse-alpha to inpaint, all of them dark-diamond cases that improve or match; the other 469 stay byte-identical. demo_banana stays on the reverse-alpha path (byte-identical). Also crop both reverse-alpha helpers to the region they actually touch, a pure O(image) -> O(mark) win that is byte-identical to the full-frame math (a uint8<->float32 round-trip is exact): - `GeminiEngine._core_and_bg` converts only the footprint+ring crop to gray, not the whole frame (~70 ms -> 0.1 ms on a 12 MP image; it runs for both the alpha-gain estimate and the new gate). Verified identical across 479 images; detector confidence unchanged. - `TextMarkEngine._apply_reverse_alpha` computes the blend on the glyph crop only (`amap` is zero outside it, so the math is a no-op there): ~275 ms -> ~2 ms per placement on a 12 MP frame, up to 2 placements per removal. Verified identical across 142 Doubao/Jimeng placements. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -298,6 +298,28 @@ class TestOverSubtractionGuard:
|
||||
dalpha = self.engine.get_interpolated_alpha(dpos[2])
|
||||
assert self.engine._reverse_alpha_oversubtracts(dark, dalpha, (dpos[0], dpos[1])) is True
|
||||
|
||||
def test_midtone_background_does_not_leave_dark_diamond(self):
|
||||
"""2026-06-18 prod report: a faint sparkle on a MID-TONE background was
|
||||
over-subtracted into a darker-than-background diamond ("the color just
|
||||
changed, not removed"). No footprint pixel crosses zero there, so the
|
||||
numerator gate misses it -- the dark-margin gate must catch it and inpaint.
|
||||
"""
|
||||
image, (x, y, w, h) = self._composite_sparkle(bg_value=160)
|
||||
footprint = image[y : y + h, x : x + w]
|
||||
# The numerator (black-pit) gate alone does NOT fire on a mid-tone background.
|
||||
alpha = self.engine.get_interpolated_alpha(w)
|
||||
roi = footprint.astype(np.float32).mean(axis=2)
|
||||
body = alpha[:h, :w] >= self.engine._FOOTPRINT_ALPHA
|
||||
numerator = roi - np.clip(alpha[:h, :w], 0.0, 0.99) * self.engine.logo_value
|
||||
assert float((numerator[body] < 0).sum()) / float(body.sum()) <= self.engine._OVERSUB_FOOTPRINT_FRAC
|
||||
# ...but the over-subtraction guard still trips (via the dark-margin path) and
|
||||
# removal leaves the footprint reading like the mid-tone background, not darker.
|
||||
assert self.engine._reverse_alpha_oversubtracts(image, alpha, (x, y)) is True
|
||||
out = self.engine.remove_watermark(image)
|
||||
cleaned = out[y : y + h, x : x + w]
|
||||
assert abs(float(cleaned.mean()) - 160.0) < 15.0, f"dark diamond: mean={cleaned.mean()}"
|
||||
assert int(cleaned.min()) > 160 - 30, f"dark pit: min={cleaned.min()}"
|
||||
|
||||
|
||||
class TestUnderSubtractionGain:
|
||||
"""Under-subtraction fix: a sparkle MORE opaque than the captured alpha must not
|
||||
|
||||
Reference in New Issue
Block a user